// 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 #include #include #include #include #include #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/macros.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 q1; absl::chunked_queue 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 using ChunkedQueueBlock = absl::container_internal::ChunkedQueueBlock>; TEST(Internal, elements_in_bytes) { EXPECT_EQ(size_t{1}, ChunkedQueueBlock::block_size_from_bytes(0)); EXPECT_EQ(size_t{1}, ChunkedQueueBlock::block_size_from_bytes( sizeof(ChunkedQueueBlock))); EXPECT_EQ(size_t{1}, ChunkedQueueBlock::block_size_from_bytes(sizeof(int))); EXPECT_EQ(size_t{2}, ChunkedQueueBlock::block_size_from_bytes( sizeof(ChunkedQueueBlock) + 2 * sizeof(int))); } TEST(Internal, BlockSizedDelete) { struct Item { int i; char c; }; std::allocator allocator; auto* block = ChunkedQueueBlock::New(3, &allocator); ChunkedQueueBlock::Delete(block, &allocator); } template void BlockSizeRounding() { struct Elem { char data[elem_size]; }; typedef ChunkedQueueBlock Block; for (size_t n = 1; n < 100; ++n) { SCOPED_TRACE(n); std::allocator 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 q = {1, 2, 3}; EXPECT_THAT(q, ElementsAre(1, 2, 3)); } TEST(ChunkedQueue, Empty) { absl::chunked_queue 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 q; q.push_back(1); absl::chunked_queue r(q); EXPECT_THAT(r, ElementsAre(1)); EXPECT_EQ(1, r.size()); } TEST(ChunkedQueue, CopyConstructMultipleChunks) { absl::chunked_queue q; q.push_back(1); q.push_back(2); q.push_back(3); absl::chunked_queue r(q); EXPECT_THAT(r, ElementsAre(1, 2, 3)); EXPECT_EQ(3, r.size()); } TEST(ChunkedQueue, BeginEndConstruct) { std::vector src = {1, 2, 3, 4, 5}; absl::chunked_queue 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 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 q(3); EXPECT_THAT(q, ElementsAre(0, 0, 0)); EXPECT_EQ(3, q.size()); } TEST(ChunkedQueue, CountValueConstruct) { absl::chunked_queue q(3, 10); EXPECT_THAT(q, ElementsAre(10, 10, 10)); EXPECT_EQ(3, q.size()); } TEST(ChunkedQueue, InitializerListAssign) { absl::chunked_queue 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 q; q.push_back(1); absl::chunked_queue r = q; EXPECT_THAT(r, ElementsAre(1)); } TEST(ChunkedQueue, CopyAssignSelf) { absl::chunked_queue 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 q; q.push_back(1); absl::chunked_queue 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 q; q.push_back(1); q.push_back(2); q.push_back(3); absl::chunked_queue 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 q; q.push_back(1); absl::chunked_queue 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 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 q; q.push_back(1); absl::chunked_queue r(std::move(q)); EXPECT_THAT(r, ElementsAre(1)); EXPECT_EQ(1, r.size()); } TEST(ChunkedQueue, MoveAssign) { absl::chunked_queue q; q.push_back(1); absl::chunked_queue 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 q; q.emplace_back(); absl::chunked_queue r; r = std::move(q); EXPECT_THAT(r, SizeIs(1)); } TEST(ChunkedQueue, MoveAssignSelf) { absl::chunked_queue q; absl::chunked_queue& 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 q; q.push_back(1); absl::chunked_queue 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 q; q.push_back(1); absl::chunked_queue 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 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& 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 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 q1; absl::chunked_queue 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 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 q; q.resize(2); EXPECT_THAT(q, ElementsAre(0, 0)); EXPECT_EQ(2, q.size()); } TEST(ChunkedQueue, ResizeShrinks) { absl::chunked_queue 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 q; q.resize(3); EXPECT_THAT(q, ElementsAre(0, 0, 0)); EXPECT_EQ(3, q.size()); } TEST(ChunkedQueue, ResizeShrinksMultipleBlocks) { absl::chunked_queue 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 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()); } TEST(ChunkedQueue, MaxSize) { absl::chunked_queue q; EXPECT_GE(q.max_size(), size_t{1} << (sizeof(size_t) * 8 - sizeof(int64_t) - 4)); } TEST(ChunkedQueue, AssignExtends) { absl::chunked_queue q; std::vector 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 q = {1, 2, 3, 4, 5}; std::vector 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 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 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 q; EXPECT_TRUE(q.begin() == q.end()); q.push_back(1); absl::chunked_queue::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::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::const_iterator; using Iter = absl::chunked_queue::iterator; ConstIter const_iter; EXPECT_TRUE(const_iter == ConstIter()); Iter iter; EXPECT_TRUE(iter == Iter()); } TEST(ChunkedQueue, IteratorConversion) { using ConstIter = absl::chunked_queue::const_iterator; using Iter = absl::chunked_queue::iterator; EXPECT_FALSE((std::is_convertible::value)); EXPECT_TRUE((std::is_convertible::value)); absl::chunked_queue 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::value)); } struct TestEntry { int x, y; }; TEST(ChunkedQueue, Iterator2) { absl::chunked_queue 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::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 q; for (int i = 0; i < 130; ++i) { absl::chunked_queue::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::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 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 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 vals; for (int j = 0; j < i; j++) { vals.push_back(MyType(j)); } MyType::constructor_calls = 0; MyType::destructor_calls = 0; { absl::chunked_queue 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> q; q.push_back(std::make_unique(10)); q.push_back(std::make_unique(11)); EXPECT_EQ(10, *q.front()); q.pop_front(); EXPECT_EQ(11, *q.front()); q.pop_front(); } TEST(ChunkedQueue, EmplaceBack1) { absl::chunked_queue> 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::string>> q; auto& v = q.emplace_back(std::make_unique(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 q; for (int i = 0; i < 10; ++i) { auto& v = q.emplace_back(Overaligned{i, i}); EXPECT_EQ(reinterpret_cast(&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; 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; using Block = absl::container_internal::ChunkedQueueBlock; 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 q; EXPECT_DEATH_IF_SUPPORTED(q.front(), ""); EXPECT_DEATH_IF_SUPPORTED(q.back(), ""); EXPECT_DEATH_IF_SUPPORTED(q.pop_front(), ""); const absl::chunked_queue cq; EXPECT_DEATH_IF_SUPPORTED(cq.front(), ""); EXPECT_DEATH_IF_SUPPORTED(cq.back(), ""); } } // namespace