/* Tests fpr intrusive double linked list for GDB, the GNU debugger. Copyright (C) 2021-2023 Free Software Foundation, Inc. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "defs.h" #include "gdbsupport/intrusive_list.h" #include "gdbsupport/selftest.h" #include /* An item type using intrusive_list_node by inheriting from it and its corresponding list type. Put another base before intrusive_list_node so that a pointer to the node != a pointer to the item. */ struct other_base { int n = 1; }; struct item_with_base : public other_base, public intrusive_list_node { explicit item_with_base (const char *name) : name (name) {} const char *const name; }; using item_with_base_list = intrusive_list; /* An item type using intrusive_list_node as a field and its corresponding list type. Put the other field before the node, so that a pointer to the node != a pointer to the item. */ struct item_with_member { explicit item_with_member (const char *name) : name (name) {} const char *const name; intrusive_list_node node; }; using item_with_member_node = intrusive_member_node; using item_with_member_list = intrusive_list; /* To run all tests using both the base and member methods, all tests are declared in this templated class, which is instantiated once for each list type. */ template struct intrusive_list_test { using item_type = typename ListType::value_type; /* Verify that LIST contains exactly the items in EXPECTED. Traverse the list forward and backwards to exercise all links. */ static void verify_items (const ListType &list, gdb::array_view expected) { int i = 0; for (typename ListType::iterator it = list.begin (); it != list.end (); ++it) { const item_type &item = *it; gdb_assert (i < expected.size ()); gdb_assert (&item == expected[i]); ++i; } gdb_assert (i == expected.size ()); for (typename ListType::reverse_iterator it = list.rbegin (); it != list.rend (); ++it) { const item_type &item = *it; --i; gdb_assert (i >= 0); gdb_assert (&item == expected[i]); } gdb_assert (i == 0); } static void test_move_constructor () { { /* Other list is not empty. */ item_type a ("a"), b ("b"), c ("c"); ListType list1; std::vector expected; list1.push_back (a); list1.push_back (b); list1.push_back (c); ListType list2 (std::move (list1)); expected = {}; verify_items (list1, expected); expected = {&a, &b, &c}; verify_items (list2, expected); } { /* Other list contains 1 element. */ item_type a ("a"); ListType list1; std::vector expected; list1.push_back (a); ListType list2 (std::move (list1)); expected = {}; verify_items (list1, expected); expected = {&a}; verify_items (list2, expected); } { /* Other list is empty. */ ListType list1; std::vector expected; ListType list2 (std::move (list1)); expected = {}; verify_items (list1, expected); expected = {}; verify_items (list2, expected); } } static void test_move_assignment () { { /* Both lists are not empty. */ item_type a ("a"), b ("b"), c ("c"), d ("d"), e ("e"); ListType list1; ListType list2; std::vector expected; list1.push_back (a); list1.push_back (b); list1.push_back (c); list2.push_back (d); list2.push_back (e); list2 = std::move (list1); expected = {}; verify_items (list1, expected); expected = {&a, &b, &c}; verify_items (list2, expected); } { /* rhs list is empty. */ item_type a ("a"), b ("b"), c ("c"); ListType list1; ListType list2; std::vector expected; list2.push_back (a); list2.push_back (b); list2.push_back (c); list2 = std::move (list1); expected = {}; verify_items (list1, expected); expected = {}; verify_items (list2, expected); } { /* lhs list is empty. */ item_type a ("a"), b ("b"), c ("c"); ListType list1; ListType list2; std::vector expected; list1.push_back (a); list1.push_back (b); list1.push_back (c); list2 = std::move (list1); expected = {}; verify_items (list1, expected); expected = {&a, &b, &c}; verify_items (list2, expected); } { /* Both lists contain 1 item. */ item_type a ("a"), b ("b"); ListType list1; ListType list2; std::vector expected; list1.push_back (a); list2.push_back (b); list2 = std::move (list1); expected = {}; verify_items (list1, expected); expected = {&a}; verify_items (list2, expected); } { /* Both lists are empty. */ ListType list1; ListType list2; std::vector expected; list2 = std::move (list1); expected = {}; verify_items (list1, expected); expected = {}; verify_items (list2, expected); } } static void test_swap () { { /* Two non-empty lists. */ item_type a ("a"), b ("b"), c ("c"), d ("d"), e ("e"); ListType list1; ListType list2; std::vector expected; list1.push_back (a); list1.push_back (b); list1.push_back (c); list2.push_back (d); list2.push_back (e); std::swap (list1, list2); expected = {&d, &e}; verify_items (list1, expected); expected = {&a, &b, &c}; verify_items (list2, expected); } { /* Other is empty. */ item_type a ("a"), b ("b"), c ("c"); ListType list1; ListType list2; std::vector expected; list1.push_back (a); list1.push_back (b); list1.push_back (c); std::swap (list1, list2); expected = {}; verify_items (list1, expected); expected = {&a, &b, &c}; verify_items (list2, expected); } { /* *this is empty. */ item_type a ("a"), b ("b"), c ("c"); ListType list1; ListType list2; std::vector expected; list2.push_back (a); list2.push_back (b); list2.push_back (c); std::swap (list1, list2); expected = {&a, &b, &c}; verify_items (list1, expected); expected = {}; verify_items (list2, expected); } { /* Both lists empty. */ ListType list1; ListType list2; std::vector expected; std::swap (list1, list2); expected = {}; verify_items (list1, expected); expected = {}; verify_items (list2, expected); } { /* Swap one element twice. */ item_type a ("a"); ListType list1; ListType list2; std::vector expected; list1.push_back (a); std::swap (list1, list2); expected = {}; verify_items (list1, expected); expected = {&a}; verify_items (list2, expected); std::swap (list1, list2); expected = {&a}; verify_items (list1, expected); expected = {}; verify_items (list2, expected); } } static void test_front_back () { item_type a ("a"), b ("b"), c ("c"); ListType list; const ListType &clist = list; list.push_back (a); list.push_back (b); list.push_back (c); gdb_assert (&list.front () == &a); gdb_assert (&clist.front () == &a); gdb_assert (&list.back () == &c); gdb_assert (&clist.back () == &c); } static void test_push_front () { item_type a ("a"), b ("b"), c ("c"); ListType list; std::vector expected; expected = {}; verify_items (list, expected); list.push_front (a); expected = {&a}; verify_items (list, expected); list.push_front (b); expected = {&b, &a}; verify_items (list, expected); list.push_front (c); expected = {&c, &b, &a}; verify_items (list, expected); } static void test_push_back () { item_type a ("a"), b ("b"), c ("c"); ListType list; std::vector expected; expected = {}; verify_items (list, expected); list.push_back (a); expected = {&a}; verify_items (list, expected); list.push_back (b); expected = {&a, &b}; verify_items (list, expected); list.push_back (c); expected = {&a, &b, &c}; verify_items (list, expected); } static void test_insert () { std::vector expected; { /* Insert at beginning. */ item_type a ("a"), b ("b"), c ("c"); ListType list; list.insert (list.begin (), a); expected = {&a}; verify_items (list, expected); list.insert (list.begin (), b); expected = {&b, &a}; verify_items (list, expected); list.insert (list.begin (), c); expected = {&c, &b, &a}; verify_items (list, expected); } { /* Insert at end. */ item_type a ("a"), b ("b"), c ("c"); ListType list; list.insert (list.end (), a); expected = {&a}; verify_items (list, expected); list.insert (list.end (), b); expected = {&a, &b}; verify_items (list, expected); list.insert (list.end (), c); expected = {&a, &b, &c}; verify_items (list, expected); } { /* Insert in the middle. */ item_type a ("a"), b ("b"), c ("c"); ListType list; list.push_back (a); list.push_back (b); list.insert (list.iterator_to (b), c); expected = {&a, &c, &b}; verify_items (list, expected); } { /* Insert in empty list. */ item_type a ("a"); ListType list; list.insert (list.end (), a); expected = {&a}; verify_items (list, expected); } } static void test_splice () { { /* Two non-empty lists. */ item_type a ("a"), b ("b"), c ("c"), d ("d"), e ("e"); ListType list1; ListType list2; std::vector expected; list1.push_back (a); list1.push_back (b); list1.push_back (c); list2.push_back (d); list2.push_back (e); list1.splice (std::move (list2)); expected = {&a, &b, &c, &d, &e}; verify_items (list1, expected); expected = {}; verify_items (list2, expected); } { /* Receiving list empty. */ item_type a ("a"), b ("b"), c ("c"); ListType list1; ListType list2; std::vector expected; list2.push_back (a); list2.push_back (b); list2.push_back (c); list1.splice (std::move (list2)); expected = {&a, &b, &c}; verify_items (list1, expected); expected = {}; verify_items (list2, expected); } { /* Giving list empty. */ item_type a ("a"), b ("b"), c ("c"); ListType list1; ListType list2; std::vector expected; list1.push_back (a); list1.push_back (b); list1.push_back (c); list1.splice (std::move (list2)); expected = {&a, &b, &c}; verify_items (list1, expected); expected = {}; verify_items (list2, expected); } { /* Both lists empty. */ item_type a ("a"), b ("b"), c ("c"); ListType list1; ListType list2; std::vector expected; list1.splice (std::move (list2)); expected = {}; verify_items (list1, expected); expected = {}; verify_items (list2, expected); } } static void test_pop_front () { item_type a ("a"), b ("b"), c ("c"); ListType list; std::vector expected; list.push_back (a); list.push_back (b); list.push_back (c); list.pop_front (); expected = {&b, &c}; verify_items (list, expected); list.pop_front (); expected = {&c}; verify_items (list, expected); list.pop_front (); expected = {}; verify_items (list, expected); } static void test_pop_back () { item_type a ("a"), b ("b"), c ("c"); ListType list; std::vector expected; list.push_back (a); list.push_back (b); list.push_back (c); list.pop_back(); expected = {&a, &b}; verify_items (list, expected); list.pop_back (); expected = {&a}; verify_items (list, expected); list.pop_back (); expected = {}; verify_items (list, expected); } static void test_erase () { item_type a ("a"), b ("b"), c ("c"); ListType list; std::vector expected; list.push_back (a); list.push_back (b); list.push_back (c); list.erase (list.iterator_to (b)); expected = {&a, &c}; verify_items (list, expected); list.erase (list.iterator_to (c)); expected = {&a}; verify_items (list, expected); list.erase (list.iterator_to (a)); expected = {}; verify_items (list, expected); } static void test_clear () { item_type a ("a"), b ("b"), c ("c"); ListType list; std::vector expected; list.push_back (a); list.push_back (b); list.push_back (c); list.clear (); expected = {}; verify_items (list, expected); /* Verify idempotency. */ list.clear (); expected = {}; verify_items (list, expected); } static void test_clear_and_dispose () { item_type a ("a"), b ("b"), c ("c"); ListType list; std::vector expected; std::unordered_set disposer_seen; int disposer_calls = 0; list.push_back (a); list.push_back (b); list.push_back (c); auto disposer = [&] (const item_type *item) { disposer_seen.insert (item); disposer_calls++; }; list.clear_and_dispose (disposer); expected = {}; verify_items (list, expected); gdb_assert (disposer_calls == 3); gdb_assert (disposer_seen.find (&a) != disposer_seen.end ()); gdb_assert (disposer_seen.find (&b) != disposer_seen.end ()); gdb_assert (disposer_seen.find (&c) != disposer_seen.end ()); /* Verify idempotency. */ list.clear_and_dispose (disposer); gdb_assert (disposer_calls == 3); } static void test_empty () { item_type a ("a"); ListType list; gdb_assert (list.empty ()); list.push_back (a); gdb_assert (!list.empty ()); list.erase (list.iterator_to (a)); gdb_assert (list.empty ()); } static void test_begin_end () { item_type a ("a"), b ("b"), c ("c"); ListType list; const ListType &clist = list; list.push_back (a); list.push_back (b); list.push_back (c); gdb_assert (&*list.begin () == &a); gdb_assert (&*list.cbegin () == &a); gdb_assert (&*clist.begin () == &a); gdb_assert (&*list.rbegin () == &c); gdb_assert (&*list.crbegin () == &c); gdb_assert (&*clist.rbegin () == &c); /* At least check that they compile. */ list.end (); list.cend (); clist.end (); list.rend (); list.crend (); clist.end (); } }; template static void test_intrusive_list_1 () { intrusive_list_test tests; tests.test_move_constructor (); tests.test_move_assignment (); tests.test_swap (); tests.test_front_back (); tests.test_push_front (); tests.test_push_back (); tests.test_insert (); tests.test_splice (); tests.test_pop_front (); tests.test_pop_back (); tests.test_erase (); tests.test_clear (); tests.test_clear_and_dispose (); tests.test_empty (); tests.test_begin_end (); } static void test_node_is_linked () { { item_with_base a ("a"); item_with_base_list list; gdb_assert (!a.is_linked ()); list.push_back (a); gdb_assert (a.is_linked ()); list.pop_back (); gdb_assert (!a.is_linked ()); } { item_with_member a ("a"); item_with_member_list list; gdb_assert (!a.node.is_linked ()); list.push_back (a); gdb_assert (a.node.is_linked ()); list.pop_back (); gdb_assert (!a.node.is_linked ()); } } static void test_intrusive_list () { test_intrusive_list_1 (); test_intrusive_list_1 (); test_node_is_linked (); } void _initialize_intrusive_list_selftests (); void _initialize_intrusive_list_selftests () { selftests::register_test ("intrusive_list", test_intrusive_list); }