From 26eb1b080e2d902acee2287e8d88a58081bec3ec Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Wed, 12 Jun 2024 00:08:14 -0400 Subject: [PATCH] Basic support for exception verification in tests This probably needs to be expanded upon. The basic functionality added is to permit a test expectation clause to be a function which takes some kind of exception type. That function can then perform any arbitrary checks and analyses it needs to confirm that the exception which was caught passes muster for that test case. --- Testing/TableTest.h | 57 ++++++++++++++++++++++++++++++++- Testing/TableTest.test/test2.cc | 13 +++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/Testing/TableTest.h b/Testing/TableTest.h index 81ae076..26a3e4f 100644 --- a/Testing/TableTest.h +++ b/Testing/TableTest.h @@ -69,6 +69,18 @@ namespace Alepha::Hydrogen::Testing ::detail:: TableTest_m else return ex.message(); } + template< typename T > + concept ExceptionLike= DerivedFrom< T, std::exception > + or DerivedFrom< T, Alepha::Exception >; + + template< typename T, typename Param > + concept FunctionOver= Concepts::UnaryFunction< T > + and Concepts::SameAs< get_args_t< T >, std::tuple< Param > >; + + template< typename T > + concept FunctionTakingThrowable= UnaryFunction< T > + and ExceptionLike< get_arg_t< T, 0 > >; + template< typename return_type, OutputMode outputMode > struct BasicUniversalHandler : compute_base_t< return_type > @@ -80,6 +92,41 @@ namespace Alepha::Hydrogen::Testing ::detail:: TableTest_m std::function< std::tuple< TestResult, std::optional< std::string > > ( Invoker, const std::string & ) > impl; + template< FunctionTakingThrowable Verifier > + BasicUniversalHandler( Verifier verifier ) : impl + { + [verifier] ( Invoker invoker, const std::string &comment ) -> std::tuple< TestResult, std::optional< std::string > > + { + try + { + invoker(); + return { TestResult::Failed, "Expected an exception but none was thrown." }; + } + catch( const get_arg_t< Verifier, 0 > &ex ) + { + if( verifier( ex ) ) return { TestResult::Passed, std::nullopt }; + return { TestResult::Failed, "Verification of exception contents failed." }; + } + catch( ... ) + { + try { throw; } + catch( const std::exception &ex ) + { + return { TestResult::Failed, "Unexpected Exception Type: " + Utility::fancyTypeName( typeid( ex ) ) }; + } + catch( const Alepha::Exception &ex ) + { + return { TestResult::Failed, "Unexpected Exception Type: " + Utility::fancyTypeName( typeid( ex ) ) }; + } + catch( ... ) + { + return { TestResult::Failed, "Unknown Exception Type." }; + } + } + } + } + {} + BasicUniversalHandler( const return_type expected ) : impl { [expected]( Invoker invoker, const std::string &comment ) -> std::tuple< TestResult, std::optional< std::string > > @@ -220,7 +267,15 @@ namespace Alepha::Hydrogen::Testing ::detail:: TableTest_m {} template< typename T > - requires( DerivedFrom< T, std::exception > or DerivedFrom< T, Alepha::Exception > ) + requires + ( + ( + false + or DerivedFrom< T, std::exception > + or DerivedFrom< T, Alepha::Exception > + ) + and not std::is_same_v< return_type, T > + ) BasicUniversalHandler( const T exemplar ) : impl { [expected= getMessageFromException( exemplar )]( Invoker invoker, const std::string &comment ) -> std::tuple< TestResult, std::optional< std::string > > diff --git a/Testing/TableTest.test/test2.cc b/Testing/TableTest.test/test2.cc index 80b90a7..c6afefd 100644 --- a/Testing/TableTest.test/test2.cc +++ b/Testing/TableTest.test/test2.cc @@ -53,6 +53,12 @@ namespace struct DerivedError : std::runtime_error { using std::runtime_error::runtime_error; + + std::optional< int > value_; + + explicit DerivedError( const std::string &s, int value_ ) : std::runtime_error{ s }, value_( value_ ) {} + + int value() const { return value_.value(); } }; "Can we use Aggregates with universal cases, correctly?"_test <= @@ -60,7 +66,7 @@ namespace < []( const int x ) { - if( x < 0 ) throw DerivedError{ "Cannot be negative." }; + if( x < 0 ) throw DerivedError{ "Cannot be negative.", x }; return Aggregate{ x, x, x }; } > @@ -73,6 +79,11 @@ namespace { "Expect exception type exception", { -42 }, std::type_identity< std::exception >{} }, { "Expect exception value specific", { -42 }, DerivedError{ "Cannot be negative." } }, { "Expect exception value specific (loose)", { -42 }, std::runtime_error{ "Cannot be negative." } }, + { "Expect exception value specific (loose)", { -42 }, + []( const DerivedError &e ) + { + return e.value() == -42; + } }, /* These cases should fail, but we don't want to fail them in normal builds. */ /* A few different ways of disabling these tests are shown below. */