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_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 args_type= Meta::product_type_decay_t< typename function_traits_type::args_type >;
|
||||||
using return_type= typename function_traits_type::return_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 >;
|
using TestDescription= std::tuple< std::string, args_type, return_type >;
|
||||||
|
|
||||||
std::vector< TestDescription > tests;
|
std::vector< TestDescription > tests;
|
||||||
|
|
||||||
explicit
|
explicit
|
||||||
Cases( std::initializer_list< TestDescription > initList )
|
ExecutionCases( std::initializer_list< TestDescription > initList )
|
||||||
: tests( initList ) {}
|
: tests( initList ) {}
|
||||||
|
|
||||||
int
|
int
|
||||||
@ -394,6 +400,66 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test
|
|||||||
return failureCount;
|
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
|
#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
|
namespace exports
|
||||||
{
|
{
|
||||||
struct TestFailureException;
|
struct TestFailure;
|
||||||
|
|
||||||
inline namespace literals
|
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.
|
// It is okay to discard this, if making tests in an enroll block.
|
||||||
inline auto
|
inline namespace impl
|
||||||
operator <= ( TestName name, std::function< void () > test )
|
|
||||||
{
|
{
|
||||||
struct TestRegistration {} rv;
|
struct TestRegistration {};
|
||||||
if( C::debugTestRegistration ) std::cerr << "Attempting to register: " << name.name << std::endl;
|
TestRegistration operator <= ( TestName name, std::function< void () > test );
|
||||||
|
}
|
||||||
|
|
||||||
registry().emplace_back( name.name, name.disabled, test );
|
struct exports::TestFailure
|
||||||
assert( not registry().empty() );
|
|
||||||
assert( std::get< 1 >( registry().back() ) == name.disabled );
|
|
||||||
|
|
||||||
return rv;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct exports::TestFailureException
|
|
||||||
{
|
{
|
||||||
int failureCount= -1;
|
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
|
inline auto
|
||||||
operator <= ( TestName name, std::function< Integer () > test )
|
operator <= ( TestName name, std::function< Integer () > test )
|
||||||
{
|
{
|
||||||
@ -106,7 +98,7 @@ namespace Alepha::Hydrogen::Testing
|
|||||||
{
|
{
|
||||||
if( not test() )
|
if( not test() )
|
||||||
{
|
{
|
||||||
throw TestFailureException{ 1 };
|
throw TestFailure{ 1 };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,7 +109,7 @@ namespace Alepha::Hydrogen::Testing
|
|||||||
auto wrapper= [test]
|
auto wrapper= [test]
|
||||||
{
|
{
|
||||||
const int failures= test();
|
const int failures= test();
|
||||||
if( failures > 0 ) throw TestFailureException{ failures };
|
if( failures > 0 ) throw TestFailure{ failures };
|
||||||
};
|
};
|
||||||
|
|
||||||
return name <= wrapper;
|
return name <= wrapper;
|
||||||
@ -141,7 +133,7 @@ namespace Alepha::Hydrogen::Testing
|
|||||||
void
|
void
|
||||||
demand( const bool state, const std::string test= "" )
|
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
|
namespace exports
|
||||||
{
|
{
|
||||||
[[nodiscard]] inline int
|
[[nodiscard]] int runAllTests( const std::vector< std::string > selections= {} );
|
||||||
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 )
|
[[nodiscard]] int runAllTests( const argcnt_t argcnt, const argvec_t argvec );
|
||||||
{
|
|
||||||
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 } );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,5 +55,6 @@ static auto init= enroll <=[]
|
|||||||
{ "Empty string", { "", ':' }, { "" } },
|
{ "Empty string", { "", ':' }, { "" } },
|
||||||
{ "Single token", { "item", ':' }, { "item" } },
|
{ "Single token", { "item", ':' }, { "item" } },
|
||||||
{ "Two tokens", { "first:second", ':' }, { "first", "second" } },
|
{ "Two tokens", { "first:second", ':' }, { "first", "second" } },
|
||||||
|
{ "Empty string many tokens", { ":::", ':' }, { "", "", "", "" } },
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user