Added thread-safety

git-svn-id: https://svn.code.sf.net/p/turtle/code/trunk@730 860be788-9bd5-4423-9f1e-828f051e677b
This commit is contained in:
mat007 2014-05-20 05:56:27 +00:00
parent f1580c95cc
commit 086a42e432
15 changed files with 464 additions and 12 deletions

View file

@ -144,4 +144,14 @@ The policy can then be activated by defining MOCK_ERROR_POLICY prior to includin
[endsect] [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] [endsect]

View file

@ -171,3 +171,8 @@ struct custom_policy
#define MOCK_ERROR_POLICY custom_policy #define MOCK_ERROR_POLICY custom_policy
#include <turtle/mock.hpp> #include <turtle/mock.hpp>
//] //]
//[ thread_safe
#define MOCK_THREAD_SAFE
#include <turtle/mock.hpp>
//]

View file

@ -39,6 +39,9 @@
<build-turtle-test name="turtle_no_variadic_macros"> <build-turtle-test name="turtle_no_variadic_macros">
<defineset define="MOCK_NO_VARIADIC_MACROS"/> <defineset define="MOCK_NO_VARIADIC_MACROS"/>
</build-turtle-test> </build-turtle-test>
<build-turtle-test name="turtle_thread_safe">
<defineset define="MOCK_THREAD_SAFE"/>
</build-turtle-test>
</target> </target>
<target name="analyse" description="run errors analyser"> <target name="analyse" description="run errors analyser">

View file

@ -39,6 +39,7 @@
<ClInclude Include="..\..\turtle\detail\is_functor.hpp" /> <ClInclude Include="..\..\turtle\detail\is_functor.hpp" />
<ClInclude Include="..\..\turtle\detail\lambda.hpp" /> <ClInclude Include="..\..\turtle\detail\lambda.hpp" />
<ClInclude Include="..\..\turtle\detail\matcher_base.hpp" /> <ClInclude Include="..\..\turtle\detail\matcher_base.hpp" />
<ClInclude Include="..\..\turtle\detail\mutex.hpp" />
<ClInclude Include="..\..\turtle\detail\object_impl.hpp" /> <ClInclude Include="..\..\turtle\detail\object_impl.hpp" />
<ClInclude Include="..\..\turtle\detail\parameter.hpp" /> <ClInclude Include="..\..\turtle\detail\parameter.hpp" />
<ClInclude Include="..\..\turtle\detail\parent.hpp" /> <ClInclude Include="..\..\turtle\detail\parent.hpp" />

View file

@ -127,5 +127,8 @@
<ClInclude Include="..\..\turtle\detail\addressof.hpp"> <ClInclude Include="..\..\turtle\detail\addressof.hpp">
<Filter>Source Files\detail</Filter> <Filter>Source Files\detail</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\turtle\detail\mutex.hpp">
<Filter>Source Files\detail</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -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( "file name", mock_error_data.last_file );
BOOST_CHECK_EQUAL( 42, mock_error_data.last_line ); BOOST_CHECK_EQUAL( 42, mock_error_data.last_line );
} }
#ifdef MOCK_THREAD_SAFE
#include <boost/thread.hpp>
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

View file

@ -576,3 +576,78 @@ namespace
MOCK_BASE_CLASS( my_comma_mock, my_base< int BOOST_PP_COMMA() int > ) MOCK_BASE_CLASS( my_comma_mock, my_base< int BOOST_PP_COMMA() int > )
{}; {};
} }
#ifdef MOCK_THREAD_SAFE
#include <boost/thread.hpp>
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

View file

@ -83,4 +83,10 @@
# endif # endif
#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 #endif // MOCK_CONFIG_HPP_INCLUDED

View file

