| // Copyright 2020 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_list.h" |
| |
| #include <array> |
| #include <cstddef> |
| #include <cstdint> |
| |
| #include "pw_compilation_testing/negative_compilation.h" |
| #include "pw_containers/vector.h" |
| #include "pw_preprocessor/util.h" |
| #include "pw_unit_test/framework.h" |
| |
| namespace pw { |
| namespace { |
| |
| class TestItem : public IntrusiveList<TestItem>::Item { |
| public: |
| constexpr TestItem() : number_(0) {} |
| constexpr TestItem(int number) : number_(number) {} |
| |
| int GetNumber() const { return number_; } |
| void SetNumber(int num) { number_ = num; } |
| |
| // Add equality comparison to ensure comparisons are done by identity rather |
| // than equality for the remove function. |
| bool operator==(const TestItem& other) const { |
| return number_ == other.number_; |
| } |
| |
| private: |
| int number_; |
| }; |
| |
| TEST(IntrusiveList, Construct_InitializerList_Empty) { |
| IntrusiveList<TestItem> list({}); |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, Construct_InitializerList_One) { |
| TestItem one(1); |
| IntrusiveList<TestItem> list({&one}); |
| |
| EXPECT_EQ(&one, &list.front()); |
| } |
| |
| TEST(IntrusiveList, Construct_InitializerList_Multiple) { |
| TestItem one(1); |
| TestItem two(2); |
| TestItem thr(3); |
| |
| IntrusiveList<TestItem> list({&one, &two, &thr}); |
| auto it = list.begin(); |
| EXPECT_EQ(&one, &(*it++)); |
| EXPECT_EQ(&two, &(*it++)); |
| EXPECT_EQ(&thr, &(*it++)); |
| EXPECT_EQ(list.end(), it); |
| } |
| |
| TEST(IntrusiveList, Construct_ObjectIterator_Empty) { |
| std::array<TestItem, 0> array; |
| IntrusiveList<TestItem> list(array.begin(), array.end()); |
| |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, Construct_ObjectIterator_One) { |
| std::array<TestItem, 1> array{{{1}}}; |
| IntrusiveList<TestItem> list(array.begin(), array.end()); |
| |
| EXPECT_EQ(&array.front(), &list.front()); |
| } |
| |
| TEST(IntrusiveList, Construct_ObjectIterator_Multiple) { |
| std::array<TestItem, 3> array{{{1}, {2}, {3}}}; |
| |
| IntrusiveList<TestItem> list(array.begin(), array.end()); |
| auto it = list.begin(); |
| EXPECT_EQ(&array[0], &(*it++)); |
| EXPECT_EQ(&array[1], &(*it++)); |
| EXPECT_EQ(&array[2], &(*it++)); |
| EXPECT_EQ(list.end(), it); |
| } |
| |
| TEST(IntrusiveList, Construct_PointerIterator_Empty) { |
| std::array<TestItem*, 0> array; |
| IntrusiveList<TestItem> list(array.begin(), array.end()); |
| |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, Construct_PointerIterator_One) { |
| std::array<TestItem, 1> array{{{1}}}; |
| std::array<TestItem*, 1> ptrs{{&array[0]}}; |
| |
| IntrusiveList<TestItem> list(ptrs.begin(), ptrs.end()); |
| |
| EXPECT_EQ(ptrs[0], &list.front()); |
| } |
| |
| TEST(IntrusiveList, Construct_PointerIterator_Multiple) { |
| std::array<TestItem, 3> array{{{1}, {2}, {3}}}; |
| std::array<TestItem*, 3> ptrs{{&array[0], &array[1], &array[2]}}; |
| |
| IntrusiveList<TestItem> list(ptrs.begin(), ptrs.end()); |
| auto it = list.begin(); |
| EXPECT_EQ(ptrs[0], &(*it++)); |
| EXPECT_EQ(ptrs[1], &(*it++)); |
| EXPECT_EQ(ptrs[2], &(*it++)); |
| EXPECT_EQ(list.end(), it); |
| } |
| |
| TEST(IntrusiveList, Assign_ReplacesPriorContents) { |
| std::array<TestItem, 3> array{{{0}, {100}, {200}}}; |
| IntrusiveList<TestItem> list(array.begin(), array.end()); |
| |
| list.assign(array.begin() + 1, array.begin() + 2); |
| |
| auto it = list.begin(); |
| EXPECT_EQ(&array[1], &(*it++)); |
| EXPECT_EQ(list.end(), it); |
| } |
| |
| TEST(IntrusiveList, Assign_EmptyRange) { |
| std::array<TestItem, 3> array{{{0}, {100}, {200}}}; |
| IntrusiveList<TestItem> list(array.begin(), array.end()); |
| |
| list.assign(array.begin() + 1, array.begin() + 1); |
| |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, PushOne) { |
| constexpr int kMagicValue = 31; |
| TestItem item1(kMagicValue); |
| IntrusiveList<TestItem> list; |
| list.push_back(item1); |
| EXPECT_FALSE(list.empty()); |
| EXPECT_EQ(list.front().GetNumber(), kMagicValue); |
| } |
| |
| TEST(IntrusiveList, PushThree) { |
| TestItem item1(1); |
| TestItem item2(2); |
| TestItem item3(3); |
| |
| IntrusiveList<TestItem> list; |
| list.push_back(item1); |
| list.push_back(item2); |
| list.push_back(item3); |
| |
| int loop_count = 0; |
| for (auto& test_item : list) { |
| loop_count++; |
| EXPECT_EQ(loop_count, test_item.GetNumber()); |
| } |
| EXPECT_EQ(loop_count, 3); |
| } |
| |
| TEST(IntrusiveList, IsEmpty) { |
| TestItem item1(1); |
| |
| IntrusiveList<TestItem> list; |
| EXPECT_TRUE(list.empty()); |
| |
| list.push_back(item1); |
| EXPECT_FALSE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, InsertAfter) { |
| // Create a test item to insert midway through the list. |
| constexpr int kMagicValue = 42; |
| TestItem inserted_item(kMagicValue); |
| |
| // Create initial values to fill in the start/end. |
| TestItem item_array[20]; |
| |
| IntrusiveList<TestItem> list; |
| // Fill the list with TestItem objects that have a value of zero. |
| for (size_t i = 0; i < PW_ARRAY_SIZE(item_array); ++i) { |
| item_array[i].SetNumber(0); |
| list.push_back(item_array[i]); |
| } |
| |
| // Move an iterator to the middle of the list, and then insert the magic item. |
| auto it = list.begin(); |
| size_t expected_index = 1; // Expected index is iterator index + 1. |
| for (size_t i = 0; i < PW_ARRAY_SIZE(item_array) / 2; ++i) { |
| it++; |
| expected_index++; |
| } |
| it = list.insert_after(it, inserted_item); |
| |
| // Ensure the returned iterator from insert_after is the newly inserted |
| // element. |
| EXPECT_EQ(it->GetNumber(), kMagicValue); |
| |
| // Ensure the value is in the expected location (index of the iterator + 1). |
| size_t i = 0; |
| for (TestItem& item : list) { |
| if (item.GetNumber() == kMagicValue) { |
| EXPECT_EQ(i, expected_index); |
| } else { |
| EXPECT_EQ(item.GetNumber(), 0); |
| } |
| i++; |
| } |
| |
| // Ensure the list didn't break and change sizes. |
| EXPECT_EQ(i, PW_ARRAY_SIZE(item_array) + 1); |
| } |
| |
| TEST(IntrusiveList, InsertAfterBeforeBegin) { |
| // Create a test item to insert at the beginning of the list. |
| constexpr int kMagicValue = 42; |
| TestItem inserted_item(kMagicValue); |
| |
| // Create initial values to fill in the start/end. |
| TestItem item_array[20]; |
| |
| IntrusiveList<TestItem> list; |
| // Fill the list with TestItem objects that have a value of zero. |
| for (size_t i = 0; i < PW_ARRAY_SIZE(item_array); ++i) { |
| item_array[i].SetNumber(0); |
| list.push_back(item_array[i]); |
| } |
| |
| auto it = list.insert_after(list.before_begin(), inserted_item); |
| |
| // Ensure the returned iterator from insert_after is the newly inserted |
| // element. |
| EXPECT_EQ(it->GetNumber(), kMagicValue); |
| |
| // Ensure the value is at the beginning of the list. |
| size_t i = 0; |
| for (TestItem& item : list) { |
| if (item.GetNumber() == kMagicValue) { |
| EXPECT_EQ(i, static_cast<size_t>(0)); |
| } else { |
| EXPECT_EQ(item.GetNumber(), 0); |
| } |
| i++; |
| } |
| } |
| |
| TEST(IntrusiveList, PushFront) { |
| constexpr int kMagicValue = 42; |
| TestItem pushed_item(kMagicValue); |
| |
| TestItem item_array[20]; |
| IntrusiveList<TestItem> list; |
| // Fill the list with TestItem objects that have a value of zero. |
| for (size_t i = 0; i < PW_ARRAY_SIZE(item_array); ++i) { |
| item_array[i].SetNumber(0); |
| list.push_back(item_array[i]); |
| } |
| |
| // Create a test item to push to the front of the list. |
| list.push_front(pushed_item); |
| EXPECT_EQ(list.front().GetNumber(), kMagicValue); |
| } |
| |
| TEST(IntrusiveList, Clear_Empty) { |
| IntrusiveList<TestItem> list; |
| EXPECT_TRUE(list.empty()); |
| list.clear(); |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, Clear_OneItem) { |
| TestItem item(42); |
| IntrusiveList<TestItem> list; |
| list.push_back(item); |
| EXPECT_FALSE(list.empty()); |
| list.clear(); |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, Clear_TwoItems) { |
| TestItem item1(42); |
| TestItem item2(42); |
| IntrusiveList<TestItem> list; |
| list.push_back(item1); |
| list.push_back(item2); |
| EXPECT_FALSE(list.empty()); |
| list.clear(); |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, Clear_ReinsertClearedItems) { |
| std::array<TestItem, 20> item_array; |
| IntrusiveList<TestItem> list; |
| EXPECT_TRUE(list.empty()); |
| list.clear(); |
| EXPECT_TRUE(list.empty()); |
| |
| // Fill the list with TestItem objects. |
| for (size_t i = 0; i < item_array.size(); ++i) { |
| item_array[i].SetNumber(0); |
| list.push_back(item_array[i]); |
| } |
| |
| // Remove everything. |
| list.clear(); |
| EXPECT_TRUE(list.empty()); |
| |
| // Ensure all the removed elements can still be added back to a list. |
| for (size_t i = 0; i < item_array.size(); ++i) { |
| item_array[i].SetNumber(0); |
| list.push_back(item_array[i]); |
| } |
| } |
| |
| TEST(IntrusiveList, PopFront) { |
| constexpr int kValue1 = 32; |
| constexpr int kValue2 = 4083; |
| |
| TestItem item1(kValue1); |
| TestItem item2(kValue2); |
| |
| IntrusiveList<TestItem> list; |
| EXPECT_TRUE(list.empty()); |
| |
| list.push_front(item2); |
| list.push_front(item1); |
| list.pop_front(); |
| EXPECT_EQ(list.front().GetNumber(), kValue2); |
| EXPECT_FALSE(list.empty()); |
| list.pop_front(); |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, PopFrontAndReinsert) { |
| constexpr int kValue1 = 32; |
| constexpr int kValue2 = 4083; |
| |
| TestItem item1(kValue1); |
| TestItem item2(kValue2); |
| |
| IntrusiveList<TestItem> list; |
| EXPECT_TRUE(list.empty()); |
| |
| list.push_front(item2); |
| list.push_front(item1); |
| list.pop_front(); |
| list.push_front(item1); |
| EXPECT_EQ(list.front().GetNumber(), kValue1); |
| } |
| |
| TEST(IntrusiveList, ListFront) { |
| TestItem item1(1); |
| TestItem item2(0); |
| TestItem item3(0xffff); |
| |
| IntrusiveList<TestItem> list; |
| list.push_back(item1); |
| list.push_back(item2); |
| list.push_back(item3); |
| |
| EXPECT_EQ(&item1, &list.front()); |
| EXPECT_EQ(&item1, &(*list.begin())); |
| } |
| |
| TEST(IntrusiveList, IteratorIncrement) { |
| TestItem item_array[20]; |
| IntrusiveList<TestItem> list; |
| for (size_t i = 0; i < PW_ARRAY_SIZE(item_array); ++i) { |
| item_array[i].SetNumber(static_cast<int>(i)); |
| list.push_back(item_array[i]); |
| } |
| |
| auto it = list.begin(); |
| int i = 0; |
| while (it != list.end()) { |
| if (i == 0) { |
| // Test pre-incrementing on the first element. |
| EXPECT_EQ((++it)->GetNumber(), item_array[++i].GetNumber()); |
| } else { |
| EXPECT_EQ((it++)->GetNumber(), item_array[i++].GetNumber()); |
| } |
| } |
| } |
| |
| TEST(IntrusiveList, ConstIteratorRead) { |
| // For this test, items are checked to be non-zero. |
| TestItem item1(1); |
| TestItem item2(99); |
| IntrusiveList<TestItem> list; |
| |
| const IntrusiveList<TestItem>* const_list = &list; |
| |
| list.push_back(item1); |
| list.push_back(item2); |
| |
| auto it = const_list->begin(); |
| while (it != const_list->end()) { |
| EXPECT_NE(it->GetNumber(), 0); |
| it++; |
| } |
| } |
| |
| TEST(IntrusiveList, CompareConstAndNonConstIterator) { |
| IntrusiveList<TestItem> list; |
| EXPECT_EQ(list.end(), list.cend()); |
| } |
| |
| #if PW_NC_TEST(IncompatibleIteratorTypes) |
| PW_NC_EXPECT("comparison (of|between) distinct pointer types"); |
| |
| struct OtherItem : public IntrusiveList<OtherItem>::Item {}; |
| |
| TEST(IntrusiveList, CompareConstAndNonConstIterator_CompilationFails) { |
| IntrusiveList<TestItem> list; |
| IntrusiveList<OtherItem> list2; |
| static_cast<void>(list.end() == list2.end()); |
| } |
| |
| #endif // PW_NC_TEST |
| |
| #if PW_NC_TEST(CannotModifyThroughConstIterator) |
| PW_NC_EXPECT("function is not marked const|discards qualifiers"); |
| |
| TEST(IntrusiveList, ConstIteratorModify) { |
| TestItem item1(1); |
| TestItem item2(99); |
| IntrusiveList<TestItem> list; |
| |
| const IntrusiveList<TestItem>* const_list = &list; |
| |
| list.push_back(item1); |
| list.push_back(item2); |
| |
| auto it = const_list->begin(); |
| while (it != const_list->end()) { |
| it->SetNumber(0); |
| it++; |
| } |
| } |
| #endif // PW_NC_TEST |
| |
| // TODO: b/235289499 - These tests should trigger a CHECK failure. This requires |
| // using a testing version of pw_assert. |
| #define TESTING_CHECK_FAILURES_IS_SUPPORTED 0 |
| #if TESTING_CHECK_FAILURES_IS_SUPPORTED |
| TEST(IntrusiveList, Construct_DuplicateItems) { |
| TestItem item(1); |
| IntrusiveList<TestItem> list({&item, &item}); |
| } |
| |
| TEST(IntrusiveList, InsertAfter_SameItem) { |
| TestItem item(1); |
| IntrusiveList<TestItem> list({&item}); |
| |
| list.insert_after(list.begin(), item); |
| } |
| |
| TEST(IntrusiveList, InsertAfter_SameItemAfterEnd) { |
| TestItem item(1); |
| IntrusiveList<TestItem> list({&item}); |
| |
| list.insert_after(list.end(), item); |
| } |
| |
| TEST(IntrusiveList, PushBack_SameItem) { |
| TestItem item(1); |
| IntrusiveList<TestItem> list({&item}); |
| |
| list.push_back(item); |
| } |
| |
| TEST(IntrusiveList, PushFront_SameItem) { |
| TestItem item(1); |
| IntrusiveList<TestItem> list({&item}); |
| |
| list.push_front(item); |
| } |
| #endif // TESTING_CHECK_FAILURES_IS_SUPPORTED |
| |
| TEST(IntrusiveList, EraseAfter_FirstItem) { |
| std::array<TestItem, 3> items{{{0}, {1}, {2}}}; |
| IntrusiveList<TestItem> list(items.begin(), items.end()); |
| |
| auto it = list.erase_after(list.before_begin()); |
| EXPECT_EQ(list.begin(), it); |
| EXPECT_EQ(&items[1], &list.front()); |
| } |
| |
| TEST(IntrusiveList, EraseAfter_LastItem) { |
| std::array<TestItem, 3> items{{{0}, {1}, {2}}}; |
| IntrusiveList<TestItem> list(items.begin(), items.end()); |
| |
| auto it = list.begin(); |
| ++it; |
| |
| it = list.erase_after(it); |
| EXPECT_EQ(list.end(), it); |
| |
| it = list.begin(); |
| ++it; |
| |
| EXPECT_EQ(&items[1], &(*it)); |
| } |
| |
| TEST(IntrusiveList, EraseAfter_AllItems) { |
| std::array<TestItem, 3> items{{{0}, {1}, {2}}}; |
| IntrusiveList<TestItem> list(items.begin(), items.end()); |
| |
| list.erase_after(list.begin()); |
| list.erase_after(list.begin()); |
| auto it = list.erase_after(list.before_begin()); |
| |
| EXPECT_EQ(list.end(), it); |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, Remove_EmptyList) { |
| std::array<TestItem, 1> items{{{3}}}; |
| IntrusiveList<TestItem> list(items.begin(), items.begin()); // Add nothing! |
| |
| EXPECT_TRUE(list.empty()); |
| EXPECT_FALSE(list.remove(items[0])); |
| } |
| |
| TEST(IntrusiveList, Remove_SingleItem_NotPresent) { |
| std::array<TestItem, 1> items{{{1}}}; |
| IntrusiveList<TestItem> list(items.begin(), items.end()); |
| |
| EXPECT_FALSE(list.remove(TestItem(1))); |
| EXPECT_EQ(&items.front(), &list.front()); |
| } |
| |
| TEST(IntrusiveList, Remove_SingleItem_Removed) { |
| std::array<TestItem, 1> items{{{1}}}; |
| IntrusiveList<TestItem> list(items.begin(), items.end()); |
| |
| EXPECT_TRUE(list.remove(items[0])); |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| TEST(IntrusiveList, Remove_MultipleItems_NotPresent) { |
| std::array<TestItem, 5> items{{{1}, {1}, {2}, {3}, {4}}}; |
| IntrusiveList<TestItem> list(items.begin(), items.end()); |
| |
| EXPECT_FALSE(list.remove(TestItem(1))); |
| } |
| |
| TEST(IntrusiveList, Remove_MultipleItems_RemoveAndPushBack) { |
| std::array<TestItem, 5> items{{{1}, {1}, {2}, {3}, {4}}}; |
| IntrusiveList<TestItem> list(items.begin(), items.end()); |
| |
| EXPECT_TRUE(list.remove(items[0])); |
| EXPECT_TRUE(list.remove(items[3])); |
| list.push_back(items[0]); // Make sure can add the item after removing it. |
| |
| auto it = list.begin(); |
| EXPECT_EQ(&items[1], &(*it++)); |
| EXPECT_EQ(&items[2], &(*it++)); |
| EXPECT_EQ(&items[4], &(*it++)); |
| EXPECT_EQ(&items[0], &(*it++)); |
| EXPECT_EQ(list.end(), it); |
| } |
| |
| TEST(IntrusiveList, ItemsRemoveThemselvesFromListsWhenDestructed) { |
| // Create a list with some items it. |
| TestItem a, b, c, d; |
| IntrusiveList<TestItem> list; |
| list.push_back(a); |
| list.push_back(b); |
| list.push_back(c); |
| list.push_back(d); |
| |
| // Insert items that will be destructed before the list. |
| { |
| TestItem x, y, z, w; |
| list.push_back(x); |
| list.push_back(z); |
| list.push_front(y); |
| list.push_front(w); |
| |
| auto it = list.begin(); |
| EXPECT_EQ(&w, &(*it++)); |
| EXPECT_EQ(&y, &(*it++)); |
| EXPECT_EQ(&a, &(*it++)); |
| EXPECT_EQ(&b, &(*it++)); |
| EXPECT_EQ(&c, &(*it++)); |
| EXPECT_EQ(&d, &(*it++)); |
| EXPECT_EQ(&x, &(*it++)); |
| EXPECT_EQ(&z, &(*it++)); |
| EXPECT_EQ(list.end(), it); |
| |
| // Here, x, y, z, w are removed from the list for the destructor. |
| } |
| |
| // Ensure we get back our original list. |
| auto it = list.begin(); |
| EXPECT_EQ(&a, &(*it++)); |
| EXPECT_EQ(&b, &(*it++)); |
| EXPECT_EQ(&c, &(*it++)); |
| EXPECT_EQ(&d, &(*it++)); |
| EXPECT_EQ(list.end(), it); |
| } |
| |
| TEST(IntrusiveList, SizeBasic) { |
| IntrusiveList<TestItem> list; |
| EXPECT_EQ(list.size(), 0u); |
| |
| TestItem one(55); |
| list.push_front(one); |
| EXPECT_EQ(list.size(), static_cast<size_t>(1)); |
| |
| TestItem two(66); |
| list.push_back(two); |
| EXPECT_EQ(list.size(), static_cast<size_t>(2)); |
| |
| TestItem thr(77); |
| list.push_back(thr); |
| EXPECT_EQ(list.size(), static_cast<size_t>(3)); |
| } |
| |
| TEST(IntrusiveList, SizeScoped) { |
| IntrusiveList<TestItem> list; |
| EXPECT_EQ(list.size(), 0u); |
| |
| // Add elements in new scopes; verify size on the way in and on the way out. |
| { |
| TestItem one(55); |
| list.push_back(one); |
| EXPECT_EQ(list.size(), static_cast<size_t>(1)); |
| |
| { |
| TestItem two(66); |
| list.push_back(two); |
| EXPECT_EQ(list.size(), static_cast<size_t>(2)); |
| { |
| TestItem thr(77); |
| list.push_back(thr); |
| EXPECT_EQ(list.size(), static_cast<size_t>(3)); |
| } |
| EXPECT_EQ(list.size(), static_cast<size_t>(2)); |
| } |
| EXPECT_EQ(list.size(), static_cast<size_t>(1)); |
| } |
| EXPECT_EQ(list.size(), static_cast<size_t>(0)); |
| } |
| |
| // Test that a list of items derived from a different Item class can be created. |
| class DerivedTestItem : public TestItem {}; |
| |
| TEST(IntrusiveList, AddItemsOfDerivedClassToList) { |
| IntrusiveList<TestItem> list; |
| |
| DerivedTestItem item1; |
| list.push_front(item1); |
| |
| TestItem item2; |
| list.push_front(item2); |
| |
| EXPECT_EQ(2u, list.size()); |
| } |
| |
| TEST(IntrusiveList, ListOfDerivedClassItems) { |
| IntrusiveList<DerivedTestItem> derived_from_compatible_item_type; |
| |
| DerivedTestItem item1; |
| derived_from_compatible_item_type.push_front(item1); |
| |
| EXPECT_EQ(1u, derived_from_compatible_item_type.size()); |
| |
| #if PW_NC_TEST(CannotAddBaseClassToDerivedClassList) |
| PW_NC_EXPECT_CLANG("cannot bind to a value of unrelated type"); |
| PW_NC_EXPECT_GCC("cannot convert"); |
| |
| TestItem item2; |
| derived_from_compatible_item_type.push_front(item2); |
| #endif |
| } |
| |
| #if PW_NC_TEST(IncompatibileItemType) |
| PW_NC_EXPECT("IntrusiveList items must be derived from IntrusiveList<T>::Item"); |
| |
| struct Foo {}; |
| |
| class BadItem : public IntrusiveList<Foo>::Item {}; |
| |
| [[maybe_unused]] IntrusiveList<BadItem> derived_from_incompatible_item_type; |
| |
| #elif PW_NC_TEST(DoesNotInheritFromItem) |
| PW_NC_EXPECT("IntrusiveList items must be derived from IntrusiveList<T>::Item"); |
| |
| struct NotAnItem {}; |
| |
| [[maybe_unused]] IntrusiveList<NotAnItem> list; |
| |
| #endif // PW_NC_TEST |
| |
| TEST(IntrusiveList, MoveUnlistedItems) { |
| TestItem item1(3); |
| EXPECT_EQ(item1.GetNumber(), 3); |
| |
| TestItem item2(std::move(item1)); |
| EXPECT_EQ(item2.GetNumber(), 3); |
| |
| TestItem item3; |
| item3 = std::move(item2); |
| EXPECT_EQ(item3.GetNumber(), 3); |
| } |
| |
| TEST(IntrusiveList, MoveListedItemsToUnlistedItems) { |
| std::array<TestItem, 2> items{{{1}, {2}}}; |
| IntrusiveList<TestItem> list(items.begin(), items.end()); |
| |
| TestItem item1(std::move(items[0])); |
| TestItem item2 = std::move(items[1]); |
| |
| ASSERT_EQ(list.size(), 2U); |
| auto iter = list.begin(); |
| EXPECT_EQ(iter->GetNumber(), item1.GetNumber()); |
| ++iter; |
| EXPECT_EQ(iter->GetNumber(), item2.GetNumber()); |
| } |
| |
| TEST(IntrusiveList, MoveUnlistedItemsToListedItems) { |
| TestItem item1(1); |
| TestItem item2(2); |
| TestItem item3(3); |
| std::array<TestItem, 3> items{{{4}, {5}, {6}}}; |
| IntrusiveList<TestItem> list(items.begin(), items.end()); |
| items[0] = std::move(item1); |
| items[1] = std::move(item2); |
| items[2] = std::move(item3); |
| int i = 0; |
| for (const auto& item : list) { |
| EXPECT_EQ(item.GetNumber(), ++i); |
| } |
| } |
| |
| TEST(IntrusiveList, MoveListedItems) { |
| std::array<TestItem, 3> src{{{1}, {2}, {3}}}; |
| IntrusiveList<TestItem> list1(src.begin(), src.end()); |
| |
| std::array<TestItem, 3> dst{{{4}, {5}, {6}}}; |
| IntrusiveList<TestItem> list2(dst.begin(), dst.end()); |
| |
| for (size_t i = 0; i < 3; ++i) { |
| dst[i] = std::move(src[i]); |
| } |
| int i = 0; |
| for (const auto& item : list1) { |
| EXPECT_EQ(item.GetNumber(), ++i); |
| } |
| EXPECT_TRUE(list2.empty()); |
| } |
| |
| TEST(IntrusiveList, MoveItemsToVector) { |
| Vector<TestItem, 3> vec; |
| vec.emplace_back(TestItem(1)); |
| vec.emplace_back(TestItem(2)); |
| vec.emplace_back(TestItem(3)); |
| IntrusiveList<TestItem> list; |
| list.assign(vec.begin(), vec.end()); |
| |
| auto iter = list.begin(); |
| for (const auto& item : vec) { |
| EXPECT_NE(iter, list.end()); |
| if (iter == list.end()) { |
| break; |
| } |
| EXPECT_EQ(item.GetNumber(), iter->GetNumber()); |
| ++iter; |
| } |
| |
| // TODO(b/313899658): Vector has an MSAN bug in its destructor when clearing |
| // items that reference themselves. Workaround it by manually clearing. |
| vec.clear(); |
| } |
| |
| } // namespace |
| } // namespace pw |