356 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			356 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===----------------------------------------------------------------------===//
 | |
| //
 | |
| // 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 "test_macros.h"
 | |
| #include "debug_mode_helper.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 >= CT_Vector && CT <= CT_ForwardList;
 | |
| }
 | |
| 
 | |
| constexpr bool isAssociative(ContainerType CT) {
 | |
|   return CT >= CT_Map && CT <= CT_MultiSet;
 | |
| }
 | |
| 
 | |
| constexpr bool isUnordered(ContainerType CT) {
 | |
|   return CT >= CT_UnorderedMap && CT <= CT_UnorderedMultiSet;
 | |
| }
 | |
| 
 | |
| 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() {
 | |
|     // 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() {
 | |
|     // 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() {
 | |
|     // 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() {
 | |
|     // 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() {
 | |
|     // 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() {
 | |
|     // 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() {
 | |
|     // 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() {
 | |
|     // 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() {
 | |
|     // 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() {
 | |
|     // 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
 |