@ -20,6 +20,7 @@
#include "invocation.hpp" #include "invocation.hpp"
#include "type_name.hpp" #include "type_name.hpp"
#include "context.hpp" #include "context.hpp"
#include "mutex.hpp"
#include <boost/preprocessor/iteration/iterate.hpp> #include <boost/preprocessor/iteration/iterate.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp> #include <boost/preprocessor/repetition/repeat_from_to.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp> #include <boost/preprocessor/repetition/enum_params.hpp>
@ -35,6 +36,57 @@
#include <vector> #include <vector>
#include <list> #include <list>
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
#define MOCK_NUM_ARGS_0 #define MOCK_NUM_ARGS_0
#include "function_template.hpp" #include "function_template.hpp"

View file

@ -37,8 +37,7 @@ namespace detail
function_impl< R ( BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, T) )> > function_impl< R ( BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, T) )> >
{ {
public: public:
typedef MOCK_ERROR_POLICY< R > error_type; typedef safe_error< R, MOCK_ERROR_POLICY< R > > error_type;
typedef expectation< typedef expectation<
R ( BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, T) ) R ( BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, T) )
> expectation_type; > expectation_type;
@ -47,6 +46,7 @@ namespace detail
function_impl() function_impl()
: context_( 0 ) : context_( 0 )
, valid_( true ) , valid_( true )
, mutex_( boost::make_shared< mutex >() )
{} {}
virtual ~function_impl() virtual ~function_impl()
{ {
@ -56,13 +56,16 @@ namespace detail
if( ! it->verify() ) if( ! it->verify() )
error_type::fail( "untriggered expectation", error_type::fail( "untriggered expectation",
boost::unit_test::lazy_ostream::instance() boost::unit_test::lazy_ostream::instance()
<< *this, it->file(), it->line() ); << lazy_context( this )
<< lazy_expectations( this ),
it->file(), it->line() );
if( context_ ) if( context_ )
context_->remove( *this ); context_->remove( *this );
} }
virtual bool verify() const virtual bool verify() const
{ {
lock _( mutex_ );
for( expectations_cit it = expectations_.begin(); for( expectations_cit it = expectations_.begin();
it != expectations_.end(); ++it ) it != expectations_.end(); ++it )
if( ! it->verify() ) if( ! it->verify() )
@ -70,35 +73,128 @@ namespace detail
valid_ = false; valid_ = false;
error_type::fail( "verification failed", error_type::fail( "verification failed",
boost::unit_test::lazy_ostream::instance() boost::unit_test::lazy_ostream::instance()
<< *this, it->file(), it->line() ); << lazy_context( this )
<< lazy_expectations( this ),
it->file(), it->line() );
} }
return valid_; return valid_;
} }
virtual void reset() virtual void reset()
{ {
lock _( mutex_ );
valid_ = true; valid_ = true;
boost::shared_ptr< function_impl > guard = boost::shared_ptr< function_impl > guard =
this->shared_from_this(); this->shared_from_this();
expectations_.clear(); 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 ) ); expectations_.push_back( expectation_type( file, line ) );
valid_ = true; valid_ = true;
return expectations_.back(); return wrapper_type( mutex_, expectations_.back() );
} }
expectation_type& expect() wrapper_type expect()
{ {
lock _( mutex_ );
expectations_.push_back( expectation_type() ); expectations_.push_back( expectation_type() );
valid_ = true; valid_ = true;
return expectations_.back(); return wrapper_type( mutex_, expectations_.back() );
} }
R operator()( R operator()(
BOOST_PP_ENUM_BINARY_PARAMS(MOCK_NUM_ARGS, T, t) ) const BOOST_PP_ENUM_BINARY_PARAMS(MOCK_NUM_ARGS, T, t) ) const
{ {
lock _( mutex_ );
valid_ = false; valid_ = false;
for( expectations_cit it = expectations_.begin(); for( expectations_cit it = expectations_.begin();
it != expectations_.end(); ++it ) it != expectations_.end(); ++it )
@ -132,6 +228,7 @@ namespace detail
boost::optional< type_name > type, boost::optional< type_name > type,
boost::unit_test::const_string name ) boost::unit_test::const_string name )
{ {
lock _( mutex_ );
if( ! context_ ) if( ! context_ )
c.add( *this ); c.add( *this );
c.add( p, *this, instance, type, name ); c.add( p, *this, instance, type, name );
@ -141,6 +238,7 @@ namespace detail
friend std::ostream& operator<<( friend std::ostream& operator<<(
std::ostream& s, const function_impl& impl ) std::ostream& s, const function_impl& impl )
{ {
lock _( impl.mutex_ );
return s << lazy_context( &impl ) << lazy_expectations( &impl ); return s << lazy_context( &impl ) << lazy_expectations( &impl );
} }
@ -183,6 +281,7 @@ namespace detail
expectations_type expectations_; expectations_type expectations_;
context* context_; context* context_;
mutable bool valid_; mutable bool valid_;
const boost::shared_ptr< mutex > mutex_;
}; };
} }
} // mock } // mock

