From 48549ff46505f56b4572d0ae3099aa76cbbe329c Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sat, 28 Oct 2023 10:57:41 -0400 Subject: [PATCH] I have the universal handler working for value-test cases... --- Testing/TableTest.h | 450 +++++++++++++++++++++++++++++++++++++++----- Testing/test.cc | 1 + word_wrap.test/0.cc | 2 +- 3 files changed, 404 insertions(+), 49 deletions(-) diff --git a/Testing/TableTest.h b/Testing/TableTest.h index 45d2021..2f2e0f0 100644 --- a/Testing/TableTest.h +++ b/Testing/TableTest.h @@ -33,6 +33,8 @@ static_assert( __cplusplus > 2020'00 ); #include +#include + #include #include @@ -53,6 +55,376 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test } } + template< OutputMode outputMode, typename T > + void + printDebugging( const T &witness, const T &expected ); + + template< Aggregate Agg, typename ... Args > + struct TupleSneak + : Agg + { + TupleSneak( Args ... args ) + : Agg( args... ) + {} + }; + + enum class TestResult { Passed, Failed }; + + struct BlankBase {}; + + template< typename T > + static consteval auto + compute_base_f() noexcept + { + if constexpr ( Aggregate< T > ) return std::type_identity< TupleSneak< T, Reflection::aggregate_tuple_t< T > > >{}; + else if constexpr( std::is_class_v< T > ) return std::type_identity< T >{}; + else return std::type_identity< BlankBase >{}; + } + + template< typename T > + using compute_base_t= typename decltype( compute_base_f< std::decay_t< T > >() )::type; + + template< typename return_type, OutputMode outputMode > + struct BasicUniversalHandler; + + template< Primitive return_type, OutputMode outputMode > + struct BasicUniversalHandler< return_type, outputMode > + { + using Invoker= std::function< return_type () >; + std::function< TestResult ( Invoker, const std::string & ) > impl; + + TestResult + operator() ( Invoker invoker, const std::string &comment ) const + { + return impl( invoker, comment ); + //if constexpr( std::is_base_of_v< std::decay_t< return_type >, ComputedBase > ) + } + +#if 1 + BasicUniversalHandler( const return_type expected ) + : impl + { + [expected]( Invoker invoker, const std::string &comment ) + { + const return_type witness= invoker(); + const auto result= witness == expected ? TestResult::Passed : TestResult::Failed; + + if( result == TestResult::Failed ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + printDebugging< outputMode >( witness, expected ); + } + else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return result; + } + } + {} +#endif + +#if 0 + template< typename ... Args > + requires ConstructibleFrom< return_type, std::decay_t< Args >... > + BasicUniversalHandler( Args &&... expected_init ) + : BasicUniversalHandler( return_type{ std::forward< Args >( expected_init )... } ) + {} +#endif + +#if 0 + template< typename T > + BasicUniversalHandler( std::type_identity< T > ) : impl + { + []( Invoker invoker, const std::string &comment ) + { + try + { + std::ignore= invoker(); + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Failed; + } + catch( const T & ) + { + std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Passed; + } + } + } + {} + + template< DerivedFrom< std::exception > T > + BasicUniversalHandler( const T exemplar ) : impl + { + [expected= std::string{ exemplar.what() }]( Invoker invoker, const std::string &comment ) + { + try + { + invoker(); + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + std::cout << " " << C::testInfo << "NOTE" << resetStyle << ": expected exception `" + << typeid( T ).name() + << "` wasn't thrown." << std::endl; + return TestResult::Failed; + } + catch( const T &ex ) + { + const std::string witness= ex.what(); + const TestResult rv= witness == expected ? TestResult::Passed : TestResult::Failed; + if( rv == TestResult::Failed ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + std::cout << " " << C::testInfo << "NOTE" << resetStyle << ": expected message did not match." << std::endl; + printDebugging< outputMode >( witness, expected ); + } + return rv; + } + } + } + {} +#endif + }; + + template< Aggregate return_type, OutputMode outputMode > + struct BasicUniversalHandler< return_type, outputMode > + : return_type + { + using ComputedBase= compute_base_t< return_type >; + //using ComputedBase::ComputedBase; + + using Invoker= std::function< return_type () >; + + std::function< TestResult ( Invoker, const std::string & ) > impl; + + TestResult + operator() ( Invoker invoker, const std::string &comment ) const + { + if( impl != nullptr ) return impl( invoker, comment ); + //if constexpr( std::is_base_of_v< std::decay_t< return_type >, ComputedBase > ) + if constexpr( true ) + { + const return_type *const expected_p= this; + const auto expected= *expected_p; + const return_type witness= invoker(); + + const auto result= witness == expected ? TestResult::Passed : TestResult::Failed; + + if( result == TestResult::Failed ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + printDebugging< outputMode >( witness, expected ); + } + else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return result; + } + else throw std::logic_error( "Somehow we didn't setup impl, and it's not an adapted case!" ); + } + +#if 0 + template< typename T_= return_type, typename= std::enable_if_t< not std::is_class_v< std::decay_t< T_ > > > > + BasicUniversalHandler( const T_ expected ) + : impl + { + [expected]( Invoker invoker, const std::string &comment ) + { + static_assert( not Aggregate< T_ > ); + static_assert( not std::is_class_v< T_ > ); + const return_type witness= invoker(); + const auto result= witness == expected ? TestResult::Passed : TestResult::Failed; + + if( result == TestResult::Failed ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + printDebugging< outputMode >( witness, expected ); + } + else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return result; + } + } + {} +#endif + +#if 0 + template< typename ... Args > + requires ConstructibleFrom< return_type, std::decay_t< Args >... > + BasicUniversalHandler( Args &&... expected_init ) + : BasicUniversalHandler( return_type{ std::forward< Args >( expected_init )... } ) + {} +#endif + +#if 0 + template< typename T > + BasicUniversalHandler( std::type_identity< T > ) : impl + { + []( Invoker invoker, const std::string &comment ) + { + try + { + std::ignore= invoker(); + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Failed; + } + catch( const T & ) + { + std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Passed; + } + } + } + {} + + template< DerivedFrom< std::exception > T > + BasicUniversalHandler( const T exemplar ) : impl + { + [expected= std::string{ exemplar.what() }]( Invoker invoker, const std::string &comment ) + { + try + { + invoker(); + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + std::cout << " " << C::testInfo << "NOTE" << resetStyle << ": expected exception `" + << typeid( T ).name() + << "` wasn't thrown." << std::endl; + return TestResult::Failed; + } + catch( const T &ex ) + { + const std::string witness= ex.what(); + const TestResult rv= witness == expected ? TestResult::Passed : TestResult::Failed; + if( rv == TestResult::Failed ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + std::cout << " " << C::testInfo << "NOTE" << resetStyle << ": expected message did not match." << std::endl; + printDebugging< outputMode >( witness, expected ); + } + return rv; + } + } + } + {} +#endif + }; + + template< typename return_type, OutputMode outputMode > + struct BasicUniversalHandler + : return_type + { + using return_type::return_type; + + BasicUniversalHandler( return_type rt ) : return_type( rt ) {} + + using Invoker= std::function< return_type () >; + + std::function< TestResult ( Invoker, const std::string & ) > impl; + + TestResult + operator() ( Invoker invoker, const std::string &comment ) const + { + if( impl != nullptr ) return impl( invoker, comment ); + //if constexpr( std::is_base_of_v< std::decay_t< return_type >, ComputedBase > ) + if constexpr( true ) + { + const return_type *const expected_p= this; + const auto expected= *expected_p; + const return_type witness= invoker(); + + const auto result= witness == expected ? TestResult::Passed : TestResult::Failed; + + if( result == TestResult::Failed ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + printDebugging< outputMode >( witness, expected ); + } + else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return result; + } + else throw std::logic_error( "Somehow we didn't setup impl, and it's not an adapted case!" ); + } + +#if 0 + template< typename T_= return_type, typename= std::enable_if_t< not std::is_class_v< std::decay_t< T_ > > > > + BasicUniversalHandler( const T_ expected ) + : impl + { + [expected]( Invoker invoker, const std::string &comment ) + { + static_assert( not Aggregate< T_ > ); + static_assert( not std::is_class_v< T_ > ); + const return_type witness= invoker(); + const auto result= witness == expected ? TestResult::Passed : TestResult::Failed; + + if( result == TestResult::Failed ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + printDebugging< outputMode >( witness, expected ); + } + else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return result; + } + } + {} +#endif + +#if 0 + template< typename ... Args > + requires ConstructibleFrom< return_type, std::decay_t< Args >... > + BasicUniversalHandler( Args &&... expected_init ) + : BasicUniversalHandler( return_type{ std::forward< Args >( expected_init )... } ) + {} +#endif + +#if 0 + template< typename T > + BasicUniversalHandler( std::type_identity< T > ) : impl + { + []( Invoker invoker, const std::string &comment ) + { + try + { + std::ignore= invoker(); + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Failed; + } + catch( const T & ) + { + std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Passed; + } + } + } + {} + + template< DerivedFrom< std::exception > T > + BasicUniversalHandler( const T exemplar ) : impl + { + [expected= std::string{ exemplar.what() }]( Invoker invoker, const std::string &comment ) + { + try + { + invoker(); + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + std::cout << " " << C::testInfo << "NOTE" << resetStyle << ": expected exception `" + << typeid( T ).name() + << "` wasn't thrown." << std::endl; + return TestResult::Failed; + } + catch( const T &ex ) + { + const std::string witness= ex.what(); + const TestResult rv= witness == expected ? TestResult::Passed : TestResult::Failed; + if( rv == TestResult::Failed ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + std::cout << " " << C::testInfo << "NOTE" << resetStyle << ": expected message did not match." << std::endl; + printDebugging< outputMode >( witness, expected ); + } + return rv; + } + } + } + {} +#endif + }; + + + template< typename F > concept FunctionVariable= requires( const F &f ) @@ -400,66 +772,48 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test return failureCount; } }; + + using ComputedBase= compute_base_t< return_type >; - class UniversalCases + struct UniversalCases { using RunDescription= std::tuple< std::string, args_type, return_type >; using Invoker= std::function< return_type () >; - enum class TestResult { Passed, Failed }; - - struct UniversalHandler - { - std::function< TestResult ( Invoker, const std::string & ) > impl; - - bool operator() ( Invoker invoker ) const { return impl( invoker ); } - - UniversalHandler( const return_type expected ) : impl - { - [expected]( Invoker invoker, const std::string &comment ) - { - const auto witness= invoker(); - const auto result= witness == expected ? TestResult::Passed : TestResult::Failed; - - if( result == TestResult::Failed ) - { - std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; - printDebugging< outputMode >( witness, expected ); - } - else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; - return result; - } - } - {} - - template< typename T > - UniversalHandler( std::type_identity< T > ) : impl - { - []( Invoker invoker ) - { - try { std::ignore= invoker(); } - catch( const T & ) { return TestResult::Passed; } - return TestResult::Failed; - } - } - {} - - UniversalHandler( const DerivedFrom< std::exception > auto exemplar ) : impl - { - [expected= std::string{ exemplar.what() }]( Invoker invoker ) - { - throw "Unimpl"; - } - } - {} - }; + using UniversalHandler= BasicUniversalHandler< return_type, outputMode >; using TestDescription= std::tuple< std::string, args_type, UniversalHandler >; + std::vector< TestDescription > tests; + + UniversalCases( std::initializer_list< TestDescription > initList ) + : tests( initList ) {} + + int + operator() () const + { + int failureCount= 0; + for( const auto &[ comment, params, checker ]: tests ) + { + if( C::debugCaseTypes ) std::cerr << boost::core::demangle( typeid( params ).name() ) << std::endl; + breakpoint(); + auto invoker= [&]{ return std::apply( function, params ); }; + const TestResult result= checker( invoker, comment ); + if( result == TestResult::Failed ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + ++failureCount; + } + else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + } + + return failureCount; + } }; // When the `UniversalCases` impl is ready to go, then this alias shim can be redirected to that form. Then I can // retire the `ExceptionCases` and `ExecutionCases` forms and replace them with an alias to `UniversalCases`. using Cases= ExecutionCases; + //using Cases= UniversalCases; }; #ifdef DISABLED diff --git a/Testing/test.cc b/Testing/test.cc index 059f195..d940fbf 100644 --- a/Testing/test.cc +++ b/Testing/test.cc @@ -61,6 +61,7 @@ namespace Alepha::Hydrogen::Testing::detail::testing throw; } catch( const TestFailure &fail ) { std::cout << " -- " << fail.failureCount << " failures."; } + catch( const std::exception &ex ) { std::cout << " -- unknown failure count (mesg: " << ex.what() << ")"; } catch( ... ) { std::cout << " -- unknown failure count"; } std::cout << std::endl; } diff --git a/word_wrap.test/0.cc b/word_wrap.test/0.cc index 86d89fd..10bbaa1 100644 --- a/word_wrap.test/0.cc +++ b/word_wrap.test/0.cc @@ -39,7 +39,7 @@ static auto init= Alepha::Utility::enroll <=[] { "Trailing spaces should be kept", { helloWorld + " ", 100, 0 }, helloWorld + " " }, { "Trailing spaces should be dropped", { helloWorld + " ", helloWorld.size(), 0 }, helloWorld }, { "All but 2 trailing spaces dropped", { helloWorld + " ", helloWorld.size() + 2, 0 }, - helloWorld + " "}, + helloWorld + " " }, { "Split line", { helloWorld, 8, 0 }, "Hello \nWorld" }, // TODO: Should we swallow trailing spaces? };