blob: 8f5664e2ebf541e47d80ed9d44c36dd030cc7b82 [file]
// Copyright 2026 The Pigweed 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 "pw_containers/intrusive_queue.h"
#include <utility>
#include "pw_unit_test/framework.h"
namespace {
class Item : public pw::IntrusiveQueue<Item>::Item {
public:
constexpr Item() = default;
constexpr Item(int number) : number_(number) {}
Item(Item&& other) : Item() { *this = std::move(other); }
Item& operator=(Item&& other) {
number_ = other.number_;
return *this;
}
int GetNumber() const { return number_; }
void SetNumber(int num) { number_ = num; }
bool operator==(const Item& other) const { return number_ == other.number_; }
bool operator<(const Item& other) const { return number_ < other.number_; }
private:
int number_ = 0;
};
using Queue = pw::IntrusiveQueue<Item>;
TEST(IntrusiveQueueTest, PushBackAndFront) {
Queue queue;
Item i1(1);
Item i2(2);
Item i3(3);
queue.push_back(i2);
queue.push_back(i3);
queue.push_front(i1);
EXPECT_FALSE(queue.empty());
EXPECT_EQ(queue.front().GetNumber(), 1);
EXPECT_EQ(queue.back().GetNumber(), 3);
queue.pop_front();
EXPECT_EQ(queue.front().GetNumber(), 2);
EXPECT_EQ(queue.back().GetNumber(), 3);
queue.pop_front();
EXPECT_EQ(queue.front().GetNumber(), 3);
EXPECT_EQ(queue.back().GetNumber(), 3);
queue.pop_front();
EXPECT_TRUE(queue.empty());
}
TEST(IntrusiveQueueTest, ConstructInitializers) {
Item i1(1);
Item i2(2);
Item i3(3);
Queue queue({&i1, &i2, &i3});
EXPECT_EQ(queue.front().GetNumber(), 1);
EXPECT_EQ(queue.back().GetNumber(), 3);
queue.clear();
EXPECT_TRUE(queue.empty());
}
TEST(IntrusiveQueueTest, InsertAfter_UpdatesTail) {
Queue queue;
Item i1(1);
Item i2(2);
Item i3(3);
queue.push_back(i1);
// Insert after tail
auto it_inserted = queue.insert_after(queue.begin(), i2);
EXPECT_EQ(queue.back().GetNumber(), 2);
EXPECT_EQ(it_inserted->GetNumber(), 2);
// Insert multiple after tail
Item items[] = {Item(4), Item(5)};
queue.insert_after(it_inserted, {&items[0], &items[1]});
EXPECT_EQ(queue.back().GetNumber(), 5);
queue.clear();
}
TEST(IntrusiveQueueTest, EraseAfter_UpdatesTail) {
Queue queue;
Item i1(1);
Item i2(2);
queue.push_back(i1);
queue.push_back(i2);
// Erase the tail element
queue.erase_after(queue.begin());
EXPECT_EQ(queue.back().GetNumber(), 1);
queue.erase_after(queue.before_begin());
EXPECT_TRUE(queue.empty());
queue.clear();
}
TEST(IntrusiveQueueTest, Remove_UpdatesTail) {
Queue queue;
Item i1(1);
Item i2(2);
Item i3(3);
queue.push_back(i1);
queue.push_back(i2);
queue.push_back(i3);
EXPECT_EQ(queue.back().GetNumber(), 3);
// Remove middle item
bool removed = queue.remove(i2);
EXPECT_TRUE(removed);
EXPECT_EQ(queue.back().GetNumber(), 3);
// Remove tail item
removed = queue.remove(i3);
EXPECT_TRUE(removed);
EXPECT_EQ(queue.back().GetNumber(), 1);
// Remove front item
removed = queue.remove(i1);
EXPECT_TRUE(removed);
EXPECT_TRUE(queue.empty());
queue.clear();
}
TEST(IntrusiveQueueTest, Swap) {
Queue queue1;
Item i1(1);
queue1.push_back(i1);
Queue queue2;
Item i2(2);
queue2.push_back(i2);
queue1.swap(queue2);
EXPECT_EQ(queue1.front().GetNumber(), 2);
EXPECT_EQ(queue1.back().GetNumber(), 2);
EXPECT_EQ(queue2.front().GetNumber(), 1);
EXPECT_EQ(queue2.back().GetNumber(), 1);
Queue queue3; // empty
queue1.swap(queue3);
EXPECT_TRUE(queue1.empty());
EXPECT_FALSE(queue3.empty());
EXPECT_EQ(queue3.front().GetNumber(), 2);
EXPECT_EQ(queue3.back().GetNumber(), 2);
queue1.clear();
queue2.clear();
queue3.clear();
}
TEST(IntrusiveQueueTest, RemoveIf_UpdatesTail) {
Queue queue;
Item i1(1);
Item i2(2);
Item i3(3);
Item i4(4);
queue.push_back(i1);
queue.push_back(i2);
queue.push_back(i3);
queue.push_back(i4);
EXPECT_EQ(queue.back().GetNumber(), 4);
// Remove even numbers (2 and 4)
size_t removed = queue.remove_if(
[](const Item& item) { return item.GetNumber() % 2 == 0; });
EXPECT_EQ(removed, 2u);
EXPECT_EQ(queue.back().GetNumber(), 3);
EXPECT_EQ(queue.front().GetNumber(), 1);
// Remove 1
removed =
queue.remove_if([](const Item& item) { return item.GetNumber() == 1; });
EXPECT_EQ(removed, 1u);
EXPECT_EQ(queue.front().GetNumber(), 3);
EXPECT_EQ(queue.back().GetNumber(), 3);
queue.clear();
}
TEST(IntrusiveQueueTest, Move) {
Queue queue1;
Item i1(1);
Item i2(2);
queue1.push_back(i1);
queue1.push_back(i2);
// Move constructor
Queue queue2(std::move(queue1));
EXPECT_TRUE(queue1.empty()); // NOLINT(bugprone-use-after-move)
EXPECT_FALSE(queue2.empty());
EXPECT_EQ(queue2.front().GetNumber(), 1);
EXPECT_EQ(queue2.back().GetNumber(), 2);
// Move assignment
Queue queue3;
Item i3(3);
queue3.push_back(i3);
queue3 = std::move(queue2);
EXPECT_TRUE(queue2.empty()); // NOLINT(bugprone-use-after-move)
EXPECT_FALSE(queue3.empty());
EXPECT_EQ(queue3.front().GetNumber(), 1);
EXPECT_EQ(queue3.back().GetNumber(), 2);
queue3.clear();
}
TEST(IntrusiveQueueTest, Assign) {
Queue queue;
Item i1(1);
Item i2(2);
Item i3(3);
// Assign range
Item* items[] = {&i1, &i2, &i3};
queue.assign(std::begin(items), std::end(items));
EXPECT_EQ(queue.front().GetNumber(), 1);
EXPECT_EQ(queue.back().GetNumber(), 3);
// Assign list
Item i4(4);
queue.assign({&i4});
EXPECT_EQ(queue.front().GetNumber(), 4);
EXPECT_EQ(queue.back().GetNumber(), 4);
queue.clear();
}
TEST(IntrusiveQueueTest, Iterators) {
Queue queue;
Item i1(1);
Item i2(2);
queue.push_back(i1);
queue.push_back(i2);
// Non-const iterators
int count = 1;
for (Item& item : queue) {
EXPECT_EQ(item.GetNumber(), count++);
}
// Const iterators
const Queue& const_queue = queue;
count = 1;
for (const Item& item : const_queue) {
EXPECT_EQ(item.GetNumber(), count++);
}
// cbegin/cend
count = 1;
for (auto it = queue.cbegin(); it != queue.cend(); ++it) {
EXPECT_EQ(it->GetNumber(), count++);
}
// before_begin
auto it = queue.before_begin();
++it;
EXPECT_EQ(it, queue.begin());
queue.clear();
}
TEST(IntrusiveQueueTest, InsertAfter_Range) {
Queue queue;
Item i1(1);
queue.push_back(i1);
Item i2(2);
Item i3(3);
Item* items[] = {&i2, &i3};
// Insert range after begin
auto it =
queue.insert_after(queue.begin(), std::begin(items), std::end(items));
EXPECT_EQ(queue.back().GetNumber(), 3);
EXPECT_EQ(it->GetNumber(), 3);
// Verify contents: 1, 2, 3
auto it_check = queue.begin();
EXPECT_EQ(it_check->GetNumber(), 1);
++it_check;
EXPECT_EQ(it_check->GetNumber(), 2);
++it_check;
EXPECT_EQ(it_check->GetNumber(), 3);
queue.clear();
}
TEST(IntrusiveQueueTest, InsertAfter_BeforeBegin) {
Queue queue;
Item i1(1);
// Insert after before_begin into empty list
auto it = queue.insert_after(queue.before_begin(), i1);
EXPECT_FALSE(queue.empty());
EXPECT_EQ(queue.front().GetNumber(), 1);
EXPECT_EQ(queue.back().GetNumber(), 1);
EXPECT_EQ(it->GetNumber(), 1);
Item i2(2);
queue.insert_after(queue.before_begin(), i2);
EXPECT_EQ(queue.front().GetNumber(), 2);
EXPECT_EQ(queue.back().GetNumber(), 1); // 1 is still back
queue.clear();
}
TEST(IntrusiveQueueTest, EraseAfter_Range) {
Queue queue;
Item i1(1);
Item i2(2);
Item i3(3);
Item i4(4);
queue.push_back(i1);
queue.push_back(i2);
queue.push_back(i3);
queue.push_back(i4);
// Erase from i1 to end (excludes i1)
// erase_after(begin(), end()) -> erases i2, i3, i4
auto it = queue.erase_after(queue.begin(), queue.end());
EXPECT_EQ(queue.back().GetNumber(), 1);
EXPECT_EQ(it, queue.end());
queue.clear();
queue.push_back(i1);
queue.push_back(i2);
queue.push_back(i3);
// Erase middle (erase i2)
auto it_mid = queue.begin(); // i1
auto it_next = it_mid;
++it_next;
++it_next; // i3
queue.erase_after(it_mid, it_next);
EXPECT_EQ(queue.front().GetNumber(), 1);
EXPECT_EQ(queue.back().GetNumber(), 3);
queue.clear();
}
TEST(IntrusiveQueueTest, ConstAccess) {
Queue queue;
Item i1(1);
queue.push_back(i1);
const Queue& const_queue = queue;
EXPECT_EQ(const_queue.front().GetNumber(), 1);
EXPECT_EQ(const_queue.back().GetNumber(), 1);
queue.clear();
}
TEST(IntrusiveQueueTest, MaxSize) {
Queue queue;
EXPECT_GT(queue.max_size(), 0u);
}
} // namespace