From 4eadca553b919f666ca6dc9e2109f07fe1905f3f Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Tue, 5 Jan 2016 17:00:29 +0100 Subject: [PATCH 1/4] Add support for asynchronous expectations. --- .../turtle/detail/expectation_template.hpp | 54 ++++++++++- .../turtle/detail/function_impl_template.hpp | 95 +++++++++++++------ include/turtle/detail/mutex.hpp | 48 +++++----- include/turtle/detail/object_impl.hpp | 22 +++-- test/test_async.cpp | 67 +++++++++++++ 5 files changed, 222 insertions(+), 64 deletions(-) create mode 100644 test/test_async.cpp diff --git a/include/turtle/detail/expectation_template.hpp b/include/turtle/detail/expectation_template.hpp index a4f14a06..1cdc763a 100644 --- a/include/turtle/detail/expectation_template.hpp +++ b/include/turtle/detail/expectation_template.hpp @@ -7,6 +7,7 @@ // http://www.boost.org/LICENSE_1_0.txt) #include "matcher_base_template.hpp" +#include #define MOCK_EXPECTATION_INITIALIZE(z, n, d) \ BOOST_PP_COMMA_IF(n) c##n##_( c##n ) @@ -132,8 +133,11 @@ namespace detail > > () ) , file_( "unknown location" ) - , line_( 0 ) - {} + , line_( 0 ) +#if defined(MOCK_THREAD_SAFE) + , blocked(ATOMIC_VAR_INIT(false)) +#endif + { } expectation( const char* file, int line ) : invocation_( boost::make_shared< unlimited >() ) , matcher_( @@ -144,7 +148,10 @@ namespace detail > () ) , file_( file ) , line_( line ) - {} +#if defined(MOCK_THREAD_SAFE) + , blocked(ATOMIC_VAR_INIT(false)) +#endif + { } ~expectation() { @@ -185,7 +192,15 @@ namespace detail } #endif #endif - +#if defined(MOCK_THREAD_SAFE) + template < typename duration> + expectation &async(const duration timeout) + { + timeout_ = timeout; + blocked = false; + return *this; + } +#endif void add( sequence& s ) { s.impl_->add( this ); @@ -196,16 +211,41 @@ namespace detail { return invocation_->verify(); } - +#if defined(MOCK_THREAD_SAFE) + bool verify(const boost::shared_ptr< condition_variable> &cv, lock &lk) const + { + if (timeout_) + { + while (!invocation_->verify()) + { + if (MOCK_THREAD_NAMESPACE::cv_status::timeout == cv->wait_for(lk.get_unique_lock(), *timeout_)) + { + std::atomic_store(&blocked,true); + return false; + } + } + } + return invocation_->verify(); + } +#endif bool is_valid( BOOST_PP_ENUM_BINARY_PARAMS(MOCK_NUM_ARGS, T, a) ) const { +#if defined(MOCK_THREAD_SAFE) + return !blocked && !invocation_->exhausted() + && (*matcher_)( BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, a) ); +#else return !invocation_->exhausted() && (*matcher_)( BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, a) ); +#endif } bool invoke() const { +#if defined(MOCK_THREAD_SAFE) + if (blocked.load(std::memory_order_acquire)) + return false; +#endif for( sequences_cit it = sequences_.begin(); it != sequences_.end(); ++it ) if( ! (*it)->is_valid( this ) ) @@ -252,6 +292,10 @@ namespace detail sequences_type sequences_; const char* file_; int line_; +#if defined(MOCK_THREAD_SAFE) + boost::optional timeout_; + mutable std::atomic_bool blocked; +#endif }; } } // mock diff --git a/include/turtle/detail/function_impl_template.hpp b/include/turtle/detail/function_impl_template.hpp index 32f04b3b..0da6e809 100644 --- a/include/turtle/detail/function_impl_template.hpp +++ b/include/turtle/detail/function_impl_template.hpp @@ -7,6 +7,7 @@ // http://www.boost.org/LICENSE_1_0.txt) #include "expectation_template.hpp" +#include #ifndef MOCK_ERROR_POLICY # error no error policy has been set @@ -44,20 +45,30 @@ namespace detail : context_( 0 ) , valid_( true ) , mutex_( boost::make_shared< mutex >() ) +#if defined(MOCK_THREAD_SAFE) + , cv_( boost::make_shared< condition_variable>() ) +#endif {} virtual ~function_impl() { - if( valid_ && ! std::uncaught_exception() ) - for( expectations_cit it = expectations_.begin(); - it != expectations_.end(); ++it ) - if( ! it->verify() ) - error_type::fail( "untriggered expectation", - boost::unit_test::lazy_ostream::instance() - << lazy_context( this ) - << lazy_expectations( this ), - it->file(), it->line() ); - if( context_ ) - context_->remove( *this ); + if (valid_ && !std::uncaught_exception()){ + lock _(mutex_); + for (expectations_cit it = expectations_.begin(); + it != expectations_.end(); ++it) +#if defined(MOCK_THREAD_SAFE) + if (!it->verify(cv_, _)) +#else + if (!it->verify()) +#endif + error_type::fail("untriggered expectation", + boost::unit_test::lazy_ostream::instance() + << lazy_context(this) + << lazy_expectations(this), + it->file(), it->line()); + } + context *c = context_.load(std::memory_order_acquire); + if (c) + c->remove( *this ); } virtual bool verify() const @@ -65,7 +76,11 @@ namespace detail lock _( mutex_ ); for( expectations_cit it = expectations_.begin(); it != expectations_.end(); ++it ) +#if defined(MOCK_THREAD_SAFE) + if( ! it->verify(cv_,_) ) +#else if( ! it->verify() ) +#endif { valid_ = false; error_type::fail( "verification failed", @@ -76,7 +91,7 @@ namespace detail } return valid_; } - + virtual void reset() { lock _( mutex_ ); @@ -97,36 +112,38 @@ namespace detail : wrapper_base< R, expectation_type >( e ) , lock_( m ) {} + + wrapper(const wrapper &) = delete; - wrapper once() + wrapper &once() { this->e_->invoke( boost::make_shared< detail::once >() ); return *this; } - wrapper never() + wrapper &never() { this->e_->invoke( boost::make_shared< detail::never >() ); return *this; } - wrapper exactly( std::size_t count ) + wrapper &exactly( std::size_t count ) { this->e_->invoke( boost::make_shared< detail::exactly >( count ) ); return *this; } - wrapper at_least( std::size_t min ) + wrapper &at_least( std::size_t min ) { this->e_->invoke( boost::make_shared< detail::at_least >( min ) ); return *this; } - wrapper at_most( std::size_t max ) + wrapper &at_most( std::size_t max ) { this->e_->invoke( boost::make_shared< detail::at_most >( max ) ); return *this; } - wrapper between( std::size_t min, std::size_t max ) + wrapper &between( std::size_t min, std::size_t max ) { this->e_->invoke( boost::make_shared< detail::between >( min, max ) ); @@ -137,7 +154,7 @@ namespace detail template< BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, typename Constraint_) > - wrapper with( + wrapper &with( BOOST_PP_ENUM_BINARY_PARAMS(MOCK_NUM_ARGS, Constraint_, c) ) { this->e_->with( @@ -146,7 +163,7 @@ namespace detail } #if MOCK_NUM_ARGS > 1 template< typename Constraint > - wrapper with( const Constraint& c ) + wrapper &with( const Constraint& c ) { this->e_->with( c ); return *this; @@ -158,7 +175,7 @@ namespace detail this->e_->add( s##n ); #define MOCK_FUNCTION_IN(z, n, d) \ - wrapper in( BOOST_PP_ENUM_PARAMS(n, sequence& s) ) \ + wrapper &in( BOOST_PP_ENUM_PARAMS(n, sequence& s) ) \ { \ BOOST_PP_REPEAT(n, MOCK_FUNCTION_IN_ADD, _) \ return *this; \ @@ -169,7 +186,13 @@ namespace detail #undef MOCK_FUNCTION_IN #undef MOCK_FUNCTION_IN_ADD - +#if defined(MOCK_THREAD_SAFE) + wrapper &async(const nanoseconds &timeout) + { + this->e_->async(timeout); + return *this; + } +#endif template< typename TT > void calls( TT t ) { @@ -211,6 +234,19 @@ namespace detail BOOST_PP_ENUM_BINARY_PARAMS(MOCK_NUM_ARGS, T, t) ) const { lock _( mutex_ ); +#if defined(MOCK_THREAD_SAFE) + struct notify_cv_on_exit + { + notify_cv_on_exit(condition_variable &cv) + : cv(cv){} + ~notify_cv_on_exit() + { + cv.notify_one(); + } + condition_variable &cv; + }; + notify_cv_on_exit _cv(*cv_); +#endif valid_ = false; for( expectations_cit it = expectations_.begin(); it != expectations_.end(); ++it ) @@ -246,11 +282,9 @@ namespace detail boost::optional< type_name > type, boost::unit_test::const_string name ) { - lock _( mutex_ ); - if( ! context_ ) + if (!context_.exchange(&c,std::memory_order_release)) c.add( *this ); c.add( p, *this, instance, type, name ); - context_ = &c; } friend std::ostream& operator<<( @@ -268,8 +302,10 @@ namespace detail friend std::ostream& operator<<( std::ostream& s, const lazy_context& c ) { - if( c.impl_->context_ ) - c.impl_->context_->serialize( s, *c.impl_ ); + + context *pContext = c.impl_->context_.load(std::memory_order_acquire); + if( pContext ) + pContext->serialize( s, *c.impl_ ); else s << '?'; return s; @@ -297,9 +333,12 @@ namespace detail typedef typename expectations_type::const_iterator expectations_cit; expectations_type expectations_; - context* context_; + std::atomic context_; mutable bool valid_; const boost::shared_ptr< mutex > mutex_; +#if defined(MOCK_THREAD_SAFE) + const boost::shared_ptr cv_; +#endif }; } } // mock diff --git a/include/turtle/detail/mutex.hpp b/include/turtle/detail/mutex.hpp index 4837c6cb..6180b6b3 100644 --- a/include/turtle/detail/mutex.hpp +++ b/include/turtle/detail/mutex.hpp @@ -17,44 +17,44 @@ #ifdef MOCK_HDR_MUTEX #include +#include +#include #else #include #include +#include +#include #endif -namespace mock -{ -namespace detail -{ #ifdef MOCK_HDR_MUTEX - typedef std::recursive_mutex mutex; - typedef std::lock_guard< mutex > scoped_lock; +#define MOCK_THREAD_NAMESPACE std #else - typedef boost::recursive_mutex mutex; - typedef boost::lock_guard< mutex > scoped_lock; +#define MOCK_THREAD_NAMESPACE boost #endif - struct lock +namespace mock +{ +namespace detail +{ + typedef MOCK_THREAD_NAMESPACE::recursive_mutex mutex; + typedef MOCK_THREAD_NAMESPACE::lock_guard< mutex > scoped_lock; + typedef MOCK_THREAD_NAMESPACE::condition_variable_any condition_variable; + typedef MOCK_THREAD_NAMESPACE::chrono::nanoseconds nanoseconds; + typedef MOCK_THREAD_NAMESPACE::unique_lock lock_base; + + struct lock : public lock_base { - lock( const boost::shared_ptr< mutex >& m ) - : m_( m ) - { - m_->lock(); - } - lock( const lock& rhs ) - { - m_.swap( rhs.m_ ); - } + lock(const boost::shared_ptr< detail::mutex > &m) + : lock_base (*m) + , m_(m) + {} ~lock() { - if( m_ ) - m_->unlock(); + unlock(); } + lock_base &get_unique_lock() { return *this; } - private: - lock& operator=( const lock& rhs ); - - mutable boost::shared_ptr< mutex > m_; + boost::shared_ptr< detail::mutex > m_; }; } } // mock diff --git a/include/turtle/detail/object_impl.hpp b/include/turtle/detail/object_impl.hpp index 6aa215f5..ba85adfd 100644 --- a/include/turtle/detail/object_impl.hpp +++ b/include/turtle/detail/object_impl.hpp @@ -31,6 +31,7 @@ namespace detail public: object_impl() : mutex_( boost::make_shared< mutex >() ) + , children_mutex_( boost::make_shared< mutex >() ) {} virtual void add( const void* /*p*/, verifiable& v, @@ -38,7 +39,7 @@ namespace detail boost::optional< type_name > type, boost::unit_test::const_string name ) { - lock _( mutex_ ); + lock _( children_mutex_ ); if( children_.empty() ) detail::root.add( *this ); children_[ &v ].update( parent_, instance, type, name ); @@ -50,16 +51,21 @@ namespace detail } virtual void remove( verifiable& v ) { - lock _( mutex_ ); - group_.remove( v ); - children_.erase( &v ); - if( children_.empty() ) - detail::root.remove( *this ); + { + lock _(mutex_); + group_.remove(v); + } + { + lock _( children_mutex_ ); + children_.erase(&v); + if (children_.empty()) + detail::root.remove(*this); + } } virtual void serialize( std::ostream& s, const verifiable& v ) const { - lock _( mutex_ ); + lock _( children_mutex_ ); children_cit it = children_.find( &v ); if( it != children_.end() ) s << it->second; @@ -72,6 +78,7 @@ namespace detail lock _( mutex_ ); return group_.verify(); } + virtual void reset() { lock _( mutex_ ); @@ -87,6 +94,7 @@ namespace detail parent parent_; children_t children_; const boost::shared_ptr< mutex > mutex_; + const boost::shared_ptr< mutex > children_mutex_; }; } } // mock diff --git a/test/test_async.cpp b/test/test_async.cpp new file mode 100644 index 00000000..f7853c35 --- /dev/null +++ b/test/test_async.cpp @@ -0,0 +1,67 @@ +// http://turtle.sourceforge.net +// +// Copyright Mathieu Champlon 2009 +// +// 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 "mock_error.hpp" +#include +#include +#include + +#if defined(MOCK_HDR_MUTEX) +#include +#else +#include +#endif + +namespace +{ + MOCK_CLASS( mock_class ) + { + MOCK_METHOD_EXT( my_method, 1, void( const std::string& ), my_tag ) + }; +} + +BOOST_FIXTURE_TEST_CASE( mock_object_asynchonous_call_expectation, mock_error_fixture ) +{ +#if defined(MOCK_THREAD_SAFE) + const mock_class m; + MOCK_EXPECT( m.my_tag ).async(MOCK_THREAD_NAMESPACE::chrono::milliseconds(50)).once().with( "some parameter" ); + MOCK_THREAD_NAMESPACE::thread context([&](){ + MOCK_THREAD_NAMESPACE::this_thread::sleep_for(MOCK_THREAD_NAMESPACE::chrono::milliseconds(10)); + m.my_method("some parameter"); + }); + mock::verify(); + CHECK_CALLS( 1 ); + context.join(); +#endif + +} + +BOOST_AUTO_TEST_CASE( mock_object_asynchonous_call_expectation_fails ) +{ +#if defined(MOCK_THREAD_SAFE) + const mock_class m; + bool unexpected_call_received = false; + MOCK_EXPECT( m.my_tag ).async(MOCK_THREAD_NAMESPACE::chrono::milliseconds(50)).once().with( "some parameter" ); + MOCK_THREAD_NAMESPACE::thread context([&](){ + MOCK_THREAD_NAMESPACE::this_thread::sleep_for(MOCK_THREAD_NAMESPACE::chrono::milliseconds(100)); + try{ + m.my_method("some parameter"); + } + catch (std::exception &) + { + unexpected_call_received = true; + } + }); + CHECK_ERROR( + BOOST_CHECK(!mock::verify()), + "verification failed", 0, "m.mock_class::my_tag\n. once().with( \"some parameter\" )"); + context.join(); + BOOST_CHECK(unexpected_call_received == true); +#endif +} \ No newline at end of file From 04497bd5b5cec56b7ee4f16c80d34e162a4c2487 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Sat, 16 Jan 2016 01:19:43 +0100 Subject: [PATCH 2/4] Removed all copies of the wrapper object --- .../turtle/detail/expectation_template.hpp | 24 +++++++++++++------ include/turtle/detail/function.hpp | 13 ++++++++++ .../turtle/detail/function_impl_template.hpp | 11 +++++++-- include/turtle/detail/mutex.hpp | 10 ++++++-- test/test_async.cpp | 12 +++++----- test/test_integration.cpp | 2 +- 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/include/turtle/detail/expectation_template.hpp b/include/turtle/detail/expectation_template.hpp index 1cdc763a..ca9f6446 100644 --- a/include/turtle/detail/expectation_template.hpp +++ b/include/turtle/detail/expectation_template.hpp @@ -133,9 +133,9 @@ namespace detail > > () ) , file_( "unknown location" ) - , line_( 0 ) + , line_( 0 ) #if defined(MOCK_THREAD_SAFE) - , blocked(ATOMIC_VAR_INIT(false)) + , blocked(false) #endif { } expectation( const char* file, int line ) @@ -149,10 +149,20 @@ namespace detail , file_( file ) , line_( line ) #if defined(MOCK_THREAD_SAFE) - , blocked(ATOMIC_VAR_INIT(false)) + , blocked(false) #endif { } + expectation(expectation && e) + : invocation_ ( e.invocation_) + , matcher_(e.matcher_) + , file_(e.file_) + , line_(e.line_) +#if defined(MOCK_THREAD_SAFE) + , blocked(false) +#endif + {} + ~expectation() { for( sequences_cit it = sequences_.begin(); @@ -197,7 +207,7 @@ namespace detail expectation &async(const duration timeout) { timeout_ = timeout; - blocked = false; + blocked.store(false,std::memory_order_release); return *this; } #endif @@ -220,7 +230,7 @@ namespace detail { if (MOCK_THREAD_NAMESPACE::cv_status::timeout == cv->wait_for(lk.get_unique_lock(), *timeout_)) { - std::atomic_store(&blocked,true); + blocked.store(true, std::memory_order_release); return false; } } @@ -232,7 +242,7 @@ namespace detail BOOST_PP_ENUM_BINARY_PARAMS(MOCK_NUM_ARGS, T, a) ) const { #if defined(MOCK_THREAD_SAFE) - return !blocked && !invocation_->exhausted() + return !blocked.load(std::memory_order_acquire) && !invocation_->exhausted() && (*matcher_)( BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, a) ); #else return !invocation_->exhausted() @@ -294,7 +304,7 @@ namespace detail int line_; #if defined(MOCK_THREAD_SAFE) boost::optional timeout_; - mutable std::atomic_bool blocked; + mutable std::atomic blocked; #endif }; } diff --git a/include/turtle/detail/function.hpp b/include/turtle/detail/function.hpp index 4a482204..f29aa65e 100644 --- a/include/turtle/detail/function.hpp +++ b/include/turtle/detail/function.hpp @@ -49,6 +49,10 @@ namespace detail : e_( &e ) {} + wrapper_base( wrapper_base && w ) + : e_( w.e_ ) + {} + template< typename T > void returns( T t ) { @@ -64,6 +68,11 @@ namespace detail : e_( &e ) {} + wrapper_base( wrapper_base && w ) + : e_( w.e_ ) + {} + + E* e_; }; template< typename R, typename E > @@ -73,6 +82,10 @@ namespace detail : e_( &e ) {} + wrapper_base( wrapper_base && w ) + : e_( w.e_ ) + {} + void returns( R* r ) { e_->returns( r ); diff --git a/include/turtle/detail/function_impl_template.hpp b/include/turtle/detail/function_impl_template.hpp index 0da6e809..1f731e37 100644 --- a/include/turtle/detail/function_impl_template.hpp +++ b/include/turtle/detail/function_impl_template.hpp @@ -91,7 +91,7 @@ namespace detail } return valid_; } - + virtual void reset() { lock _( mutex_ ); @@ -112,9 +112,16 @@ namespace detail : wrapper_base< R, expectation_type >( e ) , lock_( m ) {} - + wrapper(const wrapper &) = delete; + wrapper( wrapper && w) + : wrapper_base< R, expectation_type > (*w.e_) + , lock_( std::move(w.lock_) ) + { + + } + wrapper &once() { this->e_->invoke( boost::make_shared< detail::once >() ); diff --git a/include/turtle/detail/mutex.hpp b/include/turtle/detail/mutex.hpp index 6180b6b3..29b5fe88 100644 --- a/include/turtle/detail/mutex.hpp +++ b/include/turtle/detail/mutex.hpp @@ -41,13 +41,19 @@ namespace detail typedef MOCK_THREAD_NAMESPACE::condition_variable_any condition_variable; typedef MOCK_THREAD_NAMESPACE::chrono::nanoseconds nanoseconds; typedef MOCK_THREAD_NAMESPACE::unique_lock lock_base; - - struct lock : public lock_base + + struct lock : public lock_base { lock(const boost::shared_ptr< detail::mutex > &m) : lock_base (*m) , m_(m) {} + + lock(lock && l) + : lock_base(*l.m_) + , m_(l.m_) + { + } ~lock() { unlock(); diff --git a/test/test_async.cpp b/test/test_async.cpp index f7853c35..57f9b652 100644 --- a/test/test_async.cpp +++ b/test/test_async.cpp @@ -29,11 +29,11 @@ namespace BOOST_FIXTURE_TEST_CASE( mock_object_asynchonous_call_expectation, mock_error_fixture ) { #if defined(MOCK_THREAD_SAFE) - const mock_class m; + const mock_class m{}; MOCK_EXPECT( m.my_tag ).async(MOCK_THREAD_NAMESPACE::chrono::milliseconds(50)).once().with( "some parameter" ); - MOCK_THREAD_NAMESPACE::thread context([&](){ + MOCK_THREAD_NAMESPACE::thread context([&](){ MOCK_THREAD_NAMESPACE::this_thread::sleep_for(MOCK_THREAD_NAMESPACE::chrono::milliseconds(10)); - m.my_method("some parameter"); + m.my_method("some parameter"); }); mock::verify(); CHECK_CALLS( 1 ); @@ -45,10 +45,10 @@ BOOST_FIXTURE_TEST_CASE( mock_object_asynchonous_call_expectation, mock_error_fi BOOST_AUTO_TEST_CASE( mock_object_asynchonous_call_expectation_fails ) { #if defined(MOCK_THREAD_SAFE) - const mock_class m; + const mock_class m{}; bool unexpected_call_received = false; MOCK_EXPECT( m.my_tag ).async(MOCK_THREAD_NAMESPACE::chrono::milliseconds(50)).once().with( "some parameter" ); - MOCK_THREAD_NAMESPACE::thread context([&](){ + MOCK_THREAD_NAMESPACE::thread context([&](){ MOCK_THREAD_NAMESPACE::this_thread::sleep_for(MOCK_THREAD_NAMESPACE::chrono::milliseconds(100)); try{ m.my_method("some parameter"); @@ -64,4 +64,4 @@ BOOST_AUTO_TEST_CASE( mock_object_asynchonous_call_expectation_fails ) context.join(); BOOST_CHECK(unexpected_call_received == true); #endif -} \ No newline at end of file +} diff --git a/test/test_integration.cpp b/test/test_integration.cpp index 71ed0c68..8562da38 100644 --- a/test/test_integration.cpp +++ b/test/test_integration.cpp @@ -128,7 +128,7 @@ BOOST_FIXTURE_TEST_CASE( mock_object_method_const_disambiguation, mock_error_fix my_const_ambiguited_mock mock; MOCK_EXPECT( mock.tag1 ); BOOST_CHECK_NO_THROW( mock.my_method() ); - const my_const_ambiguited_mock const_mock; + const my_const_ambiguited_mock const_mock{}; CHECK_ERROR( const_mock.my_method(), "unexpected call", 1, "?.my_const_ambiguited_mock::tag_2()" ); } From 7024b377891190ef1dd07123e96fb666beeb283d Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Sat, 16 Jan 2016 09:15:31 +0100 Subject: [PATCH 3/4] Use boost move to implement movable only wrapper class --- include/turtle/detail/function_impl_template.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/include/turtle/detail/function_impl_template.hpp b/include/turtle/detail/function_impl_template.hpp index 1f731e37..116b4e1b 100644 --- a/include/turtle/detail/function_impl_template.hpp +++ b/include/turtle/detail/function_impl_template.hpp @@ -7,6 +7,7 @@ // http://www.boost.org/LICENSE_1_0.txt) #include "expectation_template.hpp" +#include #include #ifndef MOCK_ERROR_POLICY @@ -108,14 +109,17 @@ namespace detail struct wrapper : wrapper_base< R, expectation_type > { + private: + BOOST_MOVABLE_BUT_NOT_COPYABLE(wrapper); + + public: + wrapper( const boost::shared_ptr< mutex >& m, expectation_type& e ) : wrapper_base< R, expectation_type >( e ) , lock_( m ) {} - wrapper(const wrapper &) = delete; - - wrapper( wrapper && w) + wrapper( BOOST_RV_REF(wrapper) w) : wrapper_base< R, expectation_type > (*w.e_) , lock_( std::move(w.lock_) ) { From 98e61a5d9b9ee3d9dcb706ca614d63dd44f70a22 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Wed, 20 Jan 2016 01:01:47 +0100 Subject: [PATCH 4/4] Fix the compilation for c++98 by not supporting async --- include/turtle/config.hpp | 6 +++ include/turtle/detail/action.hpp | 6 +-- .../turtle/detail/expectation_template.hpp | 41 ++++++++++++------- include/turtle/detail/function.hpp | 6 +-- .../turtle/detail/function_impl_template.hpp | 25 +++++------ include/turtle/detail/mutex.hpp | 28 ++++++++++++- test/test_async.cpp | 30 +++++++++++++- test/test_integration.cpp | 2 +- test/test_mock.cpp | 2 +- 9 files changed, 108 insertions(+), 38 deletions(-) diff --git a/include/turtle/config.hpp b/include/turtle/config.hpp index 54000f6e..451d61ad 100644 --- a/include/turtle/config.hpp +++ b/include/turtle/config.hpp @@ -82,4 +82,10 @@ # endif #endif +#if defined(MOCK_RVALUE_REFERENCES) && defined(MOCK_THREAD_SAFE) +# ifndef MOCK_NO_ASYNC +# define MOCK_ASYNC +# endif +#endif + #endif // MOCK_CONFIG_HPP_INCLUDED diff --git a/include/turtle/detail/action.hpp b/include/turtle/detail/action.hpp index db98aad0..0dabb5d3 100644 --- a/include/turtle/detail/action.hpp +++ b/include/turtle/detail/action.hpp @@ -127,7 +127,7 @@ namespace detail value_imp( BOOST_RV_REF( value_type ) t ) : t_( boost::move( t ) ) {} - value_imp( const T& t ) + value_imp( const typename boost::remove_reference::type & t ) : t_( t ) {} template< typename Y > @@ -150,10 +150,10 @@ namespace detail return static_cast< value_imp< T >& >( *v_ ).t_; } template< typename T > - Result& store( T* t ) + typename boost::remove_reference::type & store( T* t ) { v_.reset( new value_imp< Result >( t ) ); - return static_cast< value_imp< Result >& >( *v_ ).t_; + return static_cast< value_imp< typename boost::remove_reference< Result >::type >& >( *v_ ).t_; } boost::shared_ptr< value > v_; diff --git a/include/turtle/detail/expectation_template.hpp b/include/turtle/detail/expectation_template.hpp index ca9f6446..d1c8761e 100644 --- a/include/turtle/detail/expectation_template.hpp +++ b/include/turtle/detail/expectation_template.hpp @@ -7,7 +7,7 @@ // http://www.boost.org/LICENSE_1_0.txt) #include "matcher_base_template.hpp" -#include +#include #define MOCK_EXPECTATION_INITIALIZE(z, n, d) \ BOOST_PP_COMMA_IF(n) c##n##_( c##n ) @@ -134,7 +134,7 @@ namespace detail > () ) , file_( "unknown location" ) , line_( 0 ) -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) , blocked(false) #endif { } @@ -148,21 +148,32 @@ namespace detail > () ) , file_( file ) , line_( line ) -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) , blocked(false) #endif { } - expectation(expectation && e) + expectation(BOOST_RV_REF(expectation) e) : invocation_ ( e.invocation_) , matcher_(e.matcher_) , file_(e.file_) , line_(e.line_) -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) , blocked(false) #endif {} + expectation &operator=(BOOST_RV_REF(expectation) e) + { + invocation_ = e.invocation_; + matcher_ =e.matcher_; + file_ = e.file_; + line_ = e.line_; +#if defined(MOCK_ASYNC) + blocked.store(false, boost::memory_order_release); +#endif + } + ~expectation() { for( sequences_cit it = sequences_.begin(); @@ -202,12 +213,12 @@ namespace detail } #endif #endif -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) template < typename duration> expectation &async(const duration timeout) { timeout_ = timeout; - blocked.store(false,std::memory_order_release); + blocked.store(false,boost::memory_order_release); return *this; } #endif @@ -221,7 +232,7 @@ namespace detail { return invocation_->verify(); } -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) bool verify(const boost::shared_ptr< condition_variable> &cv, lock &lk) const { if (timeout_) @@ -230,7 +241,7 @@ namespace detail { if (MOCK_THREAD_NAMESPACE::cv_status::timeout == cv->wait_for(lk.get_unique_lock(), *timeout_)) { - blocked.store(true, std::memory_order_release); + blocked.store(true, boost::memory_order_release); return false; } } @@ -241,8 +252,8 @@ namespace detail bool is_valid( BOOST_PP_ENUM_BINARY_PARAMS(MOCK_NUM_ARGS, T, a) ) const { -#if defined(MOCK_THREAD_SAFE) - return !blocked.load(std::memory_order_acquire) && !invocation_->exhausted() +#if defined(MOCK_ASYNC) + return !blocked.load(boost::memory_order_acquire) && !invocation_->exhausted() && (*matcher_)( BOOST_PP_ENUM_PARAMS(MOCK_NUM_ARGS, a) ); #else return !invocation_->exhausted() @@ -252,8 +263,8 @@ namespace detail bool invoke() const { -#if defined(MOCK_THREAD_SAFE) - if (blocked.load(std::memory_order_acquire)) +#if defined(MOCK_ASYNC) + if (blocked.load(boost::memory_order_acquire)) return false; #endif for( sequences_cit it = sequences_.begin(); @@ -302,9 +313,9 @@ namespace detail sequences_type sequences_; const char* file_; int line_; -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) boost::optional timeout_; - mutable std::atomic blocked; + mutable boost::atomic blocked; #endif }; } diff --git a/include/turtle/detail/function.hpp b/include/turtle/detail/function.hpp index f29aa65e..19a016c9 100644 --- a/include/turtle/detail/function.hpp +++ b/include/turtle/detail/function.hpp @@ -49,7 +49,7 @@ namespace detail : e_( &e ) {} - wrapper_base( wrapper_base && w ) + wrapper_base( BOOST_RV_REF(wrapper_base) w ) : e_( w.e_ ) {} @@ -68,7 +68,7 @@ namespace detail : e_( &e ) {} - wrapper_base( wrapper_base && w ) + wrapper_base( BOOST_RV_REF(wrapper_base) w ) : e_( w.e_ ) {} @@ -82,7 +82,7 @@ namespace detail : e_( &e ) {} - wrapper_base( wrapper_base && w ) + wrapper_base( BOOST_RV_REF(wrapper_base) w ) : e_( w.e_ ) {} diff --git a/include/turtle/detail/function_impl_template.hpp b/include/turtle/detail/function_impl_template.hpp index 116b4e1b..a8fa1e2f 100644 --- a/include/turtle/detail/function_impl_template.hpp +++ b/include/turtle/detail/function_impl_template.hpp @@ -8,7 +8,8 @@ #include "expectation_template.hpp" #include -#include +#include +#include #ifndef MOCK_ERROR_POLICY # error no error policy has been set @@ -46,7 +47,7 @@ namespace detail : context_( 0 ) , valid_( true ) , mutex_( boost::make_shared< mutex >() ) -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) , cv_( boost::make_shared< condition_variable>() ) #endif {} @@ -56,7 +57,7 @@ namespace detail lock _(mutex_); for (expectations_cit it = expectations_.begin(); it != expectations_.end(); ++it) -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) if (!it->verify(cv_, _)) #else if (!it->verify()) @@ -67,7 +68,7 @@ namespace detail << lazy_expectations(this), it->file(), it->line()); } - context *c = context_.load(std::memory_order_acquire); + context *c = context_.load(boost::memory_order_acquire); if (c) c->remove( *this ); } @@ -77,7 +78,7 @@ namespace detail lock _( mutex_ ); for( expectations_cit it = expectations_.begin(); it != expectations_.end(); ++it ) -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) if( ! it->verify(cv_,_) ) #else if( ! it->verify() ) @@ -121,7 +122,7 @@ namespace detail wrapper( BOOST_RV_REF(wrapper) w) : wrapper_base< R, expectation_type > (*w.e_) - , lock_( std::move(w.lock_) ) + , lock_( boost::move(w.lock_) ) { } @@ -197,7 +198,7 @@ namespace detail #undef MOCK_FUNCTION_IN #undef MOCK_FUNCTION_IN_ADD -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) wrapper &async(const nanoseconds &timeout) { this->e_->async(timeout); @@ -245,7 +246,7 @@ namespace detail BOOST_PP_ENUM_BINARY_PARAMS(MOCK_NUM_ARGS, T, t) ) const { lock _( mutex_ ); -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) struct notify_cv_on_exit { notify_cv_on_exit(condition_variable &cv) @@ -293,7 +294,7 @@ namespace detail boost::optional< type_name > type, boost::unit_test::const_string name ) { - if (!context_.exchange(&c,std::memory_order_release)) + if (!context_.exchange(&c,boost::memory_order_release)) c.add( *this ); c.add( p, *this, instance, type, name ); } @@ -314,7 +315,7 @@ namespace detail std::ostream& s, const lazy_context& c ) { - context *pContext = c.impl_->context_.load(std::memory_order_acquire); + context *pContext = c.impl_->context_.load(boost::memory_order_acquire); if( pContext ) pContext->serialize( s, *c.impl_ ); else @@ -344,10 +345,10 @@ namespace detail typedef typename expectations_type::const_iterator expectations_cit; expectations_type expectations_; - std::atomic context_; + boost::atomic context_; mutable bool valid_; const boost::shared_ptr< mutex > mutex_; -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) const boost::shared_ptr cv_; #endif }; diff --git a/include/turtle/detail/mutex.hpp b/include/turtle/detail/mutex.hpp index 29b5fe88..1c4deb65 100644 --- a/include/turtle/detail/mutex.hpp +++ b/include/turtle/detail/mutex.hpp @@ -42,6 +42,7 @@ namespace detail typedef MOCK_THREAD_NAMESPACE::chrono::nanoseconds nanoseconds; typedef MOCK_THREAD_NAMESPACE::unique_lock lock_base; +#if defined(MOCK_ASYNC) struct lock : public lock_base { lock(const boost::shared_ptr< detail::mutex > &m) @@ -49,7 +50,7 @@ namespace detail , m_(m) {} - lock(lock && l) + lock(BOOST_RV_REF(lock) l) : lock_base(*l.m_) , m_(l.m_) { @@ -62,6 +63,31 @@ namespace detail boost::shared_ptr< detail::mutex > m_; }; +#else // MOCK_ASYNC + + struct lock + { + lock(const boost::shared_ptr< detail::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< detail::mutex > m_; + }; + +#endif // MOCK_ASYNC } } // mock diff --git a/test/test_async.cpp b/test/test_async.cpp index 57f9b652..8e314be3 100644 --- a/test/test_async.cpp +++ b/test/test_async.cpp @@ -23,12 +23,14 @@ namespace MOCK_CLASS( mock_class ) { MOCK_METHOD_EXT( my_method, 1, void( const std::string& ), my_tag ) + MOCK_METHOD_EXT( my_method2, 1, void( const std::string& ), my_tag2 ) }; } + BOOST_FIXTURE_TEST_CASE( mock_object_asynchonous_call_expectation, mock_error_fixture ) { -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) const mock_class m{}; MOCK_EXPECT( m.my_tag ).async(MOCK_THREAD_NAMESPACE::chrono::milliseconds(50)).once().with( "some parameter" ); MOCK_THREAD_NAMESPACE::thread context([&](){ @@ -42,9 +44,33 @@ BOOST_FIXTURE_TEST_CASE( mock_object_asynchonous_call_expectation, mock_error_fi } +BOOST_FIXTURE_TEST_CASE( mock_object_asynchonous_call_expectation_in_sequence, mock_error_fixture ) +{ +#if defined(MOCK_ASYNC) + const mock_class m{}; + mock::sequence s; + MOCK_EXPECT( m.my_tag ).async(MOCK_THREAD_NAMESPACE::chrono::milliseconds(50)).once().in(s).with( "some parameter" ); + MOCK_EXPECT( m.my_tag ).async(MOCK_THREAD_NAMESPACE::chrono::milliseconds(150)).once().in(s).with( "some parameter2" ); + MOCK_THREAD_NAMESPACE::thread context([&](){ + MOCK_THREAD_NAMESPACE::this_thread::sleep_for(MOCK_THREAD_NAMESPACE::chrono::milliseconds(10)); + m.my_method("some parameter"); + MOCK_THREAD_NAMESPACE::this_thread::sleep_for(MOCK_THREAD_NAMESPACE::chrono::milliseconds(20)); + m.my_method("some parameter2"); + }); + mock::verify(); + CHECK_CALLS( 2 ); + context.join(); +#endif + +} + + + BOOST_AUTO_TEST_CASE( mock_object_asynchonous_call_expectation_fails ) { -#if defined(MOCK_THREAD_SAFE) +#if defined(MOCK_ASYNC) + mock::reset(); + mock_error_data.reset(); const mock_class m{}; bool unexpected_call_received = false; MOCK_EXPECT( m.my_tag ).async(MOCK_THREAD_NAMESPACE::chrono::milliseconds(50)).once().with( "some parameter" ); diff --git a/test/test_integration.cpp b/test/test_integration.cpp index 8562da38..71ed0c68 100644 --- a/test/test_integration.cpp +++ b/test/test_integration.cpp @@ -128,7 +128,7 @@ BOOST_FIXTURE_TEST_CASE( mock_object_method_const_disambiguation, mock_error_fix my_const_ambiguited_mock mock; MOCK_EXPECT( mock.tag1 ); BOOST_CHECK_NO_THROW( mock.my_method() ); - const my_const_ambiguited_mock const_mock{}; + const my_const_ambiguited_mock const_mock; CHECK_ERROR( const_mock.my_method(), "unexpected call", 1, "?.my_const_ambiguited_mock::tag_2()" ); } diff --git a/test/test_mock.cpp b/test/test_mock.cpp index 7a41f6f2..dbb8658c 100644 --- a/test/test_mock.cpp +++ b/test/test_mock.cpp @@ -367,7 +367,7 @@ namespace MOCK_BASE_CLASS( variadic, base ) { - MOCK_METHOD( m1, 0 ) + MOCK_METHOD( m1, 0, void() ) MOCK_METHOD( m2, 0, void() ) MOCK_METHOD( m3, 0, void(), m3 ) MOCK_CONST_METHOD( m4, 0, void() )