| // Copyright 2025 The Abseil 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 "absl/container/chunked_queue.h" | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <deque> | |
| #include <forward_list> | |
| #include <iterator> | |
| #include <limits> | |
| #include <list> | |
| #include <memory> | |
| #include <new> | |
| #include <string> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <vector> | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "absl/base/macros.h" | |
| #include "absl/base/throw_delegate.h" | |
| #include "absl/container/internal/test_allocator.h" | |
| #include "absl/strings/str_cat.h" | |
| using ::testing::ElementsAre; | |
| using ::testing::Pair; | |
| using ::testing::Pointee; | |
| using ::testing::SizeIs; | |
| // Hide in a namespace to make sure swap is found via ADL. | |
| namespace adl_namespace { | |
| namespace { | |
| TEST(ChunkedQueueADLTest, Swap) { | |
| absl::chunked_queue<int64_t> q1; | |
| absl::chunked_queue<int64_t> q2; | |
| q1.push_back(4); | |
| q2.push_back(5); | |
| q2.push_back(6); | |
| swap(q1, q2); | |
| EXPECT_THAT(q1, ElementsAre(5, 6)); | |
| EXPECT_THAT(q2, ElementsAre(4)); | |
| } | |
| } // namespace | |
| } // namespace adl_namespace | |
| namespace { | |
| template <class T> | |
| using ChunkedQueueBlock = | |
| absl::container_internal::ChunkedQueueBlock<T, std::allocator<T>>; | |
| TEST(Internal, elements_in_bytes) { | |
| EXPECT_EQ(size_t{1}, ChunkedQueueBlock<int>::block_size_from_bytes(0)); | |
| EXPECT_EQ(size_t{1}, ChunkedQueueBlock<int>::block_size_from_bytes( | |
| sizeof(ChunkedQueueBlock<int>))); | |
| EXPECT_EQ(size_t{1}, | |
| ChunkedQueueBlock<int>::block_size_from_bytes(sizeof(int))); | |
| EXPECT_EQ(size_t{2}, ChunkedQueueBlock<int>::block_size_from_bytes( | |
| sizeof(ChunkedQueueBlock<int>) + 2 * sizeof(int))); | |
| } | |
| TEST(Internal, BlockSizedDelete) { | |
| struct Item { | |
| int i; | |
| char c; | |
| }; | |
| std::allocator<Item> allocator; | |
| auto* block = ChunkedQueueBlock<Item>::New(3, &allocator); | |
| ChunkedQueueBlock<Item>::Delete(block, &allocator); | |
| } | |
| template <size_t elem_size> | |
| void BlockSizeRounding() { | |
| struct Elem { | |
| char data[elem_size]; | |
| }; | |
| typedef ChunkedQueueBlock<Elem> Block; | |
| for (size_t n = 1; n < 100; ++n) { | |
| SCOPED_TRACE(n); | |
| std::allocator<Elem> allocator; | |
| Block* b = Block::New(n, &allocator); | |
| EXPECT_GE(b->size(), n); | |
| Block::Delete(b, &allocator); | |
| } | |
| } | |
| TEST(Internal, BlockSizeRounding1) { BlockSizeRounding<1>(); } | |
| TEST(Internal, BlockSizeRounding17) { BlockSizeRounding<17>(); } | |
| TEST(Internal, BlockSizeRounding101) { BlockSizeRounding<101>(); } | |
| TEST(Internal, BlockSizeRounding528) { BlockSizeRounding<528>(); } | |
| TEST(ChunkedQueue, MinMaxBlockSize) { | |
| absl::chunked_queue<int64_t, 1, 2> q = {1, 2, 3}; | |
| EXPECT_THAT(q, ElementsAre(1, 2, 3)); | |
| } | |
| TEST(ChunkedQueue, Empty) { | |
| absl::chunked_queue<int64_t> q; | |
| EXPECT_TRUE(q.empty()); | |
| q.push_back(10); | |
| EXPECT_FALSE(q.empty()); | |
| EXPECT_EQ(q.front(), 10); | |
| EXPECT_EQ(q.back(), 10); | |
| q.pop_front(); | |
| EXPECT_TRUE(q.empty()); | |
| q.clear(); | |
| EXPECT_TRUE(q.empty()); | |
| } | |
| TEST(ChunkedQueue, CopyConstruct) { | |
| absl::chunked_queue<int64_t> q; | |
| q.push_back(1); | |
| absl::chunked_queue<int64_t> r(q); | |
| EXPECT_THAT(r, ElementsAre(1)); | |
| EXPECT_EQ(1, r.size()); | |
| } | |
| TEST(ChunkedQueue, CopyConstructMultipleChunks) { | |
| absl::chunked_queue<int64_t, 2> q; | |
| q.push_back(1); | |
| q.push_back(2); | |
| q.push_back(3); | |
| absl::chunked_queue<int64_t, 2> r(q); | |
| EXPECT_THAT(r, ElementsAre(1, 2, 3)); | |
| EXPECT_EQ(3, r.size()); | |
| } | |
| TEST(ChunkedQueue, BeginEndConstruct) { | |
| std::vector<int64_t> src = {1, 2, 3, 4, 5}; | |
| absl::chunked_queue<int64_t, 2> q(src.begin(), src.end()); | |
| EXPECT_THAT(q, ElementsAre(1, 2, 3, 4, 5)); | |
| EXPECT_EQ(5, q.size()); | |
| } | |
| TEST(ChunkedQueue, InitializerListConstruct) { | |
| absl::chunked_queue<int64_t, 2> q = {1, 2, 3, 4, 5}; | |
| EXPECT_THAT(q, ElementsAre(1, 2, 3, 4, 5)); | |
| EXPECT_EQ(5, q.size()); | |
| } | |
| TEST(ChunkedQueue, CountConstruct) { | |
| absl::chunked_queue<int64_t> q(3); | |
| EXPECT_THAT(q, ElementsAre(0, 0, 0)); | |
| EXPECT_EQ(3, q.size()); | |
| } | |
| TEST(ChunkedQueue, CountValueConstruct) { | |
| absl::chunked_queue<int64_t> q(3, 10); | |
| EXPECT_THAT(q, ElementsAre(10, 10, 10)); | |
| EXPECT_EQ(3, q.size()); | |
| } | |
| TEST(ChunkedQueue, InitializerListAssign) { | |
| absl::chunked_queue<int64_t, 2> q; | |
| q = {1, 2, 3, 4, 5}; | |
| EXPECT_THAT(q, ElementsAre(1, 2, 3, 4, 5)); | |
| EXPECT_EQ(5, q.size()); | |
| } | |
| TEST(ChunkedQueue, CopyAssign) { | |
| absl::chunked_queue<int64_t> q; | |
| q.push_back(1); | |
| absl::chunked_queue<int64_t> r = q; | |
| EXPECT_THAT(r, ElementsAre(1)); | |
| } | |
| TEST(ChunkedQueue, CopyAssignSelf) { | |
| absl::chunked_queue<int64_t> q; | |
| q.push_back(1); | |
| q = *&q; // Avoid -Wself-assign. | |
| EXPECT_THAT(q, ElementsAre(1)); | |
| EXPECT_EQ(1, q.size()); | |
| } | |
| TEST(ChunkedQueue, CopyAssignDestinationBigger) { | |
| absl::chunked_queue<int64_t> q; | |
| q.push_back(1); | |
| absl::chunked_queue<int64_t> r; | |
| r.push_back(9); | |
| r.push_back(9); | |
| r.push_back(9); | |
| r = q; | |
| EXPECT_THAT(r, ElementsAre(1)); | |
| EXPECT_EQ(1, r.size()); | |
| } | |
| TEST(ChunkedQueue, CopyAssignSourceBiggerMultipleChunks) { | |
| absl::chunked_queue<int64_t, 2> q; | |
| q.push_back(1); | |
| q.push_back(2); | |
| q.push_back(3); | |
| absl::chunked_queue<int64_t, 2> r; | |
| r.push_back(9); | |
| r = q; | |
| EXPECT_THAT(r, ElementsAre(1, 2, 3)); | |
| EXPECT_EQ(3, r.size()); | |
| } | |
| TEST(ChunkedQueue, CopyAssignDestinationBiggerMultipleChunks) { | |
| absl::chunked_queue<int64_t, 2> q; | |
| q.push_back(1); | |
| absl::chunked_queue<int64_t, 2> r; | |
| r.push_back(9); | |
| r.push_back(9); | |
| r.push_back(9); | |
| r = q; | |
| EXPECT_THAT(r, ElementsAre(1)); | |
| EXPECT_EQ(1, r.size()); | |
| } | |
| TEST(ChunkedQueue, AssignCountValue) { | |
| absl::chunked_queue<int64_t> q; | |
| q.assign(3, 10); | |
| EXPECT_THAT(q, ElementsAre(10, 10, 10)); | |
| EXPECT_EQ(3, q.size()); | |
| q.assign(2, 20); | |
| EXPECT_THAT(q, ElementsAre(20, 20)); | |
| EXPECT_EQ(2, q.size()); | |
| } | |
| TEST(ChunkedQueue, MoveConstruct) { | |
| absl::chunked_queue<int64_t> q; | |
| q.push_back(1); | |
| absl::chunked_queue<int64_t> r(std::move(q)); | |
| EXPECT_THAT(r, ElementsAre(1)); | |
| EXPECT_EQ(1, r.size()); | |
| } | |
| TEST(ChunkedQueue, MoveAssign) { | |
| absl::chunked_queue<int64_t> q; | |
| q.push_back(1); | |
| absl::chunked_queue<int64_t> r; | |
| r = std::move(q); | |
| EXPECT_THAT(r, ElementsAre(1)); | |
| EXPECT_EQ(1, r.size()); | |
| } | |
| TEST(ChunkedQueue, MoveAssignImmovable) { | |
| struct Immovable { | |
| Immovable() = default; | |
| Immovable(const Immovable&) = delete; | |
| Immovable& operator=(const Immovable&) = delete; | |
| Immovable(Immovable&&) = delete; | |
| Immovable& operator=(Immovable&&) = delete; | |
| }; | |
| absl::chunked_queue<Immovable> q; | |
| q.emplace_back(); | |
| absl::chunked_queue<Immovable> r; | |
| r = std::move(q); | |
| EXPECT_THAT(r, SizeIs(1)); | |
| } | |
| TEST(ChunkedQueue, MoveAssignSelf) { | |
| absl::chunked_queue<int64_t> q; | |
| absl::chunked_queue<int64_t>& q2 = q; | |
| q.push_back(1); | |
| q = std::move(q2); | |
| EXPECT_THAT(q, ElementsAre(1)); | |
| EXPECT_EQ(1, q.size()); | |
| } | |
| TEST(ChunkedQueue, MoveAssignDestinationBigger) { | |
| absl::chunked_queue<int64_t> q; | |
| q.push_back(1); | |
| absl::chunked_queue<int64_t> r; | |
| r.push_back(9); | |
| r.push_back(9); | |
| r.push_back(9); | |
| r = std::move(q); | |
| EXPECT_THAT(r, ElementsAre(1)); | |
| EXPECT_EQ(1, r.size()); | |
| } | |
| TEST(ChunkedQueue, MoveAssignDestinationBiggerMultipleChunks) { | |
| absl::chunked_queue<int64_t, 2> q; | |
| q.push_back(1); | |
| absl::chunked_queue<int64_t, 2> r; | |
| r.push_back(9); | |
| r.push_back(9); | |
| r.push_back(9); | |
| r = std::move(q); | |
| EXPECT_THAT(r, ElementsAre(1)); | |
| EXPECT_EQ(1, r.size()); | |
| } | |
| TEST(ChunkedQueue, ConstFrontBack) { | |
| absl::chunked_queue<int64_t> q; | |
| q.push_back(10); | |
| EXPECT_EQ(q.front(), 10); | |
| EXPECT_EQ(q.back(), 10); | |
| q.front() = 12; | |
| EXPECT_EQ(q.front(), 12); | |
| EXPECT_EQ(q.back(), 12); | |
| const absl::chunked_queue<int64_t>& qref = q; | |
| EXPECT_EQ(qref.front(), 12); | |
| EXPECT_EQ(qref.back(), 12); | |
| q.pop_front(); | |
| // Test at block bloundary and beyond | |
| for (int i = 0; i < 64; ++i) q.push_back(i + 10); | |
| EXPECT_EQ(q.front(), 10); | |
| EXPECT_EQ(q.back(), 73); | |
| for (int i = 64; i < 128; ++i) q.push_back(i + 10); | |
| EXPECT_EQ(q.front(), 10); | |
| EXPECT_EQ(q.back(), 137); | |
| q.clear(); | |
| EXPECT_TRUE(q.empty()); | |
| } | |
| TEST(ChunkedQueue, PushAndPop) { | |
| absl::chunked_queue<int64_t> q; | |
| EXPECT_TRUE(q.empty()); | |
| EXPECT_EQ(0, q.size()); | |
| for (int i = 0; i < 10000; i++) { | |
| q.push_back(i); | |
| EXPECT_EQ(q.front(), 0) << ": iteration " << i; | |
| EXPECT_FALSE(q.empty()); | |
| EXPECT_EQ(i + 1, q.size()); | |
| } | |
| for (int i = 0; i < 10000; i++) { | |
| EXPECT_FALSE(q.empty()); | |
| EXPECT_EQ(10000 - i, q.size()); | |
| EXPECT_EQ(q.front(), i); | |
| q.pop_front(); | |
| } | |
| EXPECT_TRUE(q.empty()); | |
| EXPECT_EQ(0, q.size()); | |
| } | |
| TEST(ChunkedQueue, Swap) { | |
| absl::chunked_queue<int64_t> q1; | |
| absl::chunked_queue<int64_t> q2; | |
| q1.push_back(4); | |
| q2.push_back(5); | |
| q2.push_back(6); | |
| q2.swap(q1); | |
| EXPECT_EQ(2, q1.size()); | |
| EXPECT_EQ(5, q1.front()); | |
| EXPECT_EQ(1, q2.size()); | |
| EXPECT_EQ(4, q2.front()); | |
| q1.pop_front(); | |
| q1.swap(q2); | |
| EXPECT_EQ(1, q1.size()); | |
| EXPECT_EQ(4, q1.front()); | |
| EXPECT_EQ(1, q2.size()); | |
| EXPECT_EQ(6, q2.front()); | |
| q1.pop_front(); | |
| q1.swap(q2); | |
| EXPECT_EQ(1, q1.size()); | |
| EXPECT_EQ(6, q1.front()); | |
| EXPECT_EQ(0, q2.size()); | |
| q1.clear(); | |
| EXPECT_TRUE(q1.empty()); | |
| } | |
| TEST(ChunkedQueue, ShrinkToFit) { | |
| absl::chunked_queue<int64_t> q; | |
| q.shrink_to_fit(); // Should work on empty | |
| EXPECT_TRUE(q.empty()); | |
| q.push_back(1); | |
| q.shrink_to_fit(); // Should work on non-empty | |
| EXPECT_THAT(q, ElementsAre(1)); | |
| q.clear(); | |
| // We know clear leaves a block and shrink_to_fit should remove it. | |
| // Hard to test internal memory state without mocks or inspection. | |
| // But at least we verify it doesn't crash or corrupt. | |
| q.shrink_to_fit(); | |
| EXPECT_TRUE(q.empty()); | |
| } | |
| TEST(ChunkedQueue, ResizeExtends) { | |
| absl::chunked_queue<int64_t> q; | |
| q.resize(2); | |
| EXPECT_THAT(q, ElementsAre(0, 0)); | |
| EXPECT_EQ(2, q.size()); | |
| } | |
| TEST(ChunkedQueue, ResizeShrinks) { | |
| absl::chunked_queue<int64_t> q; | |
| q.push_back(1); | |
| q.push_back(2); | |
| q.resize(1); | |
| EXPECT_THAT(q, ElementsAre(1)); | |
| EXPECT_EQ(1, q.size()); | |
| } | |
| TEST(ChunkedQueue, ResizeExtendsMultipleBlocks) { | |
| absl::chunked_queue<int64_t, 2> q; | |
| q.resize(3); | |
| EXPECT_THAT(q, ElementsAre(0, 0, 0)); | |
| EXPECT_EQ(3, q.size()); | |
| } | |
| TEST(ChunkedQueue, ResizeShrinksMultipleBlocks) { | |
| absl::chunked_queue<int64_t, 2> q; | |
| q.push_back(1); | |
| q.push_back(2); | |
| q.push_back(3); | |
| q.resize(1); | |
| EXPECT_THAT(q, ElementsAre(1)); | |
| EXPECT_EQ(1, q.size()); | |
| } | |
| TEST(ChunkedQueue, ResizeValue) { | |
| absl::chunked_queue<int64_t> q; | |
| q.resize(3, 10); | |
| EXPECT_THAT(q, ElementsAre(10, 10, 10)); | |
| EXPECT_EQ(3, q.size()); | |
| q.resize(5, 20); | |
| EXPECT_THAT(q, ElementsAre(10, 10, 10, 20, 20)); | |
| EXPECT_EQ(5, q.size()); | |
| q.resize(2, 30); | |
| EXPECT_THAT(q, ElementsAre(10, 10)); | |
| EXPECT_EQ(2, q.size()); | |
| } | |
| template <class T> | |
| struct LimitedAllocator { | |
| using value_type = T; | |
| int* alloc_count; | |
| int max_allocs; | |
| explicit LimitedAllocator(int* count, int max) | |
| : alloc_count(count), max_allocs(max) {} | |
| template <class U> | |
| LimitedAllocator(const LimitedAllocator<U>& other) | |
| : alloc_count(other.alloc_count), max_allocs(other.max_allocs) {} | |
| T* allocate(size_t n) { | |
| if (*alloc_count >= max_allocs) { | |
| absl::ThrowStdBadAlloc(); | |
| } | |
| ++*alloc_count; | |
| return std::allocator<T>().allocate(n); | |
| } | |
| void deallocate(T* p, size_t n) { | |
| std::allocator<T>().deallocate(p, n); | |
| } | |
| template <class U> | |
| bool operator==(const LimitedAllocator<U>& other) const { | |
| return alloc_count == other.alloc_count; | |
| } | |
| template <class U> | |
| bool operator!=(const LimitedAllocator<U>& other) const { | |
| return !(*this == other); | |
| } | |
| }; | |
| TEST(ChunkedQueue, ResizeOverflowSafe) { | |
| int alloc_count = 0; | |
| absl::chunked_queue<int64_t, 0, 0, LimitedAllocator<int64_t>> q( | |
| LimitedAllocator<int64_t>(&alloc_count, 5)); | |
| #ifdef ABSL_HAVE_EXCEPTIONS | |
| EXPECT_THROW(q.resize(std::numeric_limits<size_t>::max()), std::bad_alloc); | |
| #else | |
| EXPECT_DEATH_IF_SUPPORTED(q.resize(std::numeric_limits<size_t>::max()), ""); | |
| #endif | |
| } | |
| TEST(ChunkedQueue, MaxSize) { | |
| absl::chunked_queue<int64_t> q; | |
| EXPECT_GE(q.max_size(), | |
| size_t{1} << (sizeof(size_t) * 8 - sizeof(int64_t) - 4)); | |
| } | |
| TEST(ChunkedQueue, AssignExtends) { | |
| absl::chunked_queue<int64_t, 2> q; | |
| std::vector<int64_t> v = {1, 2, 3, 4, 5}; | |
| q.assign(v.begin(), v.end()); | |
| EXPECT_THAT(q, ElementsAre(1, 2, 3, 4, 5)); | |
| EXPECT_EQ(5, q.size()); | |
| } | |
| TEST(ChunkedQueue, AssignShrinks) { | |
| absl::chunked_queue<int64_t, 2> q = {1, 2, 3, 4, 5}; | |
| std::vector<int64_t> v = {1}; | |
| q.assign(v.begin(), v.end()); | |
| EXPECT_THAT(q, ElementsAre(1)); | |
| EXPECT_EQ(1, q.size()); | |
| } | |
| TEST(ChunkedQueue, AssignBoundaryCondition) { | |
| // Create a queue with fixed block size of 4. | |
| // 3 blocks: [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] | |
| absl::chunked_queue<int, 4> q = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; | |
| // Assign a range that fills exactly the first block (4 elements). | |
| // This triggers the boundary condition where the assignment loop ends | |
| // exactly at the limit of the first block. | |
| std::vector<int> v = {101, 102, 103, 104}; | |
| q.assign(v.begin(), v.end()); | |
| EXPECT_EQ(q.size(), 4); | |
| EXPECT_EQ(q.front(), 101); | |
| // Verify back() is valid. If tail_ was incorrectly pointing to the start | |
| // of the (now deleted) second block, this might access invalid memory | |
| // or fail assertions. | |
| EXPECT_EQ(q.back(), 104); | |
| // Verify we can continue to push elements correctly. | |
| q.push_back(105); | |
| EXPECT_EQ(q.size(), 5); | |
| EXPECT_EQ(q.back(), 105); | |
| } | |
| TEST(ChunkedQueue, Iterator) { | |
| absl::chunked_queue<int64_t> q; | |
| EXPECT_TRUE(q.begin() == q.end()); | |
| q.push_back(1); | |
| absl::chunked_queue<int64_t>::const_iterator iter = q.begin(); | |
| ASSERT_FALSE(iter == q.end()); | |
| ASSERT_EQ(*iter, 1); | |
| ++iter; | |
| ASSERT_TRUE(iter == q.end()); | |
| q.push_back(2); | |
| iter = q.begin(); | |
| ASSERT_EQ(*iter, 1); | |
| ++iter; | |
| absl::chunked_queue<int64_t>::const_iterator copy_iter = iter; | |
| ASSERT_FALSE(copy_iter == q.end()); | |
| ASSERT_EQ(*copy_iter, 2); | |
| ++copy_iter; | |
| ASSERT_TRUE(copy_iter == q.end()); | |
| copy_iter = iter; | |
| ASSERT_FALSE(iter == q.end()); | |
| ASSERT_EQ(*iter, 2); | |
| ++iter; | |
| ASSERT_TRUE(iter == q.end()); | |
| ASSERT_FALSE(copy_iter == q.end()); | |
| ASSERT_EQ(*copy_iter, 2); | |
| ++copy_iter; | |
| ASSERT_TRUE(copy_iter == q.end()); | |
| } | |
| TEST(ChunkedQueue, IteratorDefaultConstructor) { | |
| using ConstIter = absl::chunked_queue<int64_t>::const_iterator; | |
| using Iter = absl::chunked_queue<int64_t>::iterator; | |
| ConstIter const_iter; | |
| EXPECT_TRUE(const_iter == ConstIter()); | |
| Iter iter; | |
| EXPECT_TRUE(iter == Iter()); | |
| } | |
| TEST(ChunkedQueue, IteratorConversion) { | |
| using ConstIter = absl::chunked_queue<int64_t>::const_iterator; | |
| using Iter = absl::chunked_queue<int64_t>::iterator; | |
| EXPECT_FALSE((std::is_convertible_v<ConstIter, Iter>)); | |
| EXPECT_TRUE((std::is_convertible_v<Iter, ConstIter>)); | |
| absl::chunked_queue<int64_t> q; | |
| ConstIter it1 = q.begin(); | |
| ConstIter it2 = q.cbegin(); | |
| Iter it3 = q.begin(); | |
| it1 = q.end(); | |
| it2 = q.cend(); | |
| it3 = q.end(); | |
| EXPECT_FALSE((std::is_assignable_v<Iter, ConstIter>)); | |
| } | |
| struct TestEntry { | |
| int x, y; | |
| }; | |
| TEST(ChunkedQueue, Iterator2) { | |
| absl::chunked_queue<TestEntry> q; | |
| TestEntry e; | |
| e.x = 1; | |
| e.y = 2; | |
| q.push_back(e); | |
| e.x = 3; | |
| e.y = 4; | |
| q.push_back(e); | |
| absl::chunked_queue<TestEntry>::const_iterator iter = q.begin(); | |
| EXPECT_EQ(iter->x, 1); | |
| EXPECT_EQ(iter->y, 2); | |
| ++iter; | |
| EXPECT_EQ(iter->x, 3); | |
| EXPECT_EQ(iter->y, 4); | |
| ++iter; | |
| EXPECT_TRUE(iter == q.end()); | |
| } | |
| TEST(ChunkedQueue, Iterator_MultipleBlocks) { | |
| absl::chunked_queue<int64_t> q; | |
| for (int i = 0; i < 130; ++i) { | |
| absl::chunked_queue<int64_t>::const_iterator iter = q.begin(); | |
| for (int j = 0; j < i; ++j) { | |
| ASSERT_FALSE(iter == q.end()); | |
| EXPECT_EQ(*iter, j); | |
| ++iter; | |
| } | |
| ASSERT_TRUE(iter == q.end()); | |
| q.push_back(i); | |
| } | |
| for (int i = 0; i < 130; ++i) { | |
| absl::chunked_queue<int64_t>::const_iterator iter = q.begin(); | |
| for (int j = i; j < 130; ++j) { | |
| ASSERT_FALSE(iter == q.end()); | |
| EXPECT_EQ(*iter, j); | |
| ++iter; | |
| } | |
| q.pop_front(); | |
| } | |
| EXPECT_TRUE(q.empty()); | |
| EXPECT_TRUE(q.begin() == q.end()); | |
| } | |
| TEST(ChunkedQueue, Iterator_PopFrontInvalidate) { | |
| absl::chunked_queue<int64_t> q; | |
| for (int i = 0; i < 130; ++i) { | |
| q.push_back(i); | |
| } | |
| auto iter = q.begin(); | |
| for (int i = 0; i < 130; ++i) { | |
| auto prev = iter++; | |
| ASSERT_FALSE(prev == q.end()); | |
| EXPECT_EQ(*prev, i); | |
| q.pop_front(); | |
| } | |
| ASSERT_TRUE(q.empty()); | |
| } | |
| TEST(ChunkedQueue, Iterator_PushBackInvalidate) { | |
| absl::chunked_queue<int64_t, 2> q; | |
| q.push_back(0); | |
| auto i = q.begin(); | |
| EXPECT_EQ(*i, 0); | |
| q.push_back(1); | |
| EXPECT_EQ(*++i, 1); | |
| q.push_back(2); | |
| EXPECT_EQ(*++i, 2); | |
| } | |
| struct MyType { | |
| static int constructor_calls; | |
| static int destructor_calls; | |
| explicit MyType(int x) : val(x) { constructor_calls++; } | |
| MyType(const MyType& t) : val(t.val) { constructor_calls++; } | |
| ~MyType() { destructor_calls++; } | |
| int val; | |
| }; | |
| int MyType::constructor_calls = 0; | |
| int MyType::destructor_calls = 0; | |
| TEST(ChunkedQueue, ConstructorDestructorCalls) { | |
| for (int i = 0; i < 100; i++) { | |
| std::vector<MyType> vals; | |
| for (int j = 0; j < i; j++) { | |
| vals.push_back(MyType(j)); | |
| } | |
| MyType::constructor_calls = 0; | |
| MyType::destructor_calls = 0; | |
| { | |
| absl::chunked_queue<MyType> q; | |
| for (int j = 0; j < i; j++) { | |
| q.push_back(vals[j]); | |
| } | |
| if (i % 10 == 0) { | |
| q.clear(); | |
| } else { | |
| for (int j = 0; j < i; j++) { | |
| EXPECT_EQ(q.front().val, j); | |
| q.pop_front(); | |
| } | |
| } | |
| } | |
| EXPECT_EQ(MyType::constructor_calls, i); | |
| EXPECT_EQ(MyType::destructor_calls, i); | |
| } | |
| } | |
| TEST(ChunkedQueue, MoveObjects) { | |
| absl::chunked_queue<std::unique_ptr<int>> q; | |
| q.push_back(std::make_unique<int>(10)); | |
| q.push_back(std::make_unique<int>(11)); | |
| EXPECT_EQ(10, *q.front()); | |
| q.pop_front(); | |
| EXPECT_EQ(11, *q.front()); | |
| q.pop_front(); | |
| } | |
| TEST(ChunkedQueue, EmplaceBack1) { | |
| absl::chunked_queue<std::pair<int, int>> q; | |
| auto& v = q.emplace_back(1, 2); | |
| EXPECT_THAT(v, Pair(1, 2)); | |
| EXPECT_THAT(q.front(), Pair(1, 2)); | |
| EXPECT_EQ(&v, &q.back()); | |
| } | |
| TEST(ChunkedQueue, EmplaceBack2) { | |
| absl::chunked_queue<std::pair<std::unique_ptr<int>, std::string>> q; | |
| auto& v = q.emplace_back(std::make_unique<int>(11), "val12"); | |
| EXPECT_THAT(v, Pair(Pointee(11), "val12")); | |
| EXPECT_THAT(q.front(), Pair(Pointee(11), "val12")); | |
| } | |
| TEST(ChunkedQueue, OveralignmentEmplaceBack) { | |
| struct alignas(64) Overaligned { | |
| int x; | |
| int y; | |
| }; | |
| absl::chunked_queue<Overaligned, 1, 8> q; | |
| for (int i = 0; i < 10; ++i) { | |
| auto& v = q.emplace_back(Overaligned{i, i}); | |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&v) % 64, 0); | |
| } | |
| } | |
| TEST(ChunkedQueue, StatelessAllocatorDoesntAffectObjectSizes) { | |
| // When a stateless allocator type is used -- such as when no explicit | |
| // allocator type is given, and the stateless default is used -- it does not | |
| // increase the object sizes from what they used to be before allocator | |
| // support was added. (In practice this verifies that allocator support makes | |
| // use of the empty base-class optimization.) | |
| // | |
| // These "Mock*" structs model the data members of absl::chunked_queue<> and | |
| // its internal ChunkedQueueBlock<> type, without any extra storage for | |
| // allocator state. (We use these to generate expected stateless-allocator | |
| // object sizes in a portable way.) | |
| struct MockQueue { | |
| struct MockIterator { | |
| void* block; | |
| void* ptr; | |
| void* limit; | |
| }; | |
| MockIterator head; | |
| MockIterator tail; | |
| size_t size; | |
| }; | |
| struct MockBlock { | |
| void* next; | |
| void* limit; | |
| }; | |
| using TestQueueType = absl::chunked_queue<int64_t, 1, 16>; | |
| EXPECT_EQ(sizeof(TestQueueType), sizeof(MockQueue)); | |
| EXPECT_EQ(sizeof(absl::container_internal::ChunkedQueueBlock< | |
| TestQueueType::value_type, TestQueueType::allocator_type>), | |
| sizeof(MockBlock)); | |
| } | |
| TEST(ChunkedQueue, DoesNotRoundBlockSizesUpWithNonDefaultAllocator) { | |
| using OneByte = uint8_t; | |
| using CustomAllocator = absl::container_internal::CountingAllocator<OneByte>; | |
| using Block = | |
| absl::container_internal::ChunkedQueueBlock<OneByte, CustomAllocator>; | |
| int64_t allocator_live_bytes = 0; | |
| CustomAllocator allocator(&allocator_live_bytes); | |
| // Create a Block big enough to accomodate at least 1 OneByte. | |
| Block* b = Block::New(1, &allocator); | |
| ASSERT_TRUE(b != nullptr); | |
| // With a non-default allocator in play, the resulting block should have | |
| // capacity for exactly 1 element -- the implementation should not round the | |
| // allocation size up, which may be inappropriate for non-default allocators. | |
| // | |
| // (Note that we don't always round up even with the default allocator in use, | |
| // e.g. when compiling for ASAN analysis.) | |
| EXPECT_EQ(b->size(), 1); | |
| Block::Delete(b, &allocator); | |
| } | |
| TEST(ChunkedQueue, Hardening) { | |
| bool hardened = false; | |
| ABSL_HARDENING_ASSERT([&hardened]() { | |
| hardened = true; | |
| return true; | |
| }()); | |
| if (!hardened) { | |
| GTEST_SKIP() << "Not a hardened build"; | |
| } | |
| absl::chunked_queue<int> q; | |
| EXPECT_DEATH_IF_SUPPORTED(q.front(), ""); | |
| EXPECT_DEATH_IF_SUPPORTED(q.back(), ""); | |
| EXPECT_DEATH_IF_SUPPORTED(q.pop_front(), ""); | |
| const absl::chunked_queue<int> cq; | |
| EXPECT_DEATH_IF_SUPPORTED(cq.front(), ""); | |
| EXPECT_DEATH_IF_SUPPORTED(cq.back(), ""); | |
| } | |
| #ifdef ABSL_HAVE_EXCEPTIONS | |
| struct ThrowingCtor { | |
| int* ctor_count; | |
| int* dtor_count; | |
| int value; | |
| explicit ThrowingCtor(int v, bool should_throw, int* c_count, int* d_count) | |
| : ctor_count(c_count), dtor_count(d_count), value(v) { | |
| if (should_throw) { | |
| throw 0; | |
| } | |
| ++*ctor_count; | |
| } | |
| ThrowingCtor(const ThrowingCtor& other) | |
| : ctor_count(other.ctor_count), | |
| dtor_count(other.dtor_count), | |
| value(other.value) { | |
| ++*ctor_count; | |
| } | |
| ThrowingCtor(ThrowingCtor&& other) noexcept | |
| : ctor_count(other.ctor_count), | |
| dtor_count(other.dtor_count), | |
| value(other.value) { | |
| ++*ctor_count; | |
| } | |
| ~ThrowingCtor() { ++*dtor_count; } | |
| }; | |
| TEST(ChunkedQueue, StrongExceptionSafetyEmplaceBack) { | |
| int ctor_count = 0; | |
| int dtor_count = 0; | |
| absl::chunked_queue<ThrowingCtor> q; | |
| q.emplace_back(10, false, &ctor_count, &dtor_count); | |
| q.emplace_back(20, false, &ctor_count, &dtor_count); | |
| ASSERT_EQ(q.size(), 2); | |
| ASSERT_EQ(ctor_count, 2); | |
| ASSERT_EQ(dtor_count, 0); | |
| try { | |
| q.emplace_back(30, true, &ctor_count, &dtor_count); | |
| } catch (...) { | |
| } | |
| EXPECT_EQ(q.size(), 2); | |
| EXPECT_EQ(ctor_count, 2); | |
| EXPECT_EQ(dtor_count, 0); | |
| q.clear(); | |
| EXPECT_EQ(q.size(), 0); | |
| EXPECT_EQ(dtor_count, 2); | |
| } | |
| #endif | |
| } // namespace |