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]
[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]

View file

@ -171,3 +171,8 @@ struct custom_policy
#define MOCK_ERROR_POLICY custom_policy
#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">
<defineset define="MOCK_NO_VARIADIC_MACROS"/>
</build-turtle-test>
<build-turtle-test name="turtle_thread_safe">
<defineset define="MOCK_THREAD_SAFE"/>
</build-turtle-test>
</target>
<target name="analyse" description="run errors analyser">

View file

@ -39,6 +39,7 @@
<ClInclude Include="..\..\turtle\detail\is_functor.hpp" />
<ClInclude Include="..\..\turtle\detail\lambda.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\parameter.hpp" />
<ClInclude Include="..\..\turtle\detail\parent.hpp" />

View file

@ -127,5 +127,8 @@
<ClInclude Include="..\..\turtle\detail\addressof.hpp">
<Filter>Source Files\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\turtle\detail\mutex.hpp">
<Filter>Source Files\detail</Filter>
</ClInclude>
</ItemGroup>
</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( 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 > )
{};
}
#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
#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

View file

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

View file

@ -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

View file

@ -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();
}

View file

@ -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;
}
}
};
}

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 "context.hpp"
#include "child.hpp"
#include "mutex.hpp"
#include <boost/test/utils/basic_cstring/basic_cstring.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/optional.hpp>
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

View file

@ -14,6 +14,7 @@
#include "group.hpp"
#include "context.hpp"
#include "child.hpp"
#include "mutex.hpp"
#include <boost/test/utils/trivial_singleton.hpp>
#include <boost/optional.hpp>
#include <ostream>
@ -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 );