From df72d745e6903ffb3630266664329faa2f0b09d8 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sat, 11 Nov 2023 10:13:19 -0500 Subject: [PATCH] Unified testing output. This should eliminate the duplicate "PASSED" or "FAILED" lines. I think I can type-erase some of this sufficiently to make the output rendering be in its own TU... then tweaks to test output format will mostly require a relink, not a rebuild. --- Testing/TableTest.h | 105 +++++++++++++++++++++++---------------- Testing/printDebugging.h | 57 ++++++++++++++------- 2 files changed, 100 insertions(+), 62 deletions(-) diff --git a/Testing/TableTest.h b/Testing/TableTest.h index 7d714db..704a9db 100644 --- a/Testing/TableTest.h +++ b/Testing/TableTest.h @@ -8,6 +8,7 @@ static_assert( __cplusplus > 2020'99 ); #include #include +#include #include #include #include @@ -68,41 +69,47 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test using Invoker= std::function< return_type () >; - std::function< TestResult ( Invoker, const std::string & ) > impl; + std::function< std::tuple< TestResult, std::optional< std::string > > ( Invoker, const std::string & ) > impl; BasicUniversalHandler( const return_type expected ) : impl { - [expected]( Invoker invoker, const std::string &comment ) + [expected]( Invoker invoker, const std::string &comment ) -> std::tuple< TestResult, std::optional< std::string > > { - const auto witness= Utility::evaluate <=[&]() -> std::optional< return_type > + struct ErrorMessage { std::string mesg; }; + const auto witness= Utility::evaluate <=[&]() -> std::variant< return_type, ErrorMessage > { try { return invoker(); } + catch( const std::exception &ex ) + { + return ErrorMessage{ typeid( ex ).name() }; + } catch( ... ) { - return std::nullopt; + return ErrorMessage{ "Unknown Exception Type." }; } }; - const auto result= witness == expected ? TestResult::Passed : TestResult::Failed; + const auto result= ( std::holds_alternative< return_type >( witness ) and std::get< return_type >( witness ) == expected ) + ? TestResult::Passed + : TestResult::Failed; + std::ostringstream oss; if( result == TestResult::Failed ) { - if( witness.has_value() ) + if( std::holds_alternative< return_type >( witness ) ) { - std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; - printDebugging< outputMode >( witness.value(), expected ); + streamDebugging< outputMode >( oss, std::get< return_type >( witness ), expected ); } - else std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": Unexpected exception in \"" << comment << '"' << std::endl; + else oss << "Unexpected exception of type: " << std::get< ErrorMessage >( witness ).mesg; } - else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; - return result; + return { result, oss.str().empty() ? std::optional< std::string >{} : std::move( oss ).str() }; } } {} - TestResult + std::tuple< TestResult, std::optional< std::string > > operator() ( Invoker invoker, const std::string &comment ) const { if( impl != nullptr ) return impl( invoker, comment ); @@ -111,30 +118,36 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test const return_type *const expected_p= this; const auto expected= *expected_p; breakpoint(); - const auto witness= Utility::evaluate <=[&]() -> std::optional< return_type > + struct ErrorMessage { std::string mesg; }; + const auto witness= Utility::evaluate <=[&]() -> std::variant< return_type, ErrorMessage > { try { return invoker(); } + catch( const std::exception &ex ) + { + return ErrorMessage{ typeid( ex ).name() }; + } catch( ... ) { - return std::nullopt; + return ErrorMessage{ "Unknown Exception Type." }; } }; - const auto result= witness == expected ? TestResult::Passed : TestResult::Failed; + const auto result= ( std::holds_alternative< return_type >( witness ) and std::get< return_type >( witness ) == expected ) + ? TestResult::Passed + : TestResult::Failed; + std::ostringstream oss; if( result == TestResult::Failed ) { - if( witness.has_value() ) + if( std::holds_alternative< return_type >( witness ) ) { - std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; - printDebugging< outputMode >( witness.value(), expected ); + streamDebugging< outputMode >( oss, std::get< return_type >( witness ), expected ); } - else std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": Unexpected exception in \"" << comment << '"' << std::endl; + else oss << "Unexpected exception of type: " << std::get< ErrorMessage >( witness ).mesg; } - else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; - return result; + return { result, oss.str().empty() ? std::optional< std::string >{} : std::move( oss ).str() }; } else throw std::logic_error( "Somehow we didn't setup impl, and it's not an adapted case!" ); } @@ -143,19 +156,17 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test requires( not SameAs< T, void > ) BasicUniversalHandler( std::type_identity< T > ) : impl { - []( Invoker invoker, const std::string &comment ) + []( Invoker invoker, const std::string &comment ) -> std::tuple< TestResult, std::optional< std::string > > { try { std::ignore= invoker(); breakpoint(); - std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; - return TestResult::Failed; + return { TestResult::Failed, std::nullopt }; } catch( const T & ) { - std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; - return TestResult::Passed; + return { TestResult::Passed, std::nullopt }; } } } @@ -165,18 +176,16 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test requires( SameAs< T, std::type_identity< void > > or SameAs< T, std::nothrow_t > ) BasicUniversalHandler( T ) : impl { - []( Invoker invoker, const std::string &comment ) + []( Invoker invoker, const std::string &comment ) -> std::tuple< TestResult, std::optional< std::string > > { try { std::ignore= invoker(); - std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; - return TestResult::Passed; + return { TestResult::Passed, std::nullopt }; } catch( ... ) { - std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; - return TestResult::Failed; + return { TestResult::Failed, std::nullopt }; } } } @@ -185,28 +194,25 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test template< DerivedFrom< std::exception > T > BasicUniversalHandler( const T exemplar ) : impl { - [expected= std::string{ exemplar.what() }]( Invoker invoker, const std::string &comment ) + [expected= std::string{ exemplar.what() }]( Invoker invoker, const std::string &comment ) -> std::tuple< TestResult, std::optional< std::string > > { 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; + return { TestResult::Failed, + IOStreams::String{} << "expected exception `" << typeid( T ).name() << "` wasn't thrown." }; } catch( const T &ex ) { const std::string witness= ex.what(); const TestResult rv= witness == expected ? TestResult::Passed : TestResult::Failed; + std::ostringstream oss; 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 ); + oss << "expected message did not match." << std::endl; + streamDebugging< outputMode >( oss, witness, expected ); } - return rv; + return { rv, oss.str().empty() ? std::optional< std::string >{} : std::move( oss ).str() }; } } } @@ -294,13 +300,24 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test breakpoint(); return std::apply( function, params ); }; - const auto result= checker( invoker, comment ); + const auto [result, supplement]= checker( invoker, comment ); if( result == TestResult::Failed ) { - std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; ++failureCount; + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + if( supplement.has_value() ) + { + std::cout << " " << C::testWarn << "DETAILS" << resetStyle << ": " << supplement.value() << std::endl; + } + } + else + { + std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + if( supplement.has_value() ) + { + std::cout << " " << C::testWarn << "INFO: " << resetStyle << ": " << supplement.value() << std::endl; + } } - else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; breakpoint(); } diff --git a/Testing/printDebugging.h b/Testing/printDebugging.h index 694b1a1..0c81e91 100644 --- a/Testing/printDebugging.h +++ b/Testing/printDebugging.h @@ -34,6 +34,9 @@ namespace Alepha::Hydrogen::Testing ::detail:: printDebugging_m { enum class OutputMode { All, Relaxed }; + template< OutputMode outputMode, typename T > + void streamDebugging( const T &witness, const T &expected ); + template< OutputMode outputMode, typename T > void printDebugging( const T &witness, const T &expected ); } @@ -126,40 +129,51 @@ namespace Alepha::Hydrogen::Testing ::detail:: printDebugging_m } template< OutputMode outputMode, typename T > - std::string - stringifyValue( const T &v ) + struct Adaptor { - std::ostringstream oss; - streamValue< outputMode >( v, oss ); - return std::move( oss ).str(); + const T &element; + + friend std::ostream & + operator << ( std::ostream &os, const Adaptor &a ) + { + streamValue< outputMode >( a, os ); + return os; + } + }; + + template< OutputMode outputMode, typename T > + auto + streamAdaptValue( const T &v ) + { + return Adaptor< outputMode, std::decay_t< T > >{ v }; } inline void - printDebuggingForStrings( const std::string &witness, const std::string &expected ) + streamDebuggingForStrings( std::ostream &stream, const std::string &witness, const std::string &expected ) { const std::size_t amount= std::min( witness.size(), expected.size() ); if( witness.size() != expected.size() ) { - std::cout << "Witness string size did not match the expected string size. Only mismatches found in the first " + stream << "Witness string size did not match the expected string size. Only mismatches found in the first " << amount << " characters will be printed." << std::endl; } for( int i= 0; i < amount; ++i ) { if( witness.at( i ) == expected.at( i ) ) continue; - std::cout << "Mismatch at index: " << i << std::endl; - std::cout << "witness: " << witness.at( i ) << std::endl; - std::cout << "expected: " << expected.at( i ) << std::endl; + stream << "Mismatch at index: " << i << std::endl; + stream << "witness: " << witness.at( i ) << std::endl; + stream << "expected: " << expected.at( i ) << std::endl; } } template< OutputMode outputMode, typename T > void - exports::printDebugging( const T &witness, const T &expected ) + exports::streamDebugging( std::ostream &stream, const T &witness, const T &expected ) { if constexpr( std::is_same_v< std::string, std::decay_t< T > > ) { - printDebuggingForStrings( witness, expected ); + streamDebuggingForStrings( stream, witness, expected ); } else if constexpr( Meta::is_sequence_v< T > ) { @@ -167,14 +181,14 @@ namespace Alepha::Hydrogen::Testing ::detail:: printDebugging_m { if( witness.size() == expected.size() ) for( std::size_t i= 0; i < witness.size(); ++i ) { - if( witness.at( i ) != expected.at( i ) ) printDebuggingForStrings( witness.at( i ), expected.at( i ) ); + if( witness.at( i ) != expected.at( i ) ) streamDebuggingForStrings( stream, witness.at( i ), expected.at( i ) ); } } else { if( witness.size() != expected.size() ) { - std::cout << "Witness sequence size of " << witness.size() << " did not match the expected sequence size of " + stream << "Witness sequence size of " << witness.size() << " did not match the expected sequence size of " << expected.size() << std::endl; } @@ -184,7 +198,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: printDebugging_m { if( not first ) { - std::cout << "Mismatch at witness index " << std::distance( begin( witness ), next.first ) << " and " + stream << "Mismatch at witness index " << std::distance( begin( witness ), next.first ) << " and " << "expected index " << std::distance( begin( expected ), next.second ) << std::endl; ++next.first; ++next.second; } @@ -194,9 +208,16 @@ namespace Alepha::Hydrogen::Testing ::detail:: printDebugging_m } } - std::cout << std::endl - << "computed: " << stringifyValue< outputMode >( witness ) << std::endl - << "expected: " << stringifyValue< outputMode >( expected ) << std::endl << std::endl; + stream << std::endl + << "computed: " << streamAdaptValue< outputMode >( witness ) << std::endl + << "expected: " << streamAdaptValue< outputMode >( expected ) << std::endl << std::endl; + } + + template< OutputMode outputMode, typename T > + void + exports::printDebugging( const T &witness, const T &expected ) + { + streamDebugging( std::cout, witness, expected ); } }