blob: d10c8c9fc209092ca4baf69840e13c14d43e2710 [file] [log] [blame]
/*
Copyright 2005-2010 Intel Corporation. All Rights Reserved.
This file is part of Threading Building Blocks.
Threading Building Blocks is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
Threading Building Blocks 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 Threading Building Blocks; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
As a special exception, you may use this file as part of a free software
library without restriction. Specifically, if other files instantiate
templates or use macros or inline functions from this file, or you compile
this file and link it with other files to produce an executable, this
file does not by itself cause the resulting executable to be covered by
the GNU General Public License. This exception does not however
invalidate any other reasons why the executable file might be covered by
the GNU General Public License.
*/
#ifndef TBB_USE_PERFORMANCE_WARNINGS
#define TBB_USE_PERFORMANCE_WARNINGS 1
#endif
// Our tests usually include the header under test first. But this test needs
// to use the preprocessor to edit the identifier runtime_warning in concurrent_hash_map.h.
// Hence we include a few other headers before doing the abusive edit.
#include "tbb/tbb_stddef.h" /* Defines runtime_warning */
#include "harness_assert.h" /* Prerequisite for defining hooked_warning */
// The symbol internal::runtime_warning is normally an entry point into the TBB library.
// Here for sake of testing, we define it to be hooked_warning, a routine peculiar to this unit test.
#define runtime_warning hooked_warning
static bool bad_hashing = false;
namespace tbb {
namespace internal {
static void hooked_warning( const char* /*format*/, ... ) {
ASSERT(bad_hashing, "unexpected bad hashing");
}
} // namespace internal
} // namespace tbb
#define __TBB_EXTRA_DEBUG 1 // enables additional checks
#include "tbb/concurrent_hash_map.h"
// Restore runtime_warning as an entry point into the TBB library.
#undef runtime_warning
namespace Jungle {
struct Tiger {};
size_t tbb_hasher( const Tiger& ) {return 0;}
}
#if !defined(_MSC_VER) || _MSC_VER>=1400 || __INTEL_COMPILER
void test_ADL() {
tbb::tbb_hash_compare<Jungle::Tiger>::hash(Jungle::Tiger()); // Instantiation chain finds tbb_hasher via Argument Dependent Lookup
}
#endif
struct UserDefinedKeyType {
};
namespace tbb {
// Test whether tbb_hash_compare can be partially specialized as stated in Reference manual.
template<> struct tbb_hash_compare<UserDefinedKeyType> {
size_t hash( UserDefinedKeyType ) const {return 0;}
bool equal( UserDefinedKeyType /*x*/, UserDefinedKeyType /*y*/ ) {return true;}
};
};
tbb::concurrent_hash_map<UserDefinedKeyType,int> TestInstantiationWithUserDefinedKeyType;
// Test whether a sufficient set of headers were included to instantiate a concurernt_hash_map. OSS Bug #120 (& #130):
// http://www.threadingbuildingblocks.org/bug_desc.php?id=120
tbb::concurrent_hash_map<std::pair<std::pair<int,std::string>,const char*>,int> TestInstantiation;
#include "tbb/parallel_for.h"
#include "tbb/blocked_range.h"
#include "tbb/atomic.h"
#include "tbb/tick_count.h"
#include "harness.h"
#include "harness_allocator.h"
class MyException : public std::bad_alloc {
public:
virtual const char *what() const noexcept { return "out of items limit"; }
virtual ~MyException() noexcept {}
};
/** Has tightly controlled interface so that we can verify
that concurrent_hash_map uses only the required interface. */
class MyKey {
private:
void operator=( const MyKey& ); // Deny access
int key;
friend class MyHashCompare;
friend class YourHashCompare;
public:
static MyKey make( int i ) {
MyKey result;
result.key = i;
return result;
}
int value_of() const {return key;}
};
tbb::atomic<long> MyDataCount;
long MyDataCountLimit = 0;
class MyData {
protected:
friend class MyData2;
int data;
enum state_t {
LIVE=0x1234,
DEAD=0x5678
} my_state;
void operator=( const MyData& ); // Deny access
public:
MyData(int i = 0) {
my_state = LIVE;
data = i;
if(MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit)
__TBB_THROW( MyException() );
++MyDataCount;
}
MyData( const MyData& other ) {
ASSERT( other.my_state==LIVE, NULL );
my_state = LIVE;
data = other.data;
if(MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit)
__TBB_THROW( MyException() );
++MyDataCount;
}
~MyData() {
--MyDataCount;
my_state = DEAD;
}
static MyData make( int i ) {
MyData result;
result.data = i;
return result;
}
int value_of() const {
ASSERT( my_state==LIVE, NULL );
return data;
}
void set_value( int i ) {
ASSERT( my_state==LIVE, NULL );
data = i;
}
bool operator==( const MyData& other ) const {
ASSERT( other.my_state==LIVE, NULL );
ASSERT( my_state==LIVE, NULL );
return data == other.data;
}
};
class MyData2 : public MyData {
public:
MyData2( ) {}
MyData2( const MyData& other ) {
ASSERT( other.my_state==LIVE, NULL );
ASSERT( my_state==LIVE, NULL );
data = other.data;
}
void operator=( const MyData& other ) {
ASSERT( other.my_state==LIVE, NULL );
ASSERT( my_state==LIVE, NULL );
data = other.data;
}
void operator=( const MyData2& other ) {
ASSERT( other.my_state==LIVE, NULL );
ASSERT( my_state==LIVE, NULL );
data = other.data;
}
bool operator==( const MyData2& other ) const {
ASSERT( other.my_state==LIVE, NULL );
ASSERT( my_state==LIVE, NULL );
return data == other.data;
}
};
class MyHashCompare {
public:
bool equal( const MyKey& j, const MyKey& k ) const {
return j.key==k.key;
}
unsigned long hash( const MyKey& k ) const {
return k.key;
}
};
class YourHashCompare {
public:
bool equal( const MyKey& j, const MyKey& k ) const {
return j.key==k.key;
}
unsigned long hash( const MyKey& ) const {
return 1;
}
};
typedef local_counting_allocator<std::allocator<MyData> > MyAllocator;
typedef tbb::concurrent_hash_map<MyKey,MyData,MyHashCompare,MyAllocator> MyTable;
typedef tbb::concurrent_hash_map<MyKey,MyData2,MyHashCompare> MyTable2;
typedef tbb::concurrent_hash_map<MyKey,MyData,YourHashCompare> YourTable;
template<typename MyTable>
inline void CheckAllocator(MyTable &table, size_t expected_allocs, size_t expected_frees, bool exact = true) {
size_t items_allocated = table.get_allocator().items_allocated, items_freed = table.get_allocator().items_freed;
size_t allocations = table.get_allocator().allocations, frees = table.get_allocator().frees;
REMARK("checking allocators: items %u/%u, allocs %u/%u\n",
unsigned(items_allocated), unsigned(items_freed), unsigned(allocations), unsigned(frees) );
ASSERT( items_allocated == allocations, NULL); ASSERT( items_freed == frees, NULL);
if(exact) {
ASSERT( allocations == expected_allocs, NULL); ASSERT( frees == expected_frees, NULL);
} else {
ASSERT( allocations >= expected_allocs, NULL); ASSERT( frees >= expected_frees, NULL);
ASSERT( allocations - frees == expected_allocs - expected_frees, NULL );
}
}
inline bool UseKey( size_t i ) {
return (i&3)!=3;
}
struct Insert {
static void apply( MyTable& table, int i ) {
if( UseKey(i) ) {
if( i&4 ) {
MyTable::accessor a;
table.insert( a, MyKey::make(i) );
if( i&1 )
(*a).second.set_value(i*i);
else
a->second.set_value(i*i);
} else
if( i&1 ) {
MyTable::accessor a;
table.insert( a, std::make_pair(MyKey::make(i), MyData(i*i)) );
ASSERT( (*a).second.value_of()==i*i, NULL );
} else {
MyTable::const_accessor ca;
table.insert( ca, std::make_pair(MyKey::make(i), MyData(i*i)) );
ASSERT( ca->second.value_of()==i*i, NULL );
}
}
}
};
struct Find {
static void apply( MyTable& table, int i ) {
MyTable::accessor a;
const MyTable::accessor& ca = a;
bool b = table.find( a, MyKey::make(i) );
ASSERT( b==!a.empty(), NULL );
if( b ) {
if( !UseKey(i) )
REPORT("Line %d: unexpected key %d present\n",__LINE__,i);
AssertSameType( &*a, static_cast<MyTable::value_type*>(0) );
ASSERT( ca->second.value_of()==i*i, NULL );
ASSERT( (*ca).second.value_of()==i*i, NULL );
if( i&1 )
ca->second.set_value( ~ca->second.value_of() );
else
(*ca).second.set_value( ~ca->second.value_of() );
} else {
if( UseKey(i) )
REPORT("Line %d: key %d missing\n",__LINE__,i);
}
}
};
struct FindConst {
static void apply( const MyTable& table, int i ) {
MyTable::const_accessor a;
const MyTable::const_accessor& ca = a;
bool b = table.find( a, MyKey::make(i) );
ASSERT( b==(table.count(MyKey::make(i))>0), NULL );
ASSERT( b==!a.empty(), NULL );
ASSERT( b==UseKey(i), NULL );
if( b ) {
AssertSameType( &*ca, static_cast<const MyTable::value_type*>(0) );
ASSERT( ca->second.value_of()==~(i*i), NULL );
ASSERT( (*ca).second.value_of()==~(i*i), NULL );
}
}
};
tbb::atomic<int> EraseCount;
struct Erase {
static void apply( MyTable& table, int i ) {
bool b;
if(i&4) {
if(i&8) {
MyTable::const_accessor a;
b = table.find( a, MyKey::make(i) ) && table.erase( a );
} else {
MyTable::accessor a;
b = table.find( a, MyKey::make(i) ) && table.erase( a );
}
} else
b = table.erase( MyKey::make(i) );
if( b ) ++EraseCount;
ASSERT( table.count(MyKey::make(i)) == 0, NULL );
}
};
static const int IE_SIZE = 2;
tbb::atomic<YourTable::size_type> InsertEraseCount[IE_SIZE];
struct InsertErase {
static void apply( YourTable& table, int i ) {
if ( i%3 ) {
int key = i%IE_SIZE;
if ( table.insert( std::make_pair(MyKey::make(key), MyData2()) ) )
++InsertEraseCount[key];
} else {
int key = i%IE_SIZE;
if( i&1 ) {
YourTable::accessor res;
if(table.find( res, MyKey::make(key) ) && table.erase( res ) )
--InsertEraseCount[key];
} else {
YourTable::const_accessor res;
if(table.find( res, MyKey::make(key) ) && table.erase( res ) )
--InsertEraseCount[key];
}
}
}
};
// Test for the deadlock discussed at:
// http://softwarecommunity.intel.com/isn/Community/en-US/forums/permalink/30253302/30253302/ShowThread.aspx#30253302
struct InnerInsert {
static void apply( YourTable& table, int i ) {
YourTable::accessor a1, a2;
if(i&1) __TBB_Yield();
table.insert( a1, MyKey::make(1) );
__TBB_Yield();
table.insert( a2, MyKey::make(1 + (1<<30)) ); // the same chain
table.erase( a2 ); // if erase by key it would lead to deadlock for single thread
}
};
template<typename Op, typename MyTable>
class TableOperation: NoAssign {
MyTable& my_table;
public:
void operator()( const tbb::blocked_range<int>& range ) const {
for( int i=range.begin(); i!=range.end(); ++i )
Op::apply(my_table,i);
}
TableOperation( MyTable& table ) : my_table(table) {}
};
template<typename Op, typename TableType>
void DoConcurrentOperations( TableType& table, int n, const char* what, int nthread ) {
REMARK("testing %s with %d threads\n",what,nthread);
tbb::tick_count t0 = tbb::tick_count::now();
tbb::parallel_for( tbb::blocked_range<int>(0,n,100), TableOperation<Op,TableType>(table) );
tbb::tick_count t1 = tbb::tick_count::now();
REMARK("time for %s = %g with %d threads\n",what,(t1-t0).seconds(),nthread);
}
//! Test traversing the table with an iterator.
void TraverseTable( MyTable& table, size_t n, size_t expected_size ) {
REMARK("testing traversal\n");
size_t actual_size = table.size();
ASSERT( actual_size==expected_size, NULL );
size_t count = 0;
bool* array = new bool[n];
memset( array, 0, n*sizeof(bool) );
const MyTable& const_table = table;
MyTable::const_iterator ci = const_table.begin();
for( MyTable::iterator i = table.begin(); i!=table.end(); ++i ) {
// Check iterator
int k = i->first.value_of();
ASSERT( UseKey(k), NULL );
ASSERT( (*i).first.value_of()==k, NULL );
ASSERT( 0<=k && size_t(k)<n, "out of bounds key" );
ASSERT( !array[k], "duplicate key" );
array[k] = true;
++count;
// Check lower/upper bounds
std::pair<MyTable::iterator, MyTable::iterator> er = table.equal_range(i->first);
std::pair<MyTable::const_iterator, MyTable::const_iterator> cer = const_table.equal_range(i->first);
ASSERT(cer.first == er.first && cer.second == er.second, NULL);
ASSERT(cer.first == i, NULL);
ASSERT(std::distance(cer.first, cer.second) == 1, NULL);
// Check const_iterator
ASSERT( ci->first.value_of()==k, NULL );
ASSERT( (*ci).first.value_of()==k, NULL );
++ci;
}
ASSERT( ci==const_table.end(), NULL );
delete[] array;
if( count!=expected_size ) {
REPORT("Line %d: count=%ld but should be %ld\n",__LINE__,long(count),long(expected_size));
}
}
typedef tbb::atomic<unsigned char> AtomicByte;
template<typename RangeType>
struct ParallelTraverseBody: NoAssign {
const size_t n;
AtomicByte* const array;
ParallelTraverseBody( AtomicByte array_[], size_t n_ ) :
n(n_),
array(array_)
{}
void operator()( const RangeType& range ) const {
for( typename RangeType::iterator i = range.begin(); i!=range.end(); ++i ) {
int k = i->first.value_of();
ASSERT( 0<=k && size_t(k)<n, NULL );
++array[k];
}
}
};
void Check( AtomicByte array[], size_t n, size_t expected_size ) {
if( expected_size )
for( size_t k=0; k<n; ++k ) {
if( array[k] != int(UseKey(k)) ) {
REPORT("array[%d]=%d != %d=UseKey(%d)\n",
int(k), int(array[k]), int(UseKey(k)), int(k));
ASSERT(false,NULL);
}
}
}
//! Test travering the tabel with a parallel range
void ParallelTraverseTable( MyTable& table, size_t n, size_t expected_size ) {
REMARK("testing parallel traversal\n");
ASSERT( table.size()==expected_size, NULL );
AtomicByte* array = new AtomicByte[n];
memset( array, 0, n*sizeof(AtomicByte) );
MyTable::range_type r = table.range(10);
tbb::parallel_for( r, ParallelTraverseBody<MyTable::range_type>( array, n ));
Check( array, n, expected_size );
const MyTable& const_table = table;
memset( array, 0, n*sizeof(AtomicByte) );
MyTable::const_range_type cr = const_table.range(10);
tbb::parallel_for( cr, ParallelTraverseBody<MyTable::const_range_type>( array, n ));
Check( array, n, expected_size );
delete[] array;
}
void TestInsertFindErase( int nthread ) {
int n=250000;
// compute m = number of unique keys
int m = 0;
for( int i=0; i<n; ++i )
m += UseKey(i);
MyAllocator a; a.items_freed = a.frees = 100;
ASSERT( MyDataCount==0, NULL );
MyTable table(a);
TraverseTable(table,n,0);
ParallelTraverseTable(table,n,0);
CheckAllocator(table, 0, 100);
DoConcurrentOperations<Insert,MyTable>(table,n,"insert",nthread);
ASSERT( MyDataCount==m, NULL );
TraverseTable(table,n,m);
ParallelTraverseTable(table,n,m);
CheckAllocator(table, m, 100);
DoConcurrentOperations<Find,MyTable>(table,n,"find",nthread);
ASSERT( MyDataCount==m, NULL );
CheckAllocator(table, m, 100);
DoConcurrentOperations<FindConst,MyTable>(table,n,"find(const)",nthread);
ASSERT( MyDataCount==m, NULL );
CheckAllocator(table, m, 100);
EraseCount=0;
DoConcurrentOperations<Erase,MyTable>(table,n,"erase",nthread);
ASSERT( EraseCount==m, NULL );
ASSERT( MyDataCount==0, NULL );
TraverseTable(table,n,0);
CheckAllocator(table, m, m+100);
bad_hashing = true;
table.clear();
bad_hashing = false;
if(nthread > 1) {
YourTable ie_table;
for( int i=0; i<IE_SIZE; ++i )
InsertEraseCount[i] = 0;
DoConcurrentOperations<InsertErase,YourTable>(ie_table,n/2,"insert_erase",nthread);
for( int i=0; i<IE_SIZE; ++i )
ASSERT( InsertEraseCount[i]==ie_table.count(MyKey::make(i)), NULL );
DoConcurrentOperations<InnerInsert,YourTable>(ie_table,2000,"inner insert",nthread);
}
}
volatile int Counter;
class AddToTable: NoAssign {
MyTable& my_table;
const int my_nthread;
const int my_m;
public:
AddToTable( MyTable& table, int nthread, int m ) : my_table(table), my_nthread(nthread), my_m(m) {}
void operator()( int ) const {
for( int i=0; i<my_m; ++i ) {
// Busy wait to synchronize threads
int j = 0;
while( Counter<i ) {
if( ++j==1000000 ) {
// If Counter<i after a million iterations, then we almost surely have
// more logical threads than physical threads, and should yield in
// order to let suspended logical threads make progress.
j = 0;
__TBB_Yield();
}
}
// Now all threads attempt to simultaneously insert a key.
int k;
{
MyTable::accessor a;
MyKey key = MyKey::make(i);
if( my_table.insert( a, key ) )
a->second.set_value( 1 );
else
a->second.set_value( a->second.value_of()+1 );
k = a->second.value_of();
}
if( k==my_nthread )
Counter=i+1;
}
}
};
class RemoveFromTable: NoAssign {
MyTable& my_table;
const int my_nthread;
const int my_m;
public:
RemoveFromTable( MyTable& table, int nthread, int m ) : my_table(table), my_nthread(nthread), my_m(m) {}
void operator()(int) const {
for( int i=0; i<my_m; ++i ) {
bool b;
if(i&4) {
if(i&8) {
MyTable::const_accessor a;
b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
} else {
MyTable::accessor a;
b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
}
} else
b = my_table.erase( MyKey::make(i) );
if( b ) ++EraseCount;
}
}
};
//! Test for memory leak in concurrent_hash_map (TR #153).
void TestConcurrency( int nthread ) {
REMARK("testing multiple insertions/deletions of same key with %d threads\n", nthread);
{
ASSERT( MyDataCount==0, NULL );
MyTable table;
const int m = 1000;
Counter = 0;
tbb::tick_count t0 = tbb::tick_count::now();
NativeParallelFor( nthread, AddToTable(table,nthread,m) );
tbb::tick_count t1 = tbb::tick_count::now();
REMARK("time for %u insertions = %g with %d threads\n",unsigned(MyDataCount),(t1-t0).seconds(),nthread);
ASSERT( MyDataCount==m, "memory leak detected" );
EraseCount = 0;
t0 = tbb::tick_count::now();
NativeParallelFor( nthread, RemoveFromTable(table,nthread,m) );
t1 = tbb::tick_count::now();
REMARK("time for %u deletions = %g with %d threads\n",unsigned(EraseCount),(t1-t0).seconds(),nthread);
ASSERT( MyDataCount==0, "memory leak detected" );
ASSERT( EraseCount==m, "return value of erase() is broken" );
CheckAllocator(table, m, m, /*exact*/nthread <= 1);
}
ASSERT( MyDataCount==0, "memory leak detected" );
}
void TestTypes() {
AssertSameType( static_cast<MyTable::key_type*>(0), static_cast<MyKey*>(0) );
AssertSameType( static_cast<MyTable::mapped_type*>(0), static_cast<MyData*>(0) );
AssertSameType( static_cast<MyTable::value_type*>(0), static_cast<std::pair<const MyKey,MyData>*>(0) );
AssertSameType( static_cast<MyTable::accessor::value_type*>(0), static_cast<MyTable::value_type*>(0) );
AssertSameType( static_cast<MyTable::const_accessor::value_type*>(0), static_cast<const MyTable::value_type*>(0) );
AssertSameType( static_cast<MyTable::size_type*>(0), static_cast<size_t*>(0) );
AssertSameType( static_cast<MyTable::difference_type*>(0), static_cast<ptrdiff_t*>(0) );
}
template<typename Iterator, typename T>
void TestIteratorTraits() {
AssertSameType( static_cast<typename Iterator::difference_type*>(0), static_cast<ptrdiff_t*>(0) );
AssertSameType( static_cast<typename Iterator::value_type*>(0), static_cast<T*>(0) );
AssertSameType( static_cast<typename Iterator::pointer*>(0), static_cast<T**>(0) );
AssertSameType( static_cast<typename Iterator::iterator_category*>(0), static_cast<std::forward_iterator_tag*>(0) );
T x;
typename Iterator::reference xr = x;
typename Iterator::pointer xp = &x;
ASSERT( &xr==xp, NULL );
}
template<typename Iterator1, typename Iterator2>
void TestIteratorAssignment( Iterator2 j ) {
Iterator1 i(j), k;
ASSERT( i==j, NULL ); ASSERT( !(i!=j), NULL );
k = j;
ASSERT( k==j, NULL ); ASSERT( !(k!=j), NULL );
}
template<typename Range1, typename Range2>
void TestRangeAssignment( Range2 r2 ) {
Range1 r1(r2); r1 = r2;
}
//------------------------------------------------------------------------
// Test for copy constructor and assignment
//------------------------------------------------------------------------
template<typename MyTable>
static void FillTable( MyTable& x, int n ) {
for( int i=1; i<=n; ++i ) {
MyKey key( MyKey::make(-i) ); // hash values must not be specified in direct order
typename MyTable::accessor a;
bool b = x.insert(a,key);
ASSERT(b, NULL);
a->second.set_value( i*i );
}
}
template<typename MyTable>
static void CheckTable( const MyTable& x, int n ) {
ASSERT( x.size()==size_t(n), "table is different size than expected" );
ASSERT( x.empty()==(n==0), NULL );
ASSERT( x.size()<=x.max_size(), NULL );
for( int i=1; i<=n; ++i ) {
MyKey key( MyKey::make(-i) );
typename MyTable::const_accessor a;
bool b = x.find(a,key);
ASSERT( b, NULL );
ASSERT( a->second.value_of()==i*i, NULL );
}
int count = 0;
int key_sum = 0;
for( typename MyTable::const_iterator i(x.begin()); i!=x.end(); ++i ) {
++count;
key_sum += -i->first.value_of();
}
ASSERT( count==n, NULL );
ASSERT( key_sum==n*(n+1)/2, NULL );
}
static void TestCopy() {
REMARK("testing copy\n");
MyTable t1;
for( int i=0; i<10000; i=(i<100 ? i+1 : i*3) ) {
MyDataCount = 0;
FillTable(t1,i);
// Do not call CheckTable(t1,i) before copying, it enforces rehashing
MyTable t2(t1);
// Check that copy constructor did not mangle source table.
CheckTable(t1,i);
swap(t1, t2);
CheckTable(t1,i);
ASSERT( !(t1 != t2), NULL );
// Clear original table
t2.clear();
swap(t2, t1);
CheckTable(t1,0);
// Verify that copy of t1 is correct, even after t1 is cleared.
CheckTable(t2,i);
t2.clear();
t1.swap( t2 );
CheckTable(t1,0);
CheckTable(t2,0);
ASSERT( MyDataCount==0, "data leak?" );
}
}
void TestAssignment() {
REMARK("testing assignment\n");
for( int i=0; i<1000; i=(i<30 ? i+1 : i*5) ) {
for( int j=0; j<1000; j=(j<30 ? j+1 : j*7) ) {
MyTable t1;
MyTable t2;
FillTable(t1,i);
FillTable(t2,j);
ASSERT( (t1 == t2) == (i == j), NULL );
CheckTable(t2,j);
MyTable& tref = t2=t1;
ASSERT( &tref==&t2, NULL );
ASSERT( t1 == t2, NULL );
CheckTable(t1,i);
CheckTable(t2,i);
t1.clear();
CheckTable(t1,0);
CheckTable(t2,i);
ASSERT( MyDataCount==i, "data leak?" );
t2.clear();
CheckTable(t1,0);
CheckTable(t2,0);
ASSERT( MyDataCount==0, "data leak?" );
}
}
}
void TestIteratorsAndRanges() {
REMARK("testing iterators compliance\n");
TestIteratorTraits<MyTable::iterator,MyTable::value_type>();
TestIteratorTraits<MyTable::const_iterator,const MyTable::value_type>();
MyTable v;
MyTable const &u = v;
TestIteratorAssignment<MyTable::const_iterator>( u.begin() );
TestIteratorAssignment<MyTable::const_iterator>( v.begin() );
TestIteratorAssignment<MyTable::iterator>( v.begin() );
// doesn't compile as expected: TestIteratorAssignment<typename V::iterator>( u.begin() );
// check for non-existing
ASSERT(v.equal_range(MyKey::make(-1)) == std::make_pair(v.end(), v.end()), NULL);
ASSERT(u.equal_range(MyKey::make(-1)) == std::make_pair(u.end(), u.end()), NULL);
REMARK("testing ranges compliance\n");
TestRangeAssignment<MyTable::const_range_type>( u.range() );
TestRangeAssignment<MyTable::const_range_type>( v.range() );
TestRangeAssignment<MyTable::range_type>( v.range() );
// doesn't compile as expected: TestRangeAssignment<typename V::range_type>( u.range() );
REMARK("testing construction and insertion from iterators range\n");
FillTable( v, 1000 );
MyTable2 t(v.begin(), v.end());
v.rehash();
CheckTable(t, 1000);
t.insert(v.begin(), v.end()); // do nothing
CheckTable(t, 1000);
t.clear();
t.insert(v.begin(), v.end()); // restore
CheckTable(t, 1000);
REMARK("testing comparison\n");
typedef tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare,MyAllocator> YourTable1;
typedef tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare> YourTable2;
YourTable1 t1;
FillTable( t1, 10 );
CheckTable(t1, 10 );
YourTable2 t2(t1.begin(), t1.end());
MyKey key( MyKey::make(-5) ); MyData2 data;
ASSERT(t2.erase(key), NULL);
YourTable2::accessor a;
ASSERT(t2.insert(a, key), NULL);
data.set_value(0); a->second = data;
ASSERT( t1 != t2, NULL);
data.set_value(5*5); a->second = data;
ASSERT( t1 == t2, NULL);
}
void TestRehash() {
REMARK("testing rehashing\n");
MyTable w;
w.insert( std::make_pair(MyKey::make(-5), MyData()) );
w.rehash(); // without this, assertion will fail
MyTable::const_iterator it = w.begin();
int i = 0; // check for non-rehashed buckets
for( ; it != w.end(); ++it, i++ )
w.count( it->first );
ASSERT( i == 1, NULL );
for( i=0; i<1000; i=(i<29 ? i+1 : i*2) ) {
for( int j=max(256+i, i*2); j<10000; j*=3 ) {
MyTable v;
FillTable( v, i );
ASSERT(int(v.size()) == i, NULL);
ASSERT(int(v.bucket_count()) <= j, NULL);
v.rehash( j );
ASSERT(int(v.bucket_count()) >= j, NULL);
CheckTable( v, i );
}
}
}
#if TBB_USE_EXCEPTIONS
void TestExceptions() {
typedef local_counting_allocator<tbb::tbb_allocator<MyData2> > allocator_t;
typedef tbb::concurrent_hash_map<MyKey,MyData2,MyHashCompare,allocator_t> ThrowingTable;
enum methods {
zero_method = 0,
ctor_copy, op_assign, op_insert,
all_methods
};
REMARK("testing exception-safety guarantees\n");
ThrowingTable src;
FillTable( src, 1000 );
ASSERT( MyDataCount==1000, NULL );
try {
for(int t = 0; t < 2; t++) // exception type
for(int m = zero_method+1; m < all_methods; m++)
{
allocator_t a;
if(t) MyDataCountLimit = 101;
else a.set_limits(101);
ThrowingTable victim(a);
MyDataCount = 0;
try {
switch(m) {
case ctor_copy: {
ThrowingTable acopy(src, a);
} break;
case op_assign: {
victim = src;
} break;
case op_insert: {
FillTable( victim, 1000 );
} break;
default:;
}
ASSERT(false, "should throw an exception");
} catch(std::bad_alloc &e) {
MyDataCountLimit = 0;
size_t size = victim.size();
switch(m) {
case op_assign:
ASSERT( MyDataCount==100, "data leak?" );
ASSERT( size>=100, NULL );
CheckAllocator(victim, 100+t, t);
case ctor_copy:
CheckTable(src, 1000);
break;
case op_insert:
ASSERT( size==size_t(100-t), NULL );
ASSERT( MyDataCount==100-t, "data leak?" );
CheckTable(victim, 100-t);
CheckAllocator(victim, 100, t);
break;
default:; // nothing to check here
}
REMARK("Exception %d: %s\t- ok ()\n", m, e.what());
}
catch ( ... ) {
ASSERT ( __TBB_EXCEPTION_TYPE_INFO_BROKEN, "Unrecognized exception" );
}
}
} catch(...) {
ASSERT(false, "unexpected exception");
}
src.clear(); MyDataCount = 0;
}
#endif /* TBB_USE_EXCEPTIONS */
//------------------------------------------------------------------------
// Test driver
//------------------------------------------------------------------------
#include "tbb/task_scheduler_init.h"
int TestMain () {
if( MinThread<0 ) {
REPORT("ERROR: must use at least one thread\n");
exit(1);
}
// Do serial tests
TestTypes();
TestCopy();
TestRehash();
TestAssignment();
TestIteratorsAndRanges();
#if TBB_USE_EXCEPTIONS
TestExceptions();
#endif /* TBB_USE_EXCEPTIONS */
// Do concurrency tests.
for( int nthread=MinThread; nthread<=MaxThread; ++nthread ) {
tbb::task_scheduler_init init( nthread );
TestInsertFindErase( nthread );
TestConcurrency( nthread );
}
// check linking
if(bad_hashing) { //should be false
tbb::internal::runtime_warning("none\nERROR: it must not be executed");
}
return Harness::Done;
}