diff --git a/test/exception/Jamfile.v2 b/test/exception/Jamfile.v2 index 7f32840e..e099c772 100644 --- a/test/exception/Jamfile.v2 +++ b/test/exception/Jamfile.v2 @@ -5,7 +5,8 @@ import testing ; -alias framework : /boost/test//boost_unit_test_framework/speed ; +#alias framework : /boost/test//boost_unit_test_framework/speed ; +alias framework : /boost/test//boost_unit_test_framework ; project unordered-test/exception-tests : requirements diff --git a/test/exception/erase_tests.cpp b/test/exception/erase_tests.cpp index c1709a9d..ef1fd2f4 100644 --- a/test/exception/erase_tests.cpp +++ b/test/exception/erase_tests.cpp @@ -27,7 +27,7 @@ struct erase_test_base : public test::exception_base void check(T const& x) const { std::string scope(test::scope); - BOOST_CHECK(scope.find("hash::") != std::string::npos || + HASH_CHECK(scope.find("hash::") != std::string::npos || scope.find("equal_to::") != std::string::npos || scope == "operator==(object, object)"); diff --git a/test/exception/swap_tests.cpp b/test/exception/swap_tests.cpp index 66747e08..5fe64763 100644 --- a/test/exception/swap_tests.cpp +++ b/test/exception/swap_tests.cpp @@ -24,7 +24,7 @@ struct self_swap_base : public test::exception_base std::string scope(test::scope); #if BOOST_UNORDERED_SWAP_METHOD != 2 - BOOST_CHECK( + HASH_CHECK( scope == "hash::operator(hash)" || scope == "hash::operator=(hash)" || scope == "equal_to::operator(equal_to)" || diff --git a/test/objects/exception.hpp b/test/objects/exception.hpp index 7b3911f6..ff00bbe8 100644 --- a/test/objects/exception.hpp +++ b/test/objects/exception.hpp @@ -16,6 +16,7 @@ #include #include #include "../helpers/fwd.hpp" +#include #define RUN_EXCEPTION_TESTS(test_seq, param_seq) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT(RUN_EXCEPTION_TESTS_OP, (test_seq)(param_seq)) @@ -37,7 +38,6 @@ } #define SCOPE(scope_name) \ - BOOST_ITEST_SCOPE(scope_name); \ for(::test::scope_guard unordered_test_guard( \ BOOST_STRINGIZE(scope_name)); \ !unordered_test_guard.dismissed(); \ @@ -53,6 +53,8 @@ #define DISABLE_EXCEPTIONS \ ::test::exceptions_enable BOOST_PP_CAT(ENABLE_EXCEPTIONS_, __LINE__)(false) +#define HASH_CHECK(test) if(!(test)) BOOST_ERROR(BOOST_STRINGIZE(test)) + namespace test { static char const* scope = ""; bool exceptions_enabled = false; @@ -175,6 +177,161 @@ namespace test { namespace exception { + namespace detail + { + // This annoymous namespace won't cause ODR violations as I won't + // be linking multiple translation units together. I'll probably + // move this into a cpp file before a full release, but for now it's + // the most convenient way. + namespace + { + template + struct malloc_allocator + { + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef T* pointer; + typedef T const* const_pointer; + typedef T& reference; + typedef T const& const_reference; + typedef T value_type; + + template struct rebind { typedef malloc_allocator other; }; + + malloc_allocator() {} + template malloc_allocator(malloc_allocator const& x) {} + malloc_allocator(malloc_allocator const& x) {} + + pointer address(reference r) { return &r; } + const_pointer address(const_reference r) { return &r; } + + pointer allocate(size_type n) { + return static_cast(malloc(n * sizeof(T))); + } + + pointer allocate(size_type n, const_pointer u) { return allocate(n); } + void deallocate(pointer p, size_type n) { free(p); } + void construct(pointer p, T const& t) { new(p) T(t); } + void destroy(pointer p) { p->~T(); } + + size_type max_size() const { + return (std::numeric_limits::max)(); + } + + bool operator==(malloc_allocator const& x) const { return true; } + bool operator!=(malloc_allocator const& x) const { return false; } + }; + + struct memory_area { + void const* start; + void const* end; + + memory_area(void const* s, void const* e) + : start(s), end(e) + { + } + + // This is a bit dodgy as it defines overlapping + // areas as 'equal', so this isn't a total ordering. + // But it is for non-overlapping memory regions - which + // is what'll be stored. + // + // All searches will be for areas entirely contained by + // a member of the set - so it should find the area that contains + // the region that is searched for. + bool operator<(memory_area const& other) const { + return end < other.start; + } + }; + + struct memory_track { + explicit memory_track(int tag = -1) : + tag_(tag) {} + + int tag_; + }; + + typedef std::map, + malloc_allocator > > + allocated_memory_type; + allocated_memory_type allocated_memory; + unsigned int count_allocators = 0; + unsigned int count_allocations = 0; + unsigned int count_constructions = 0; + } + + void allocator_ref() + { + if(count_allocators == 0) { + count_allocations = 0; + count_constructions = 0; + allocated_memory.clear(); + } + ++count_allocators; + } + + void allocator_unref() + { + HASH_CHECK(count_allocators > 0); + if(count_allocators > 0) { + --count_allocators; + if(count_allocators == 0) { + bool no_allocations_left = (count_allocations == 0); + bool no_constructions_left = (count_constructions == 0); + bool allocated_memory_empty = allocated_memory.empty(); + + // Clearing the data before the checks terminate the tests. + count_allocations = 0; + count_constructions = 0; + allocated_memory.clear(); + + HASH_CHECK(no_allocations_left); + HASH_CHECK(no_constructions_left); + HASH_CHECK(allocated_memory_empty); + } + } + } + + void track_allocate(void *ptr, std::size_t n, std::size_t size, int tag) + { + if(n == 0) { + BOOST_ERROR("Allocating 0 length array."); + } + else { + ++count_allocations; + allocated_memory[memory_area(ptr, (char*) ptr + n * size)] = + memory_track(tag); + } + } + + void track_deallocate(void* ptr, std::size_t n, std::size_t size, int tag) + { + allocated_memory_type::iterator pos + = allocated_memory.find(memory_area(ptr, ptr)); + if(pos == allocated_memory.end()) { + BOOST_ERROR("Deallocating unknown pointer."); + } else { + HASH_CHECK(pos->first.start == ptr); + HASH_CHECK(pos->first.end == (char*) ptr + n * size); + HASH_CHECK(pos->second.tag_ == tag); + allocated_memory.erase(pos); + } + HASH_CHECK(count_allocations > 0); + if(count_allocations > 0) --count_allocations; + } + + void track_construct(void* ptr, std::size_t /*size*/, int tag) + { + ++count_constructions; + } + + void track_destroy(void* ptr, std::size_t /*size*/, int tag) + { + HASH_CHECK(count_constructions > 0); + if(count_constructions > 0) --count_constructions; + } + } + class object; class hash; class equal_to; @@ -379,6 +536,7 @@ namespace exception class allocator { public: + int tag_; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; typedef T* pointer; @@ -389,32 +547,38 @@ namespace exception template struct rebind { typedef allocator other; }; - explicit allocator(int = 0) + explicit allocator(int t = 0) : tag_(t) { SCOPE(allocator::allocator()) { EPOINT("Mock allocator default constructor."); } + detail::allocator_ref(); } - template allocator(allocator const&) + template allocator(allocator const& x) : tag_(x.tag_) { SCOPE(allocator::allocator()) { EPOINT("Mock allocator template copy constructor."); } + detail::allocator_ref(); } - allocator(allocator const&) + allocator(allocator const& x) : tag_(x.tag_) { SCOPE(allocator::allocator()) { EPOINT("Mock allocator copy constructor."); } + detail::allocator_ref(); } - ~allocator() {} + ~allocator() { + detail::allocator_unref(); + } - allocator& operator=(allocator const&) { + allocator& operator=(allocator const& x) { SCOPE(allocator::allocator()) { EPOINT("Mock allocator assignment operator."); + tag_ = x.tag_; } return *this; } @@ -446,6 +610,7 @@ namespace exception ptr = (T*) malloc(n * sizeof(T)); if(!ptr) throw std::bad_alloc(); } + detail::track_allocate((void*) ptr, n, sizeof(T), tag_); return pointer(ptr); //return pointer(static_cast(::operator new(n * sizeof(T)))); @@ -461,15 +626,17 @@ namespace exception ptr = (T*) malloc(n * sizeof(T)); if(!ptr) throw std::bad_alloc(); } + detail::track_allocate((void*) ptr, n, sizeof(T), tag_); return pointer(ptr); //return pointer(static_cast(::operator new(n * sizeof(T)))); } - void deallocate(pointer p, size_type) + void deallocate(pointer p, size_type n) { //::operator delete((void*) p); if(p) { + detail::track_deallocate((void*) p, n, sizeof(T), tag_); using namespace std; free(p); } @@ -480,9 +647,13 @@ namespace exception EPOINT("Mock allocator construct function."); new(p) T(t); } + detail::track_construct((void*) p, sizeof(T), tag_); } - void destroy(pointer p) { p->~T(); } + void destroy(pointer p) { + detail::track_destroy((void*) p, sizeof(T), tag_); + p->~T(); + } size_type max_size() const { SCOPE(allocator::construct(pointer, T)) { @@ -496,12 +667,12 @@ namespace exception // two can throw. So they don't. template - inline bool operator==(allocator const&, allocator const&) + inline bool operator==(allocator const& x, allocator const& y) { //SCOPE(operator==(allocator, allocator)) { // EPOINT("Mock allocator equality operator."); //} - return true; + return x.tag_ == y.tag_; } template @@ -510,9 +681,10 @@ namespace exception //SCOPE(operator!=(allocator, allocator)) { // EPOINT("Mock allocator inequality operator."); //} - return false; + return x.tag_ != y.tag_; } } } #endif + diff --git a/test/objects/test.hpp b/test/objects/test.hpp index 9f4702c0..1803b2ac 100644 --- a/test/objects/test.hpp +++ b/test/objects/test.hpp @@ -154,6 +154,10 @@ namespace test namespace detail { + // This annoymous namespace won't cause ODR violations as I won't + // be linking multiple translation units together. I'll probably + // move this into a cpp file before a full release, but for now it's + // the most convenient way. namespace { struct memory_area { void const* start; @@ -186,7 +190,8 @@ namespace test int tag_; }; - std::map allocated_memory; + typedef std::map allocated_memory_type; + allocated_memory_type allocated_memory; unsigned int count_allocators = 0; unsigned int count_allocations = 0; unsigned int count_constructions = 0; @@ -238,7 +243,7 @@ namespace test void track_deallocate(void* ptr, std::size_t n, std::size_t size, int tag) { - std::map::iterator pos + allocated_memory_type::iterator pos = allocated_memory.find(memory_area(ptr, ptr)); if(pos == allocated_memory.end()) { BOOST_ERROR("Deallocating unknown pointer."); @@ -255,7 +260,7 @@ namespace test void track_construct(void* ptr, std::size_t /*size*/, int tag) { - std::map::iterator pos + allocated_memory_type::iterator pos = allocated_memory.find(memory_area(ptr, ptr)); if(pos == allocated_memory.end()) BOOST_ERROR("Constructing unknown pointer."); @@ -266,7 +271,7 @@ namespace test void track_destroy(void* ptr, std::size_t /*size*/, int tag) { - std::map::iterator pos + allocated_memory_type::iterator pos = allocated_memory.find(memory_area(ptr, ptr)); if(pos == allocated_memory.end()) BOOST_ERROR("Destroying unknown pointer."); @@ -349,7 +354,7 @@ namespace test return tag_ != x.tag_; } }; - + template bool equivalent_impl(allocator const& x, allocator const& y, test::derived_type) { return x == y; diff --git a/test/unordered/Jamfile.v2 b/test/unordered/Jamfile.v2 index ffa20a42..f69c158a 100644 --- a/test/unordered/Jamfile.v2 +++ b/test/unordered/Jamfile.v2 @@ -18,6 +18,7 @@ test-suite unordered-tests [ run copy_tests.cpp ] [ run assign_tests.cpp ] [ run insert_tests.cpp ] + [ run unnecessary_copy_tests.cpp ] [ run erase_tests.cpp ] [ run erase_equiv_tests.cpp ] [ run find_tests.cpp ] diff --git a/test/unordered/swap_tests.cpp b/test/unordered/swap_tests.cpp index 9473c7a0..35d70eb2 100644 --- a/test/unordered/swap_tests.cpp +++ b/test/unordered/swap_tests.cpp @@ -74,11 +74,10 @@ void swap_tests2(X* ptr = 0) X x(v.begin(), v.end(), 0, hasher(1), key_equal(1)); X y(0, hasher(2), key_equal(2)); swap_test_impl(x, y); - swap_test_impl(x, y); } { - test::random_values vx(1000), vy(1000); + test::random_values vx(100), vy(50); X x(vx.begin(), vx.end(), 0, hasher(1), key_equal(1)); X y(vy.begin(), vy.end(), 0, hasher(2), key_equal(2)); swap_test_impl(x, y); @@ -87,7 +86,7 @@ void swap_tests2(X* ptr = 0) #if BOOST_UNORDERED_SWAP_METHOD == 1 { - test::random_values vx(1000), vy(1000); + test::random_values vx(100), vy(50); X x(vx.begin(), vx.end(), 0, hasher(), key_equal(), allocator_type(1)); X y(vy.begin(), vy.end(), 0, hasher(), key_equal(), allocator_type(2)); try { @@ -97,15 +96,14 @@ void swap_tests2(X* ptr = 0) } #else { - test::random_values vx(1000), vy(1000); + test::random_values vx(50), vy(100); X x(vx.begin(), vx.end(), 0, hasher(), key_equal(), allocator_type(1)); X y(vy.begin(), vy.end(), 0, hasher(), key_equal(), allocator_type(2)); swap_test_impl(x, y); - swap_test_impl(x, y); } { - test::random_values vx(1000), vy(1000); + test::random_values vx(100), vy(100); X x(vx.begin(), vx.end(), 0, hasher(1), key_equal(1), allocator_type(1)); X y(vy.begin(), vy.end(), 0, hasher(2), key_equal(2), allocator_type(2)); swap_test_impl(x, y); diff --git a/test/unordered/unnecessary_copy_tests.cpp b/test/unordered/unnecessary_copy_tests.cpp new file mode 100644 index 00000000..3991d1f2 --- /dev/null +++ b/test/unordered/unnecessary_copy_tests.cpp @@ -0,0 +1,49 @@ + +// Copyright 2006 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +struct count_copies +{ + static int count; + count_copies() { ++count; } + count_copies(count_copies const& x) { ++count; } +private: + count_copies& operator=(count_copies const&); +}; + +std::size_t hash_value(count_copies const& x) { + return 0; +} + +bool operator==(count_copies const& x, count_copies const& y) { + return true; +} + +int count_copies::count; + +template +void unnecessary_copy_test(T*) +{ + count_copies::count = 0; + T x; + typename T::value_type a; + BOOST_TEST(count_copies::count == 1); + x.insert(a); + BOOST_TEST(count_copies::count == 2); +} + + +int main() +{ + unnecessary_copy_test((boost::unordered_set*) 0); + unnecessary_copy_test((boost::unordered_multiset*) 0); + unnecessary_copy_test((boost::unordered_map*) 0); + unnecessary_copy_test((boost::unordered_multimap*) 0); + + return boost::report_errors(); +}