forked from Alepha/Alepha
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.
This commit is contained in:
@ -8,6 +8,7 @@ static_assert( __cplusplus > 2020'99 );
|
|||||||
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
@ -68,41 +69,47 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test
|
|||||||
|
|
||||||
using Invoker= std::function< return_type () >;
|
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
|
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
|
try
|
||||||
{
|
{
|
||||||
return invoker();
|
return invoker();
|
||||||
}
|
}
|
||||||
|
catch( const std::exception &ex )
|
||||||
|
{
|
||||||
|
return ErrorMessage{ typeid( ex ).name() };
|
||||||
|
}
|
||||||
catch( ... )
|
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( result == TestResult::Failed )
|
||||||
{
|
{
|
||||||
if( witness.has_value() )
|
if( std::holds_alternative< return_type >( witness ) )
|
||||||
{
|
{
|
||||||
std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl;
|
streamDebugging< outputMode >( oss, std::get< return_type >( witness ), expected );
|
||||||
printDebugging< outputMode >( witness.value(), 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, oss.str().empty() ? std::optional< std::string >{} : std::move( oss ).str() };
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
TestResult
|
std::tuple< TestResult, std::optional< std::string > >
|
||||||
operator() ( Invoker invoker, const std::string &comment ) const
|
operator() ( Invoker invoker, const std::string &comment ) const
|
||||||
{
|
{
|
||||||
if( impl != nullptr ) return impl( invoker, comment );
|
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 return_type *const expected_p= this;
|
||||||
const auto expected= *expected_p;
|
const auto expected= *expected_p;
|
||||||
breakpoint();
|
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
|
try
|
||||||
{
|
{
|
||||||
return invoker();
|
return invoker();
|
||||||
}
|
}
|
||||||
|
catch( const std::exception &ex )
|
||||||
|
{
|
||||||
|
return ErrorMessage{ typeid( ex ).name() };
|
||||||
|
}
|
||||||
catch( ... )
|
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( result == TestResult::Failed )
|
||||||
{
|
{
|
||||||
if( witness.has_value() )
|
if( std::holds_alternative< return_type >( witness ) )
|
||||||
{
|
{
|
||||||
std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl;
|
streamDebugging< outputMode >( oss, std::get< return_type >( witness ), expected );
|
||||||
printDebugging< outputMode >( witness.value(), 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, oss.str().empty() ? std::optional< std::string >{} : std::move( oss ).str() };
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
else throw std::logic_error( "Somehow we didn't setup impl, and it's not an adapted case!" );
|
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 > )
|
requires( not SameAs< T, void > )
|
||||||
BasicUniversalHandler( std::type_identity< T > ) : impl
|
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
|
try
|
||||||
{
|
{
|
||||||
std::ignore= invoker();
|
std::ignore= invoker();
|
||||||
breakpoint();
|
breakpoint();
|
||||||
std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl;
|
return { TestResult::Failed, std::nullopt };
|
||||||
return TestResult::Failed;
|
|
||||||
}
|
}
|
||||||
catch( const T & )
|
catch( const T & )
|
||||||
{
|
{
|
||||||
std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl;
|
return { TestResult::Passed, std::nullopt };
|
||||||
return TestResult::Passed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,18 +176,16 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test
|
|||||||
requires( SameAs< T, std::type_identity< void > > or SameAs< T, std::nothrow_t > )
|
requires( SameAs< T, std::type_identity< void > > or SameAs< T, std::nothrow_t > )
|
||||||
BasicUniversalHandler( T ) : impl
|
BasicUniversalHandler( T ) : impl
|
||||||
{
|
{
|
||||||
[]( Invoker invoker, const std::string &comment )
|
[]( Invoker invoker, const std::string &comment ) -> std::tuple< TestResult, std::optional< std::string > >
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::ignore= invoker();
|
std::ignore= invoker();
|
||||||
std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl;
|
return { TestResult::Passed, std::nullopt };
|
||||||
return TestResult::Passed;
|
|
||||||
}
|
}
|
||||||
catch( ... )
|
catch( ... )
|
||||||
{
|
{
|
||||||
std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl;
|
return { TestResult::Failed, std::nullopt };
|
||||||
return TestResult::Failed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,28 +194,25 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test
|
|||||||
template< DerivedFrom< std::exception > T >
|
template< DerivedFrom< std::exception > T >
|
||||||
BasicUniversalHandler( const T exemplar ) : impl
|
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
|
try
|
||||||
{
|
{
|
||||||
invoker();
|
invoker();
|
||||||
std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl;
|
return { TestResult::Failed,
|
||||||
std::cout << " " << C::testInfo << "NOTE" << resetStyle << ": expected exception `"
|
IOStreams::String{} << "expected exception `" << typeid( T ).name() << "` wasn't thrown." };
|
||||||
<< typeid( T ).name()
|
|
||||||
<< "` wasn't thrown." << std::endl;
|
|
||||||
return TestResult::Failed;
|
|
||||||
}
|
}
|
||||||
catch( const T &ex )
|
catch( const T &ex )
|
||||||
{
|
{
|
||||||
const std::string witness= ex.what();
|
const std::string witness= ex.what();
|
||||||
const TestResult rv= witness == expected ? TestResult::Passed : TestResult::Failed;
|
const TestResult rv= witness == expected ? TestResult::Passed : TestResult::Failed;
|
||||||
|
std::ostringstream oss;
|
||||||
if( rv == TestResult::Failed )
|
if( rv == TestResult::Failed )
|
||||||
{
|
{
|
||||||
std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl;
|
oss << "expected message did not match." << std::endl;
|
||||||
std::cout << " " << C::testInfo << "NOTE" << resetStyle << ": expected message did not match." << std::endl;
|
streamDebugging< outputMode >( oss, witness, expected );
|
||||||
printDebugging< outputMode >( 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();
|
breakpoint();
|
||||||
return std::apply( function, params );
|
return std::apply( function, params );
|
||||||
};
|
};
|
||||||
const auto result= checker( invoker, comment );
|
const auto [result, supplement]= checker( invoker, comment );
|
||||||
if( result == TestResult::Failed )
|
if( result == TestResult::Failed )
|
||||||
{
|
{
|
||||||
std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl;
|
|
||||||
++failureCount;
|
++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();
|
breakpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,9 @@ namespace Alepha::Hydrogen::Testing ::detail:: printDebugging_m
|
|||||||
{
|
{
|
||||||
enum class OutputMode { All, Relaxed };
|
enum class OutputMode { All, Relaxed };
|
||||||
|
|
||||||
|
template< OutputMode outputMode, typename T >
|
||||||
|
void streamDebugging( const T &witness, const T &expected );
|
||||||
|
|
||||||
template< OutputMode outputMode, typename T >
|
template< OutputMode outputMode, typename T >
|
||||||
void printDebugging( const T &witness, const T &expected );
|
void printDebugging( const T &witness, const T &expected );
|
||||||
}
|
}
|
||||||
@ -126,40 +129,51 @@ namespace Alepha::Hydrogen::Testing ::detail:: printDebugging_m
|
|||||||
}
|
}
|
||||||
|
|
||||||
template< OutputMode outputMode, typename T >
|
template< OutputMode outputMode, typename T >
|
||||||
std::string
|
struct Adaptor
|
||||||
stringifyValue( const T &v )
|
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
const T &element;
|
||||||
streamValue< outputMode >( v, oss );
|
|
||||||
return std::move( oss ).str();
|
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
|
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() );
|
const std::size_t amount= std::min( witness.size(), expected.size() );
|
||||||
if( 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;
|
<< amount << " characters will be printed." << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
for( int i= 0; i < amount; ++i )
|
for( int i= 0; i < amount; ++i )
|
||||||
{
|
{
|
||||||
if( witness.at( i ) == expected.at( i ) ) continue;
|
if( witness.at( i ) == expected.at( i ) ) continue;
|
||||||
std::cout << "Mismatch at index: " << i << std::endl;
|
stream << "Mismatch at index: " << i << std::endl;
|
||||||
std::cout << "witness: " << witness.at( i ) << std::endl;
|
stream << "witness: " << witness.at( i ) << std::endl;
|
||||||
std::cout << "expected: " << expected.at( i ) << std::endl;
|
stream << "expected: " << expected.at( i ) << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template< OutputMode outputMode, typename T >
|
template< OutputMode outputMode, typename T >
|
||||||
void
|
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 > > )
|
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 > )
|
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.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
|
else
|
||||||
{
|
{
|
||||||
if( witness.size() != expected.size() )
|
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;
|
<< expected.size() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +198,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: printDebugging_m
|
|||||||
{
|
{
|
||||||
if( not first )
|
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;
|
<< "expected index " << std::distance( begin( expected ), next.second ) << std::endl;
|
||||||
++next.first; ++next.second;
|
++next.first; ++next.second;
|
||||||
}
|
}
|
||||||
@ -194,9 +208,16 @@ namespace Alepha::Hydrogen::Testing ::detail:: printDebugging_m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << std::endl
|
stream << std::endl
|
||||||
<< "computed: " << stringifyValue< outputMode >( witness ) << std::endl
|
<< "computed: " << streamAdaptValue< outputMode >( witness ) << std::endl
|
||||||
<< "expected: " << stringifyValue< outputMode >( expected ) << std::endl << 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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user