diff options
Diffstat (limited to 'libcxx/test/support/container_debug_tests.h')
-rw-r--r-- | libcxx/test/support/container_debug_tests.h | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/libcxx/test/support/container_debug_tests.h b/libcxx/test/support/container_debug_tests.h new file mode 100644 index 00000000000..347196ac79c --- /dev/null +++ b/libcxx/test/support/container_debug_tests.h @@ -0,0 +1,359 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef TEST_SUPPORT_CONTAINER_DEBUG_TESTS_H +#define TEST_SUPPORT_CONTAINER_DEBUG_TESTS_H + +#include <ciso646> +#ifndef _LIBCPP_VERSION +#error This header may only be used for libc++ tests" +#endif + +#ifndef _LIBCPP_DEBUG +#error _LIBCPP_DEBUG must be defined before including this header +#endif + +#include <__debug> +#include <utility> +#include <cstddef> +#include <cstdlib> +#include <cassert> +#include <string> +#include <sstream> +#include <iostream> + +#include "test_macros.h" +#include "debug_mode_helper.h" +#include "assert_checkpoint.h" +#include "test_allocator.h" + +// These test make use of 'if constexpr'. +#if TEST_STD_VER <= 14 +#error This header may only be used in C++17 and greater +#endif + +#ifndef __cpp_if_constexpr +#error These tests require if constexpr +#endif + + +namespace IteratorDebugChecks { + +enum ContainerType { + CT_None, + CT_String, + CT_Vector, + CT_VectorBool, + CT_List, + CT_Deque, + CT_ForwardList, + CT_Map, + CT_Set, + CT_MultiMap, + CT_MultiSet, + CT_UnorderedMap, + CT_UnorderedSet, + CT_UnorderedMultiMap, + CT_UnorderedMultiSet +}; + +constexpr bool isSequential(ContainerType CT) { + return CT_Vector >= CT && CT_ForwardList <= CT; +} + +constexpr bool isAssociative(ContainerType CT) { + return CT_Map >= CT && CT_MultiSet <= CT; +} + +constexpr bool isUnordered(ContainerType CT) { + return CT_UnorderedMap >= CT && CT_UnorderedMultiSet <= CT; +} + +constexpr bool isSet(ContainerType CT) { + return CT == CT_Set + || CT == CT_MultiSet + || CT == CT_UnorderedSet + || CT == CT_UnorderedMultiSet; +} + +constexpr bool isMap(ContainerType CT) { + return CT == CT_Map + || CT == CT_MultiMap + || CT == CT_UnorderedMap + || CT == CT_UnorderedMultiMap; +} + +constexpr bool isMulti(ContainerType CT) { + return CT == CT_MultiMap + || CT == CT_MultiSet + || CT == CT_UnorderedMultiMap + || CT == CT_UnorderedMultiSet; +} + +template <class Container, class ValueType = typename Container::value_type> +struct ContainerDebugHelper { + static_assert(std::is_constructible<ValueType, int>::value, + "must be constructible from int"); + + static ValueType makeValueType(int val = 0, int = 0) { + return ValueType(val); + } +}; + +template <class Container> +struct ContainerDebugHelper<Container, char> { + static char makeValueType(int = 0, int = 0) { + return 'A'; + } +}; + +template <class Container, class Key, class Value> +struct ContainerDebugHelper<Container, std::pair<const Key, Value> > { + using ValueType = std::pair<const Key, Value>; + static_assert(std::is_constructible<Key, int>::value, + "must be constructible from int"); + static_assert(std::is_constructible<Value, int>::value, + "must be constructible from int"); + + static ValueType makeValueType(int key = 0, int val = 0) { + return ValueType(key, val); + } +}; + +template <class Container, ContainerType CT, + class Helper = ContainerDebugHelper<Container> > +struct BasicContainerChecks { + using value_type = typename Container::value_type; + using iterator = typename Container::iterator; + using const_iterator = typename Container::const_iterator; + using allocator_type = typename Container::allocator_type; + using traits = std::iterator_traits<iterator>; + using category = typename traits::iterator_category; + + static_assert(std::is_same<test_allocator<value_type>, allocator_type>::value, + "the container must use a test allocator"); + + static constexpr bool IsBiDir = + std::is_convertible<category, std::bidirectional_iterator_tag>::value; + + public: + static void run() { + run_iterator_tests(); + run_container_tests(); + run_allocator_aware_tests(); + } + + static void run_iterator_tests() { + TestNullIterators<iterator>(); + TestNullIterators<const_iterator>(); + if constexpr (IsBiDir) { DecrementBegin(); } + IncrementEnd(); + DerefEndIterator(); + } + + static void run_container_tests() { + CopyInvalidatesIterators(); + MoveInvalidatesIterators(); + if constexpr (CT != CT_ForwardList) { + EraseIter(); + EraseIterIter(); + } + } + + static void run_allocator_aware_tests() { + SwapNonEqualAllocators(); + if constexpr (CT != CT_ForwardList ) { + // FIXME: This should work for both forward_list and string + SwapInvalidatesIterators(); + } + } + + static Container makeContainer(int size, allocator_type A = allocator_type()) { + Container C(A); + if constexpr (CT == CT_ForwardList) { + for (int i = 0; i < size; ++i) + C.insert_after(C.before_begin(), Helper::makeValueType(i)); + } else { + for (int i = 0; i < size; ++i) + C.insert(C.end(), Helper::makeValueType(i)); + assert(C.size() == static_cast<std::size_t>(size)); + } + return C; + } + + static value_type makeValueType(int value) { + return Helper::makeValueType(value); + } + + private: + // Iterator tests + template <class Iter> + static void TestNullIterators() { + CHECKPOINT("testing null iterator"); + Iter it; + EXPECT_DEATH( ++it ); + EXPECT_DEATH( it++ ); + EXPECT_DEATH( *it ); + if constexpr (CT != CT_VectorBool) { + EXPECT_DEATH( it.operator->() ); + } + if constexpr (IsBiDir) { + EXPECT_DEATH( --it ); + EXPECT_DEATH( it-- ); + } + } + + static void DecrementBegin() { + CHECKPOINT("testing decrement on begin"); + Container C = makeContainer(1); + iterator i = C.end(); + const_iterator ci = C.cend(); + --i; + --ci; + assert(i == C.begin()); + EXPECT_DEATH( --i ); + EXPECT_DEATH( i-- ); + EXPECT_DEATH( --ci ); + EXPECT_DEATH( ci-- ); + } + + static void IncrementEnd() { + CHECKPOINT("testing increment on end"); + Container C = makeContainer(1); + iterator i = C.begin(); + const_iterator ci = C.begin(); + ++i; + ++ci; + assert(i == C.end()); + EXPECT_DEATH( ++i ); + EXPECT_DEATH( i++ ); + EXPECT_DEATH( ++ci ); + EXPECT_DEATH( ci++ ); + } + + static void DerefEndIterator() { + CHECKPOINT("testing deref end iterator"); + Container C = makeContainer(1); + iterator i = C.begin(); + const_iterator ci = C.cbegin(); + (void)*i; (void)*ci; + if constexpr (CT != CT_VectorBool) { + i.operator->(); + ci.operator->(); + } + ++i; ++ci; + assert(i == C.end()); + EXPECT_DEATH( *i ); + EXPECT_DEATH( *ci ); + if constexpr (CT != CT_VectorBool) { + EXPECT_DEATH( i.operator->() ); + EXPECT_DEATH( ci.operator->() ); + } + } + + // Container tests + static void CopyInvalidatesIterators() { + CHECKPOINT("copy invalidates iterators"); + Container C1 = makeContainer(3); + iterator i = C1.begin(); + Container C2 = C1; + if constexpr (CT == CT_ForwardList) { + iterator i_next = i; + ++i_next; + (void)*i_next; + EXPECT_DEATH( C2.erase_after(i) ); + C1.erase_after(i); + EXPECT_DEATH( *i_next ); + } else { + EXPECT_DEATH( C2.erase(i) ); + (void)*i; + C1.erase(i); + EXPECT_DEATH( *i ); + } + } + + static void MoveInvalidatesIterators() { + CHECKPOINT("copy move invalidates iterators"); + Container C1 = makeContainer(3); + iterator i = C1.begin(); + Container C2 = std::move(C1); + (void) *i; + if constexpr (CT == CT_ForwardList) { + EXPECT_DEATH( C1.erase_after(i) ); + C2.erase_after(i); + } else { + EXPECT_DEATH( C1.erase(i) ); + C2.erase(i); + EXPECT_DEATH(*i); + } + } + + static void EraseIter() { + CHECKPOINT("testing erase invalidation"); + Container C1 = makeContainer(2); + iterator it1 = C1.begin(); + iterator it1_next = it1; + ++it1_next; + Container C2 = C1; + EXPECT_DEATH( C2.erase(it1) ); // wrong container + EXPECT_DEATH( C2.erase(C2.end()) ); // erase with end + C1.erase(it1_next); + EXPECT_DEATH( C1.erase(it1_next) ); // invalidated iterator + C1.erase(it1); + EXPECT_DEATH( C1.erase(it1) ); // invalidated iterator + } + + static void EraseIterIter() { + CHECKPOINT("testing erase iter iter invalidation"); + Container C1 = makeContainer(2); + iterator it1 = C1.begin(); + iterator it1_next = it1; + ++it1_next; + Container C2 = C1; + iterator it2 = C2.begin(); + iterator it2_next = it2; + ++it2_next; + EXPECT_DEATH( C2.erase(it1, it1_next) ); // begin from wrong container + EXPECT_DEATH( C2.erase(it1, it2_next) ); // end from wrong container + EXPECT_DEATH( C2.erase(it2, it1_next) ); // both from wrong container + C2.erase(it2, it2_next); + } + + // Allocator aware tests + static void SwapInvalidatesIterators() { + CHECKPOINT("testing swap invalidates iterators"); + Container C1 = makeContainer(3); + Container C2 = makeContainer(3); + iterator it1 = C1.begin(); + iterator it2 = C2.begin(); + swap(C1, C2); + EXPECT_DEATH( C1.erase(it1) ); + if (CT == CT_String) { + EXPECT_DEATH(C1.erase(it2)); + } else + C1.erase(it2); + //C2.erase(it1); + EXPECT_DEATH( C1.erase(it1) ); + } + + static void SwapNonEqualAllocators() { + CHECKPOINT("testing swap with non-equal allocators"); + Container C1 = makeContainer(3, allocator_type(1)); + Container C2 = makeContainer(1, allocator_type(2)); + Container C3 = makeContainer(2, allocator_type(2)); + swap(C2, C3); + EXPECT_DEATH( swap(C1, C2) ); + } + + private: + BasicContainerChecks() = delete; +}; + +} // namespace IteratorDebugChecks + +#endif // TEST_SUPPORT_CONTAINER_DEBUG_TESTS_H |