1
0
forked from Alepha/Alepha

Merged branch 'universal-handler'

Now `TableTest::UniversalCases` seems to work and has been
made the default for `Cases` and `ExceptionCases`.  THe original
versions still remain, for now.  These will be retired once
a few more use-cases get tried.

The `UniversalHandler` that implements the per-case entries
in `UniversalCases` is kinda messy right now.  I have to
clean that up; there's a lot of code duplication therein.
This commit is contained in:
2023-10-29 06:02:14 -04:00
6 changed files with 628 additions and 55 deletions

View File

@ -6,6 +6,8 @@ static_assert( __cplusplus > 2020'00 );
#include <boost/preprocessor.hpp>
#include <Alepha/meta.h>
#include <Alepha/Reflection/detail/config.h>
#include <Alepha/Reflection/aggregate_members.h>
@ -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 & >() ) ) > >;
}
}

View File

@ -33,6 +33,8 @@ static_assert( __cplusplus > 2020'00 );
#include <Alepha/Utility/evaluation_helpers.h>
#include <Alepha/Reflection/tuplizeAggregate.h>
#include <Alepha/TotalOrder.h>
#include <Alepha/Console.h>
@ -45,6 +47,8 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test
enum class OutputMode { All, Relaxed };
}
inline void breakpoint() {}
namespace C
{
inline namespace Colors
@ -53,6 +57,506 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test
}
}
template< OutputMode outputMode, typename T >
void printDebugging( const T &witness, const T &expected );
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 )
{
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 };
struct BlankBase {};
template< typename T >
static consteval auto
compute_base_f() noexcept
{
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 >{};
}
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 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 )
{
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;
}
}
{}
#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 1
template< typename T >
requires( not SameAs< T, void > )
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< 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( ... )
{
std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl;
return TestResult::Failed;
}
}
}
{}
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 >
: compute_base_t< 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;
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 )
{
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;
}
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 1
template< typename T >
requires( not SameAs< T, void > )
BasicUniversalHandler( std::type_identity< T > ) : impl
{
[]( Invoker invoker, const std::string &comment )
{
try
{
std::ignore= invoker();
breakpoint();
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< 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( ... )
{
std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl;
return TestResult::Failed;
}
}
}
{}
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 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 )
{
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;
}
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 1
template< typename T >
requires( not SameAs< T, void > )
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< 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( ... )
{
std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl;
return TestResult::Failed;
}
}
}
{}
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 )
@ -66,8 +570,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;
@ -288,7 +790,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test
}
};
struct ExceptionCases
struct ExceptionCases_real
{
using Invoker= std::function< void () >;
struct ExceptionHandler
@ -312,6 +814,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( ... ) { return false; }
}
}
{}
template< typename T >
requires( not SameAs< T, void > )
ExceptionHandler( std::type_identity< T > ) : impl
{
[]( Invoker invoker )
@ -376,7 +895,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
@ -401,65 +920,65 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test
}
};
class UniversalCases
using ComputedBase= compute_base_t< return_type >;
struct UniversalCases
{
using RunDescription= std::tuple< std::string, args_type, return_type >;
using Invoker= std::function< return_type () >;
enum class TestResult { Passed, Failed };
using UniversalHandler= BasicUniversalHandler< return_type, outputMode >;
struct UniversalHandler
using TestDescription= std::tuple< std::string, args_type, UniversalHandler >;
std::vector< TestDescription > tests;
UniversalCases( std::initializer_list< TestDescription > initList )
{
std::function< TestResult ( Invoker, const std::string & ) > impl;
bool operator() ( Invoker invoker ) const { return impl( invoker ); }
UniversalHandler( const return_type expected ) : impl
for( const auto &desc: initList )
{
[expected]( Invoker invoker, const std::string &comment )
if constexpr( Aggregate< return_type > )
{
const auto witness= invoker();
const auto result= witness == expected ? TestResult::Passed : TestResult::Failed;
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
{
int failureCount= 0;
for( const auto &[ comment, params, checker ]: tests )
{
if( C::debugCaseTypes ) std::cerr << boost::core::demangle( typeid( params ).name() ) << std::endl;
auto invoker= [&]
{
breakpoint();
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;
printDebugging< outputMode >( witness, expected );
++failureCount;
}
else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl;
return result;
breakpoint();
}
}
{}
template< typename T >
UniversalHandler( std::type_identity< T > ) : impl
{
[]( Invoker invoker )
{
try { std::ignore= invoker(); }
catch( const T & ) { return TestResult::Passed; }
return TestResult::Failed;
return failureCount;
}
}
{}
UniversalHandler( const DerivedFrom< std::exception > auto exemplar ) : impl
{
[expected= std::string{ exemplar.what() }]( Invoker invoker )
{
throw "Unimpl";
}
}
{}
};
using TestDescription= std::tuple< std::string, args_type, UniversalHandler >;
};
// 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= ExecutionCases;
using Cases= UniversalCases;
//using ExceptionCases= ExceptionCases_real;
using ExceptionCases= UniversalCases;
};
#ifdef DISABLED

View File

@ -1,9 +1,15 @@
static_assert( __cplusplus > 2020'00 );
#include <Alepha/Testing/TableTest.h>
#include <Alepha/Testing/test.h>
#include <Alepha/Utility/evaluation_helpers.h>
#include <Alepha/IOStreams/delimiters.h>
#include <Alepha/IOStreams/OStreamable.h>
#include <Alepha/comparisons.h>
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
};
};
}

View File

@ -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;
}

View File

@ -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" } }, '$' },