1
0
forked from Alepha/Alepha

Merge branch 'master' of github.com:adamlsd/Alepha

This commit is contained in:
2023-10-26 02:34:35 -04:00
5 changed files with 165 additions and 82 deletions

View File

@ -1,3 +1,3 @@
add_subdirectory( TableTest.test )
add_library( unit-test SHARED testlib.cc )
add_library( unit-test SHARED testlib.cc test.cc )

View File

@ -249,14 +249,20 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test
using args_type= Meta::product_type_decay_t< typename function_traits_type::args_type >;
using return_type= typename function_traits_type::return_type;
struct Cases
// The classic table-test engine would only support `Cases` which were run-and-test-value
// without the ability to test exceptions. The `ExceptionCases` construct was used to
// test throwing cases.
//
// A unified `Cases` type is forthcoming, and thus `ExecutionCases` exists for backwards
// compatibility.
struct ExecutionCases
{
using TestDescription= std::tuple< std::string, args_type, return_type >;
std::vector< TestDescription > tests;
explicit
Cases( std::initializer_list< TestDescription > initList )
ExecutionCases( std::initializer_list< TestDescription > initList )
: tests( initList ) {}
int
@ -394,6 +400,66 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test
return failureCount;
}
};
class 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 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;
};
#ifdef DISABLED

80
Testing/test.cc Normal file
View File

