// 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 #include #include #include "gtest/gtest.h" #include "pw_preprocessor/util.h" namespace pw { namespace { class TestItem : public IntrusiveList::Item { public: TestItem() : number_(0) {} 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 list({}); EXPECT_TRUE(list.empty()); } TEST(IntrusiveList, Construct_InitializerList_One) { TestItem one(1); IntrusiveList list({&one}); EXPECT_EQ(&one, &list.front()); } TEST(IntrusiveList, Construct_InitializerList_Multiple) { TestItem one(1); TestItem two(2); TestItem thr(3); IntrusiveList 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 array; IntrusiveList list(array.begin(), array.end()); EXPECT_TRUE(list.empty()); } TEST(IntrusiveList, Construct_ObjectIterator_One) { std::array array{{{1}}}; IntrusiveList list(array.begin(), array.end()); EXPECT_EQ(&array.front(), &list.front()); } TEST(IntrusiveList, Construct_ObjectIterator_Multiple) { std::array array{{{1}, {2}, {3}}}; IntrusiveList 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 array; IntrusiveList list(array.begin(), array.end()); EXPECT_TRUE(list.empty()); } TEST(IntrusiveList, Construct_PointerIterator_One) { std::array array{{{1}}}; std::array ptrs{{&array[0]}}; IntrusiveList list(ptrs.begin(), ptrs.end()); EXPECT_EQ(ptrs[0], &list.front()); } TEST(IntrusiveList, Construct_PointerIterator_Multiple) { std::array array{{{1}, {2}, {3}}}; std::array ptrs{{&array[0], &array[1], &array[2]}}; IntrusiveList 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 array{{{0}, {100}, {200}}}; IntrusiveList 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 array{{{0}, {100}, {200}}}; IntrusiveList 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 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 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 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 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 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(0)); } else { EXPECT_EQ(item.GetNumber(), 0); } i++; } } TEST(IntrusiveList, PushFront) { constexpr int kMagicValue = 42; TestItem pushed_item(kMagicValue); TestItem item_array[20]; IntrusiveList 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 list; EXPECT_TRUE(list.empty()); list.clear(); EXPECT_TRUE(list.empty()); } TEST(IntrusiveList, Clear_OneItem) { TestItem item(42); IntrusiveList 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 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 item_array; IntrusiveList 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 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 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 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 list; for (size_t i = 0; i < PW_ARRAY_SIZE(item_array); ++i) { item_array[i].SetNumber(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 list; const IntrusiveList* 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 list; EXPECT_EQ(list.end(), list.cend()); } #if defined(PW_COMPILE_FAIL_TEST_incompatible_iterator_types) struct OtherItem : public IntrusiveList::Item {}; TEST(IntrusiveList, CompareConstAndNonConstIterator_CompilationFails) { IntrusiveList list; IntrusiveList list2; static_cast(list.end() == list2.end()); } #endif // TODO(pwbug/47): These tests should fail to compile, enable when no-compile // tests are set up in Pigweed. #if defined(PW_COMPILE_FAIL_TEST_cannot_modify_through_const_iterator) TEST(IntrusiveList, ConstIteratorModify) { TestItem item1(1); TestItem item2(99); IntrusiveList list; const IntrusiveList* 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 // Compile failure test // TODO(pwbug/88): 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 list({&item, &item}); } TEST(IntrusiveList, InsertAfter_SameItem) { TestItem item(1); IntrusiveList list({&item}); list.insert_after(list.begin(), item); } TEST(IntrusiveList, InsertAfter_SameItemAfterEnd) { TestItem item(1); IntrusiveList list({&item}); list.insert_after(list.end(), item); } TEST(IntrusiveList, PushBack_SameItem) { TestItem item(1); IntrusiveList list({&item}); list.push_back(item); } TEST(IntrusiveList, PushFront_SameItem) { TestItem item(1); IntrusiveList list({&item}); list.push_front(item); } #endif // TESTING_CHECK_FAILURES_IS_SUPPORTED TEST(IntrusiveList, EraseAfter_FirstItem) { std::array items{{{0}, {1}, {2}}}; IntrusiveList 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 items{{{0}, {1}, {2}}}; IntrusiveList 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 items{{{0}, {1}, {2}}}; IntrusiveList 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 items{{{3}}}; IntrusiveList list(items.begin(), items.begin()); // Add nothing! EXPECT_TRUE(list.empty()); EXPECT_FALSE(list.remove(items[0])); } TEST(IntrusiveList, Remove_SingleItem_NotPresent) { std::array items{{{1}}}; IntrusiveList list(items.begin(), items.end()); EXPECT_FALSE(list.remove(TestItem(1))); EXPECT_EQ(&items.front(), &list.front()); } TEST(IntrusiveList, Remove_SingleItem_Removed) { std::array items{{{1}}}; IntrusiveList list(items.begin(), items.end()); EXPECT_TRUE(list.remove(items[0])); EXPECT_TRUE(list.empty()); } TEST(IntrusiveList, Remove_MultipleItems_NotPresent) { std::array items{{{1}, {1}, {2}, {3}, {4}}}; IntrusiveList list(items.begin(), items.end()); EXPECT_FALSE(list.remove(TestItem(1))); } TEST(IntrusiveList, Remove_MultipleItems_RemoveAndPushBack) { std::array items{{{1}, {1}, {2}, {3}, {4}}}; IntrusiveList 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 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 list; EXPECT_EQ(list.size(), 0u); TestItem one(55); list.push_front(one); EXPECT_EQ(list.size(), static_cast(1)); TestItem two(66); list.push_back(two); EXPECT_EQ(list.size(), static_cast(2)); TestItem thr(77); list.push_back(thr); EXPECT_EQ(list.size(), static_cast(3)); } TEST(IntrusiveList, SizeScoped) { IntrusiveList 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(1)); { TestItem two(66); list.push_back(two); EXPECT_EQ(list.size(), static_cast(2)); { TestItem thr(77); list.push_back(thr); EXPECT_EQ(list.size(), static_cast(3)); } EXPECT_EQ(list.size(), static_cast(2)); } EXPECT_EQ(list.size(), static_cast(1)); } EXPECT_EQ(list.size(), static_cast(0)); } // Test that a list of items derived from a different Item class can be created. class DerivedTestItem : public TestItem {}; TEST(InstrusiveList, AddItemsOfDerivedClassToList) { IntrusiveList list; DerivedTestItem item1; list.push_front(item1); TestItem item2; list.push_front(item2); EXPECT_EQ(2u, list.size()); } TEST(InstrusiveList, ListOfDerivedClassItems) { IntrusiveList derived_from_compatible_item_type; DerivedTestItem item1; derived_from_compatible_item_type.push_front(item1); EXPECT_EQ(1u, derived_from_compatible_item_type.size()); // TODO(pwbug/47): Make these proper automated compilation failure tests. #if defined(PW_COMPILE_FAIL_TEST_cannot_add_base_class_to_derived_class_list) TestItem item2; derived_from_compatible_item_type.push_front(item2); #endif } #if defined(PW_COMPILE_FAIL_TEST_incompatibile_item_type) struct Foo {}; class BadItem : public IntrusiveList::Item {}; [[maybe_unused]] IntrusiveList derived_from_incompatible_item_type; #elif defined(PW_COMPILE_FAIL_TEST_does_not_inherit_from_item) struct NotAnItem {}; [[maybe_unused]] IntrusiveList list; #endif } // namespace } // namespace pw