forked from Alepha/Alepha
Merge branch 'master' of github.com:adamlsd/Alepha
This commit is contained in:
@ -1,3 +1,3 @@
|
||||
add_subdirectory( TableTest.test )
|
||||
|
||||
add_library( unit-test SHARED testlib.cc )
|
||||
add_library( unit-test SHARED testlib.cc test.cc )
|
||||
|
@ -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
80
Testing/test.cc
Normal 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 } );
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user