From 086a42e4329a2d80d4eb0b0c868199671e21330e Mon Sep 17 00:00:00 2001 From: mat007 Date: Tue, 20 May 2014 05:56:27 +0000 Subject: [PATCH] Added thread-safety git-svn-id: https://svn.code.sf.net/p/turtle/code/trunk@730 860be788-9bd5-4423-9f1e-828f051e677b --- build/boost/doc/customization.qbk | 10 ++ build/boost/doc/example/customization.cpp | 5 + build/build.xml | 3 + build/vc100/turtle.vcxproj | 1 + build/vc100/turtle.vcxproj.filters | 3 + test/detail/test_function.cpp | 30 +++++ test/test_integration.cpp | 75 ++++++++++++ turtle/config.hpp | 6 + turtle/detail/function.hpp | 52 ++++++++ turtle/detail/function_impl_template.hpp | 115 ++++++++++++++++-- turtle/detail/function_template.hpp | 6 +- turtle/detail/functor.hpp | 7 ++ turtle/detail/mutex.hpp | 141 ++++++++++++++++++++++ turtle/detail/object_impl.hpp | 14 ++- turtle/detail/root.hpp | 8 ++ 15 files changed, 464 insertions(+), 12 deletions(-) create mode 100644 turtle/detail/mutex.hpp diff --git a/build/boost/doc/customization.qbk b/build/boost/doc/customization.qbk index 9e99f77..dcaa799 100644 --- a/build/boost/doc/customization.qbk +++ b/build/boost/doc/customization.qbk @@ -144,4 +144,14 @@ The policy can then be activated by defining MOCK_ERROR_POLICY prior to includin [endsect] +[section Thread safety] + +Thread safety is not activated by default however defining MOCK_THREAD_SAFE before including the library will make creations and calls to mock objects thread-safe : + +[thread_safe] + +If available the library will rely on the C++11 standard mutexes and locks, otherwise Boost.Thread will be used. + +[endsect] + [endsect] diff --git a/build/boost/doc/example/customization.cpp b/build/boost/doc/example/customization.cpp index 3baaa1f..d679ac8 100644 --- a/build/boost/doc/example/customization.cpp +++ b/build/boost/doc/example/customization.cpp @@ -171,3 +171,8 @@ struct custom_policy #define MOCK_ERROR_POLICY custom_policy #include //] + +//[ thread_safe +#define MOCK_THREAD_SAFE +#include +//] diff --git a/build/build.xml b/build/build.xml index e0f04ff..73a92b5 100644 --- a/build/build.xml +++ b/build/build.xml @@ -39,6 +39,9 @@ + + + diff --git a/build/vc100/turtle.vcxproj b/build/vc100/turtle.vcxproj index 04d89d8..063d2b1 100644 --- a/build/vc100/turtle.vcxproj +++ b/build/vc100/turtle.vcxproj @@ -39,6 +39,7 @@ + diff --git a/build/vc100/turtle.vcxproj.filters b/build/vc100/turtle.vcxproj.filters index af95176..397e0f6 100644 --- a/build/vc100/turtle.vcxproj.filters +++ b/build/vc100/turtle.vcxproj.filters @@ -127,5 +127,8 @@ Source Files\detail + + Source Files\detail + \ No newline at end of file diff --git a/test/detail/test_function.cpp b/test/detail/test_function.cpp index 57cb063..a180288 100644 --- a/test/detail/test_function.cpp +++ b/test/detail/test_function.cpp @@ -846,3 +846,33 @@ BOOST_FIXTURE_TEST_CASE( adding_file_and_line_number_information, mock_error_fix BOOST_CHECK_EQUAL( "file name", mock_error_data.last_file ); BOOST_CHECK_EQUAL( 42, mock_error_data.last_line ); } + +#ifdef MOCK_THREAD_SAFE + +#include + +namespace +{ + void iterate( mock::detail::function< int() >& f ) + { + f.expect().once().returns( 0 ); + try + { + f(); + } + catch( ... ) + {} + } +} + +BOOST_FIXTURE_TEST_CASE( function_is_thread_safe, mock_error_fixture ) +{ + mock::detail::function< int() > f; + boost::thread_group group; + for( int i = 0; i < 100; ++i ) + group.create_thread( boost::bind( &iterate, boost::ref( f ) ) ); + group.join_all(); + CHECK_CALLS( 100 ); +} + +#endif // MOCK_THREAD_SAFE diff --git a/test/test_integration.cpp b/test/test_integration.cpp index 4639ede..aa54a0c 100644 --- a/test/test_integration.cpp +++ b/test/test_integration.cpp @@ -576,3 +576,78 @@ namespace MOCK_BASE_CLASS( my_comma_mock, my_base< int BOOST_PP_COMMA() int > ) {}; } + +#ifdef MOCK_THREAD_SAFE + +#include + +namespace +{ + void create_class() + { + my_mock m; + MOCK_EXPECT( m.my_tag ).once().with( 3 ).returns( 42 ); + try + { + m.my_method( 3 ); + } + catch( ... ) + {} + } +} + +BOOST_FIXTURE_TEST_CASE( mock_class_creation_is_thread_safe, mock_error_fixture ) +{ + boost::thread_group group; + for( int i = 0; i < 100; ++i ) + group.create_thread( &create_class ); + group.join_all(); + CHECK_CALLS( 100 ); +} + +namespace +{ + void create_functor( int i ) + { + mock::detail::functor< void( int ) > f; + boost::this_thread::sleep( boost::posix_time::milliseconds( 100 ) ); + mock::detail::functor< void( int ) > f_mock; + MOCK_EXPECT( f ).once().with( i ); + try + { + f( i ); + } + catch( ... ) + {} + } +} + +BOOST_FIXTURE_TEST_CASE( mock_functor_creation_is_thread_safe, mock_error_fixture ) +{ + boost::thread_group group; + for( int i = 0; i < 100; ++i ) + group.create_thread( boost::bind( &create_functor, i ) ); + group.join_all(); + CHECK_CALLS( 100 ); +} + +namespace +{ + void iterate( my_mock& m ) + { + MOCK_EXPECT( m.my_tag ).once().with( 3 ).returns( 42 ); + BOOST_CHECK_EQUAL( 42, m.my_method( 3 ) ); + } +} + +BOOST_FIXTURE_TEST_CASE( mock_class_is_thread_safe, mock_error_fixture ) +{ + my_mock m; + boost::thread_group group; + for( int i = 0; i < 100; ++i ) + group.create_thread( boost::bind( &iterate, boost::ref( m ) ) ); + group.join_all(); + CHECK_CALLS( 100 ); +} + +#endif // MOCK_THREAD_SAFE diff --git a/turtle/config.hpp b/turtle/config.hpp index e9dcb1d..36b90f8 100644 --- a/turtle/config.hpp +++ b/turtle/config.hpp @@ -83,4 +83,10 @@ # endif #endif +#if !defined(BOOST_NO_CXX11_HDR_MUTEX) && !defined(BOOST_NO_0X_HDR_MUTEX) +# ifndef MOCK_NO_HDR_MUTEX +# define MOCK_HDR_MUTEX +# endif +#endif + #endif // MOCK_CONFIG_HPP_INCLUDED diff --git a/turtle/detail/function.hpp b/turtle/detail/function.hpp index ffee900..77eb4cd 100644 --- a/turtle/detail/function.hpp +++ b/turtle/detail/function.hpp @@ -20,6 +20,7 @@ #include "invocation.hpp" #include "type_name.hpp" #include "context.hpp" +#include "mutex.hpp" #include #include #include @@ -35,6 +36,57 @@ #include #include +namespace mock +{ +namespace detail +{ + template< typename R, typename E > + struct wrapper_base + { + wrapper_base( E& e ) + : e_( &e ) + {} + + template< typename T > + void returns( T t ) + { + e_->returns( t ); + } + + E* e_; + }; + template< typename E > + struct wrapper_base< void, E > + { + wrapper_base( E& e ) + : e_( &e ) + {} + + E* e_; + }; + template< typename R, typename E > + struct wrapper_base< R*, E > + { + wrapper_base( E& e ) + : e_( &e ) + {} + + void returns( R* r ) + { + e_->returns( r ); + } + template< typename Y > + void returns( const boost::reference_wrapper< Y >& r ) + { + e_->returns( r ); + } + + E* e_; + }; + +} +} + #define MOCK_NUM_ARGS 0 #define MOCK_NUM_ARGS_0 #include "function_template.hpp" diff --git a/turtle/detail/function_impl_template.hpp b/turtle/detail/function_impl_template.hpp index de8c1ec..b691634 100644 --- a/turtle/detail/function_impl_template.hpp +++ b/turtle/detail/function_impl_template.hpp @@ -37,8 +37,7 @@ namespace detail function_impl< R ( BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, T) )> > { public: - typedef MOCK_ERROR_POLICY< R > error_type; - + typedef safe_error< R, MOCK_ERROR_POLICY< R > > error_type; typedef expectation< R ( BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, T) ) > expectation_type; @@ -47,6 +46,7 @@ namespace detail function_impl() : context_( 0 ) , valid_( true ) + , mutex_( boost::make_shared< mutex >() ) {} virtual ~function_impl() { @@ -56,13 +56,16 @@ namespace detail if( ! it->verify() ) error_type::fail( "untriggered expectation", boost::unit_test::lazy_ostream::instance() - << *this, it->file(), it->line() ); + << lazy_context( this ) + << lazy_expectations( this ), + it->file(), it->line() ); if( context_ ) context_->remove( *this ); } virtual bool verify() const { + lock _( mutex_ ); for( expectations_cit it = expectations_.begin(); it != expectations_.end(); ++it ) if( ! it->verify() ) @@ -70,35 +73,128 @@ namespace detail valid_ = false; error_type::fail( "verification failed", boost::unit_test::lazy_ostream::instance() - << *this, it->file(), it->line() ); + << lazy_context( this ) + << lazy_expectations( this ), + it->file(), it->line() ); } return valid_; } virtual void reset() { + lock _( mutex_ ); valid_ = true; boost::shared_ptr< function_impl > guard = this->shared_from_this(); expectations_.clear(); } - expectation_type& expect( const char* file, int line ) + private: + template< typename T > + struct wrapper : wrapper_base< T, expectation_type > { + wrapper( const boost::shared_ptr< mutex >& m, expectation_type& e ) + : wrapper_base< T, expectation_type >( e ) + , lock_( m ) + {} + + wrapper once() + { + this->e_->once(); + return *this; + } + wrapper never() + { + this->e_->never(); + return *this; + } + wrapper exactly( std::size_t count ) + { + this->e_->exactly( count ); + return *this; + } + wrapper at_least( std::size_t min ) + { + this->e_->at_least( min ); + return *this; + } + wrapper at_most( std::size_t max ) + { + this->e_->at_most( max ); + return *this; + } + wrapper between( std::size_t min, std::size_t max ) + { + this->e_->between( min, max ); + return *this; + } + +#ifndef MOCK_NUM_ARGS_0 + template< + BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, typename Constraint_) + > + wrapper with( + BOOST_PP_ENUM_BINARY_PARAMS(MOCK_NUM_ARGS, Constraint_, c) ) + { + this->e_->with( + BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, c) ); + return *this; + } +#endif + +#define MOCK_FUNCTION_IN(z, n, d) \ + wrapper in( BOOST_PP_ENUM_PARAMS(n, sequence& s) ) \ + { \ + this->e_->in( BOOST_PP_ENUM_PARAMS(n, s) ); \ + return *this; \ + } + + BOOST_PP_REPEAT(MOCK_MAX_SEQUENCES, + MOCK_FUNCTION_IN, _) + +#undef MOCK_FUNCTION_IN + + template< typename TT > + void calls( TT t ) + { + this->e_->calls( t ); + } + template< typename TT > + void throws( TT t ) + { + this->e_->throws( t ); + } + template< typename TT > + void moves( TT t ) + { + this->e_->moves( t ); + } + + lock lock_; + }; + + public: + typedef wrapper< R > wrapper_type; + + wrapper_type expect( const char* file, int line ) + { + lock _( mutex_ ); expectations_.push_back( expectation_type( file, line ) ); valid_ = true; - return expectations_.back(); + return wrapper_type( mutex_, expectations_.back() ); } - expectation_type& expect() + wrapper_type expect() { + lock _( mutex_ ); expectations_.push_back( expectation_type() ); valid_ = true; - return expectations_.back(); + return wrapper_type( mutex_, expectations_.back() ); } R operator()( BOOST_PP_ENUM_BINARY_PARAMS(MOCK_NUM_ARGS, T, t) ) const { + lock _( mutex_ ); valid_ = false; for( expectations_cit it = expectations_.begin(); it != expectations_.end(); ++it ) @@ -132,6 +228,7 @@ namespace detail boost::optional< type_name > type, boost::unit_test::const_string name ) { + lock _( mutex_ ); if( ! context_ ) c.add( *this ); c.add( p, *this, instance, type, name ); @@ -141,6 +238,7 @@ namespace detail friend std::ostream& operator<<( std::ostream& s, const function_impl& impl ) { + lock _( impl.mutex_ ); return s << lazy_context( &impl ) << lazy_expectations( &impl ); } @@ -183,6 +281,7 @@ namespace detail expectations_type expectations_; context* context_; mutable bool valid_; + const boost::shared_ptr< mutex > mutex_; }; } } // mock diff --git a/turtle/detail/function_template.hpp b/turtle/detail/function_template.hpp index bbab134..1093f28 100644 --- a/turtle/detail/function_template.hpp +++ b/turtle/detail/function_template.hpp @@ -38,7 +38,7 @@ namespace detail typedef function_impl< R ( BOOST_PP_REPEAT(MOCK_NUM_ARGS, MOCK_FUNCTION_CALL, _) ) > impl_type; - typedef typename impl_type::expectation_type expectation_type; + typedef typename impl_type::wrapper_type expectation_type; typedef typename impl_type::error_type error_type; public: @@ -65,12 +65,12 @@ namespace detail impl_->reset(); } - expectation_type& expect( const char* file, int line ) + expectation_type expect( const char* file, int line ) { error_type::pass( file, line ); return impl_->expect( file, line ); } - expectation_type& expect() + expectation_type expect() { return impl_->expect(); } diff --git a/turtle/detail/functor.hpp b/turtle/detail/functor.hpp index 2fbeb5d..b45f3a1 100644 --- a/turtle/detail/functor.hpp +++ b/turtle/detail/functor.hpp @@ -11,6 +11,7 @@ #include "../config.hpp" #include "function.hpp" +#include "mutex.hpp" namespace mock { @@ -21,14 +22,20 @@ namespace detail { functor() { + static mutex m_; + scoped_lock _( m_ ); static functor* f = 0; if( f ) { *this = *f; f = 0; + m_.unlock(); } else + { + m_.lock(); f = this; + } } }; } diff --git a/turtle/detail/mutex.hpp b/turtle/detail/mutex.hpp new file mode 100644 index 0000000..3f10c68 --- /dev/null +++ b/turtle/detail/mutex.hpp @@ -0,0 +1,141 @@ +// http://turtle.sourceforge.net +// +// Copyright Mathieu Champlon 2014 +// +// 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) + +#ifndef MOCK_MUTEX_HPP_INCLUDED +#define MOCK_MUTEX_HPP_INCLUDED + +#include "../config.hpp" +#include +#include + +#ifdef MOCK_THREAD_SAFE + +#ifdef MOCK_HDR_MUTEX +#include +#else +#include +#include +#endif + +namespace mock +{ +namespace detail +{ +#ifdef MOCK_HDR_MUTEX + typedef std::recursive_mutex mutex; + typedef std::lock_guard< mutex > scoped_lock; +#else + typedef boost::recursive_mutex mutex; + typedef boost::lock_guard< mutex > scoped_lock; +#endif + + struct lock + { + lock( const boost::shared_ptr< mutex >& m ) + : m_( m ) + { + m_->lock(); + } + lock( const lock& rhs ) + { + m_.swap( rhs.m_ ); + } + ~lock() + { + if( m_ ) + m_->unlock(); + } + + private: + lock& operator=( const lock& rhs ); + + mutable boost::shared_ptr< mutex > m_; + }; +} +} // mock + +#else // MOCK_THREAD_SAFE + +namespace mock +{ +namespace detail +{ + struct mutex + { + mutex() + {} + void lock() + {} + void unlock() + {} + }; + struct scoped_lock + { + scoped_lock( mutex& ) + {} + }; + struct lock + { + lock( const boost::shared_ptr< mutex >& ) + {} + }; +} +} // mock + +#endif // MOCK_THREAD_SAFE + +namespace mock +{ +namespace detail +{ + class error_mutex_t : public boost::unit_test::singleton< error_mutex_t >, + public mutex + { + private: + BOOST_TEST_SINGLETON_CONS( error_mutex_t ); + }; + BOOST_TEST_SINGLETON_INST( error_mutex ) + +#ifdef BOOST_MSVC +# pragma warning( push, 0 ) +# pragma warning( disable: 4702 ) +#endif + template< typename Result, typename Error > + struct safe_error + { + static Result abort() + { + scoped_lock _( error_mutex ); + return Error::abort(); + } + template< typename Context > + static void fail( const char* message, const Context& context, + const char* file = "unknown location", int line = 0 ) + { + scoped_lock _( error_mutex ); + Error::fail( message, context, file, line ); + } + template< typename Context > + static void call( const Context& context, const char* file, int line ) + { + scoped_lock _( error_mutex ); + Error::call( context, file, line ); + } + static void pass( const char* file, int line ) + { + scoped_lock _( error_mutex ); + Error::pass( file, line ); + } + }; +#ifdef BOOST_MSVC +# pragma warning( pop ) +#endif +} +} // mock + +#endif // MOCK_MUTEX_HPP_INCLUDED diff --git a/turtle/detail/object_impl.hpp b/turtle/detail/object_impl.hpp index 6d3276f..20e3fa1 100644 --- a/turtle/detail/object_impl.hpp +++ b/turtle/detail/object_impl.hpp @@ -15,9 +15,10 @@ #include "type_name.hpp" #include "context.hpp" #include "child.hpp" +#include "mutex.hpp" #include #include -#include +#include #include namespace mock @@ -28,21 +29,28 @@ namespace detail public boost::enable_shared_from_this< object_impl > { public: + object_impl() + : mutex_( boost::make_shared< mutex >() ) + {} + virtual void add( const void* /*p*/, verifiable& v, boost::unit_test::const_string instance, boost::optional< type_name > type, boost::unit_test::const_string name ) { + lock _( mutex_ ); if( children_.empty() ) detail::root.add( *this ); children_[ &v ].update( parent_, instance, type, name ); } virtual void add( verifiable& v ) { + lock _( mutex_ ); group_.add( v ); } virtual void remove( verifiable& v ) { + lock _( mutex_ ); group_.remove( v ); children_.erase( &v ); if( children_.empty() ) @@ -51,6 +59,7 @@ namespace detail virtual void serialize( std::ostream& s, const verifiable& v ) const { + lock _( mutex_ ); children_cit it = children_.find( &v ); if( it != children_.end() ) s << it->second; @@ -60,10 +69,12 @@ namespace detail virtual bool verify() const { + lock _( mutex_ ); return group_.verify(); } virtual void reset() { + lock _( mutex_ ); boost::shared_ptr< object_impl > guard = shared_from_this(); group_.reset(); } @@ -75,6 +86,7 @@ namespace detail group group_; parent parent_; children_t children_; + const boost::shared_ptr< mutex > mutex_; }; } } // mock diff --git a/turtle/detail/root.hpp b/turtle/detail/root.hpp index 2f234c4..d56954c 100644 --- a/turtle/detail/root.hpp +++ b/turtle/detail/root.hpp @@ -14,6 +14,7 @@ #include "group.hpp" #include "context.hpp" #include "child.hpp" +#include "mutex.hpp" #include #include #include @@ -31,6 +32,7 @@ namespace detail boost::optional< type_name > type, boost::unit_test::const_string name ) { + scoped_lock _( mutex_ ); children_t::iterator it = children_.lower_bound( &v ); if( it == children_.end() || children_.key_comp()( &v, it->first ) ) @@ -40,26 +42,31 @@ namespace detail } virtual void add( verifiable& v ) { + scoped_lock _( mutex_ ); group_.add( v ); } virtual void remove( verifiable& v ) { + scoped_lock _( mutex_ ); group_.remove( v ); children_.erase( &v ); } bool verify() const { + scoped_lock _( mutex_ ); return group_.verify(); } void reset() { + scoped_lock _( mutex_ ); group_.reset(); } virtual void serialize( std::ostream& s, const verifiable& v ) const { + scoped_lock _( mutex_ ); children_cit it = children_.find( &v ); if( it != children_.end() ) s << it->second; @@ -119,6 +126,7 @@ namespace detail parents_t parents_; children_t children_; group group_; + mutable mutex mutex_; private: BOOST_TEST_SINGLETON_CONS( root_t );