/* Copyright (c) 2005-2021 Intel Corporation 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 http://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. */ #ifndef __TBB_test_common_concurrent_associative_common_H #define __TBB_test_common_concurrent_associative_common_H #include "config.h" #include "custom_allocators.h" #include "utils.h" #include "utils_concurrency_limit.h" #include "container_move_support.h" #include "checktype.h" #include "range_based_for_support.h" #include "initializer_list_support.h" #include "node_handling_support.h" #include "containers_common.h" #include "test_comparisons.h" #include "concepts_common.h" #include #include #include "oneapi/tbb/parallel_for.h" // This structure should be specialized for multimap and multiset classes template struct AllowMultimapping : std::false_type {}; template inline void CheckAllocator(typename Table::allocator_type& a, size_t expected_allocs, size_t expected_frees, bool exact = true) { if(exact) { REQUIRE( a.allocations == expected_allocs); REQUIRE( a.frees == expected_frees); } else { REQUIRE( a.allocations >= expected_allocs); REQUIRE( a.frees >= expected_frees); REQUIRE( a.allocations - a.frees == expected_allocs - expected_frees ); } } // value generator for maps template > struct ValueFactoryBase { using strip_key = typename std::remove_const::type; static Value make( const Key& key ) { return Value(key, key); } static Value make( const Key& key, const Key& mapped ) { return Value(key, mapped); } static strip_key key( const Value& value ) { return value.first; } static strip_key get( const Value& value ) { return strip_key(value.second); } template static U convert( const Value& value ) { return U(value.second); } }; template struct SpecialTests { static void Test() {} }; // value generator for sets template struct ValueFactoryBase { static Key make( const Key& key ) { return key; } static Key make( const Key& key, const Key& ) { return key; } static Key key( const Key& value ) { return value; } static Key get( const Key& value ) { return value; } template static U convert( const Key& value ) { return U(value); } }; template struct Value : ValueFactoryBase { template static bool compare( const typename Container::iterator& it, U val ) { return (Value::template convert(*it) == val); } }; template void SpecialMapTests(){ Map cont; const Map &ccont( cont ); // mapped_type& operator[](const key_type& k); cont[1] = 2; // bool empty() const; REQUIRE_MESSAGE( !ccont.empty( ), "Concurrent container empty after adding an element" ); // size_type size() const; REQUIRE_MESSAGE( ccont.size( ) == 1, "Concurrent container size incorrect" ); REQUIRE_MESSAGE( cont[1] == 2, "Concurrent container value incorrect" ); // mapped_type& at( const key_type& k ); // const mapped_type& at(const key_type& k) const; REQUIRE_MESSAGE( cont.at( 1 ) == 2, "Concurrent container value incorrect" ); REQUIRE_MESSAGE( ccont.at( 1 ) == 2, "Concurrent container value incorrect" ); // iterator find(const key_type& k); typename Map::iterator it = cont.find( 1 ); REQUIRE_MESSAGE((it != cont.end( ) && Value::get( *(it) ) == 2), "Element with key 1 not properly found" ); cont.unsafe_erase( it ); it = cont.find( 1 ); REQUIRE_MESSAGE( it == cont.end( ), "Element with key 1 not properly erased" ); } template void CheckMultiMap(MultiMap &m, int *targets, int tcount, int key) { std::vector vfound(tcount,false); std::pair range = m.equal_range( key ); for(typename MultiMap::iterator it = range.first; it != range.second; ++it) { bool found = false; for( int i = 0; i < tcount; ++i) { if((*it).second == targets[i]) { if(!vfound[i]) { // we can insert duplicate values vfound[i] = found = true; break; } } } // just in case an extra value in equal_range... REQUIRE_MESSAGE(found, "extra value from equal range"); } for(int i = 0; i < tcount; ++i) REQUIRE_MESSAGE(vfound[i], "missing value"); } template void MultiMapEraseTests(){ MultiMap cont1, cont2; // Assignment to begin to avoid maybe-uninitialized warning typename MultiMap::iterator erased_it = cont1.begin(); for (int i = 0; i < 10; ++i) { if ( i != 1 ) { cont1.insert(std::make_pair(1, i)); cont2.insert(std::make_pair(1, i)); } else { erased_it = cont1.insert(std::make_pair(1, i)).first; } } cont1.unsafe_erase(erased_it); REQUIRE_MESSAGE(cont1.size() == cont2.size(), "Incorrect count of elements was erased"); typename MultiMap::iterator it1 = cont1.begin(); typename MultiMap::iterator it2 = cont2.begin(); for (typename MultiMap::size_type i = 0; i < cont2.size(); ++i) { REQUIRE_MESSAGE((*(it1++) == *(it2++)), "Multimap repetitive key was not erased properly"); } } template void SpecialMultiMapTests(){ int one_values[] = { 7, 2, 13, 23, 13 }; int zero_values[] = { 4, 9, 13, 29, 42, 111}; int n_zero_values = sizeof(zero_values) / sizeof(int); int n_one_values = sizeof(one_values) / sizeof(int); MultiMap cont; const MultiMap &ccont( cont ); // mapped_type& operator[](const key_type& k); cont.insert( std::make_pair( 1, one_values[0] ) ); // bool empty() const; REQUIRE_MESSAGE( !ccont.empty( ), "Concurrent container empty after adding an element" ); // size_type size() const; REQUIRE_MESSAGE( ccont.size( ) == 1, "Concurrent container size incorrect" ); REQUIRE_MESSAGE( (*(cont.begin( ))).second == one_values[0], "Concurrent container value incorrect" ); REQUIRE_MESSAGE( (*(cont.equal_range( 1 )).first).second == one_values[0], "Improper value from equal_range" ); REQUIRE_MESSAGE( ((cont.equal_range( 1 )).second == cont.end( )), "Improper iterator from equal_range" ); cont.insert( std::make_pair( 1, one_values[1] ) ); // bool empty() const; REQUIRE_MESSAGE( !ccont.empty( ), "Concurrent container empty after adding an element" ); // size_type size() const; REQUIRE_MESSAGE( ccont.size( ) == 2, "Concurrent container size incorrect" ); CheckMultiMap(cont, one_values, 2, 1); // insert the other {1,x} values for( int i = 2; i < n_one_values; ++i ) { cont.insert( std::make_pair( 1, one_values[i] ) ); } CheckMultiMap(cont, one_values, n_one_values, 1); REQUIRE_MESSAGE( (cont.equal_range( 1 )).second == cont.end( ), "Improper iterator from equal_range" ); cont.insert( std::make_pair( 0, zero_values[0] ) ); // bool empty() const; REQUIRE_MESSAGE( !ccont.empty( ), "Concurrent container empty after adding an element" ); // size_type size() const; REQUIRE_MESSAGE( ccont.size( ) == (size_t)(n_one_values+1), "Concurrent container size incorrect" ); CheckMultiMap(cont, one_values, n_one_values, 1); CheckMultiMap(cont, zero_values, 1, 0); REQUIRE_MESSAGE( (*cont.find(0)).second == zero_values[0], "Concurrent container value incorrect"); // insert the rest of the zero values for( int i = 1; i < n_zero_values; ++i) { cont.insert( std::make_pair( 0, zero_values[i] ) ); } CheckMultiMap(cont, one_values, n_one_values, 1); CheckMultiMap(cont, zero_values, n_zero_values, 0); // clear, reinsert interleaved cont.clear(); int bigger_num = ( n_one_values > n_zero_values ) ? n_one_values : n_zero_values; for( int i = 0; i < bigger_num; ++i ) { if(i < n_one_values) cont.insert( std::make_pair( 1, one_values[i] ) ); if(i < n_zero_values) cont.insert( std::make_pair( 0, zero_values[i] ) ); } CheckMultiMap(cont, one_values, n_one_values, 1); CheckMultiMap(cont, zero_values, n_zero_values, 0); MultiMapEraseTests(); } template void check_value_state( const T& t, std::true_type ) { REQUIRE_MESSAGE(is_state_predicate{}(t), "Unexpected value state"); } template void check_value_state(const T&, std::false_type) {} template void test_rvalue_insert( Key k1, Key k2 ) { Container cont; std::pair ins = cont.insert(Value::make(k1)); REQUIRE_MESSAGE(ins.second, "Element 1 has not been inserted"); REQUIRE_MESSAGE(Value::get(*ins.first) == k1, "Element 1 has not been inserted"); check_value_state(*ins.first, CheckElementState{}); typename Container::iterator it2 = cont.insert(ins.first, Value::make(k2)); REQUIRE_MESSAGE(Value::get(*it2) == k2, "Element 2 has not been inserted"); check_value_state(*it2, CheckElementState{}); } namespace emplace_helpers { template std::pair call_emplace_impl( Container& c, Arg&& k, Value* ) { // Call emplace implementation for sets return c.emplace(std::forward(k)); } template std::pair call_emplace_impl( Container& c, Arg&& k, std::pair* ) { // Call emplace implementation for maps return c.emplace(k, std::forward(k)); } template typename Container::iterator call_emplace_hint_impl( Container& c, typename Container::const_iterator hint, Arg&& k, Value* ) { // Call emplace_hint implementation for sets return c.emplace_hint(hint, std::forward(k)); } template typename Container::iterator call_emplace_hint_impl( Container& c, typename Container::const_iterator hint, Arg&& k, std::pair* ) { // Call emplace_hint implementation for maps return c.emplace_hint(hint, k, std::forward(k)); } template std::pair call_emplace( Container& c, Arg&& k ) { typename Container::value_type* selector = nullptr; return call_emplace_impl(c, std::forward(k), selector); } template typename Container::iterator call_emplace_hint( Container& c, typename Container::const_iterator hint, Arg&& k ) { typename Container::value_type* selector = nullptr; return call_emplace_hint_impl(c, hint, std::forward(k), selector); } } // namespace emplace_helpers template void test_emplace_insert( Key key1, Key key2 ) { Container cont; std::pair ins = emplace_helpers::call_emplace(cont, key1); REQUIRE_MESSAGE(ins.second, "Element 1 has not been inserted"); REQUIRE_MESSAGE(Value::compare(ins.first, key1), "Element 1 has not been inserted"); check_value_state(*ins.first, CheckElementState{}); //if (!AllowMultimapping::value) { // std::pair rep_ins = emplace_helpers::call_emplace(cont, key1); // REQUIRE_MESSAGE(!rep_ins.second, "Element 1 has been emplaced twice into the container with unique keys"); // REQUIRE(Value::compare(rep_ins.first, key1)); //} typename Container::iterator it2 = emplace_helpers::call_emplace_hint(cont, ins.first, key2); REQUIRE_MESSAGE(Value::compare(it2, key2), "Element 2 has not been inserted"); check_value_state(*it2, CheckElementState{}); } template std::pair CheckRecursiveRange( Range range ) { std::pair sum(0, 0); // count, sum for (Iterator i = range.begin(); i != range.end(); ++i) { ++sum.first; sum.second += Value::get(*i); } if (range.is_divisible()) { Range range2(range, tbb::split{}); auto sum1 = CheckRecursiveRange(range); auto sum2 = CheckRecursiveRange(range2); sum1.first += sum2.first; sum1.second += sum2.second; REQUIRE_MESSAGE(sum == sum1, "Mismatched ranges afted division"); } return sum; } using atomic_byte_type = std::atomic; void CheckRange( atomic_byte_type array[], int n, bool allow_multimapping, int odd_count ) { if (allow_multimapping) { for (int k = 0; k < n; ++k) { if (k % 2) { REQUIRE(array[k] == odd_count); } else { REQUIRE(array[k] == 2); } } } else { for (int k = 0; k < n; ++k) { REQUIRE(array[k] == 1); } } } template void check_equal( const T& cont1, const T& cont2 ) { REQUIRE_MESSAGE(cont1 == cont2, "Containers should be equal"); REQUIRE_MESSAGE(cont2 == cont1, "Containers should be equal"); REQUIRE_MESSAGE(!(cont1 != cont2), "Containers should not be unequal"); REQUIRE_MESSAGE(!(cont2 != cont1), "Containers should not be unequal"); } template void check_unequal( const T& cont1, const T& cont2 ) { REQUIRE_MESSAGE(cont1 != cont2, "Containers should be unequal"); REQUIRE_MESSAGE(cont2 != cont1, "Containers should be unequal"); REQUIRE_MESSAGE(!(cont1 == cont2), "Containers should not be equal"); REQUIRE_MESSAGE(!(cont2 == cont1), "Containers shuold not be equal"); } // Break value for maps template void break_value( std::pair& value ) { ++value.second; } template void break_value( std::pair& value ) { ++value.second.bar(); } // Break value for sets template void break_value( T& value ) { ++value; } void break_value( move_support_tests::FooWithAssign& value ) { ++value.bar(); } template void test_comparison_operators() { T cont; check_equal(cont, cont); cont.insert(Value::make(1)); cont.insert(Value::make(2)); T cont2 = cont; check_equal(cont, cont2); T cont3; check_unequal(cont, cont3); T cont4; cont4.insert(Value::make(1)); cont4.insert(Value::make(2)); check_equal(cont, cont4); T cont5; cont5.insert(Value::make(1)); cont5.insert(Value::make(3)); check_unequal(cont, cont5); T cont6; cont6.insert(Value::make(1)); auto value2 = Value::make(2); break_value(value2); cont6.insert(value2); check_unequal(cont, cont6); } template void test_basic_common() { T cont; const T &ccont(cont); CheckNoAllocations(cont); // bool empty() const; REQUIRE_MESSAGE(ccont.empty(), "Concurrent container is not empty after construction"); // size_type size() const; REQUIRE_MESSAGE(ccont.size() == 0, "Concurrent container is not empty after construction"); // size_type max_size() const; REQUIRE_MESSAGE(ccont.max_size() > 0, "Concurrent container max size is invalid"); //iterator begin(); //iterator end(); REQUIRE_MESSAGE(cont.begin() == cont.end(), "Concurrent container iterators are invalid after construction"); REQUIRE_MESSAGE(ccont.begin() == ccont.end(), "Concurrent container iterators are invalid after construction"); REQUIRE_MESSAGE(cont.cbegin() == cont.cend(), "Concurrent container iterators are invalid after construction"); //std::pair insert(const value_type& obj); std::pair ins = cont.insert(Value::make(1)); REQUIRE_MESSAGE((ins.second == true && Value::get(*(ins.first)) == 1), "Element 1 has not been inserted properly"); test_rvalue_insert(1,2); test_emplace_insert(1,2); // bool empty() const; REQUIRE_MESSAGE(!ccont.empty(), "Concurrent container is empty after adding an element"); // size_type size() const; REQUIRE_MESSAGE(ccont.size() == 1, "Concurrent container size is incorrect"); std::pair ins2 = cont.insert(Value::make(1)); if (AllowMultimapping::value) { // std::pair insert(const value_type& obj); REQUIRE_MESSAGE((ins2.second == true && Value::get(*(ins2.first)) == 1), "Element 1 has not been inserted properly"); // size_type size() const; REQUIRE_MESSAGE(ccont.size() == 2, "Concurrent container size is incorrect"); // size_type count(const key_type& k) const; REQUIRE_MESSAGE(ccont.count(1) == 2, "Concurrent container count(1) is incorrect"); // std::pair equal_range(const key_type& k); std::pair range = cont.equal_range(1); typename T::iterator it; it = range.first; REQUIRE_MESSAGE((it != cont.end() && Value::get(*it) == 1), "Element 1 has not been found properly"); unsigned int count = 0; for (; it != range.second; it++) { count++; REQUIRE_MESSAGE((Value::get(*it) == 1), "Element 1 has not been found properly"); } REQUIRE_MESSAGE(count == 2, "Range doesn't have the right number of elements"); } else { // std::pair insert(const value_type& obj); REQUIRE_MESSAGE((ins2.second == false && ins2.first == ins.first), "Element 1 should not be re-inserted"); // size_type size() const; REQUIRE_MESSAGE(ccont.size() == 1, "Concurrent container size is incorrect"); // size_type count(const key_type& k) const; REQUIRE_MESSAGE(ccont.count(1) == 1, "Concurrent container count(1) is incorrect"); // std::pair equal_range(const key_type& k) const; // std::pair equal_range(const key_type& k); std::pair range = cont.equal_range(1); typename T::iterator it = range.first; REQUIRE_MESSAGE((it != cont.end() && Value::get(*it) == 1), "Element 1 has not been found properly"); REQUIRE_MESSAGE((++it == range.second), "Range doesn't have the right number of elements"); } // const_iterator find(const key_type& k) const; // iterator find(const key_type& k); typename T::iterator it = cont.find(1); REQUIRE_MESSAGE((it != cont.end() && Value::get(*(it)) == 1), "Element 1 has not been found properly"); REQUIRE_MESSAGE(ccont.find(1) == it, "Element 1 has not been found properly"); //bool contains(const key_type&k) const REQUIRE_MESSAGE(cont.contains(1), "contains() cannot detect existing element"); REQUIRE_MESSAGE(!cont.contains(0), "contains() detect not existing element"); // iterator insert(const_iterator hint, const value_type& obj); typename T::iterator it2 = cont.insert(ins.first, Value::make(2)); REQUIRE_MESSAGE((Value::get(*it2) == 2), "Element 2 has not been inserted properly"); // T(const T& _Umap) T newcont = ccont; REQUIRE_MESSAGE((AllowMultimapping{} ? (newcont.size() == 3) : (newcont.size() == 2)), "Copy construction has not copied the elements properly"); // size_type unsafe_erase(const key_type& k); typename T::size_type size; #if _MSC_VER && __INTEL_COMPILER == 1900 // The compiler optimizes the next line too aggressively. #pragma noinline #endif size = cont.unsafe_erase(1); REQUIRE_MESSAGE((AllowMultimapping{} ? (size == 2) : (size == 1)), "Erase has not removed the right number of elements"); // iterator unsafe_erase(iterator position); typename T::iterator it4 = cont.unsafe_erase(cont.find(2)); REQUIRE_MESSAGE((it4 == cont.end() && cont.size() == 0), "Erase has not removed the last element properly"); // iterator unsafe_erase(const_iterator position); cont.insert(Value::make(3)); typename T::iterator it5 = cont.unsafe_erase(cont.cbegin()); REQUIRE_MESSAGE((it5 == cont.end() && cont.size() == 0), "Erase has not removed the last element properly"); // template void insert(InputIterator first, InputIterator last); cont.insert(newcont.begin(), newcont.end()); REQUIRE_MESSAGE((AllowMultimapping{} ? (cont.size() == 3) : (cont.size() == 2)), "Range insert has not copied the elements properly"); // iterator unsafe_erase(const_iterator first, const_iterator last); std::pair range2 = newcont.equal_range(1); newcont.unsafe_erase(range2.first, range2.second); REQUIRE_MESSAGE(newcont.size() == 1, "Range erase has not erased the elements properly"); // void clear(); newcont.clear(); REQUIRE_MESSAGE((newcont.begin() == newcont.end() && newcont.size() == 0), "Clear has not cleared the container"); // void insert(std::initializer_list &il); newcont.insert( { Value::make( 1 ), Value::make( 2 ), Value::make( 1 ) } ); if (AllowMultimapping::value) { REQUIRE_MESSAGE(newcont.size() == 3, "Concurrent container size is incorrect"); REQUIRE_MESSAGE(newcont.count(1) == 2, "Concurrent container count(1) is incorrect"); REQUIRE_MESSAGE(newcont.count(2) == 1, "Concurrent container count(2) is incorrect"); std::pair range = cont.equal_range(1); it = range.first; // REQUIRE_MESSAGE((it != newcont.end() && Value::get(*it) == 1), "Element 1 has not been found properly"); REQUIRE_MESSAGE((it != newcont.end()), "iterator" ); REQUIRE_MESSAGE((Value::get(*it) == 1), "value"); unsigned int count = 0; for (; it != range.second; it++) { count++; REQUIRE_MESSAGE(Value::get(*it) == 1, "Element 1 has not been found properly"); } REQUIRE_MESSAGE(count == 2, "Range doesn't have the right number of elements"); range = newcont.equal_range(2); it = range.first; REQUIRE_MESSAGE((it != newcont.end() && Value::get(*it) == 2), "Element 2 has not been found properly"); count = 0; for (; it != range.second; it++) { count++; REQUIRE_MESSAGE(Value::get(*it) == 2, "Element 2 has not been found properly"); } REQUIRE_MESSAGE(count == 1, "Range doesn't have the right number of elements"); } else { REQUIRE_MESSAGE(newcont.size() == 2, "Concurrent container size is incorrect"); REQUIRE_MESSAGE(newcont.count(1) == 1, "Concurrent container count(1) is incorrect"); REQUIRE_MESSAGE(newcont.count(2) == 1, "Concurrent container count(2) is incorrect"); std::pair range = newcont.equal_range(1); it = range.first; REQUIRE_MESSAGE((it != newcont.end() && Value::get(*it) == 1), "Element 1 has not been found properly"); REQUIRE_MESSAGE((++it == range.second), "Range doesn't have the right number of elements"); range = newcont.equal_range(2); it = range.first; REQUIRE_MESSAGE((it != newcont.end() && Value::get(*it) == 2), "Element 2 has not been found properly"); REQUIRE_MESSAGE((++it == range.second), "Range doesn't have the right number of elements"); } // T& operator=(const T& _Umap) newcont = ccont; REQUIRE_MESSAGE((AllowMultimapping{} ? (newcont.size() == 3) : (newcont.size() == 2)), "Assignment operator has not copied the elements properly"); cont.clear(); CheckNoAllocations(cont); for (int i = 0; i < 256; i++) { std::pair ins3 = cont.insert(Value::make(i)); REQUIRE_MESSAGE((ins3.second == true && Value::get(*(ins3.first)) == i), "Element 1 has not been inserted properly"); } REQUIRE_MESSAGE(cont.size() == 256, "Wrong number of elements have been inserted"); REQUIRE(!cont.range().empty()); REQUIRE(!ccont.range().empty()); REQUIRE((256 == CheckRecursiveRange(cont.range()).first)); REQUIRE((256 == CheckRecursiveRange(ccont.range()).first)); REQUIRE(cont.range().grainsize() > 0); REQUIRE(ccont.range().grainsize() > 0); // void swap(T&); cont.swap(newcont); REQUIRE_MESSAGE(newcont.size() == 256, "Wrong number of elements after swap"); REQUIRE_MESSAGE(newcont.count(200) == 1, "Element with key 200 is not present after swap"); REQUIRE_MESSAGE(newcont.count(16) == 1, "Element with key 16 is not present after swap"); REQUIRE_MESSAGE(newcont.count(99) == 1, "Element with key 99 is not present after swap"); REQUIRE_MESSAGE((AllowMultimapping{} ? (cont.size() == 3) : (cont.size() == 2)), "Assignment operator has not copied the elements properly"); T newcont_bkp = newcont; newcont.swap(newcont); REQUIRE_MESSAGE(newcont == newcont_bkp, "Unexpected swap-with-itself behavior"); test_comparison_operators(); SpecialTests::Test(); } template class FillTable { Container& my_table; const int my_items; bool my_asymptotic; using pair_ib = std::pair; public: FillTable(Container& table, int items, bool asymptotic) : my_table(table), my_items(items), my_asymptotic(asymptotic) { REQUIRE((!(items&1) && items > 100)); } void operator()( int thread_index ) const { if (thread_index == 0) { // Fill even keys forward (single thread) bool last_inserted = true; for (int i = 0; i < my_items; i += 2) { int val = my_asymptotic ? 1 : i; pair_ib pib = my_table.insert(Value::make(val)); REQUIRE_MESSAGE((Value::get(*(pib.first)) == val), "Element not properly inserted"); REQUIRE_MESSAGE((last_inserted || !pib.second), "Previous key was not inserted but current key is inserted"); last_inserted = pib.second; } } else if (thread_index == 1) { // Fill even keys backward (single thread) bool last_inserted = true; for (int i = my_items - 2; i >= 0; i -= 2) { int val = my_asymptotic ? 1 : i; pair_ib pib = my_table.insert(Value::make(val)); REQUIRE_MESSAGE((Value::get(*(pib.first)) == val), "Element not properly inserted"); REQUIRE_MESSAGE((last_inserted || !pib.second), "Previous key was not inserted but current key is inserted"); last_inserted = pib.second; } } else if (!(thread_index & 1)) { // Fill odd keys forward (multiple threads) for (int i = 1; i < my_items; i += 2) { if (i % 32 == 1 && i + 6 < my_items) { if (my_asymptotic) { auto init = { Value::make(1), Value::make(1), Value::make(1) }; my_table.insert(init); REQUIRE_MESSAGE(Value::get(*my_table.find(1)) == 1, "Element not properly inserted"); } else { auto init = { Value::make(i), Value::make(i + 2), Value::make(i + 4) }; my_table.insert(init); REQUIRE_MESSAGE(Value::get(*my_table.find(i)) == i, "Element i not properly inserted"); REQUIRE_MESSAGE(Value::get(*my_table.find(i + 2)) == i + 2, "Element i + 2 not properly inserted"); REQUIRE_MESSAGE(Value::get(*my_table.find(i + 4)) == i + 4, "Element i + 4 not properly inserted"); } i += 4; } else { pair_ib pib = my_table.insert(Value::make(my_asymptotic ? 1 : i)); REQUIRE_MESSAGE(Value::get(*(pib.first)) == (my_asymptotic ? 1 : i), "Element not properly inserted"); } } } else { // Check odd keys backward (multiple threads) if (!my_asymptotic) { bool last_found = false; for (int i = my_items - 1; i >= 0; i -= 2) { typename Container::iterator it = my_table.find(i); if (it != my_table.end()) { // found REQUIRE_MESSAGE(Value::get(*it) == i, "Element not properly inserted"); last_found = true; } else { REQUIRE_MESSAGE(!last_found, "Previous key was found, but current was not found"); } } } } } }; // class FillTable template struct ParallelTraverseBody { const int n; atomic_byte_type* const array; ParallelTraverseBody( atomic_byte_type arr[], int num ) : n(num), array(arr) {} void operator()( const Range& range ) const { for (auto i = range.begin(); i != range.end(); ++i) { int k = static_cast(Value::key(*i)); REQUIRE(k == Value::get(*i)); REQUIRE(0 <= k); REQUIRE(k < n); ++array[k]; } } }; // struct ParallelTraverseBody template class CheckTable : utils::NoAssign { T& table; public: CheckTable(T& t) : NoAssign(), table(t) {} void operator()(int i) const { int c = (int)table.count(i); CHECK_MESSAGE(c, "must exist"); } }; template void test_concurrent_common( bool asymptotic = false ) { #if __TBB_USE_ASSERT int items = 2000; #else int items = 20000; #endif int items_inserted = 0; int num_threads = 16; Container table; if (AllowMultimapping::value) { // TODO: comment items = 4 * items / (num_threads + 2); items_inserted = items + (num_threads - 2) * items / 4; } else { items_inserted = items; } utils::NativeParallelFor(num_threads, FillTable(table, items, asymptotic)); REQUIRE(int(table.size()) == items_inserted); if (!asymptotic) { atomic_byte_type* array = new atomic_byte_type[items]; std::memset(static_cast(array), 0, items * sizeof(atomic_byte_type)); typename Container::range_type r = table.range(); auto p = CheckRecursiveRange(r); REQUIRE(items_inserted == p.first); tbb::parallel_for(r, ParallelTraverseBody(array, items)); CheckRange(array, items, AllowMultimapping::value, (num_threads - 1)/2); const Container& const_table = table; std::memset(static_cast(array), 0, items * sizeof(atomic_byte_type)); typename Container::const_range_type cr = const_table.range(); p = CheckRecursiveRange(cr); REQUIRE(items_inserted == p.first); tbb::parallel_for(cr, ParallelTraverseBody(array, items)); CheckRange(array, items, AllowMultimapping::value, (num_threads - 1) / 2); delete[] array; tbb::parallel_for(0, items, CheckTable(table)); } table.clear(); // TODO: add check for container allocator counters } template void test_rvalue_ref_support() { using namespace move_support_tests; test_move_constructor(); test_move_assignment(); #if TBB_USE_EXCEPTIONS test_ex_move_constructor(); #endif } template void test_range_based_for_support() { using namespace range_based_for_support_tests; Container cont; const int sequence_length = 100; for (int i = 1; i <= sequence_length; ++i) { cont.insert(Value::make(i)); } auto range_based_for_result = range_based_for_accumulate(cont, UnifiedSummer{}, 0); auto reference_result = gauss_summ_of_int_sequence(sequence_length); REQUIRE_MESSAGE(range_based_for_result == reference_result, "Incorrect accumulated value generated via range based for"); } template void test_initializer_list_support( std::initializer_list init ) { using namespace initializer_list_support_tests; test_initializer_list_support_without_assign(init); test_initializer_list_support_without_assign({}); } template void test_set_specific_types() { // TODO: add tests for atomics Checker check_types; const int num = 10; // Test int std::list arr_int; for (int i = 0; i != num; ++i) { arr_int.emplace_back(i); } check_types.template check(arr_int); // Test reference_wrapper std::list> arr_ref; for (auto it = arr_int.begin(); it != arr_int.end(); ++it) { arr_ref.emplace_back(*it); } check_types.template check(arr_ref); // Test share_ptr std::list> arr_shr; for (int i = 0; i != num; ++i) { arr_shr.emplace_back(std::make_shared(i)); } check_types.template check(arr_shr); // Test weak_ptr std::list> arr_weak; std::copy(arr_shr.begin(), arr_shr.end(), std::back_inserter(arr_weak)); check_types.template check(arr_weak); // Test std::pair std::list> arr_pairs; for (int i = 0; i != num; ++i) { arr_pairs.emplace_back(i, i); } check_types.template check(arr_pairs); // Test std::basic_string std::list, tbb::tbb_allocator>> arr_strings; for (int i = 0; i != num; ++i) { arr_strings.emplace_back(i, char(i)); } check_types.template check(arr_strings); } template void test_map_specific_types() { // TODO: add tests for int-atomic pairs Checker check_types; const int num = 10; // Test int-int pairs std::list> arr_int_int_pairs; for (int i = 0; i != num; ++i) { arr_int_int_pairs.emplace_back(i, num - i); } check_types.template check(arr_int_int_pairs); // Test reference_wrapper-int pairs std::list, int>> arr_ref_int_pairs; for (auto& item : arr_int_int_pairs) { arr_ref_int_pairs.emplace_back(item.first, item.second); } check_types.template check(arr_ref_int_pairs); // Test int-reference_wrapper pairs std::list>> arr_int_ref_pairs; for (auto& item : arr_int_int_pairs) { arr_int_ref_pairs.emplace_back(item.first, item.second); } check_types.template check(arr_int_ref_pairs); // Test shared_ptr std::list, std::shared_ptr>> arr_shared_pairs; for (int i = 0; i != num; ++i) { const int num_minus_i = num - i; arr_shared_pairs.emplace_back(std::make_shared(i), std::make_shared(num_minus_i)); } check_types.template check(arr_shared_pairs); // Test weak_ptr std::list, std::weak_ptr>> arr_wick_pairs; std::copy(arr_shared_pairs.begin(), arr_shared_pairs.end(), std::back_inserter(arr_wick_pairs)); check_types.template check(arr_wick_pairs); // Test std::pair using pair_key_type = std::pair; std::list> arr_pair_int_pairs; for (int i = 0; i != num; ++i) { arr_pair_int_pairs.emplace_back(pair_key_type{i, i}, i); } check_types.template check(arr_pair_int_pairs); // Test std::basic_string using tbb_string_key_type = std::basic_string, tbb::tbb_allocator>; std::list> arr_tbb_string_pairs; for (int i = 0; i != num; ++i) { tbb_string_key_type key(i, char(i)); arr_tbb_string_pairs.emplace_back(key, i); } check_types.template check(arr_tbb_string_pairs); } namespace test { // For the sake of simplified testing, make std::unique_ptr implicitly convertible to/from the pointer template class unique_ptr : public std::unique_ptr { public: using pointer = typename std::unique_ptr::pointer; unique_ptr( pointer p ) : std::unique_ptr(p) {} operator pointer() const { return this->get(); } }; // class unique_ptr } // namespace test namespace std { template struct hash> { std::size_t operator()(const test::unique_ptr& ptr) const { return std::hash>{}(ptr); } }; } template struct CallIf { template void operator()( Func func ) const { func(); } }; template <> struct CallIf { template void operator()( Func ) const {} }; template class TestOperatorSquareBrackets { using value_type = typename Table::value_type; Table& my_c; const value_type& my_value; public: TestOperatorSquareBrackets( Table& c, const value_type& value ) : my_c(c), my_value(value) {} void operator()() const { utils::IsEqual equal; REQUIRE(equal(my_c[my_value.first], my_value.second)); typename Table::key_type temp_key = my_value.first; REQUIRE(equal(my_c[std::move(temp_key)], my_value.second)); } }; // class TestOperatorSquareBrackets template void TestSquareBracketsAndAt( Table&, const Value&, /*multimap = */std::true_type ) { // operator [] and at are not presented in the multimap } template void TestSquareBracketsAndAt( Table& c, const Value& value, /*multimap = */std::false_type ) { CallIf()(TestOperatorSquareBrackets(c, value)); utils::IsEqual equal; REQUIRE(equal(c.at(value.first), value.second)); // TODO: add test for at exceptions const Table& constC = c; REQUIRE(equal(constC.at(value.first), value.second)); } template void TestMapSpecificMethods( Table&, const Value& ) {} template void TestMapSpecificMethods( Table& c, const std::pair& value ) { TestSquareBracketsAndAt(c, value, std::integral_constant::value>{}); } template class CheckValue { Table& my_c; public: CheckValue( Table& c ) : my_c(c) {} void operator()( const typename Table::value_type& value ) { using iterator = typename Table::iterator; using const_iterator = typename Table::const_iterator; const Table& constC = my_c; // count REQUIRE(my_c.count(Value
::key(value)) == 1); // find utils::IsEqual equal; REQUIRE(equal(*my_c.find(Value
::key(value)), value)); REQUIRE(equal(*constC.find(Value
::key(value)), value)); // erase REQUIRE(my_c.unsafe_erase(Value
::key(value)) != 0); REQUIRE(my_c.unsafe_erase(Value
::key(value)) == 0); // insert std::pair res = my_c.insert(value); REQUIRE(equal(*res.first, value)); REQUIRE(res.second); // erase iterator it = res.first; ++it; REQUIRE(my_c.unsafe_erase(res.first) == it); // insert REQUIRE(equal(*my_c.insert(my_c.begin(), value), value)); // equal_range std::pair r1 = my_c.equal_range(Value
::key(value)); REQUIRE((equal(*r1.first, value) && ++r1.first == r1.second)); std::pair r2 = constC.equal_range(Value
::key(value)); REQUIRE((equal(*r2.first, value) && ++r2.first == r2.second)); TestMapSpecificMethods(my_c, value); } }; // class CheckValue namespace detail { #if (__INTEL_COMPILER || __clang__ ) && __TBB_GLIBCXX_VERSION && __TBB_GLIBCXX_VERSION < 40900 template struct assignable_atomic : std::atomic { using std::atomic::operator=; assignable_atomic& operator=(const assignable_atomic& a) { store(a.load(std::memory_order_relaxed), std::memory_order_relaxed); } }; template using atomic_type = assignable_atomic; #else template using atomic_type = std::atomic; #endif } template class TestRange { const std::list& my_lst; std::vector>& my_marks; public: TestRange( const std::list& lst, std::vector>& marks ) : my_lst(lst), my_marks(marks) { std::fill(my_marks.begin(), my_marks.end(), false); } template void operator()( const Range& r ) const { do_test_range(r.begin(), r.end()); } template void do_test_range( Iterator i, Iterator j ) const { for (Iterator it = i; it != j;) { Iterator prev_it = it++; auto it2 = std::search(my_lst.begin(), my_lst.end(), prev_it, it, utils::IsEqual()); REQUIRE(it2 != my_lst.end()); auto dist = std::distance(my_lst.begin(), it2); REQUIRE(!my_marks[dist]); my_marks[dist] = true; } } }; // class TestRange template void CommonExamine( Table c, const std::list& lst ) { using value_type = typename Table::value_type; if (!(!c.empty() && c.size() == lst.size() && c.max_size() >= c.size())) { std::cout << std::boolalpha; std::cout << "Empty? " << c.empty() << std::endl; std::cout << "sizes equal? " << (c.size() == lst.size()) << std::endl; std::cout << "\t" << c.size() << std::endl; std::cout << "\t" << lst.size() << std::endl; std::cout << "Max size greater? " << (c.max_size() >= c.size()) << std::endl; } REQUIRE((!c.empty() && c.size() == lst.size() && c.max_size() >= c.size())); std::for_each(lst.begin(), lst.end(), CheckValue(c)); std::vector> marks(lst.size()); TestRange(lst, marks).do_test_range(c.begin(), c.end()); REQUIRE(std::find(marks.begin(), marks.end(), false) == marks.end()); const Table constC = c; REQUIRE(c.size() == constC.size()); TestRange(lst, marks).do_test_range(c.begin(), c.end()); REQUIRE(std::find(marks.begin(), marks.end(), false) == marks.end()); tbb::parallel_for(c.range(), TestRange(lst, marks)); REQUIRE(std::find(marks.begin(), marks.end(), false) == marks.end()); tbb::parallel_for(constC.range(), TestRange(lst, marks)); REQUIRE(std::find(marks.begin(), marks.end(), false) == marks.end()); Table c2; auto begin5 = lst.begin(); std::advance(begin5, 5); c2.insert(lst.begin(), begin5); std::for_each(lst.begin(), begin5, CheckValue(c2)); c2.swap(c); REQUIRE(c2.size() == lst.size()); REQUIRE(c.size() == 5); std::for_each(lst.begin(), lst.end(), CheckValue(c2)); c2.clear(); REQUIRE(c2.size() == 0); auto alloc = c.get_allocator(); value_type* ptr = alloc.allocate(1); REQUIRE(ptr != nullptr); alloc.deallocate(ptr, 1); } template void test_scoped_allocator() { using allocator_data_type = AllocatorAwareData>>; using basic_allocator_type = std::scoped_allocator_adaptor>; using container_value_type = typename ContainerTraits::template container_value_type; using allocator_type = typename std::allocator_traits::template rebind_alloc; using container_type = typename ContainerTraits::template container_type; allocator_type allocator; allocator_data_type key1(1, allocator); allocator_data_type key2(2, allocator); container_value_type value1 = Value::make(key1); container_value_type value2 = Value::make(key2); auto init_list = { value1, value2 }; container_type c1(allocator); container_type c2(allocator); allocator_data_type::activate(); emplace_helpers::call_emplace(c1, key1); emplace_helpers::call_emplace(c2, std::move(key2)); c1.clear(); c2.clear(); c1.insert(value1); c2.insert(std::move(value2)); c1.clear(); c2.clear(); c1.insert(init_list); c2.insert(value1); c1 = c2; c2 = std::move(c1); allocator_data_type::deactivate(); } struct int_key { int my_item; int_key(int i) : my_item(i) {} }; bool operator==(const int_key& ik, int i) { return ik.my_item == i; } bool operator==(int i, const int_key& ik) { return i == ik.my_item; } bool operator==(const int_key& ik1, const int_key& ik2) { return ik1.my_item == ik2.my_item; } bool operator<( const int_key& ik, int i ) { return ik.my_item < i; } bool operator<( int i, const int_key& ik ) { return i < ik.my_item; } bool operator<( const int_key& ik1, const int_key& ik2 ) { return ik1.my_item < ik2.my_item; } struct char_key { const char* my_item; char_key(const char* c) : my_item(c) {} const char& operator[] (std::size_t pos) const { return my_item[pos]; } std::size_t size() const { return std::strlen(my_item); } }; bool operator==(const char_key& ck, std::string c) { std::size_t i = 0; while (ck[i] != '\0' && i < c.size() && ck[i] == c[i]) { ++i;} return c.size() == i && ck[i] == '\0'; } bool operator==(std::string c, const char_key& ck) { return ck == c; } bool operator==(const char_key& ck1, const char_key& ck2) { std::size_t i = 0; while (ck1[i] != '\0' && ck2[i] != '\0' && ck1[i] == ck2[i]) { ++i;} return ck1[i] == ck2[i]; } bool operator<( const char_key& ck, std::string c ) { return std::lexicographical_compare(ck.my_item, ck.my_item + ck.size(), c.begin(), c.end()); } bool operator<(std::string c, const char_key& ck) { return std::lexicographical_compare(c.begin(), c.end(), ck.my_item, ck.my_item + ck.size()); } bool operator<( const char_key& ck1, const char_key& ck2 ) { return std::lexicographical_compare(ck1.my_item, ck1.my_item + ck1.size(), ck2.my_item, ck2.my_item + ck2.size()); } struct equal_to { using is_transparent = int; template bool operator()(const T &lhs, const W &rhs) const { return lhs == rhs; } }; struct hash_with_transparent_key_equal { template size_t operator()(const T& key) const { return hash(key); } using transparent_key_equal = equal_to; int prime = 433494437; int first_factor = 41241245; int second_factor = 2523422; size_t hash(const int& key) const { return (first_factor * key + second_factor) % prime; } size_t hash(const int_key& key) const { return (first_factor * key.my_item + second_factor) % prime; } size_t hash(const std::string& key) const { int sum = 0; for (auto it = key.begin(); it != key.end(); ++it) { sum += first_factor * (*it) + second_factor; } return sum % prime; } size_t hash(const char_key& key) const { int sum = 0; int i = 0; while (key[i] != '\0') { sum += first_factor * key[i++] + second_factor; } return sum % prime; } }; template void check_heterogeneous_functions_key_int_impl() { static_assert(std::is_same::value, "incorrect key_type for heterogeneous lookup test"); // Initialization Container c; int size = 10; for (int i = 0; i < size; i++) { c.insert(Value::make(i)); } // Insert first duplicated element for multicontainers if (AllowMultimapping::value) { c.insert(Value::make(0)); } // Look up testing for (int i = 0; i < size; i++) { int_key k(i); int key = i; REQUIRE_MESSAGE(c.find(k) == c.find(key), "Incorrect heterogeneous find return value"); REQUIRE_MESSAGE(c.count(k) == c.count(key), "Incorrect heterogeneous count return value"); } // unsafe_extract testing for (int i = 0; i < size; i++) { Container extract_c = c; int_key int_k(i); auto int_key_extract = extract_c.unsafe_extract(int_k); if (!AllowMultimapping::value) { REQUIRE_MESSAGE(extract_c.find(int_k) == extract_c.end(), "Key exists after extract"); } REQUIRE_MESSAGE(!int_key_extract.empty(), "Empty node with exists key"); REQUIRE_MESSAGE(node_handling_tests::compare_handle_getters(int_key_extract, Value::make(i)), "Incorrect node"); } // unsafe_extract not exists key auto not_exists = c.unsafe_extract(int_key(100)); REQUIRE_MESSAGE(not_exists.empty(), "Not empty node with not exists key"); // multimap unsafe_extract testing if (AllowMultimapping::value) { Container extract_m; for (int i = 0; i < size; i++) { extract_m.insert(Value::make(i)); extract_m.insert(Value::make(i, i + 1)); } for (int i = 0; i < size; i++) { int_key int_k(i); auto int_key_extract = extract_m.unsafe_extract(int_k); REQUIRE_MESSAGE(!int_key_extract.empty(), "Empty node with exists key"); REQUIRE_MESSAGE((node_handling_tests::compare_handle_getters(int_key_extract, Value::make(i, i)) || node_handling_tests::compare_handle_getters(int_key_extract, Value::make(i, i + 1))), "Incorrect node"); REQUIRE_MESSAGE(extract_m.find(int_k) != extract_m.end(), "All nodes for key deleted"); } } // Erase testing for (int i = 0; i < size; i++) { auto count_before_erase = c.count(i); auto result = c.unsafe_erase(int_key(i)); REQUIRE_MESSAGE(count_before_erase == result,"Incorrect erased elements count"); REQUIRE_MESSAGE(c.count(i) == 0, "Some elements was not erased"); } } template void check_heterogeneous_functions_key_string_impl() { static_assert(std::is_same::value, "incorrect key_type for heterogeneous lookup test"); // Initialization std::vector keys{"key1", "key2", "key3", "key4", "key5", "key6", "key7", "key8", "key9", "key10"}; std::vector values{"value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8", "value9", "value10", "value11"}; Container c; for (auto it = keys.begin(); it != keys.end(); ++it) { c.insert(Value::make(*it)); } // Insert first duplicated element for multicontainers if (AllowMultimapping::value) { c.insert(Value::make(*keys.begin())); } // Look up testing for (auto it = keys.begin(); it != keys.end(); ++it) { std::string key = *it; char_key k{*it}; REQUIRE_MESSAGE(c.find(k) == c.find(key), "Incorrect heterogeneous find return value"); REQUIRE_MESSAGE(c.count(k) == c.count(key), "Incorrect heterogeneous count return value"); } // unsafe_extract testing for (auto it = keys.begin(); it != keys.end(); ++it) { Container extract_c = c; char_key k{*it}; auto char_key_extract = extract_c.unsafe_extract(k); REQUIRE_MESSAGE(!char_key_extract.empty(), "Empty node with exists key"); REQUIRE_MESSAGE(node_handling_tests::compare_handle_getters(char_key_extract, Value::make(*it)), "Incorrect node"); } // unsafe_extract not exists key auto not_exists = c.unsafe_extract(char_key("not exists")); REQUIRE_MESSAGE(not_exists.empty(), "Not empty node with not exists key"); // multimap unsafe_extract testing if (AllowMultimapping::value){ Container extract_m; for (std::size_t i = 0; i < keys.size(); i++) { extract_m.insert(Value::make(keys[i], values[i])); extract_m.insert(Value::make(keys[i], values[i + 1])); } for (std::size_t i = 0; i < keys.size(); i++) { char_key char_k(keys[i]); auto char_key_extract = extract_m.unsafe_extract(char_k); REQUIRE_MESSAGE(!char_key_extract.empty(), "Empty node with exists key"); REQUIRE_MESSAGE((node_handling_tests::compare_handle_getters(char_key_extract, Value::make(keys[i], values[i])) || node_handling_tests::compare_handle_getters(char_key_extract, Value::make(keys[i], values[i + 1]))), "Incorrect node"); REQUIRE_MESSAGE(extract_m.find(char_k) != extract_m.end(), "All nodes for key deleted"); } } // Erase testing for (auto it = keys.begin(); it != keys.end(); ++it) { std::string key = *it; char_key k{*it}; auto count_before_erase = c.count(key); auto result = c.unsafe_erase(k); REQUIRE_MESSAGE(count_before_erase==result,"Incorrect erased elements count"); REQUIRE_MESSAGE(c.count(key)==0, "Some elements was not erased"); } } struct CountingKey { static std::size_t counter; CountingKey() { ++counter; } CountingKey( const CountingKey& ) { ++counter; } ~CountingKey() {} CountingKey& operator=( const CountingKey& ) { return *this; } static void reset() { counter = 0; } }; std::size_t CountingKey::counter; namespace std { template <> struct hash { std::size_t operator()( const CountingKey& ) const { return 0; } }; } bool operator==( const CountingKey&, const CountingKey& ) { return true; } bool operator<( const CountingKey&, const CountingKey& ) { return true; } struct int_constructible_object { int_constructible_object(int k) : key(k) {} int key; }; // struct int_constructible_object bool operator==( const int_constructible_object& lhs, const int_constructible_object rhs ) { return lhs.key == rhs.key; } // A test for // template insert(P&&) in maps template