View file

@ -38,7 +38,7 @@ namespace detail
typedef function_impl< typedef function_impl<
R ( BOOST_PP_REPEAT(MOCK_NUM_ARGS, MOCK_FUNCTION_CALL, _) ) R ( BOOST_PP_REPEAT(MOCK_NUM_ARGS, MOCK_FUNCTION_CALL, _) )
> impl_type; > 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; typedef typename impl_type::error_type error_type;
public: public:
@ -65,12 +65,12 @@ namespace detail
impl_->reset(); impl_->reset();
} }
expectation_type& expect( const char* file, int line ) expectation_type expect( const char* file, int line )
{ {
error_type::pass( file, line ); error_type::pass( file, line );
return impl_->expect( file, line ); return impl_->expect( file, line );
} }
expectation_type& expect() expectation_type expect()
{ {
return impl_->expect(); return impl_->expect();
} }

View file

@ -11,6 +11,7 @@
#include "../config.hpp" #include "../config.hpp"
#include "function.hpp" #include "function.hpp"
#include "mutex.hpp"
namespace mock namespace mock
{ {
@ -21,15 +22,21 @@ namespace detail
{ {
functor() functor()
{ {
static mutex m_;
scoped_lock _( m_ );
static functor* f = 0; static functor* f = 0;
if( f ) if( f )
{ {
*this = *f; *this = *f;
f = 0; f = 0;
m_.unlock();
} }
else else
{
m_.lock();
f = this; f = this;
} }
}
}; };
} }
} // mock } // mock

141
turtle/detail/mutex.hpp Normal file
View file

@ -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 <boost/test/utils/trivial_singleton.hpp>
#include <boost/shared_ptr.hpp>
#ifdef MOCK_THREAD_SAFE
#ifdef MOCK_HDR_MUTEX
#include <mutex>
#else
#include <boost/thread/recursive_mutex.hpp>
#include <boost/thread/lock_guard.hpp>
#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

View file

