@ -190,70 +190,6 @@ class TrackedObject {
~ TrackedObject ( ) noexcept { ConstructorTracker : : ObjectDestructed ( this ) ; }
} ;
template < typename Factory , typename Operation , typename Contract >
absl : : optional < testing : : AssertionResult > TestSingleContractAtCountdownImpl (
const Factory & factory , const Operation & operation , int count ,
const Contract & contract ) {
auto t_ptr = factory ( ) ;
absl : : optional < testing : : AssertionResult > current_res ;
SetCountdown ( count ) ;
try {
operation ( t_ptr . get ( ) ) ;
} catch ( const exceptions_internal : : TestException & e ) {
current_res . emplace ( contract ( t_ptr . get ( ) ) ) ;
if ( ! current_res . value ( ) ) {
* current_res < < e . what ( ) < < " failed contract check " ;
}
}
UnsetCountdown ( ) ;
return current_res ;
}
template < typename Factory , typename Operation >
absl : : optional < testing : : AssertionResult > TestSingleContractAtCountdownImpl (
const Factory & factory , const Operation & operation , int count ,
StrongGuaranteeTagType ) {
using TPtr = typename decltype ( factory ( ) ) : : pointer ;
auto t_is_strong = [ & ] ( TPtr t ) { return * t = = * factory ( ) ; } ;
return TestSingleContractAtCountdownImpl ( factory , operation , count ,
t_is_strong ) ;
}
template < typename Factory , typename Operation , typename Contract >
int TestSingleContractAtCountdown (
const Factory & factory , const Operation & operation , int count ,
const Contract & contract ,
absl : : optional < testing : : AssertionResult > * reduced_res ) {
// If reduced_res is empty, it means the current call to
// TestSingleContractAtCountdown(...) is the first test being run so we do
// want to run it. Alternatively, if it's not empty (meaning a previous test
// has run) we want to check if it passed. If the previous test did pass, we
// want to contine running tests so we do want to run the current one. If it
// failed, we want to short circuit so as not to overwrite the AssertionResult
// output. If that's the case, we do not run the current test and instead we
// simply return.
if ( ! reduced_res - > has_value ( ) | | reduced_res - > value ( ) ) {
* reduced_res =
TestSingleContractAtCountdownImpl ( factory , operation , count , contract ) ;
}
return 0 ;
}
template < typename Factory , typename Operation , typename . . . Contracts >
inline absl : : optional < testing : : AssertionResult > TestAllContractsAtCountdown (
const Factory & factory , const Operation & operation , int count ,
const Contracts & . . . contracts ) {
absl : : optional < testing : : AssertionResult > reduced_res ;
// Run each checker, short circuiting after the first failure
int dummy [ ] = {
0 , ( TestSingleContractAtCountdown ( factory , operation , count , contracts ,
& reduced_res ) ) . . . } ;
static_cast < void > ( dummy ) ;
return reduced_res ;
}
} // namespace exceptions_internal
extern exceptions_internal : : NoThrowTag nothrow_ctor ;
@ -871,7 +807,7 @@ testing::AssertionResult TestNothrowOp(const Operation& operation) {
namespace exceptions_internal {
// Dummy struct for ExceptionSafetyTester<> partial state.
// Dummy struct for ExceptionSafetyTestBuild er<> partial state.
struct UninitializedT { } ;
template < typename T >
@ -893,20 +829,97 @@ using EnableIfTestable = typename absl::enable_if_t<
template < typename Factory = UninitializedT ,
typename Operation = UninitializedT , typename . . . Contracts >
class ExceptionSafetyTester ;
class ExceptionSafetyTestBuild er ;
} // namespace exceptions_internal
exceptions_internal : : ExceptionSafetyTester < > MakeExceptionSafetyTester ( ) ;
/*
* Constructs an empty ExceptionSafetyTestBuilder . All
* ExceptionSafetyTestBuilder objects are immutable and all With [ thing ] mutation
* methods return new instances of ExceptionSafetyTestBuilder .
*
* In order to test a T for exception safety , a factory for that T , a testable
* operation , and at least one contract callback returning an assertion
* result must be applied using the respective methods .
*/
exceptions_internal : : ExceptionSafetyTestBuilder < > MakeExceptionSafetyTester ( ) ;
namespace exceptions_internal {
template < typename T >
struct IsUniquePtr : std : : false_type { } ;
template < typename T , typename D >
struct IsUniquePtr < std : : unique_ptr < T , D > > : std : : true_type { } ;
template < typename Factory >
struct FactoryPtrTypeHelper {
using type = decltype ( std : : declval < const Factory & > ( ) ( ) ) ;
static_assert ( IsUniquePtr < type > : : value , " Factories must return a unique_ptr " ) ;
} ;
template < typename Factory >
using FactoryPtrType = typename FactoryPtrTypeHelper < Factory > : : type ;
template < typename Factory >
using FactoryElementType = typename FactoryPtrType < Factory > : : element_type ;
template < typename T >
class ExceptionSafetyTest {
using Factory = std : : function < std : : unique_ptr < T > ( ) > ;
using Operation = std : : function < void ( T * ) > ;
using Contract = std : : function < AssertionResult ( T * ) > ;
public :
template < typename . . . Contracts >
explicit ExceptionSafetyTest ( const Factory & f , const Operation & op ,
const Contracts & . . . contracts )
: factory_ ( f ) , operation_ ( op ) , contracts_ { WrapContract ( contracts ) . . . } { }
AssertionResult Test ( ) const {
for ( int count = 0 ; ; + + count ) {
exceptions_internal : : ConstructorTracker ct ( count ) ;
for ( const auto & contract : contracts_ ) {
auto t_ptr = factory_ ( ) ;
try {
SetCountdown ( count ) ;
operation_ ( t_ptr . get ( ) ) ;
// Unset for the case that the operation throws no exceptions, which
// would leave the countdown set and break the *next* exception safety
// test after this one.
UnsetCountdown ( ) ;
return AssertionSuccess ( ) ;
} catch ( const exceptions_internal : : TestException & e ) {
if ( ! contract ( t_ptr . get ( ) ) ) {
return AssertionFailure ( ) < < e . what ( ) < < " failed contract check " ;
}
}
}
}
}
private :
template < typename ContractFn >
Contract WrapContract ( const ContractFn & contract ) {
return [ contract ] ( T * t_ptr ) { return AssertionResult ( contract ( t_ptr ) ) ; } ;
}
Contract WrapContract ( StrongGuaranteeTagType ) {
return [ this ] ( T * t_ptr ) { return AssertionResult ( * factory_ ( ) = = * t_ptr ) ; } ;
}
Factory factory_ ;
Operation operation_ ;
std : : vector < Contract > contracts_ ;
} ;
/*
* Builds a tester object that tests if performing a operation on a T follows
* exception safety guarantees . Verification is done via contract assertion
* callbacks applied to T instances post - throw .
*
* Template parameters for ExceptionSafetyTester :
* Template parameters for ExceptionSafetyTestBuild er :
*
* - Factory : The factory object ( passed in via tester . WithFactory ( . . . ) or
* tester . WithInitialValue ( . . . ) ) must be invocable with the signature
@ -933,13 +946,13 @@ namespace exceptions_internal {
* please .
*/
template < typename Factory , typename Operation , typename . . . Contracts >
class ExceptionSafetyTester {
class ExceptionSafetyTestBuild er {
public :
/*
* Returns a new ExceptionSafetyTester with an included T factory based on the
* provided T instance . The existing factory will not be included in the newly
* created tester instance . The created factory returns a new T instance by
* copy - constructing the provided const T & t .
* Returns a new ExceptionSafetyTestBuild er with an included T factory based
* on the provided T instance . The existing factory will not be included in
* the newly created tester instance . The created factory returns a new T
* instance by copy - constructing the provided const T & t .
*
* Preconditions for tester . WithInitialValue ( const T & t ) :
*
@ -948,41 +961,41 @@ class ExceptionSafetyTester {
* tester . WithFactory ( . . . ) .
*/
template < typename T >
ExceptionSafetyTester < DefaultFactory < T > , Operation , Contracts . . . >
ExceptionSafetyTestBuild er < DefaultFactory < T > , Operation , Contracts . . . >
WithInitialValue ( const T & t ) const {
return WithFactory ( DefaultFactory < T > ( t ) ) ;
}
/*
* Returns a new ExceptionSafetyTester with the provided T factory included .
* The existing factory will not be included in the newly - created tester
* instance . This method is intended for use with types lacking a copy
* Returns a new ExceptionSafetyTestBuild er with the provided T factory
* included . The existing factory will not be included in the newly - created
* tester instance . This method is intended for use with types lacking a copy
* constructor . Types that can be copy - constructed should instead use the
* method tester . WithInitialValue ( . . . ) .
*/
template < typename NewFactory >
ExceptionSafetyTester < absl : : decay_t < NewFactory > , Operation , Contracts . . . >
ExceptionSafetyTestBuild er < absl : : decay_t < NewFactory > , Operation , Contracts . . . >
WithFactory ( const NewFactory & new_factory ) const {
return { new_factory , operation_ , contracts_ } ;
}
/*
* Returns a new ExceptionSafetyTester with the provided testable operation
* included . The existing operation will not be included in the newly created
* tester .
* Returns a new ExceptionSafetyTestBuild er with the provided testable
* operation included . The existing operation will not be included in the
* newly created tester .
*/
template < typename NewOperation >
ExceptionSafetyTester < Factory , absl : : decay_t < NewOperation > , Contracts . . . >
ExceptionSafetyTestBuild er < Factory , absl : : decay_t < NewOperation > , Contracts . . . >
WithOperation ( const NewOperation & new_operation ) const {
return { factory_ , new_operation , contracts_ } ;
}
/*
* Returns a new ExceptionSafetyTester with the provided MoreContracts . . .
* Returns a new ExceptionSafetyTestBuild er with the provided MoreContracts . . .
* combined with the Contracts . . . that were already included in the instance
* on which the method was called . Contracts . . . cannot be removed or replaced
* once added to an ExceptionSafetyTester instance . A fresh object must be
* created in order to get an empty Contracts . . . list .
* once added to an ExceptionSafetyTestBuild er instance . A fresh object must
* be created in order to get an empty Contracts . . . list .
*
* In addition to passing in custom contract assertion callbacks , this method
* accepts ` testing : : strong_guarantee ` as an argument which checks T instances
@ -991,8 +1004,8 @@ class ExceptionSafetyTester {
* properly rolled back .
*/
template < typename . . . MoreContracts >
ExceptionSafetyTester < Factory , Operation , Contracts . . . ,
absl : : decay_t < MoreContracts > . . . >
ExceptionSafetyTestBuild er < Factory , Operation , Contracts . . . ,
absl : : decay_t < MoreContracts > . . . >
WithContracts ( const MoreContracts & . . . more_contracts ) const {
return {
factory_ , operation_ ,
@ -1039,48 +1052,27 @@ class ExceptionSafetyTester {
typename LazyOperation = Operation ,
typename = EnableIfTestable < sizeof . . . ( Contracts ) , Factory , LazyOperation > >
testing : : AssertionResult Test ( ) const {
return TestImpl ( operation_ , absl : : index_sequence_for < Contracts . . . > ( ) ) ;
return Test ( operation_ ) ;
}
private :
template < typename , typename , typename . . . >
friend class ExceptionSafetyTester ;
friend class ExceptionSafetyTestBuild er ;
friend ExceptionSafetyTester < > testing : : MakeExceptionSafetyTester ( ) ;
friend ExceptionSafetyTestBuild er < > testing : : MakeExceptionSafetyTester ( ) ;
ExceptionSafetyTester ( ) { }
ExceptionSafetyTestBuild er ( ) { }
ExceptionSafetyTester ( const Factory & f , const Operation & o ,
const std : : tuple < Contracts . . . > & i )
ExceptionSafetyTestBuild er ( const Factory & f , const Operation & o ,
const std : : tuple < Contracts . . . > & i )
: factory_ ( f ) , operation_ ( o ) , contracts_ ( i ) { }
template < typename SelectedOperation , size_t . . . Indices >
testing : : AssertionResult TestImpl ( const SelectedOperation & selected_operation ,
testing : : AssertionResult TestImpl ( SelectedOperation selected_operation ,
absl : : index_sequence < Indices . . . > ) const {
// Starting from 0 and counting upwards until one of the exit conditions is
// hit...
for ( int count = 0 ; ; + + count ) {
exceptions_internal : : ConstructorTracker ct ( count ) ;
// Run the full exception safety test algorithm for the current countdown
auto reduced_res =
TestAllContractsAtCountdown ( factory_ , selected_operation , count ,
std : : get < Indices > ( contracts_ ) . . . ) ;
// If there is no value in the optional, no contracts were run because no
// exception was thrown. This means that the test is complete and the loop
// can exit successfully.
if ( ! reduced_res . has_value ( ) ) {
return testing : : AssertionSuccess ( ) ;
}
// If the optional is not empty and the value is falsy, an contract check
// failed so the test must exit to propegate the failure.
if ( ! reduced_res . value ( ) ) {
return reduced_res . value ( ) ;
}
// If the optional is not empty and the value is not falsy, it means
// exceptions were thrown but the contracts passed so the test must
// continue to run.
}
return ExceptionSafetyTest < FactoryElementType < Factory > > (
factory_ , selected_operation , std : : get < Indices > ( contracts_ ) . . . )
. Test ( ) ;
}
Factory factory_ ;
@ -1090,20 +1082,6 @@ class ExceptionSafetyTester {
} // namespace exceptions_internal
/*
* Constructs an empty ExceptionSafetyTester . All ExceptionSafetyTester
* objects are immutable and all With [ thing ] mutation methods return new
* instances of ExceptionSafetyTester .
*
* In order to test a T for exception safety , a factory for that T , a testable
* operation , and at least one contract callback returning an assertion
* result must be applied using the respective methods .
*/
inline exceptions_internal : : ExceptionSafetyTester < >
MakeExceptionSafetyTester ( ) {
return { } ;
}
} // namespace testing
# endif // ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_