From 3a3e709236c13300f09cf7e5409bef30353da4d6 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sat, 28 Oct 2023 10:46:56 -0400 Subject: [PATCH 1/6] Helper to get the type of an aggregate decomposition. --- Reflection/tuplizeAggregate.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Reflection/tuplizeAggregate.h b/Reflection/tuplizeAggregate.h index e631743..f959801 100644 --- a/Reflection/tuplizeAggregate.h +++ b/Reflection/tuplizeAggregate.h @@ -6,6 +6,8 @@ static_assert( __cplusplus > 2020'00 ); #include +#include + #include #include @@ -192,6 +194,9 @@ namespace Alepha::Hydrogen::Reflection { return tuplizeAggregate< compute_salient_members_count_v< std::decay_t< Aggregate > > >( std::forward< Aggregate >( agg ) ); } + + template< typename Aggregate > + using aggregate_tuple_t= decay_tuple_t< std::decay_t< decltype( tuplizeAggregate( std::declval< const Aggregate & >() ) ) > >; } } 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 2/6] 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? }; From bca067add4d869ec7ec90ad0a3d667aa096b0fc0 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sat, 28 Oct 2023 11:59:23 -0400 Subject: [PATCH 3/6] Permit `std::nothrow_t` for no throwing cases, in addition to `std::type_identity< void >`. --- Testing/TableTest.h | 94 +++++++++++++++++++++++++++++++++++-- string_algorithms.test/0.cc | 6 ++- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/Testing/TableTest.h b/Testing/TableTest.h index 2f2e0f0..fab7e00 100644 --- a/Testing/TableTest.h +++ b/Testing/TableTest.h @@ -129,8 +129,9 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test {} #endif -#if 0 +#if 1 template< typename T > + requires( not SameAs< T, void > ) BasicUniversalHandler( std::type_identity< T > ) : impl { []( Invoker invoker, const std::string &comment ) @@ -150,6 +151,27 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test } {} + template< typename T > + requires( SameAs< T, std::type_identity< void > > or SameAs< T, std::nothrow_t > ) + BasicUniversalHandler( T ) : impl + { + []( Invoker invoker, const std::string &comment ) + { + try + { + std::ignore= invoker(); + std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Passed; + } + catch( const T & ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Failed; + } + } + } + {} + template< DerivedFrom< std::exception > T > BasicUniversalHandler( const T exemplar ) : impl { @@ -251,6 +273,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test #if 0 template< typename T > + requires( not SameAs< T, void > ) BasicUniversalHandler( std::type_identity< T > ) : impl { []( Invoker invoker, const std::string &comment ) @@ -270,6 +293,27 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test } {} + template< typename T > + requires( SameAs< T, std::type_identity< void > > or SameAs< T, std::nothrow_t > ) + BasicUniversalHandler( T ) : impl + { + []( Invoker invoker, const std::string &comment ) + { + try + { + std::ignore= invoker(); + std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Passed; + } + catch( const T & ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Failed; + } + } + } + {} + template< DerivedFrom< std::exception > T > BasicUniversalHandler( const T exemplar ) : impl { @@ -370,8 +414,9 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test {} #endif -#if 0 +#if 1 template< typename T > + requires( not SameAs< T, void > ) BasicUniversalHandler( std::type_identity< T > ) : impl { []( Invoker invoker, const std::string &comment ) @@ -391,6 +436,27 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test } {} + template< typename T > + requires( SameAs< T, std::type_identity< void > > or SameAs< T, std::nothrow_t > ) + BasicUniversalHandler( T ) : impl + { + []( Invoker invoker, const std::string &comment ) + { + try + { + std::ignore= invoker(); + std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Passed; + } + catch( const T & ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + return TestResult::Failed; + } + } + } + {} + template< DerivedFrom< std::exception > T > BasicUniversalHandler( const T exemplar ) : impl { @@ -660,7 +726,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test } }; - struct ExceptionCases + struct ExceptionCases_real { using Invoker= std::function< void () >; struct ExceptionHandler @@ -684,6 +750,23 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test {} template< typename T > + requires( SameAs< T, std::type_identity< void > > or SameAs< T, std::nothrow_t > ) + ExceptionHandler( T ) : impl + { + []( Invoker invoker ) + { + try + { + invoker(); + return true; + } + catch( const T & ) { return false; } + } + } + {} + + template< typename T > + requires( not SameAs< T, void > ) ExceptionHandler( std::type_identity< T > ) : impl { []( Invoker invoker ) @@ -748,7 +831,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test explicit - ExceptionCases( std::initializer_list< TestDescription > initList ) + ExceptionCases_real( std::initializer_list< TestDescription > initList ) : tests( initList ) {} int @@ -814,6 +897,9 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test // retire the `ExceptionCases` and `ExecutionCases` forms and replace them with an alias to `UniversalCases`. using Cases= ExecutionCases; //using Cases= UniversalCases; + + using ExceptionCases= ExceptionCases_real; + //using ExceptionCases= UniversalCases; }; #ifdef DISABLED diff --git a/string_algorithms.test/0.cc b/string_algorithms.test/0.cc index ba13f4e..2c1f4f9 100644 --- a/string_algorithms.test/0.cc +++ b/string_algorithms.test/0.cc @@ -42,7 +42,11 @@ static auto init= enroll <=[] { { "Complete var", { "$H$ $W$", { { "H", lambaste<="Hello" }, { "W", lambaste<="World" } }, '$' }, - {} + std::nothrow + }, + { "Complete var", + { "$H$ $W$", { { "H", lambaste<="Hello" }, { "W", lambaste<="World" } }, '$' }, + std::type_identity< void >{} }, { "Incomplete var", { "$H$ $W", { { "H", lambaste<="Hello" }, { "W", lambaste<="World" } }, '$' }, From 19f060b2a6a6177808f0c4cf79d181007d69a0be Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sat, 28 Oct 2023 18:00:13 -0400 Subject: [PATCH 4/6] Move breakpoint stub to the top of the file. --- Testing/TableTest.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Testing/TableTest.h b/Testing/TableTest.h index fab7e00..b6e8997 100644 --- a/Testing/TableTest.h +++ b/Testing/TableTest.h @@ -47,6 +47,8 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test enum class OutputMode { All, Relaxed }; } + inline void breakpoint() {} + namespace C { inline namespace Colors @@ -504,8 +506,6 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test template< FunctionVariable auto, OutputMode outputMode= OutputMode::All > struct TableTest; } - inline void breakpoint() {} - namespace C { const bool debug= false; From a61d6222c7e6a4f3c2ef774523d59f7010b48ca5 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sun, 29 Oct 2023 00:22:56 -0400 Subject: [PATCH 5/6] Minor reformat --- Testing/TableTest.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Testing/TableTest.h b/Testing/TableTest.h index b6e8997..a3d61ad 100644 --- a/Testing/TableTest.h +++ b/Testing/TableTest.h @@ -58,8 +58,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test } template< OutputMode outputMode, typename T > - void - printDebugging( const T &witness, const T &expected ); + void printDebugging( const T &witness, const T &expected ); template< Aggregate Agg, typename ... Args > struct TupleSneak From b9dcec564df91c5b49905343eaa9f8cf5011bc5d Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sun, 29 Oct 2023 03:13:40 -0400 Subject: [PATCH 6/6] I think I have everything working with universal testing cases. --- Testing/TableTest.h | 140 +++++++++++++++++++++++++------- Testing/TableTest.test/test2.cc | 44 ++++++++++ 2 files changed, 154 insertions(+), 30 deletions(-) diff --git a/Testing/TableTest.h b/Testing/TableTest.h index a3d61ad..d8f9348 100644 --- a/Testing/TableTest.h +++ b/Testing/TableTest.h @@ -60,13 +60,36 @@ 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 + template< Aggregate Agg, TypeListType > + struct TupleSneak; + + template< Aggregate Agg > + struct TupleSneak< Agg, Nil > : Agg { + TupleSneak() { std::cerr << "The inherited default ctor was called." << std::endl; } + + protected: + void set( Agg agg ) { static_cast< Agg & >( *this )= agg; } + }; + + template< Aggregate Agg, typename ... Args > + struct TupleSneak< Agg, TypeList< Args... > > + : TupleSneak< Agg, cdr_t< TypeList< Args... > > > + { + using Parent= TupleSneak< Agg, cdr_t< TypeList< Args... > > >; + using Parent::Parent; + TupleSneak( Args ... args ) - : Agg( args... ) - {} + { + std::cerr << "I was the ctor called, with " << sizeof...( Args ) << " arguments." << std::endl; + tuple_for_each( std::tuple{ args... } ) <= + []( const auto element ) + { + std::cerr << "Element: " << element << std::endl; + }; + this->set( { args... } ); + } }; enum class TestResult { Passed, Failed }; @@ -77,7 +100,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test static consteval auto compute_base_f() noexcept { - if constexpr ( Aggregate< T > ) return std::type_identity< TupleSneak< T, Reflection::aggregate_tuple_t< T > > >{}; + if constexpr ( Aggregate< T > ) return std::type_identity< TupleSneak< T, list_from_tuple_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 >{}; } @@ -107,13 +130,27 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test { [expected]( Invoker invoker, const std::string &comment ) { - const return_type witness= invoker(); + const auto witness= Utility::evaluate <=[&]() -> std::optional< return_type > + { + try + { + return invoker(); + } + catch( ... ) + { + return std::nullopt; + } + }; 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 ); + if( witness.has_value() ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + printDebugging< outputMode >( witness.value(), expected ); + } + else std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": Unexpected exception in \"" << comment << '"' << std::endl; } else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; return result; @@ -164,7 +201,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; return TestResult::Passed; } - catch( const T & ) + catch( ... ) { std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; return TestResult::Failed; @@ -207,10 +244,10 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test template< Aggregate return_type, OutputMode outputMode > struct BasicUniversalHandler< return_type, outputMode > - : return_type + : compute_base_t< return_type > { using ComputedBase= compute_base_t< return_type >; - //using ComputedBase::ComputedBase; + using ComputedBase::ComputedBase; using Invoker= std::function< return_type () >; @@ -225,14 +262,28 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test { const return_type *const expected_p= this; const auto expected= *expected_p; - const return_type witness= invoker(); - + breakpoint(); + const auto witness= Utility::evaluate <=[&]() -> std::optional< return_type > + { + try + { + return invoker(); + } + catch( ... ) + { + return std::nullopt; + } + }; 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 ); + if( witness.has_value() ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + printDebugging< outputMode >( witness.value(), expected ); + } + else std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": Unexpected exception in \"" << comment << '"' << std::endl; } else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; return result; @@ -272,7 +323,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test {} #endif -#if 0 +#if 1 template< typename T > requires( not SameAs< T, void > ) BasicUniversalHandler( std::type_identity< T > ) : impl @@ -282,6 +333,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test try { std::ignore= invoker(); + breakpoint(); std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; return TestResult::Failed; } @@ -306,7 +358,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; return TestResult::Passed; } - catch( const T & ) + catch( ... ) { std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; return TestResult::Failed; @@ -368,14 +420,27 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test { const return_type *const expected_p= this; const auto expected= *expected_p; - const return_type witness= invoker(); - + const auto witness= Utility::evaluate <=[&]() -> std::optional< return_type > + { + try + { + return invoker(); + } + catch( ... ) + { + return std::nullopt; + } + }; 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 ); + if( witness.has_value() ) + { + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + printDebugging< outputMode >( witness.value(), expected ); + } + else std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": Unexpected exception in \"" << comment << '"' << std::endl; } else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; return result; @@ -449,7 +514,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; return TestResult::Passed; } - catch( const T & ) + catch( ... ) { std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; return TestResult::Failed; @@ -759,7 +824,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test invoker(); return true; } - catch( const T & ) { return false; } + catch( ... ) { return false; } } } {} @@ -868,7 +933,18 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test std::vector< TestDescription > tests; UniversalCases( std::initializer_list< TestDescription > initList ) - : tests( initList ) {} + { + for( const auto &desc: initList ) + { + if constexpr( Aggregate< return_type > ) + { + std::cerr << "Case: " << std::get< 0 >( desc ); + const return_type &v= std::get< 2 >( desc ); + std::cerr << " (" << v << ")" << std::endl; + tests.push_back( desc ); + } + } + } int operator() () const @@ -877,8 +953,11 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test 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 ); }; + auto invoker= [&] + { + breakpoint(); + return std::apply( function, params ); + }; const TestResult result= checker( invoker, comment ); if( result == TestResult::Failed ) { @@ -886,6 +965,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test ++failureCount; } else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + breakpoint(); } return failureCount; @@ -894,11 +974,11 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test // 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; + //using Cases= ExecutionCases; + using Cases= UniversalCases; - using ExceptionCases= ExceptionCases_real; - //using ExceptionCases= UniversalCases; + //using ExceptionCases= ExceptionCases_real; + using ExceptionCases= UniversalCases; }; #ifdef DISABLED diff --git a/Testing/TableTest.test/test2.cc b/Testing/TableTest.test/test2.cc index 7b412ea..ecad23a 100644 --- a/Testing/TableTest.test/test2.cc +++ b/Testing/TableTest.test/test2.cc @@ -1,9 +1,15 @@ static_assert( __cplusplus > 2020'00 ); #include + #include #include +#include +#include + +#include + namespace { @@ -28,11 +34,49 @@ namespace { "Righthand identity", { 25, 0 }, 25 }, }; + template< typename= Alepha::Capabilities< Alepha::IOStreams::OStreamable, Alepha::comparable > > + struct Aggregate_core + { + int x, y, z; + + friend bool operator == ( Aggregate_core, Aggregate_core ) noexcept= default; + }; + using Aggregate= Aggregate_core<>; + auto alltests= enroll <=[] { "addition.two.local"_test <=TableTest< add >::Cases { { "Negative case", { -10, -20 }, -30 }, }; + + "Can we use Aggregates with universal cases, correctly?"_test <= + TableTest + < + []( const int x ) + { + if( x < 0 ) throw std::runtime_error{ "Cannot be negative." }; + return Aggregate{ x, x, x }; + } + > + ::UniversalCases + { + { "Basic value case", { 42 }, { 42, 42, 42 } }, + { "Ignore exceptions case (`std::nothrow`)", { 42 }, std::nothrow }, + { "Ignore exceptions case (`std::type_identity< void >`)", { 42 }, std::type_identity< void >{} }, + { "Expect exception type runtime_error", { -42 }, std::type_identity< std::runtime_error >{} }, + { "Expect exception type exception", { -42 }, std::type_identity< std::exception >{} }, + { "Expect exception value specific", { -42 }, std::runtime_error{ "Cannot be negative." } }, + + /* These cases should fail, but we don't want to fail them in normal builds. */ + #if 0 + { "Failing: Basic value case", { -42 }, { 42, 42, 42 } }, + { "Failing: Ignore exceptions case (`std::nothrow`)", { -42 }, std::nothrow }, + { "Failing: Ignore exceptions case (`std::type_identity< void >`)", { -42 }, std::type_identity< void >{} }, + { "Failing: Expect exception type runtime_error", { 42 }, std::type_identity< std::runtime_error >{} }, + { "Failing: Expect exception type exception", { 42 }, std::type_identity< std::exception >{} }, + { "Failing: Expect exception value specific", { 42 }, std::runtime_error{ "Cannot be negative." } }, + #endif + }; }; }