2024-04-08 14:50:48 +02:00
//
// Created by Patrick Maschek on 08/04/2024.
//
2024-06-05 19:53:41 +02:00
# ifndef CONST_CONTAINER_TEST_HPP_
# define CONST_CONTAINER_TEST_HPP_
2024-04-08 14:50:48 +02:00
2024-04-22 11:24:40 +02:00
# include <any>
2024-04-08 14:50:48 +02:00
# include <array>
# include <concepts>
2024-04-22 11:24:40 +02:00
# include <iostream>
# include <ranges>
2024-04-08 14:50:48 +02:00
# include <tuple>
2024-06-05 19:44:22 +02:00
# include <numeric>
2024-04-08 14:50:48 +02:00
2024-07-22 23:32:31 +02:00
# define _EXPAND_STR(s) #s
# define _EXPAND_MACRO(s) _EXPAND_STR(s)
# define _LOCATION " (at " _EXPAND_MACRO(__FILE__) ":" _EXPAND_MACRO(__LINE__) ")"
2024-07-23 01:49:09 +02:00
# define ADD_TYPE_HINT(type) template <> constexpr const char* const to_type_hint_str::value<type> = #type
2024-04-22 12:03:58 +02:00
# define TEST_FAIL(msg) ret_val_s { "", ReturnCode::FAILED, msg }
2024-06-05 19:44:22 +02:00
# define TEST_PASS() ret_val_s { "", ReturnCode::PASSED, nullptr }
# define TEST_PASS_MSG(msg) ret_val_s { "", ReturnCode::PASSED, msg }
2024-07-23 01:49:09 +02:00
# define TEST_SKIP() ret_val_s { "", ReturnCode::SKIPPED, nullptr}
# define TEST_FAIL_TYPE(msg, type_hint) ret_val_s { "", ReturnCode::FAILED, msg, to_type_hint_str::value<type_hint> }
# define TEST_PASS_TYPE(type_hint) ret_val_s { "", ReturnCode::PASSED, nullptr, to_type_hint_str::value<type_hint> }
# define TEST_PASS_MSG_TYPE(msg, type_hint) ret_val_s { "", ReturnCode::PASSED, msg, to_type_hint_str::value<type_hint> }
# define TEST_SKIP_TYPE(type_hint) ret_val_s { "", ReturnCode::SKIPPED, nullptr, to_type_hint_str::value<type_hint> }
2024-04-08 14:50:48 +02:00
2024-07-23 01:49:09 +02:00
# define ASSERT_TYPE(condition, type) ASSERT_TYPE_MSG(condition, #condition "" _LOCATION, type)
# define ASSERT_TYPE_MSG(condition, msg, type) { if (!(condition)) return TEST_FAIL_TYPE(msg, type); } static_assert(true, "")
# define ASSERT_TYPE_ALL_EQ(first, last, eq, type) ASSERT_TYPE_MSG(std::all_of(first, last, all_eq_arr_elem_test_func(eq)), "Not all elements in (" #first ", " #last ") equal " #eq "" _LOCATION, type)
# define ASSERT_TYPE_C_ARR_EQ(first, last, eq, type) ASSERT_TYPE_MSG(std::equal(first, last, eq, all_eq_arr_arr_test_func<decltype(eq)>()), "Elements in (" #first ", " #last ") and " #eq " differ" _LOCATION, type)
# define ASSERT_TYPE_ITER_EQ(first, last, eq, type) ASSERT_TYPE_MSG(std::equal(first, last, (eq).begin(), all_eq_arr_arr_test_func<std::iterator_traits<decltype(eq)::iterator>::value_type>()), "Elements in (" #first ", " #last ") and " #eq " differ" _LOCATION, type)
2024-07-22 23:32:31 +02:00
2024-07-23 01:49:09 +02:00
# define ASSERT(condition) ASSERT_TYPE(condition, std::nullptr_t)
# define ASSERT_MSG(condition, msg) ASSERT_TYPE_MSG(condition, msg, std::nullptr_t)
# define ASSERT_ALL_EQ(first, last, eq) ASSERT_TYPE_ALL_EQ(first, last, eq, std::nullptr_t)
# define ASSERT_C_ARR_EQ(first, last, eq) ASSERT_TYPE_C_ARR_EQ(first, last, eq, std::nullptr_t)
# define ASSERT_ITER_EQ(first, last, eq) ASSERT_TYPE_ITER_EQ(first, last, eq, std::nullptr_t)
# define ASSERT_TYPE_THROWS(operation, exception_type, type) if not consteval { try { operation; ASSERT_TYPE_MSG(false, #operation " did not throw " #exception_type _LOCATION, type); } catch (exception_type &e) {} } static_assert(true, "")
# define ASSERT_THROWS(operation, exception_type) ASSERT_TYPE_THROWS(operation, exception_type, std::nullptr_t)
2024-06-18 21:38:20 +02:00
template < typename T >
constexpr auto all_eq_arr_elem_test_func ( T & & eq ) {
return [ = ] ( auto & & e ) constexpr { return e = = eq ; } ;
}
template < const char * >
constexpr auto all_eq_arr_elem_test_func ( const char * eq ) {
return [ = ] ( auto & & e ) constexpr { return std : : string_view ( e ) = = eq ; } ;
}
template < typename T >
constexpr auto all_eq_arr_arr_test_func ( ) {
return [ ] ( auto & & a , auto & & b ) constexpr { return a = = b ; } ;
}
template < const char * >
constexpr auto all_eq_arr_arr_test_func ( ) {
return [ ] ( auto & & a , auto & & b ) constexpr { return std : : string_view ( a ) = = b ; } ;
}
2024-06-05 19:53:41 +02:00
enum class EvalFlag { RUNTIME , CONSTEVAL , RUNTIME_CONSTEVAL } ;
enum class ReturnCode { FAILED = - 1 , PASSED = 0 , SKIPPED = 1 , NOT_EVALUATED = 2 } ;
2024-07-23 01:49:09 +02:00
struct to_type_hint_str {
template < typename T >
static constexpr const char * value = nullptr ;
} ;
ADD_TYPE_HINT ( bool ) ;
ADD_TYPE_HINT ( int ) ;
ADD_TYPE_HINT ( char ) ;
ADD_TYPE_HINT ( const char * ) ;
ADD_TYPE_HINT ( float ) ;
ADD_TYPE_HINT ( double ) ;
2024-04-22 11:24:40 +02:00
template < typename Suite >
struct quick_test_def ;
2024-06-05 19:53:41 +02:00
inline std : : ostream & operator < < ( std : : ostream & os , ReturnCode rc ) ;
struct ret_val_s {
const char * test_name = " " ;
ReturnCode val = ReturnCode : : FAILED ;
const char * msg = " " ;
2024-07-23 01:49:09 +02:00
const char * type_hint = nullptr ;
2024-06-05 19:53:41 +02:00
} ;
template < std : : size_t Nr >
struct ret_val {
const char * name ;
std : : array < ret_val_s , Nr > vals ;
[[nodiscard]] constexpr inline std : : size_t size ( ) const { return vals . size ( ) ; }
constexpr inline ret_val_s & operator [ ] ( std : : size_t i ) { return vals [ i ] ; }
constexpr inline const ret_val_s & operator [ ] ( std : : size_t i ) const { return vals [ i ] ; }
} ;
2024-04-22 11:24:40 +02:00
class test_definition {
public :
[[nodiscard]] virtual constexpr ret_val_s evaluate ( ) const = 0 ;
[[nodiscard]] virtual const char * name ( ) const = 0 ;
[[nodiscard]] virtual EvalFlag evalFlag ( ) const = 0 ;
[[nodiscard]] virtual const ret_val_s & c_res ( ) const = 0 ;
2024-04-08 14:50:48 +02:00
} ;
template < std : : invocable Func , typename . . . Args >
2024-04-22 11:24:40 +02:00
class test_definition_impl : public test_definition {
public :
using FuncType = Func ;
static constexpr std : : size_t ARG_SIZE = sizeof . . . ( Args ) ;
2024-04-08 14:50:48 +02:00
2024-04-22 11:24:40 +02:00
constexpr test_definition_impl ( const char * name , Func func , EvalFlag evalFlag , Args . . . args )
: _name ( name ) , _func ( func ) , _evalFlag ( evalFlag ) , _args ( std : : make_tuple ( std : : forward ( args ) . . . ) ) ,
_c_res ( _name , ReturnCode : : FAILED , " Could not be evaluated at compile time " ) {
if consteval {
if ( evalFlag = = EvalFlag : : RUNTIME_CONSTEVAL | | evalFlag = = EvalFlag : : CONSTEVAL ) {
_c_res = evaluate ( ) ;
}
}
}
2024-04-08 14:50:48 +02:00
2024-04-22 12:03:58 +02:00
[[nodiscard]] constexpr ret_val_s evaluate ( ) const override {
ret_val_s r = std : : apply ( _func , _args ) ;
r . test_name = _name ;
return r ;
}
2024-04-08 14:50:48 +02:00
2024-04-22 11:24:40 +02:00
[[nodiscard]] const char * name ( ) const override { return _name ; }
[[nodiscard]] EvalFlag evalFlag ( ) const override { return _evalFlag ; }
[[nodiscard]] const ret_val_s & c_res ( ) const override { return _c_res ; }
2024-04-08 14:50:48 +02:00
2024-04-22 11:24:40 +02:00
private :
const char * _name ;
EvalFlag _evalFlag ;
Func _func ;
std : : tuple < Args . . . > _args ;
ret_val_s _c_res ;
2024-04-08 14:50:48 +02:00
} ;
template < typename . . . TestDefs >
2024-04-22 11:24:40 +02:00
requires ( sizeof . . . ( TestDefs ) = = 0 | | ( std : : derived_from < TestDefs , test_definition > & & . . . ) )
class test_suite {
public :
static constexpr std : : size_t TEST_NR = sizeof . . . ( TestDefs ) ;
constexpr test_suite ( const char * name , std : : tuple < TestDefs . . . > tests ) : _name ( name ) , _tests ( tests ) { }
2024-04-08 14:50:48 +02:00
2024-04-22 11:24:40 +02:00
int run ( ) const {
auto test_arr = expand_test_tuple ( _tests , std : : make_index_sequence < TEST_NR > ( ) ) ;
2024-04-22 12:03:58 +02:00
int num_failed = 0 ;
2024-04-22 11:24:40 +02:00
2024-06-05 19:44:22 +02:00
std : : array < std : : array < ReturnCode , 2 > , TEST_NR > ret_vals = { { ReturnCode : : NOT_EVALUATED } } ;
2024-04-22 11:24:40 +02:00
for ( auto [ i , test_ref ] : std : : ranges : : views : : enumerate ( test_arr ) ) {
const auto & test = test_ref . get ( ) ;
2024-04-22 11:51:35 +02:00
std : : cout < < " Running Test: \" " < < test . name ( ) < < " \" "
" (Test " < < i + 1 < < " / " < < test_arr . size ( ) < < " ) \n " ;
2024-04-22 11:24:40 +02:00
if ( test . evalFlag ( ) = = EvalFlag : : RUNTIME | | test . evalFlag ( ) = = EvalFlag : : RUNTIME_CONSTEVAL ) {
ret_val_s ret ;
std : : string ret_exc_str ;
try {
ret = test . evaluate ( ) ;
} catch ( std : : exception & e ) {
2024-04-22 11:51:35 +02:00
ret_exc_str = std : : string ( " Test failed with Exception: \" " ) + e . what ( ) + " \" " ;
2024-04-22 11:24:40 +02:00
ret = ret_val_s ( test . name ( ) , ReturnCode : : FAILED , ret_exc_str . c_str ( ) ) ;
}
2024-06-05 19:44:22 +02:00
std : : cout < < " Result of Runtime Evaluation of Test \" " < < ret . test_name < < " \" : " < < ret . val ;
if ( ret . msg ! = nullptr ) {
std : : cout < < " \n \t " < < ret . msg ;
2024-04-22 12:03:58 +02:00
}
2024-07-23 01:49:09 +02:00
if ( ret . type_hint ! = nullptr ) {
std : : cout < < " \n \t " < < " with type ' " < < ret . type_hint < < " ' " ;
}
2024-06-05 19:44:22 +02:00
std : : cout < < std : : endl ;
ret_vals [ i ] [ 0 ] = ret . val ;
2024-04-22 11:24:40 +02:00
}
if ( test . evalFlag ( ) = = EvalFlag : : CONSTEVAL | | test . evalFlag ( ) = = EvalFlag : : RUNTIME_CONSTEVAL ) {
2024-06-05 19:44:22 +02:00
const ret_val_s & ret = test . c_res ( ) ;
std : : cout < < " Result of Consteval Evaluation of Test \" " < < ret . test_name < < " \" : " < < ret . val ;
if ( ret . msg ! = nullptr ) {
std : : cout < < " \n \t " < < ret . msg ;
2024-04-22 12:03:58 +02:00
}
2024-07-23 01:49:09 +02:00
if ( ret . type_hint ! = nullptr ) {
std : : cout < < " \n \t " < < " with type ' " < < ret . type_hint < < " ' " ;
}
2024-06-05 19:44:22 +02:00
std : : cout < < std : : endl ;
ret_vals [ i ] [ 1 ] = ret . val ;
2024-04-22 11:24:40 +02:00
}
2024-06-05 19:44:22 +02:00
std : : cout < < " -------------- \n " ;
}
auto ret_vals_j = ret_vals | std : : ranges : : views : : join ;
auto correct = std : : ranges : : count_if ( ret_vals , [ ] ( auto & & e ) {
return std : : ranges : : none_of ( e , [ ] ( auto & & c ) { return c = = ReturnCode : : FAILED ; } )
& & std : : ranges : : any_of ( e , [ ] ( auto & & c ) { return c = = ReturnCode : : PASSED ; } ) ;
} ) ;
auto failed = std : : ranges : : count_if ( ret_vals , [ ] ( auto & & e ) {
return std : : ranges : : any_of ( e , [ ] ( auto & & c ) { return c = = ReturnCode : : FAILED ; } ) ; } ) ;
auto full_skipped = std : : ranges : : count_if ( ret_vals , [ ] ( auto & & e ) {
return std : : ranges : : all_of ( e , [ ] ( auto & & c ) { return c = = ReturnCode : : SKIPPED | | c = = ReturnCode : : NOT_EVALUATED ; } ) ; } ) ;
auto part_skipped = std : : ranges : : count_if ( ret_vals , [ ] ( auto & & e ) {
return std : : ranges : : any_of ( e , [ ] ( auto & & c ) { return c = = ReturnCode : : SKIPPED ; } ) ; } ) ;
std : : size_t num_tests = ret_vals . size ( ) ;
std : : cout < < " Final Result: " < < " \n "
< < correct < < " / " < < num_tests < < " tests evaluated correctly " < < " \n "
< < failed < < " / " < < num_tests < < " tests failed " < < " \n "
< < full_skipped < < " / " < < num_tests < < " tests skipped " < < " \n "
< < part_skipped < < " / " < < num_tests < < " tests have been partially skipped " < < " \n " ;
return - failed ;
2024-04-22 11:24:40 +02:00
}
private :
const char * _name ;
std : : tuple < TestDefs . . . > _tests ;
template < std : : size_t . . . Is >
static constexpr auto expand_test_tuple ( const auto & tests , std : : index_sequence < Is . . . > ) {
return std : : array {
std : : reference_wrapper (
dynamic_cast < const test_definition & > ( std : : get < Is > ( tests ) )
) . . . } ;
}
friend class quick_test_def < test_suite < TestDefs . . . > > ;
2024-04-08 14:50:48 +02:00
} ;
template < typename Suite >
struct quick_test_def {
Suite current ;
2024-04-22 11:24:40 +02:00
2024-04-08 14:50:48 +02:00
template < std : : invocable Func , typename . . . Args >
constexpr auto operator ( ) ( const char * name , Func func , Args . . . args ) {
2024-04-22 11:24:40 +02:00
return operator ( ) ( name , func , EvalFlag : : RUNTIME , std : : forward ( args ) . . . ) ;
}
template < std : : invocable Func , typename . . . Args >
constexpr auto operator ( ) ( const char * name , Func func , EvalFlag evalFlag , Args . . . args ) {
auto test = test_definition_impl ( name , func , evalFlag , args . . . ) ;
auto new_suite = test_suite ( current . _name , std : : tuple_cat ( current . _tests , std : : make_tuple ( test ) ) ) ;
2024-04-08 14:50:48 +02:00
return quick_test_def < decltype ( new_suite ) > { new_suite } ;
}
2024-04-22 11:24:40 +02:00
2024-04-08 14:50:48 +02:00
constexpr operator Suite ( ) {
return current ;
}
} ;
template < typename . . . TestDefs >
test_suite ( quick_test_def < test_suite < TestDefs . . . > > ) - > test_suite < TestDefs . . . > ;
constexpr auto define_tests ( const char * name ) {
return quick_test_def { test_suite < > { name , std : : make_tuple ( ) } } ;
}
2024-06-05 19:53:41 +02:00
inline std : : ostream & operator < < ( std : : ostream & os , ReturnCode rc ) {
switch ( rc ) {
case ReturnCode : : FAILED :
return os < < " FAILED " ;
case ReturnCode : : PASSED :
return os < < " PASSED " ;
case ReturnCode : : SKIPPED :
return os < < " SKIPPED " ;
case ReturnCode : : NOT_EVALUATED :
return os < < " NOT EVALUATED " ;
default :
return os ;
}
}
2024-07-23 00:39:28 +02:00
template < typename . . . Ts >
constexpr auto _repeat_for_types ( auto f ) {
std : : array < ret_val_s , sizeof . . . ( Ts ) > rets { ( f . template operator ( ) < Ts > ( ) ) . . . } ;
return rets ;
}
# define REPEAT_FOR_TYPES(func, ...) { \
auto r = _repeat_for_types < __VA_ARGS__ > ( func ) ; \
auto it = std : : ranges : : find_if ( r , [ ] ( auto & & e ) { return e . val = = ReturnCode : : FAILED ; } ) ; \
if ( it ! = std : : ranges : : end ( r ) ) { \
return * it ; \
} \
}
2024-07-22 20:36:19 +02:00
# define CREATE_FROM_IL(type, il, len) \
( [ ] < std : : size_t N , typename ArgType > ( std : : initializer_list < ArgType > args ) { \
auto creator = [ & args ] < std : : size_t . . . _idx > ( std : : index_sequence < _idx . . . > ) { \
return type { ( * std : : next ( std : : begin ( args ) , _idx ) ) . . . } ; \
} ; \
return creator ( std : : make_index_sequence < N > ( ) ) ; \
} ) . operator ( ) < len > ( il )
2024-07-21 22:28:35 +02:00
2024-07-22 23:32:52 +02:00
# define OPERATOR_EQ_IL(obj, il, len) \
( [ & ] < std : : size_t N , typename ArgType > ( std : : initializer_list < ArgType > args ) { \
auto creator = [ & ] < std : : size_t . . . _idx > ( std : : index_sequence < _idx . . . > ) { \
return obj = { ( * std : : next ( std : : begin ( args ) , _idx ) ) . . . } ; \
} ; \
return creator ( std : : make_index_sequence < N > ( ) ) ; \
} ) . operator ( ) < len > ( il )
template < typename T >
constexpr typename std : : remove_cvref_t < T > & & force_move ( T & & obj ) {
return static_cast < std : : remove_cvref_t < T > & & > ( obj ) ;
}
2024-06-05 19:53:41 +02:00
# endif //CONST_CONTAINER_TEST_HPP_