// SPDX-License-Identifier: BSD-2-Clause // author: Max Kellermann #include "util/IntrusiveList.hxx" #include "util/SortList.hxx" #include #include namespace { template struct CharItem final : IntrusiveListHook { char ch; constexpr CharItem(char _ch) noexcept:ch(_ch) {} }; template static std::string ToString(const IntrusiveList> &list, typename IntrusiveList>::const_iterator it, std::size_t n) noexcept { std::string result; for (std::size_t i = 0; i < n; ++i, ++it) result.push_back(it == list.end() ? '_' : it->ch); return result; } template static std::string ToStringReverse(const IntrusiveList> &list, typename IntrusiveList>::const_iterator it, std::size_t n) noexcept { std::string result; for (std::size_t i = 0; i < n; ++i, --it) result.push_back(it == list.end() ? '_' : it->ch); return result; } } // anonymous namespace TEST(IntrusiveList, Basic) { using Item = CharItem; Item items[]{'a', 'b', 'c'}; IntrusiveList list; for (auto &i : items) list.push_back(i); ASSERT_EQ(ToString(list, list.begin(), 5), "abc_a"); ASSERT_EQ(ToStringReverse(list, list.begin(), 5), "a_cba"); items[1].unlink(); ASSERT_EQ(ToString(list, list.begin(), 4), "ac_a"); ASSERT_EQ(ToStringReverse(list, list.begin(), 4), "a_ca"); IntrusiveList other_list; Item other_items[]{'d', 'e', 'f', 'g'}; for (auto &i : other_items) other_list.push_back(i); list.splice(std::next(list.begin()), other_list, other_list.iterator_to(other_items[1]), other_list.iterator_to(other_items[3]), 2); ASSERT_EQ(ToString(other_list, other_list.begin(), 4), "dg_d"); ASSERT_EQ(ToStringReverse(other_list, other_list.begin(), 4), "d_gd"); ASSERT_EQ(ToString(list, list.begin(), 6), "aefc_a"); ASSERT_EQ(ToStringReverse(list, list.begin(), 6), "a_cfea"); } TEST(IntrusiveList, Track) { using Item = CharItem; Item items[]{'a', 'b', 'c'}; for (const auto &i : items) ASSERT_FALSE(i.is_linked()); IntrusiveList list; list.push_back(items[1]); list.push_back(items[2]); list.push_front(items[0]); for (const auto &i : items) ASSERT_TRUE(i.is_linked()); ASSERT_EQ(ToString(list, list.begin(), 5), "abc_a"); ASSERT_EQ(ToStringReverse(list, list.begin(), 5), "a_cba"); items[1].unlink(); ASSERT_TRUE(items[0].is_linked()); ASSERT_FALSE(items[1].is_linked()); ASSERT_TRUE(items[2].is_linked()); ASSERT_EQ(ToString(list, list.begin(), 4), "ac_a"); ASSERT_EQ(ToStringReverse(list, list.begin(), 4), "a_ca"); list.erase(list.iterator_to(items[0])); ASSERT_FALSE(items[0].is_linked()); ASSERT_FALSE(items[1].is_linked()); ASSERT_TRUE(items[2].is_linked()); ASSERT_EQ(ToString(list, list.begin(), 3), "c_c"); ASSERT_EQ(ToStringReverse(list, list.begin(), 3), "c_c"); list.clear(); ASSERT_FALSE(items[0].is_linked()); ASSERT_FALSE(items[1].is_linked()); ASSERT_FALSE(items[2].is_linked()); ASSERT_EQ(ToString(list, list.begin(), 2), "__"); ASSERT_EQ(ToStringReverse(list, list.begin(), 2), "__"); { IntrusiveList list2; list2.push_back(items[0]); ASSERT_TRUE(items[0].is_linked()); } ASSERT_FALSE(items[0].is_linked()); } TEST(IntrusiveList, AutoUnlink) { using Item = CharItem; Item a{'a'}; ASSERT_FALSE(a.is_linked()); IntrusiveList list; Item b{'b'}; ASSERT_FALSE(b.is_linked()); { Item c{'c'}; list.push_back(a); list.push_back(b); list.push_back(c); ASSERT_TRUE(a.is_linked()); ASSERT_TRUE(b.is_linked()); ASSERT_TRUE(c.is_linked()); ASSERT_EQ(ToString(list, list.begin(), 5), "abc_a"); } ASSERT_EQ(ToString(list, list.begin(), 5), "ab_ab"); ASSERT_TRUE(a.is_linked()); ASSERT_TRUE(b.is_linked()); } TEST(IntrusiveList, Tag) { struct A {}; struct B {}; struct TaggedItem : public IntrusiveListHook, public IntrusiveListHook {}; TaggedItem one, two; IntrusiveList> a; IntrusiveList> b; EXPECT_TRUE(a.empty()); EXPECT_TRUE(b.empty()); a.push_back(one); a.push_back(two); EXPECT_FALSE(a.empty()); EXPECT_TRUE(b.empty()); b.push_back(one); EXPECT_FALSE(a.empty()); EXPECT_FALSE(b.empty()); a.clear(); EXPECT_TRUE(a.empty()); EXPECT_FALSE(b.empty()); a.push_back(two); a.push_back(one); EXPECT_FALSE(a.empty()); EXPECT_FALSE(b.empty()); b.erase(b.iterator_to(one)); EXPECT_FALSE(a.empty()); EXPECT_TRUE(b.empty()); } TEST(IntrusiveList, Merge) { using Item = CharItem; const auto predicate = [](const Item &a, const Item &b){ return a.ch < b.ch; }; Item items[]{'c', 'k', 'u'}; IntrusiveList list; for (auto &i : items) list.push_back(i); IntrusiveList other_list; Item other_items[]{'a', 'b', 'g', 'm', 'n', 'x', 'y', 'z'}; for (auto &i : other_items) other_list.push_back(i); MergeList(list, other_list, predicate); ASSERT_EQ(ToString(list, list.begin(), 13), "abcgkmnuxyz_a"); ASSERT_TRUE(other_list.empty()); Item more_items[]{'a', 'o', 'p', 'q'}; for (auto &i : more_items) other_list.push_back(i); MergeList(list, other_list, predicate); ASSERT_EQ(ToString(list, list.begin(), 17), "aabcgkmnopquxyz_a"); ASSERT_EQ(&*list.begin(), &other_items[0]); ASSERT_EQ(&*std::next(list.begin()), &more_items[0]); } TEST(IntrusiveList, Sort) { using Item = CharItem; const auto predicate = [](const Item &a, const Item &b){ return a.ch < b.ch; }; Item items[]{'z', 'a', 'b', 'q', 'b', 'c', 't', 'm', 'y'}; IntrusiveList list; SortList(list, predicate); ASSERT_EQ(ToString(list, list.begin(), 2), "__"); list.push_back(items[0]); SortList(list, predicate); ASSERT_EQ(ToString(list, list.begin(), 3), "z_z"); list.push_back(items[1]); SortList(list, predicate); ASSERT_EQ(ToString(list, list.begin(), 4), "az_a"); SortList(list, predicate); ASSERT_EQ(ToString(list, list.begin(), 4), "az_a"); list.clear(); for (auto &i : items) list.push_back(i); SortList(list, predicate); ASSERT_EQ(ToString(list, list.begin(), 11), "abbcmqtyz_a"); ASSERT_EQ(&*std::next(list.begin(), 1), &items[2]); ASSERT_EQ(&*std::next(list.begin(), 2), &items[4]); } /** * Call clear_and_dispose(), and let the disposer unlink the last * item. */ TEST(IntrusiveList, ClearDisposeUnlink) { using Item = CharItem; Item a{'a'}, b{'b'}; bool a_disposed = false; EXPECT_FALSE(a.is_linked()); EXPECT_FALSE(b.is_linked()); IntrusiveList list; list.push_back(a); list.push_back(b); EXPECT_TRUE(a.is_linked()); EXPECT_TRUE(b.is_linked()); list.clear_and_dispose([&](Item *item){ EXPECT_FALSE(a.is_linked()); EXPECT_TRUE(b.is_linked()); EXPECT_EQ(item, &a); EXPECT_FALSE(a_disposed); a_disposed = true; b.unlink(); }); EXPECT_TRUE(a_disposed); EXPECT_TRUE(list.empty()); } /** * Call clear_and_dispose(), and let the disposer push a new item. */ TEST(IntrusiveList, ClearDisposePush) { using Item = CharItem; Item a{'a'}, b{'b'}; bool a_disposed = false, b_added = false, b_disposed = false; EXPECT_FALSE(a.is_linked()); EXPECT_FALSE(b.is_linked()); IntrusiveList list; list.push_back(a); EXPECT_TRUE(a.is_linked()); EXPECT_FALSE(b.is_linked()); list.clear_and_dispose([&](Item *item){ if (!a_disposed) { EXPECT_EQ(item, &a); EXPECT_FALSE(a.is_linked()); EXPECT_FALSE(b.is_linked()); EXPECT_FALSE(a_disposed); EXPECT_FALSE(b_disposed); EXPECT_FALSE(b_added); a_disposed = true; list.push_back(b); EXPECT_FALSE(a.is_linked()); EXPECT_TRUE(b.is_linked()); b_added = true; } else if (!b_disposed) { EXPECT_TRUE(b_added); EXPECT_EQ(item, &b); EXPECT_FALSE(a.is_linked()); EXPECT_FALSE(b.is_linked()); EXPECT_TRUE(a_disposed); EXPECT_FALSE(b_disposed); b_disposed = true; } else { FAIL(); } }); EXPECT_TRUE(a_disposed); EXPECT_TRUE(b_added); EXPECT_TRUE(b_disposed); EXPECT_TRUE(list.empty()); }