|  | // Copyright (c) 2024 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_intrusive_list.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <iterator> | 
|  | #include <list> | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | #include "quiche/common/platform/api/quiche_test.h" | 
|  |  | 
|  | namespace quiche { | 
|  | namespace test { | 
|  |  | 
|  | struct ListId2 {}; | 
|  |  | 
|  | struct TestItem : public QuicheIntrusiveLink<TestItem>, | 
|  | public QuicheIntrusiveLink<TestItem, ListId2> { | 
|  | int n; | 
|  | }; | 
|  | typedef QuicheIntrusiveList<TestItem> TestList; | 
|  | typedef std::list<TestItem *> CanonicalList; | 
|  |  | 
|  | void swap(TestItem &a, TestItem &b) { | 
|  | using std::swap; | 
|  | swap(a.n, b.n); | 
|  | } | 
|  |  | 
|  | class IntrusiveListTest : public quiche::test::QuicheTest { | 
|  | protected: | 
|  | void CheckLists() { | 
|  | CheckLists(l1, ll1); | 
|  | if (quiche::test::QuicheTest::HasFailure()) return; | 
|  | CheckLists(l2, ll2); | 
|  | } | 
|  |  | 
|  | void CheckLists(const TestList &list_a, const CanonicalList &list_b) { | 
|  | ASSERT_EQ(list_a.size(), list_b.size()); | 
|  | TestList::const_iterator it_a = list_a.begin(); | 
|  | CanonicalList::const_iterator it_b = list_b.begin(); | 
|  | while (it_a != list_a.end()) { | 
|  | EXPECT_EQ(&*it_a++, *it_b++); | 
|  | } | 
|  | EXPECT_EQ(list_a.end(), it_a); | 
|  | EXPECT_EQ(list_b.end(), it_b); | 
|  | } | 
|  |  | 
|  | void PrepareLists(int num_elems_1, int num_elems_2 = 0) { | 
|  | FillLists(&l1, &ll1, e, num_elems_1); | 
|  | FillLists(&l2, &ll2, e + num_elems_1, num_elems_2); | 
|  | } | 
|  |  | 
|  | void FillLists(TestList *list_a, CanonicalList *list_b, TestItem *elems, | 
|  | int num_elems) { | 
|  | list_a->clear(); | 
|  | list_b->clear(); | 
|  | for (int i = 0; i < num_elems; ++i) { | 
|  | list_a->push_back(elems + i); | 
|  | list_b->push_back(elems + i); | 
|  | } | 
|  | CheckLists(*list_a, *list_b); | 
|  | } | 
|  |  | 
|  | TestItem e[10]; | 
|  | TestList l1, l2; | 
|  | CanonicalList ll1, ll2; | 
|  | }; | 
|  |  | 
|  | TEST(NewIntrusiveListTest, Basic) { | 
|  | TestList list1; | 
|  |  | 
|  | EXPECT_EQ(sizeof(QuicheIntrusiveLink<TestItem>), sizeof(void *) * 2); | 
|  |  | 
|  | for (int i = 0; i < 10; ++i) { | 
|  | TestItem *e = new TestItem; | 
|  | e->n = i; | 
|  | list1.push_front(e); | 
|  | } | 
|  | EXPECT_EQ(list1.size(), 10u); | 
|  |  | 
|  | // Verify we can reverse a list because we defined swap for TestItem. | 
|  | std::reverse(list1.begin(), list1.end()); | 
|  | EXPECT_EQ(list1.size(), 10u); | 
|  |  | 
|  | // Check both const and non-const forward iteration. | 
|  | const TestList &clist1 = list1; | 
|  | int i = 0; | 
|  | TestList::iterator iter = list1.begin(); | 
|  | for (; iter != list1.end(); ++iter, ++i) { | 
|  | EXPECT_EQ(iter->n, i); | 
|  | } | 
|  | EXPECT_EQ(iter, clist1.end()); | 
|  | EXPECT_NE(iter, clist1.begin()); | 
|  | i = 0; | 
|  | iter = list1.begin(); | 
|  | for (; iter != list1.end(); ++iter, ++i) { | 
|  | EXPECT_EQ(iter->n, i); | 
|  | } | 
|  | EXPECT_EQ(iter, clist1.end()); | 
|  | EXPECT_NE(iter, clist1.begin()); | 
|  |  | 
|  | EXPECT_EQ(list1.front().n, 0); | 
|  | EXPECT_EQ(list1.back().n, 9); | 
|  |  | 
|  | // Verify we can swap 2 lists. | 
|  | TestList list2; | 
|  | list2.swap(list1); | 
|  | EXPECT_EQ(list1.size(), 0u); | 
|  | EXPECT_EQ(list2.size(), 10u); | 
|  |  | 
|  | // Check both const and non-const reverse iteration. | 
|  | const TestList &clist2 = list2; | 
|  | TestList::reverse_iterator riter = list2.rbegin(); | 
|  | i = 9; | 
|  | for (; riter != list2.rend(); ++riter, --i) { | 
|  | EXPECT_EQ(riter->n, i); | 
|  | } | 
|  | EXPECT_EQ(riter, clist2.rend()); | 
|  | EXPECT_NE(riter, clist2.rbegin()); | 
|  |  | 
|  | riter = list2.rbegin(); | 
|  | i = 9; | 
|  | for (; riter != list2.rend(); ++riter, --i) { | 
|  | EXPECT_EQ(riter->n, i); | 
|  | } | 
|  | EXPECT_EQ(riter, clist2.rend()); | 
|  | EXPECT_NE(riter, clist2.rbegin()); | 
|  |  | 
|  | while (!list2.empty()) { | 
|  | TestItem *e = &list2.front(); | 
|  | list2.pop_front(); | 
|  | delete e; | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(NewIntrusiveListTest, Erase) { | 
|  | TestList l; | 
|  | TestItem *e[10]; | 
|  |  | 
|  | // Create a list with 10 items. | 
|  | for (int i = 0; i < 10; ++i) { | 
|  | e[i] = new TestItem; | 
|  | l.push_front(e[i]); | 
|  | } | 
|  |  | 
|  | // Test that erase works. | 
|  | for (int i = 0; i < 10; ++i) { | 
|  | EXPECT_EQ(l.size(), (10u - i)); | 
|  |  | 
|  | TestList::iterator iter = l.erase(e[i]); | 
|  | EXPECT_NE(iter, TestList::iterator(e[i])); | 
|  |  | 
|  | EXPECT_EQ(l.size(), (10u - i - 1)); | 
|  | delete e[i]; | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(NewIntrusiveListTest, Insert) { | 
|  | TestList l; | 
|  | TestList::iterator iter = l.end(); | 
|  | TestItem *e[10]; | 
|  |  | 
|  | // Create a list with 10 items. | 
|  | for (int i = 9; i >= 0; --i) { | 
|  | e[i] = new TestItem; | 
|  | iter = l.insert(iter, e[i]); | 
|  | EXPECT_EQ(&(*iter), e[i]); | 
|  | } | 
|  |  | 
|  | EXPECT_EQ(l.size(), 10u); | 
|  |  | 
|  | // Verify insertion order. | 
|  | iter = l.begin(); | 
|  | for (TestItem *item : e) { | 
|  | EXPECT_EQ(&(*iter), item); | 
|  | iter = l.erase(item); | 
|  | delete item; | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(NewIntrusiveListTest, Move) { | 
|  | // Move contructible. | 
|  |  | 
|  | {  // Move-construct from an empty list. | 
|  | TestList src; | 
|  | TestList dest(std::move(src)); | 
|  | EXPECT_TRUE(dest.empty()); | 
|  | } | 
|  |  | 
|  | {  // Move-construct from a single item list. | 
|  | TestItem e; | 
|  | TestList src; | 
|  | src.push_front(&e); | 
|  |  | 
|  | TestList dest(std::move(src)); | 
|  | EXPECT_TRUE(src.empty());  // NOLINT bugprone-use-after-move | 
|  | ASSERT_THAT(dest.size(), 1); | 
|  | EXPECT_THAT(&dest.front(), &e); | 
|  | EXPECT_THAT(&dest.back(), &e); | 
|  | } | 
|  |  | 
|  | {  // Move-construct from a list with multiple items. | 
|  | TestItem items[10]; | 
|  | TestList src; | 
|  | for (TestItem &e : items) src.push_back(&e); | 
|  |  | 
|  | TestList dest(std::move(src)); | 
|  | EXPECT_TRUE(src.empty());  // NOLINT bugprone-use-after-move | 
|  | // Verify the items on the destination list. | 
|  | ASSERT_THAT(dest.size(), 10); | 
|  | int i = 0; | 
|  | for (TestItem &e : dest) { | 
|  | EXPECT_THAT(&e, &items[i++]) << " for index " << i; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(NewIntrusiveListTest, StaticInsertErase) { | 
|  | TestList l; | 
|  | TestItem e[2]; | 
|  | TestList::iterator i = l.begin(); | 
|  | TestList::insert(i, &e[0]); | 
|  | TestList::insert(&e[0], &e[1]); | 
|  | TestList::erase(&e[0]); | 
|  | TestList::erase(TestList::iterator(&e[1])); | 
|  | EXPECT_TRUE(l.empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(IntrusiveListTest, Splice) { | 
|  | // We verify that the contents of this secondary list aren't affected by any | 
|  | // of the splices. | 
|  | QuicheIntrusiveList<TestItem, ListId2> secondary_list; | 
|  | for (int i = 0; i < 3; ++i) { | 
|  | secondary_list.push_back(&e[i]); | 
|  | } | 
|  |  | 
|  | // Test the basic cases: | 
|  | // - The lists range from 0 to 2 elements. | 
|  | // - The insertion point ranges from begin() to end() | 
|  | // - The transfered range has multiple sizes and locations in the source. | 
|  | for (int l1_count = 0; l1_count < 3; ++l1_count) { | 
|  | for (int l2_count = 0; l2_count < 3; ++l2_count) { | 
|  | for (int pos = 0; pos <= l1_count; ++pos) { | 
|  | for (int first = 0; first <= l2_count; ++first) { | 
|  | for (int last = first; last <= l2_count; ++last) { | 
|  | PrepareLists(l1_count, l2_count); | 
|  |  | 
|  | l1.splice(std::next(l1.begin(), pos), std::next(l2.begin(), first), | 
|  | std::next(l2.begin(), last)); | 
|  | ll1.splice(std::next(ll1.begin(), pos), ll2, | 
|  | std::next(ll2.begin(), first), | 
|  | std::next(ll2.begin(), last)); | 
|  |  | 
|  | CheckLists(); | 
|  |  | 
|  | ASSERT_EQ(3u, secondary_list.size()); | 
|  | for (int i = 0; i < 3; ++i) { | 
|  | EXPECT_EQ(&e[i], &*std::next(secondary_list.begin(), i)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Build up a set of classes which form "challenging" type hierarchies to use | 
|  | // with an QuicheIntrusiveList. | 
|  | struct BaseLinkId {}; | 
|  | struct DerivedLinkId {}; | 
|  |  | 
|  | struct AbstractBase : public QuicheIntrusiveLink<AbstractBase, BaseLinkId> { | 
|  | virtual ~AbstractBase() = 0; | 
|  | virtual std::string name() { return "AbstractBase"; } | 
|  | }; | 
|  | AbstractBase::~AbstractBase() {} | 
|  | struct DerivedClass : public QuicheIntrusiveLink<DerivedClass, DerivedLinkId>, | 
|  | public AbstractBase { | 
|  | ~DerivedClass() override {} | 
|  | std::string name() override { return "DerivedClass"; } | 
|  | }; | 
|  | struct VirtuallyDerivedBaseClass : public virtual AbstractBase { | 
|  | ~VirtuallyDerivedBaseClass() override = 0; | 
|  | std::string name() override { return "VirtuallyDerivedBaseClass"; } | 
|  | }; | 
|  | VirtuallyDerivedBaseClass::~VirtuallyDerivedBaseClass() {} | 
|  | struct VirtuallyDerivedClassA | 
|  | : public QuicheIntrusiveLink<VirtuallyDerivedClassA, DerivedLinkId>, | 
|  | public virtual VirtuallyDerivedBaseClass { | 
|  | ~VirtuallyDerivedClassA() override {} | 
|  | std::string name() override { return "VirtuallyDerivedClassA"; } | 
|  | }; | 
|  | struct NonceClass { | 
|  | virtual ~NonceClass() {} | 
|  | int data_; | 
|  | }; | 
|  | struct VirtuallyDerivedClassB | 
|  | : public QuicheIntrusiveLink<VirtuallyDerivedClassB, DerivedLinkId>, | 
|  | public virtual NonceClass, | 
|  | public virtual VirtuallyDerivedBaseClass { | 
|  | ~VirtuallyDerivedClassB() override {} | 
|  | std::string name() override { return "VirtuallyDerivedClassB"; } | 
|  | }; | 
|  | struct VirtuallyDerivedClassC | 
|  | : public QuicheIntrusiveLink<VirtuallyDerivedClassC, DerivedLinkId>, | 
|  | public virtual AbstractBase, | 
|  | public virtual NonceClass, | 
|  | public virtual VirtuallyDerivedBaseClass { | 
|  | ~VirtuallyDerivedClassC() override {} | 
|  | std::string name() override { return "VirtuallyDerivedClassC"; } | 
|  | }; | 
|  |  | 
|  | // Test for multiple layers between the element type and the link. | 
|  | namespace templated_base_link { | 
|  | template <typename T> | 
|  | struct AbstractBase : public QuicheIntrusiveLink<T> { | 
|  | virtual ~AbstractBase() = 0; | 
|  | }; | 
|  | template <typename T> | 
|  | AbstractBase<T>::~AbstractBase() {} | 
|  | struct DerivedClass : public AbstractBase<DerivedClass> { | 
|  | int n; | 
|  | }; | 
|  | }  // namespace templated_base_link | 
|  |  | 
|  | TEST(NewIntrusiveListTest, HandleInheritanceHierarchies) { | 
|  | { | 
|  | QuicheIntrusiveList<DerivedClass, DerivedLinkId> list; | 
|  | DerivedClass elements[2]; | 
|  | EXPECT_TRUE(list.empty()); | 
|  | list.push_back(&elements[0]); | 
|  | EXPECT_EQ(1u, list.size()); | 
|  | list.push_back(&elements[1]); | 
|  | EXPECT_EQ(2u, list.size()); | 
|  | list.pop_back(); | 
|  | EXPECT_EQ(1u, list.size()); | 
|  | list.pop_back(); | 
|  | EXPECT_TRUE(list.empty()); | 
|  | } | 
|  | { | 
|  | QuicheIntrusiveList<VirtuallyDerivedClassA, DerivedLinkId> list; | 
|  | VirtuallyDerivedClassA elements[2]; | 
|  | EXPECT_TRUE(list.empty()); | 
|  | list.push_back(&elements[0]); | 
|  | EXPECT_EQ(1u, list.size()); | 
|  | list.push_back(&elements[1]); | 
|  | EXPECT_EQ(2u, list.size()); | 
|  | list.pop_back(); | 
|  | EXPECT_EQ(1u, list.size()); | 
|  | list.pop_back(); | 
|  | EXPECT_TRUE(list.empty()); | 
|  | } | 
|  | { | 
|  | QuicheIntrusiveList<VirtuallyDerivedClassC, DerivedLinkId> list; | 
|  | VirtuallyDerivedClassC elements[2]; | 
|  | EXPECT_TRUE(list.empty()); | 
|  | list.push_back(&elements[0]); | 
|  | EXPECT_EQ(1u, list.size()); | 
|  | list.push_back(&elements[1]); | 
|  | EXPECT_EQ(2u, list.size()); | 
|  | list.pop_back(); | 
|  | EXPECT_EQ(1u, list.size()); | 
|  | list.pop_back(); | 
|  | EXPECT_TRUE(list.empty()); | 
|  | } | 
|  | { | 
|  | QuicheIntrusiveList<AbstractBase, BaseLinkId> list; | 
|  | DerivedClass d1; | 
|  | VirtuallyDerivedClassA d2; | 
|  | VirtuallyDerivedClassB d3; | 
|  | VirtuallyDerivedClassC d4; | 
|  | EXPECT_TRUE(list.empty()); | 
|  | list.push_back(&d1); | 
|  | EXPECT_EQ(1u, list.size()); | 
|  | list.push_back(&d2); | 
|  | EXPECT_EQ(2u, list.size()); | 
|  | list.push_back(&d3); | 
|  | EXPECT_EQ(3u, list.size()); | 
|  | list.push_back(&d4); | 
|  | EXPECT_EQ(4u, list.size()); | 
|  | QuicheIntrusiveList<AbstractBase, BaseLinkId>::iterator it = list.begin(); | 
|  | EXPECT_EQ("DerivedClass", (it++)->name()); | 
|  | EXPECT_EQ("VirtuallyDerivedClassA", (it++)->name()); | 
|  | EXPECT_EQ("VirtuallyDerivedClassB", (it++)->name()); | 
|  | EXPECT_EQ("VirtuallyDerivedClassC", (it++)->name()); | 
|  | } | 
|  | { | 
|  | QuicheIntrusiveList<templated_base_link::DerivedClass> list; | 
|  | templated_base_link::DerivedClass elements[2]; | 
|  | EXPECT_TRUE(list.empty()); | 
|  | list.push_back(&elements[0]); | 
|  | EXPECT_EQ(1u, list.size()); | 
|  | list.push_back(&elements[1]); | 
|  | EXPECT_EQ(2u, list.size()); | 
|  | list.pop_back(); | 
|  | EXPECT_EQ(1u, list.size()); | 
|  | list.pop_back(); | 
|  | EXPECT_TRUE(list.empty()); | 
|  | } | 
|  | } | 
|  |  | 
|  | class IntrusiveListTagTypeTest : public quiche::test::QuicheTest { | 
|  | protected: | 
|  | struct Tag {}; | 
|  | class Element : public QuicheIntrusiveLink<Element, Tag> {}; | 
|  | }; | 
|  |  | 
|  | TEST_F(IntrusiveListTagTypeTest, TagTypeListID) { | 
|  | QuicheIntrusiveList<Element, Tag> list; | 
|  | { | 
|  | Element e; | 
|  | list.push_back(&e); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace test | 
|  | }  // namespace quiche |