@ -0,0 +1,80 @@
static_assert( __cplusplus > 2020'00 );
#include "test.h"
namespace Alepha::Hydrogen::Testing::detail::testing
{
StaticValue< std::vector< std::tuple< std::string, bool, std::function< void() > > > > registry;
TestRegistration
impl::operator <= ( TestName name, std::function< void () > test )
{
if( C::debugTestRegistration ) std::cerr << "Attempting to register: " << name.name << std::endl;
registry().emplace_back( name.name, name.disabled, test );
assert( not registry().empty() );
assert( std::get< 1 >( registry().back() ) == name.disabled );
return {};
};
[[nodiscard]] int
exports::runAllTests( const std::vector< std::string > selections )
{
if( C::debugTestRun )
{
std::cerr << "Going to run all tests. (I see " << registry().size() << " tests.)" << std::endl;
}
bool failed= false;
const auto selected= [ selections ]( const std::string test )
{
for( const auto &selection: selections )
{
if( test.find( selection ) != std::string::npos ) return true;
}
return empty( selections );
};
const auto explicitlyNamed= [ selections ]( const std::string s )
{
return std::find( begin( selections ), end( selections ), s ) != end( selections );
};
for( const auto &[ name, disabled, test ]: registry() )
{
if( C::debugTestRun ) std::cerr << "Trying test " << name << std::endl;
if( explicitlyNamed( name ) or not disabled and selected( name ) )
{
std::cout << C::testInfo << "BEGIN" << resetStyle << " : " << name << std::endl;
try
{
test();
std::cout << " " << C::testPass << "SUCCESS" << resetStyle << ": " << name << std::endl;
}
catch( ... )
{
try
{
failed= true;
std::cout << " " << C::testFail << "FAILURE" << resetStyle << ": " << name;
throw;
}
catch( const TestFailure &fail ) { std::cout << " -- " << fail.failureCount << " failures."; }
catch( ... ) { std::cout << " -- unknown failure count"; }
std::cout << std::endl;
}
std::cout << C::testInfo << "FINISHED" << resetStyle << ": " << name << std::endl;
}
}
return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
[[nodiscard]] int
exports::runAllTests( const argcnt_t argcnt, const argvec_t argvec )
{
return runAllTests( { argvec + 1, argvec + argcnt } );
}
}

View File

@ -60,7 +60,7 @@ namespace Alepha::Hydrogen::Testing
namespace exports
{
struct TestFailureException;
struct TestFailure;
inline namespace literals
{
@ -72,31 +72,23 @@ namespace Alepha::Hydrogen::Testing
}
}
StaticValue< std::vector< std::tuple< std::string, bool, std::function< void() > > > > registry;
auto initRegistry= enroll <=registry;
// It is okay to discard this, if making tests in an enroll block.
inline auto
operator <= ( TestName name, std::function< void () > test )
inline namespace impl
{
struct TestRegistration {} rv;
if( C::debugTestRegistration ) std::cerr << "Attempting to register: " << name.name << std::endl;
struct TestRegistration {};
TestRegistration operator <= ( TestName name, std::function< void () > test );
}
registry().emplace_back( name.name, name.disabled, test );
assert( not registry().empty() );
assert( std::get< 1 >( registry().back() ) == name.disabled );
return rv;
};
struct exports::TestFailureException
struct exports::TestFailure
{
int failureCount= -1;
std::string message_;
explicit TestFailureException( const int failureCount ) : failureCount( failureCount ) {}
explicit TestFailure( const int failureCount )
: failureCount( failureCount ) {}
};
template< typename Integer, typename= std::enable_if_t< std::is_integral_v< Integer > > >
template< Integral Integer >
inline auto
operator <= ( TestName name, std::function< Integer () > test )
{
@ -106,7 +98,7 @@ namespace Alepha::Hydrogen::Testing
{
if( not test() )
{
throw TestFailureException{ 1 };
throw TestFailure{ 1 };
}
};
@ -117,7 +109,7 @@ namespace Alepha::Hydrogen::Testing
auto wrapper= [test]
{
const int failures= test();
if( failures > 0 ) throw TestFailureException{ failures };
if( failures > 0 ) throw TestFailure{ failures };
};
return name <= wrapper;
@ -141,7 +133,7 @@ namespace Alepha::Hydrogen::Testing
void
demand( const bool state, const std::string test= "" )
{
if( not state ) throw TestFailureException( failures.size() + 1 );
if( not state ) throw TestFailure( failures.size() + 1 );
}
};
@ -169,65 +161,9 @@ namespace Alepha::Hydrogen::Testing
namespace exports
{
[[nodiscard]] inline int
runAllTests( const std::vector< std::string > selections= {} )
{
if( C::debugTestRun )
{
std::cerr << "Going to run all tests. (I see " << registry().size() << " tests.)" << std::endl;
}
bool failed= false;
const auto selected= [ selections ]( const std::string test )
{
for( const auto &selection: selections )
{
if( test.find( selection ) != std::string::npos ) return true;
}
return empty( selections );
};
[[nodiscard]] int runAllTests( const std::vector< std::string > selections= {} );
const auto explicitlyNamed= [ selections ]( const std::string s )
{
return std::find( begin( selections ), end( selections ), s ) != end( selections );
};
for( const auto &[ name, disabled, test ]: registry() )
{
if( C::debugTestRun ) std::cerr << "Trying test " << name << std::endl;
if( explicitlyNamed( name ) or not disabled and selected( name ) )
{
std::cout << C::testInfo << "BEGIN" << resetStyle << " : " << name << std::endl;
try
{
test();
std::cout << " " << C::testPass << "SUCCESS" << resetStyle << ": " << name << std::endl;
}
catch( ... )
{
try
{
failed= true;
std::cout << " " << C::testFail << "FAILURE" << resetStyle << ": " << name;
throw;
}
catch( const TestFailureException &fail ) { std::cout << " -- " << fail.failureCount << " failures."; }
catch( ... ) { std::cout << " -- unknown failure count"; }
std::cout << std::endl;
}
std::cout << C::testInfo << "FINISHED" << resetStyle << ": " << name << std::endl;
}
}
return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
[[nodiscard]] inline int
runAllTests( const argcnt_t argcnt, const argvec_t argvec )
{
return runAllTests( { argvec + 1, argvec + argcnt } );
}
[[nodiscard]] int runAllTests( const argcnt_t argcnt, const argvec_t argvec );
}
}