@ -15,9 +15,10 @@
#include "type_name.hpp" #include "type_name.hpp"
#include "context.hpp" #include "context.hpp"
#include "child.hpp" #include "child.hpp"
#include "mutex.hpp"
#include <boost/test/utils/basic_cstring/basic_cstring.hpp> #include <boost/test/utils/basic_cstring/basic_cstring.hpp>
#include <boost/enable_shared_from_this.hpp> #include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp> #include <boost/make_shared.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
namespace mock namespace mock
@ -28,21 +29,28 @@ namespace detail
public boost::enable_shared_from_this< object_impl > public boost::enable_shared_from_this< object_impl >
{ {
public: public:
object_impl()
: mutex_( boost::make_shared< mutex >() )
{}
virtual void add( const void* /*p*/, verifiable& v, virtual void add( const void* /*p*/, verifiable& v,
boost::unit_test::const_string instance, boost::unit_test::const_string instance,
boost::optional< type_name > type, boost::optional< type_name > type,
boost::unit_test::const_string name ) boost::unit_test::const_string name )
{ {
lock _( mutex_ );
if( children_.empty() ) if( children_.empty() )
detail::root.add( *this ); detail::root.add( *this );
children_[ &v ].update( parent_, instance, type, name ); children_[ &v ].update( parent_, instance, type, name );
} }
virtual void add( verifiable& v ) virtual void add( verifiable& v )
{ {
lock _( mutex_ );
group_.add( v ); group_.add( v );
} }
virtual void remove( verifiable& v ) virtual void remove( verifiable& v )
{ {
lock _( mutex_ );
group_.remove( v ); group_.remove( v );
children_.erase( &v ); children_.erase( &v );
if( children_.empty() ) if( children_.empty() )
@ -51,6 +59,7 @@ namespace detail
virtual void serialize( std::ostream& s, const verifiable& v ) const virtual void serialize( std::ostream& s, const verifiable& v ) const
{ {
lock _( mutex_ );
children_cit it = children_.find( &v ); children_cit it = children_.find( &v );
if( it != children_.end() ) if( it != children_.end() )
s << it->second; s << it->second;
@ -60,10 +69,12 @@ namespace detail
virtual bool verify() const virtual bool verify() const
{ {
lock _( mutex_ );
return group_.verify(); return group_.verify();
} }
virtual void reset() virtual void reset()
{ {
lock _( mutex_ );
boost::shared_ptr< object_impl > guard = shared_from_this(); boost::shared_ptr< object_impl > guard = shared_from_this();
group_.reset(); group_.reset();
} }
@ -75,6 +86,7 @@ namespace detail
group group_; group group_;
parent parent_; parent parent_;
children_t children_; children_t children_;
const boost::shared_ptr< mutex > mutex_;
}; };
} }
} // mock } // mock

View file

@ -14,6 +14,7 @@
#include "group.hpp" #include "group.hpp"
#include "context.hpp" #include "context.hpp"
#include "child.hpp" #include "child.hpp"
#include "mutex.hpp"
#include <boost/test/utils/trivial_singleton.hpp> #include <boost/test/utils/trivial_singleton.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <ostream> #include <ostream>
@ -31,6 +32,7 @@ namespace detail
boost::optional< type_name > type, boost::optional< type_name > type,
boost::unit_test::const_string name ) boost::unit_test::const_string name )
{ {
scoped_lock _( mutex_ );
children_t::iterator it = children_.lower_bound( &v ); children_t::iterator it = children_.lower_bound( &v );
if( it == children_.end() || if( it == children_.end() ||
children_.key_comp()( &v, it->first ) ) children_.key_comp()( &v, it->first ) )
@ -40,26 +42,31 @@ namespace detail
} }
virtual void add( verifiable& v ) virtual void add( verifiable& v )
{ {
scoped_lock _( mutex_ );
group_.add( v ); group_.add( v );
} }
virtual void remove( verifiable& v ) virtual void remove( verifiable& v )
{ {
scoped_lock _( mutex_ );
group_.remove( v ); group_.remove( v );
children_.erase( &v ); children_.erase( &v );
} }
bool verify() const bool verify() const
{ {
scoped_lock _( mutex_ );
return group_.verify(); return group_.verify();
} }
void reset() void reset()
{ {
scoped_lock _( mutex_ );
group_.reset(); group_.reset();
} }
virtual void serialize( std::ostream& s, const verifiable& v ) const virtual void serialize( std::ostream& s, const verifiable& v ) const
{ {
scoped_lock _( mutex_ );
children_cit it = children_.find( &v ); children_cit it = children_.find( &v );
if( it != children_.end() ) if( it != children_.end() )
s << it->second; s << it->second;
@ -119,6 +126,7 @@ namespace detail
parents_t parents_; parents_t parents_;
children_t children_; children_t children_;
group group_; group group_;
mutable mutex mutex_;
private: private:
BOOST_TEST_SINGLETON_CONS( root_t ); BOOST_TEST_SINGLETON_CONS( root_t );