diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 48cae26..2ac4b21 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,4 +6,15 @@ include(${ROTGEN_SOURCE_DIR}/cmake/unit.cmake) rotgen_setup_test_targets() -rotgen_glob_unit(PATTERN "basic/*.cpp" INTERFACE rotgen) \ No newline at end of file +##====================================================================================================================== +## Compiler options for Unit Tests +##====================================================================================================================== +add_library(rotgen_test INTERFACE) +target_compile_features(rotgen_test INTERFACE cxx_std_20) +target_include_directories( rotgen_test INTERFACE ${PROJECT_SOURCE_DIR}/test) +target_link_libraries(rotgen_test INTERFACE rotgen) + +##====================================================================================================================== +## Unit Tests registration +##====================================================================================================================== +rotgen_glob_unit(PATTERN "basic/*.cpp" INTERFACE rotgen_test) \ No newline at end of file diff --git a/test/basic/io.cpp b/test/basic/io.cpp index 67014b8..3a4e5ee 100644 --- a/test/basic/io.cpp +++ b/test/basic/io.cpp @@ -1,8 +1,26 @@ -#include +//================================================================================================== +/* + ROTGEN - Runtime Overlay for Eigen + Copyright : CODE RECKONS + SPDX-License-Identifier: BSL-1.0 +*/ +//================================================================================================== +#define TTS_MAIN #include +#include +#include "tts.hpp" -int main() +TTS_CASE("Sample test") { rotgen::matrix x; - std::cout << x << "\n"; -} \ No newline at end of file + std::ostringstream os; + os << x; + + std::string ref = "0 0 0 0 0\n" + "0 0 0 0 0\n" + "0 0 0 0 0\n" + "0 0 0 0 0\n" + "0 0 0 0 0"; + + TTS_EQUAL(os.str(), ref); +}; \ No newline at end of file diff --git a/test/tts.hpp b/test/tts.hpp new file mode 100644 index 0000000..6264054 --- /dev/null +++ b/test/tts.hpp @@ -0,0 +1,1910 @@ +//================================================================================================== +/** + TTS - Tiny Test System + Copyright : TTS Contributors & Maintainers + SPDX-License-Identifier: BSL-1.0 +**/ +//================================================================================================== +#pragma once +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" +#endif +/// Main TTS namespace +namespace tts {} +#if defined( __ANDROID__ ) +#include +namespace tts +{ + template + concept integral = std::is_integral_v; + template + concept floating_point = std::is_floating_point_v; + template + concept same_as_impl = std::is_same_v; + template + concept same_as = same_as_impl && same_as_impl; +} +#else +#include +namespace tts +{ + using std::integral; + using std::floating_point; + using std::same_as; +} +#endif +#include +namespace tts::detail +{ + inline int usage(const char* name) + { + std::cout << "TTS Unit Tests Driver\n"; + std::cout << "Usage: " << name << " [OPTION...]\n"; + std::cout << "\nFlags:\n"; + std::cout << " -h, --help Display this help message\n"; + std::cout << " -x, --hex Print the floating results in hexfloat mode\n"; + std::cout << " -s, --scientific Print the floating results in scientific mode\n"; + std::cout << "\nParameters:\n"; + std::cout << " --precision=arg Set the precision for displaying floating pint values\n"; + std::cout << " --seed=arg Set the PRNG seeds (default is time-based)\n"; + std::cout << "\nRange specifics Parameters:\n"; + std::cout << " --block=arg Set size of range checks samples (min. 32)\n"; + std::cout << " --loop=arg Repeat each range checks arg times\n"; + std::cout << " --ulpmax=arg Set global failure ulp threshold for range tests (default is 2.0)\n"; + std::cout << " --valmax=arg Set maximal value for range tests (default is code)\n"; + std::cout << " --valmin=arg Set minimal value for range tests (default is code)\n"; + std::cout << std::endl; + return 0; + } +} +#include +#include +namespace tts::detail +{ + struct env + { + void pass() { test_count++; success_count++; } + void fail() { test_count++; failure_count++; } + void fatal() { test_count++; failure_count++; fatal_count++; } + void invalid() { test_count++; invalid_count++; } + int report(std::ptrdiff_t fails, std::ptrdiff_t invalids) const + { + auto test_txt = test_count > 1 ? "tests" : "test"; + auto pass_txt = success_count > 1 ? "successes" : "success"; + auto fail_txt = failure_count > 1 ? "failures" : "failure"; + auto inv_txt = invalid_count > 1 ? "invalids" : "invalid"; + auto passes = (fails || invalids) ? 0 : test_count; + std::cout << "----------------------------------------------------------------\n"; + std::cout << "Results: " << test_count << " " << test_txt << " - " + << success_count << "/" << passes << " " << pass_txt << " - " + << failure_count << "/" << fails << " " << fail_txt << " - " + << invalid_count << "/" << invalids << " " << inv_txt << "\n"; + if(!fails && !invalids) return test_count == success_count ? 0 : 1; + else return (failure_count == fails && invalid_count == invalids) ? 0 : 1; + } + int test_count = 0, + success_count = 0, + failure_count = 0, + fatal_count = 0, + invalid_count = 0; + bool fail_status = false; + }; +} +namespace tts +{ + inline ::tts::detail::env global_runtime; + inline bool global_logger_status = false; + inline bool fatal_error_status = false; + inline int report(std::ptrdiff_t fails, std::ptrdiff_t invalids) + { + return global_runtime.report(fails,invalids); + } +} +#include +namespace tts::detail +{ + struct fatal_signal {}; + struct logger + { + logger(bool status = true) : display(status), done(false) {} + template logger& operator<<(Data const& d) + { + if(display) + { + if(!done) + { + std::cout << ">> Additional information: \n"; + done = true; + } + std::cout << d; + } + return *this; + } + ~logger() noexcept(false) + { + if(display && done) std::cout << "\n"; + if(::tts::fatal_error_status) throw ::tts::detail::fatal_signal(); + } + bool display, done; + }; +} +#include +namespace tts::detail +{ + struct callable + { + public: + using signature_t = void(*)(void*); + signature_t invoker = {}; + signature_t cleanup = {}; + void* payload = {}; + constexpr callable() = default; + template + constexpr callable(Function f) + : invoker{invoke}, cleanup{destroy} + , payload{new Function{std::move(f)}} + {} + constexpr callable(callable&& other) noexcept + : invoker{std::move(other.invoker)}, cleanup{std::move(other.cleanup)} + , payload{std::move(other.payload)} + { + other.payload = {}; + } + ~callable() { cleanup(payload); } + constexpr callable(const callable&) = delete; + constexpr callable& operator=(const callable&) = delete; + constexpr callable& operator=(callable&&) = delete; + constexpr void operator()() { invoker(payload); } + constexpr void operator()() const { invoker(payload); } + explicit constexpr operator bool() const { return payload != nullptr; } + private: + template + static void invoke(void* data) { (*static_cast(data))(); } + template + static void destroy(void* data) { delete static_cast(data); } + }; +} +#include +#include +namespace tts::detail +{ + inline std::string current_test = ""; + struct test + { + void operator()() + { + current_test = name; + behaviour(); + } + static inline bool acknowledge(test&& f); + std::string name; + tts::detail::callable behaviour; + }; + inline std::vector& suite() + { + static std::vector that = {}; + return that; + } + bool inline test::acknowledge(test&& f) + { + suite().emplace_back( std::forward(f)); + return true; + } +} +#include +#include +#include +#include +#include +#include +namespace tts +{ + namespace detail + { + struct option + { + option() = default; + option( std::string arg ) : token(std::move(arg)), position(token.rfind( '=' )) {} + auto flag() const { return token.substr(0, position); } + bool is_valid() const { return !flag().empty(); } + template T get(T const& def = T{}) const + { + T that; + if(is_valid()) + { + std::istringstream os(token.substr(position+1)); + if(os >> that) return that; + else return def; + } + else + { + return def; + } + } + std::string token = ""; + size_t position = std::string::npos; + }; + } + struct options + { + using params_t = std::initializer_list; + bool operator[](params_t fs) const { return find(fs).is_valid(); } + bool operator[](const char* f ) const { return operator[]({f}); } + template T value(params_t fs, T that = {}) const + { + if( auto o = find(fs); o.is_valid()) that = o.template get(that); + return that; + } + template T value(const char* f, T that = {}) const + { + return value({f},that); + } + bool is_valid() { return argc && argv != nullptr; } + int argc; + char const** argv; + private: + detail::option find(const char* f ) const { return find({f}); } + detail::option find(params_t fs) const + { + for(int i=1;i(now.time_since_epoch().count()); + } + detail::current_seed = s; + } + return detail::current_seed; + } +} +#if defined(TTS_DOXYGEN_INVOKED) +#define TTS_CUSTOM_DRIVER_FUNCTION +#define TTS_MAIN +#endif +#if !defined(TTS_CUSTOM_DRIVER_FUNCTION) +# define TTS_CUSTOM_DRIVER_FUNCTION main +namespace tts::detail { constexpr bool use_main = true; } +#else +namespace tts::detail { constexpr bool use_main = false; } +#endif +#if defined(TTS_MAIN) +int TTS_CUSTOM_DRIVER_FUNCTION([[maybe_unused]] int argc,[[maybe_unused]] char const** argv) +{ + ::tts::initialize(argc,argv); + if( ::tts::arguments()[{"-h","--help"}] ) + return ::tts::detail::usage(argv[0]); + auto nb_tests = ::tts::detail::suite().size(); + std::size_t done_tests = 0; + try + { + for(auto &t: ::tts::detail::suite()) + { + auto test_count = ::tts::global_runtime.test_count; + auto failure_count = ::tts::global_runtime.failure_count; + ::tts::global_runtime.fail_status = false; + t(); + done_tests++; + if(test_count == ::tts::global_runtime.test_count) + { + ::tts::global_runtime.invalid(); + std::cout << "[!] - " << ::tts::detail::current_test << " : EMPTY TEST CASE\n"; + } + else if(failure_count == ::tts::global_runtime.failure_count) + { + std::cout << "[V] - " << ::tts::detail::current_test << "\n"; + } + } + } + catch( ::tts::detail::fatal_signal& ) + { + std::cout << "@@ ABORTING DUE TO EARLY FAILURE @@ - " + << (nb_tests - done_tests - 1) << " Tests not run\n"; + } + if constexpr( ::tts::detail::use_main ) return ::tts::report(0,0); + else return 0; +} +#endif +#include +#include +#include +namespace tts::detail +{ + template + concept support_std_to_string = requires(T e) { std::to_string(e); }; + template + concept support_to_string = requires(T e) { to_string(e); }; + template + concept sequence = requires(T e) {std::begin(e); std::end(e); }; + template + concept streamable = requires(T e, std::ostream& o) { o << e; }; +} +#if defined(_MSC_VER) + #define TTS_DISABLE_WARNING_PUSH __pragma(warning( push )) + #define TTS_DISABLE_WARNING_POP __pragma(warning( pop )) + #define TTS_DISABLE_WARNING(warningNumber) __pragma(warning( disable : warningNumber )) + #define TTS_DISABLE_WARNING_SHADOW +#elif defined(__GNUC__) || defined(__clang__) + #define TTS_DO_PRAGMA(X) _Pragma(#X) + #define TTS_DISABLE_WARNING_PUSH TTS_DO_PRAGMA(GCC diagnostic push) + #define TTS_DISABLE_WARNING_POP TTS_DO_PRAGMA(GCC diagnostic pop) + #define TTS_DISABLE_WARNING(warningName) TTS_DO_PRAGMA(GCC diagnostic ignored #warningName) + #define TTS_DISABLE_WARNING_SHADOW TTS_DISABLE_WARNING(-Wshadow) +#else + #define TTS_DISABLE_WARNING_PUSH + #define TTS_DISABLE_WARNING_POP + #define TTS_DISABLE_WARNING_SHADOW +#endif +#ifndef TTS_FUNCTION +#define TTS_FUNCTION TTS_UNIQUE(tts_function) +#endif +#ifndef TTS_REGISTRATION +#define TTS_REGISTRATION TTS_UNIQUE(tts_registration) +#endif +#define TTS_UNIQUE3(ID, LINE) ID##LINE +#define TTS_UNIQUE2(ID, LINE) TTS_UNIQUE3(ID, LINE) +#define TTS_UNIQUE(ID) TTS_UNIQUE2(ID, __COUNTER__) +#define TTS_CAT(x, y) TTS_CAT_I(x, y) +#define TTS_CAT_I(x, y) x##y +#define TTS_STRING(...) TTS_STRING_((__VA_ARGS__)) +#define TTS_STRING__(...) #__VA_ARGS__ +#define TTS_STRING_(TXT) TTS_STRING__ TXT +#define TTS_COUNT(...) TTS_COUNT_(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0) +#define TTS_COUNT_(A0, A1, A2, A3, A4, A5, A6, A7, ...) A7 +#define TTS_ARG0() +#define TTS_ARG1(A0) auto&& A0 +#define TTS_ARG2(A0, A1) auto&& A0, auto&& A1 +#define TTS_ARG3(A0, A1, A2) TTS_ARG2(A0, A1) , auto&& A2 +#define TTS_ARG4(A0, A1, A2, A3) TTS_ARG3(A0, A1, A2) , auto&& A3 +#define TTS_ARG5(A0, A1, A2, A3, A4) TTS_ARG4(A0, A1, A2, A3) , auto&& A4 +#define TTS_ARG6(A0, A1, A2, A3, A4, A5) TTS_ARG5(A0, A1, A2, A3, A4) , auto&& A5 +#define TTS_ARG7(A0, A1, A2, A3, A4, A5, A6) TTS_ARG6(A0, A1, A2, A3, A4, A5), auto&& A6 +#define TTS_ARG(...) TTS_CAT(TTS_ARG, TTS_COUNT(__VA_ARGS__))(__VA_ARGS__) +#define TTS_VAL(x) x +#define TTS_REVERSE_1(a) (a) +#define TTS_REVERSE_2(a,b) (b, a) +#define TTS_REVERSE_3(a,b,c) (c, b, a) +#define TTS_REVERSE_4(a,b,c,d) (d, c, b, a) +#define TTS_REVERSE_5(a,b,c,d,e) (e, d, c, b, a) +#define TTS_REVERSE_6(a,b,c,d,e,f) (f, e, d, c, b, a) +#define TTS_REVERSE_7(a,b,c,d,e,f,g) (g, f, e, d, c, b, a) +#define TTS_REVERSE_IMPL(N,...) TTS_VAL(TTS_REVERSE_ ## N(__VA_ARGS__)) +#define TTS_REVERSE_(N,...) TTS_REVERSE_IMPL( N, __VA_ARGS__) +#define TTS_REVERSE(...) TTS_REVERSE_( TTS_COUNT(__VA_ARGS__), __VA_ARGS__) +#define TTS_REMOVE_PARENS(x) TTS_EVAL((TTS_REMOVE_PARENS_I x), x) +#define TTS_REMOVE_PARENS_I(...) 1, 1 +#define TTS_APPLY(macro, args) TTS_APPLY_I(macro, args) +#define TTS_APPLY_I(macro, args) macro args +#define TTS_EVAL_I(test, x) TTS_MAYBE_STRIP_PARENS(TTS_TEST_ARITY test, x) +#define TTS_EVAL(test, x) TTS_EVAL_I(test, x) +#define TTS_TEST_ARITY(...) TTS_APPLY(TTS_TEST_ARITY_I, (__VA_ARGS__, 2, 1)) +#define TTS_TEST_ARITY_I(a, b, c, ...) c +#define TTS_MAYBE_STRIP_PARENS(cond, x) TTS_MAYBE_STRIP_PARENS_I(cond, x) +#define TTS_MAYBE_STRIP_PARENS_I(cond, x) TTS_CAT(TTS_MAYBE_STRIP_PARENS_, cond)(x) +#define TTS_MAYBE_STRIP_PARENS_1(x) x +#define TTS_MAYBE_STRIP_PARENS_2(x) TTS_APPLY(TTS_MAYBE_STRIP_PARENS_2_I, x) +#define TTS_MAYBE_STRIP_PARENS_2_I(...) __VA_ARGS__ +#include +#include +namespace tts::detail +{ + template struct typename_impl + { + static auto value() noexcept + { + #if defined(_MSC_VER ) + std::string_view data(__FUNCSIG__); + auto i = data.find('<') + 1, + j = data.find(">::value"); + auto name = data.substr(i, j - i); + #else + std::string_view data(__PRETTY_FUNCTION__); + auto i = data.find('=') + 2, + j = data.find_last_of(']'); + auto name = data.substr(i, j - i); + #endif + return std::string(name.data(), name.size()); + } + }; +} +namespace tts +{ + template inline auto const typename_ = detail::typename_impl::value(); + template constexpr auto name(T const&){ return typename_; } +} +#include +namespace tts +{ + template + struct types + { + template constexpr types operator+( types const&) const; + }; + template struct concatenate { using type = decltype( (Ls{} + ...) ); }; + template using concatenate_t = typename concatenate::type; + template struct type {}; + using real_types = types < double,float>; + using int_types = types < std::int64_t , std::int32_t , std::int16_t , std::int8_t>; + using signed_types = concatenate_t; + using uint_types = types < std::uint64_t , std::uint32_t , std::uint16_t , std::uint8_t>; + using integral_types = concatenate_t; + using arithmetic_types = concatenate_t; +} +#include +namespace tts::detail +{ + struct test_capture + { + test_capture(const char* id) : name(id) {} + auto operator+(auto body) const { return test::acknowledge( {name, body} ); } + const char* name; + }; + inline std::string current_type = {}; + template struct test_captures + { + test_captures(const char* id) : name(id) {} + auto operator+(auto body) const + { + return test::acknowledge( { name + , [=]() + { + ( ( (current_type = " with [T = " + typename_ + "]") + , body(type()) + ) + , ... + ); + current_type.clear(); + } + } + ); + } + std::string name; + }; + template + struct test_captures> : test_captures + {}; + template + requires requires(Generator g) { typename Generator::types_list; } + struct test_captures : test_captures + {}; +} +namespace tts::detail +{ + template struct test_generators + { + test_generators(const char* id, Generator g, Types...) : name(id), generator(g) {} + friend auto operator<<(test_generators tg, auto body) + { + return test::acknowledge( { tg.name + , [tg,body]() mutable + { + using t_t = std::mt19937::result_type; + std::mt19937 gen(static_cast(::tts::random_seed())); + ( ( (current_type = " with [T = " + typename_ + "]") + , std::apply(body, tg.generator(type{}, gen)) + ), ... + ); + current_type.clear(); + } + } + ); + } + std::string name; + Generator generator; + }; + template + test_generators(const char*,Generator,Types...) -> test_generators; + template + struct test_generators> + : test_generators + { + using parent = test_generators; + test_generators(const char* id, Generator g, types) : parent(id,g,Types{}...) {} + }; + template + requires requires(TypeGenerator g) { typename TypeGenerator::types_list; } + struct test_generators + : test_generators + { + using parent = test_generators; + test_generators ( const char* id, Generator g, TypeGenerator ) + : parent(id,g,typename TypeGenerator::types_list{}) {} + }; +} +#define TTS_PROTOTYPE(...) [] __VA_ARGS__ +#define TTS_CASE(ID) \ +[[maybe_unused]] static bool const TTS_CAT(case_,TTS_FUNCTION) = ::tts::detail::test_capture{ID} + TTS_PROTOTYPE(()) \ + +#define TTS_CASE_TPL(ID,...) \ +[[maybe_unused]] static bool const TTS_CAT(case_,TTS_FUNCTION) = ::tts::detail::test_captures<__VA_ARGS__>{ID} \ + + TTS_PROTOTYPE() \ + +#define TTS_CASE_WITH(ID, TYPES, GENERATOR) \ +[[maybe_unused]] static bool const TTS_CAT(case_,TTS_FUNCTION) \ + = ::tts::detail::test_generators{ID,GENERATOR,TYPES{}} << TTS_PROTOTYPE() \ + +#include +#include +namespace tts +{ + class source_location + { + public: + [[nodiscard]] static constexpr auto current ( const char* file = __builtin_FILE() + , int line = __builtin_LINE() + ) noexcept + { + source_location sl{}; + sl.file_ = file; + sl.line_ = line; + return sl; + } + [[nodiscard]] constexpr auto filename() const noexcept + { + std::string_view f(file_); + return f.substr(f.find_last_of('/')+1); + } + [[nodiscard]] constexpr auto line() const noexcept { return line_; } + friend std::ostream& operator<<(std::ostream& os, source_location const& s) + { + return os << "[" << s.filename() << ":" << s.line() << "]"; + } + private: + const char* file_{"unknown"}; + int line_{}; + }; +} +#define TTS_PASS(Message) \ + do \ + { \ + ::tts::global_runtime.pass(); \ + } while(0) +#define TTS_FAIL(Message) \ + do \ + { \ + ::tts::global_runtime.fail(); \ + if(!::tts::global_runtime.fail_status) \ + { \ + ::tts::global_runtime.fail_status = true; \ + std::cout << "[X] - " << ::tts::detail::current_test << "\n"; \ + } \ + if( !::tts::detail::current_type.empty()) \ + { \ + std::cout << " > " << ::tts::detail::current_type << "\n"; \ + } \ + std::cout << " " << ::tts::source_location::current() << " - ** FAILURE **" \ + << " : " << Message << std::endl; \ + } while(0) +#define TTS_FATAL(Message) \ + do \ + { \ + ::tts::global_runtime.fatal(); \ + if(!::tts::global_runtime.fail_status) \ + { \ + ::tts::global_runtime.fail_status = true; \ + std::cout << "[@] - " << ::tts::detail::current_test<< "\n"; \ + } \ + if( !::tts::detail::current_type.empty()) \ + { \ + std::cout << " > " << ::tts::detail::current_type << "\n"; \ + } \ + std::cout << " " << ::tts::source_location::current() << " - @@ FATAL @@" \ + << " : " << Message << std::endl; \ + ::tts::fatal_error_status = true; \ + } while(0) +#define TTS_EXPECT(EXPR, ...) TTS_EXPECT_ ## __VA_ARGS__ ( EXPR ) +#define TTS_EXPECT_(EXPR) TTS_EXPECT_IMPL((EXPR),TTS_FAIL) +#define TTS_EXPECT_REQUIRED(EXPR) TTS_EXPECT_IMPL((EXPR),TTS_FATAL) +#define TTS_EXPECT_IMPL(EXPR,FAILURE) \ +[&](auto&& local_tts_expr) \ +{ \ + if( local_tts_expr ) \ + { \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ + } \ + else \ + { \ + FAILURE ( "Expression: " << TTS_STRING(TTS_REMOVE_PARENS(EXPR)) << " evaluates to false." ); \ + return ::tts::detail::logger{}; \ + } \ +}(EXPR) \ + +#define TTS_EXPECT_NOT(EXPR, ...) TTS_EXPECT_NOT_ ## __VA_ARGS__ ( EXPR ) +#define TTS_EXPECT_NOT_(EXPR) TTS_EXPECT_NOT_IMPL(EXPR,TTS_FAIL) +#define TTS_EXPECT_NOT_REQUIRED(EXPR) TTS_EXPECT_NOT_IMPL(EXPR,TTS_FATAL) +#define TTS_EXPECT_NOT_IMPL(EXPR,FAILURE) \ +[&](auto&& local_tts_expr) \ +{ \ + if( !local_tts_expr ) \ + { \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ + } \ + else \ + { \ + FAILURE ( "Expression: " << TTS_STRING(EXPR) << " evaluates to true." ); \ + return ::tts::detail::logger{}; \ + } \ +}(EXPR) \ + +#define TTS_CONSTEXPR_EXPECT(EXPR, ...) TTS_CEXPR_EXPECT_ ## __VA_ARGS__ ( EXPR ) +#define TTS_CEXPR_EXPECT_(EXPR) TTS_CEXPR_EXPECT_IMPL(EXPR,TTS_FAIL) +#define TTS_CEXPR_EXPECT_REQUIRED(EXPR) TTS_CEXPR_EXPECT_IMPL(EXPR,TTS_FATAL) +#define TTS_CEXPR_EXPECT_IMPL(EXPR,FAILURE) \ +::tts::global_logger_status = false; \ +do \ +{ \ + constexpr auto result_tts = EXPR; \ + if( result_tts ) \ + { \ + ::tts::global_runtime.pass(); \ + ::tts::global_logger_status = false; \ + } \ + else \ + { \ + FAILURE ( "Expression: " << TTS_STRING(EXPR) << " evaluates to false." ); \ + ::tts::global_logger_status = true; \ + } \ +}while(0); \ +::tts::detail::logger{::tts::global_logger_status} \ + +#define TTS_CONSTEXPR_EXPECT_NOT(EXPR, ...) TTS_CEXPR_EXPECT_NOT_ ## __VA_ARGS__ ( EXPR ) +#define TTS_CEXPR_EXPECT_NOT_(EXPR) TTS_CEXPR_EXPECT_NOT_IMPL(EXPR,TTS_FAIL) +#define TTS_CEXPR_EXPECT_NOT_REQUIRED(EXPR) TTS_CEXPR_EXPECT_NOT_IMPL(EXPR,TTS_FATAL) +#define TTS_CEXPR_EXPECT_NOT_IMPL(EXPR,FAILURE) \ +::tts::global_logger_status = false; \ +do \ +{ \ + constexpr auto result_tts = EXPR; \ + if( !result_tts ) \ + { \ + ::tts::global_runtime.pass(); \ + ::tts::global_logger_status = false; \ + } \ + else \ + { \ + FAILURE ( "Expression: " << TTS_STRING(EXPR) << " evaluates to true." ); \ + ::tts::global_logger_status = true; \ + } \ +}while(0); \ +::tts::detail::logger{::tts::global_logger_status} \ + +#include +#include +namespace tts::detail +{ + template + struct block + { + block() : nbelems{0} {} + block(std::size_t sz) : storage{} ,nbelems{sz} {} + std::size_t size() const { return nbelems; } + std::size_t capacity() const { return N; } + std::size_t empty() const { return nbelems == 0; } + void resize(std::size_t n) { nbelems = n; } + void push_back(T v) { storage[nbelems] = v; nbelems++; } + void push_front(T v) { insert(begin(),v); } + void insert(auto it, T v) + { + std::memmove(it+1, it, size()*sizeof(T)); + *it = v; + nbelems++; + } + void clear() { nbelems = 0; } + T& back() { return storage[size()-1]; } + T back() const { return storage[size()-1]; } + T& front() { return storage[0]; } + T front() const { return storage[0]; } + T& operator[](std::size_t i) { return storage[i]; } + T operator[](std::size_t i) const { return storage[i]; } + auto begin() { return &storage[0]; } + auto end() { return begin() + nbelems; } + auto begin() const { return &storage[0]; } + auto end() const { return begin() + nbelems; } + private: + std::array storage; + std::size_t nbelems; + }; +} +#include +#include +#include +#include +#include +namespace tts::detail +{ + template + struct fp_dist + { + using result_type = T; + struct param_type + { + param_type( T pa = 0, T pb = 1 + , std::size_t sz = 65536 + , T mnp = std::numeric_limits::epsilon() + , T mxp = 1./std::numeric_limits::epsilon() + ) + : a(pa), b(pb), n(sz), minpos(mnp), maxpos(mxp) + { + if(b0 && a < minpos) a = minpos; + if(b > maxpos) b = maxpos; + else if(b<0 && b < -minpos) b = -minpos; + } + T a, b; + std::size_t n; + T minpos, maxpos; + }; + fp_dist() noexcept : fp_dist(param_type{}) {} + fp_dist(T a, T b) noexcept : fp_dist(param_type{a,b}) {} + fp_dist(T a, T b, std::size_t n) noexcept : fp_dist(param_type{a,b,n}) {} + fp_dist(T a, T b, std::size_t n, T mn ) noexcept : fp_dist(param_type{a,b,n,mn}) {} + fp_dist(T a, T b, std::size_t n, T mn, T mx) noexcept : fp_dist(param_type{a,b,n,mn,mx}) {} + fp_dist(param_type const& pr) noexcept { param(pr); } + param_type const& param() const { return params; } + void param(param_type const& p) + { + params = p; + find_limits(); + find_indexes(static_cast(1+params.minpos/2)); + selector.param( std::uniform_int_distribution::param_type(0, size()-1)); + } + auto size() const noexcept { return sizes.back(); } + auto min() const noexcept { return params.a; } + auto max() const noexcept { return params.b; } + template< class Generator > result_type operator()( Generator& gen ) + { + auto p = selector(gen); + auto it = std::upper_bound(sizes.begin(), sizes.end(), p) - 1; + auto i = std::distance(sizes.begin(),it); + return generate(limits[i],limits[i+1],p - *it,params.n); + } + tts::detail::block limits; + tts::detail::block sizes; + std::uniform_int_distribution selector; + param_type params; + private: + void find_limits() noexcept + { + limits.clear(); + std::array zs{-params.maxpos,-1,-params.minpos,0,params.minpos,1,params.maxpos}; + for(auto z : zs) + { + if(z>=params.a && z<=params.b) limits.push_back(z); + } + if(limits.empty()) { limits.push_back(params.a); limits.push_back(params.b);} + else + { + if(limits.front() > params.a) { limits.push_front(params.a); } + if(limits.back() < params.b) { limits.push_back(params.b); } + } + } + void find_indexes(std::size_t nbzero) noexcept + { + sizes.resize(limits.size()); + std::size_t t = 0; + sizes[0] = 0; + for(std::size_t i=1;i double + { + if(x<1) return 1./std::exp2(std::lerp(std::log2(1./y), std::log2(1./x), i/sz)); + else return std::exp2(std::lerp(std::log2(x) , std::log2(y) , i/sz)); + }; + if(va==0 || vb==0) return 0.; + auto f = va<0 ? -1 : 1; + return static_cast(f * eval(f * va, f * vb,p,n-1)); + } + }; + template + struct char_dist + : std::uniform_int_distribution < std::conditional_t< std::is_signed_v + , short + , unsigned short + > > + { + using parent = std::uniform_int_distribution< std::conditional_t + , short + , unsigned short + > + >; + using result_type = T; + using parent::parent; + template< class Generator > result_type operator()( Generator& gen ) + { + return static_cast(parent::operator()(gen)); + } + }; + template + struct choose_distribution; + template + requires(sizeof(T) > 1) + struct choose_distribution + { + using type = std::uniform_int_distribution; + }; + template + requires(sizeof(T) == 1) + struct choose_distribution + { + using type = char_dist; + }; + template + struct choose_distribution + { + using type = fp_dist; + }; +} +namespace tts +{ + template + using realistic_distribution = typename detail::choose_distribution::type; +} +#include +namespace tts +{ + template auto as_value(V const& v) { return static_cast(v); } + template struct rebuild; + template class Seq, typename T, typename... S, typename U> + struct rebuild,U> { using type = Seq; }; + template class Seq, typename T, std::size_t N, typename U> + struct rebuild,U> { using type = Seq; }; + template auto produce(type const& t, auto g, auto& rng, auto... others) + { + return g(t,rng, others...); + } + template + auto produce(type const&, auto g, auto& rng, auto... args) + { + using elmt_type = std::remove_cvref_t()))>; + using value_type = decltype(g(tts::type{},rng,0,0ULL,args...)); + typename rebuild::type that; + auto b = std::begin(that); + auto e = std::end(that); + auto sz = e - b; + for(std::ptrdiff_t i=0;i(g(tts::type{},rng,i,sz,args...)); + } + return that; + } + template inline auto generate(G... g) + { + return [=](auto const& t, auto& rng, auto... others) + { + return std::make_tuple(produce(t,g,rng,others...)...); + }; + } + template struct value + { + value(T v) : seed(v) {} + template + auto operator()(tts::type, auto&, auto...) const { return as_value(seed); } + T seed; + }; + template struct ramp + { + ramp(T s) : start(s), step(1) {} + ramp(T s, U st) : start(s), step(st) {} + template + auto operator()(tts::type, auto&) const { return as_value(start); } + template + auto operator()(tts::type, auto&, auto idx, auto...) const { return as_value(start+idx*step); } + T start; + U step; + }; + template struct reverse_ramp + { + reverse_ramp(T s) : start(s), step(1) {} + reverse_ramp(T s, U st) : start(s), step(st) {} + template + auto operator()(tts::type, auto&) const { return as_value(start); } + template + auto operator()(tts::type, auto&, auto idx, auto sz, auto...) const { return as_value(start+(sz-1-idx)*step); } + T start; + U step; + }; + template struct between + { + between(T s, U st) : first(s), last(st) {} + template + auto operator()(tts::type, auto&) const { return as_value(first); } + template + auto operator()(tts::type, auto&, auto idx, auto sz, auto...) const + { + auto w1 = as_value(first); + auto w2 = as_value(last); + auto step = (sz-1) ? (w2-w1)/(sz-1) : 0; + return std::min( as_value(w1 + idx*step), w2); + } + T first; + U last; + }; + template struct sample + { + sample(Distribution d) : dist(std::move(d)) {} + template auto operator()(tts::type, auto& rng, auto...) { return dist(rng); } + Distribution dist; + }; + template struct randoms + { + randoms(Mn mn, Mx mx) : mini(mn), maxi(mx) {} + template auto operator()(tts::type, auto& rng, auto...) + { + tts::realistic_distribution dist(as_value(mini), as_value(maxi)); + return dist(rng); + } + Mn mini; + Mx maxi; + }; +} +namespace tts::detail +{ + template + concept comparable_equal = requires(L l, R r) { compare_equal(l,r); }; + template + concept comparable_less = requires(L l, R r) { compare_less(l,r); }; + template inline constexpr bool eq(L const &l, R const &r) + { + if constexpr( comparable_equal ) return compare_equal(l,r); + else return l == r; + } + template inline constexpr bool neq(L const &l, R const &r) + { + return !eq(l,r); + } + template inline constexpr bool lt(L const &l, R const &r) + { + if constexpr( comparable_less ) return compare_less(l,r); + else return l < r; + } + template inline constexpr bool le(L const &l, R const &r) + { + return lt(l, r) || eq(l, r); + } + template inline constexpr bool gt(L const &l, R const &r) + { + return !le(l,r); + } + template inline constexpr bool ge(L const &l, R const &r) + { + return !lt(l,r); + } +} +#include +#include +#include +#include +namespace tts +{ + template std::string as_string(T const& e) + { + if constexpr( std::is_pointer_v ) + { + std::ostringstream os; + os << typename_ << "(" << (void*)(e) << ")"; + return os.str(); + } + else if constexpr( tts::floating_point ) + { + auto precision = ::tts::arguments().value({"--precision"}, -1); + bool hexmode = ::tts::arguments()[{"-x","--hex"}]; + bool scimode = ::tts::arguments()[{"-s","--scientific"}]; + std::ostringstream os; + if(precision != -1 ) os << std::setprecision(precision); + if(hexmode) os << std::hexfloat << e << std::defaultfloat; + else if(scimode) os << std::scientific << e << std::defaultfloat; + else os << e; + return os.str(); + } + else if constexpr( detail::support_std_to_string ) + { + return std::to_string(e); + } + else if constexpr( detail::streamable ) + { + std::ostringstream os; + auto precision = ::tts::arguments().value({"--precision"}, -1); + bool hexmode = ::tts::arguments()[{"-x","--hex"}]; + bool scimode = ::tts::arguments()[{"-s","--scientific"}]; + if(precision != -1 ) os << std::setprecision(precision); + if(hexmode) os << std::hexfloat; + else if(scimode) os << std::scientific << e << std::defaultfloat; + os << e; + if(hexmode || scimode) os << std::defaultfloat; + return os.str(); + } + else if constexpr( detail::support_to_string ) + { + return to_string(e); + } + else if constexpr( detail::sequence ) + { + std::string that = "{ "; + for(auto const& v : e) that += as_string(v) + " "; + that += "}"; + return that; + } + else + { + std::ostringstream os; + os << "[" << typename_ << "]@(" << &e << ")"; + return os.str(); + } + } + inline std::string as_string(bool b) { return b ? std::string("true") : std::string("false"); } + inline std::string as_string(std::string const& e) { return e; } + inline std::string as_string(std::string_view const& e) { return std::string(e); } + inline std::string as_string(std::nullptr_t) { return std::string("nullptr"); } + template + std::string as_string(std::optional const& o) + { + if(o) return std::string("optional<") + typename_ +">{" + as_string(*o) + "}"; + else return std::string("optional<") + typename_ + ">{}"; + } +} +#define TTS_RELATION_BASE(A, B, OP, T, F, FAILURE) \ +if( ::tts::detail::OP(local_tts_a,local_tts_b) ) \ +{ \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ +} \ +else \ +{ \ + FAILURE ( "Expression: " << TTS_STRING(A) << " " T " " << TTS_STRING(B) \ + << " is false because: " << ::tts::as_string(local_tts_a) \ + << " " F " " << ::tts::as_string(local_tts_b) \ + ); \ + return ::tts::detail::logger{}; \ +} \ + +#define TTS_CEXPR_RELATION_BASE( A, B, OP, T, F, FAILURE) \ +constexpr auto result_tts = ::tts::detail::OP(A,B); \ +if( result_tts ) \ +{ \ + ::tts::global_runtime.pass(); \ + ::tts::global_logger_status = false; \ +} \ +else \ +{ \ + FAILURE ( "Expression: " << TTS_STRING(A) << " " << T << " " << TTS_STRING(B) \ + << " is false because: " \ + << ::tts::as_string(A) << " " << F << " " << ::tts::as_string(B) \ + ); \ + \ + ::tts::global_logger_status = true; \ +} \ + +#define TTS_RELATION(A, B, OP, T, F, ...) TTS_RELATION_ ## __VA_ARGS__ (A,B,OP,T,F) +#define TTS_RELATION_(A, B, OP, T, F) TTS_RELATION_IMPL(A,B,OP,T,F,TTS_FAIL) +#define TTS_RELATION_REQUIRED(A, B, OP, T, F) TTS_RELATION_IMPL(A,B,OP,T,F,TTS_FATAL) +#define TTS_RELATION_IMPL(A, B, OP, T, F, FAILURE) \ +[&](auto&& local_tts_a, auto&& local_tts_b) \ +{ \ + TTS_RELATION_BASE(A, B, OP, T, F, FAILURE) \ +}(A,B) \ + +#define TTS_EQUAL(LHS, RHS, ...) TTS_RELATION(LHS,RHS, eq , "==" , "!=" , __VA_ARGS__) +#define TTS_NOT_EQUAL(LHS, RHS, ...) TTS_RELATION(LHS,RHS, neq, "!=" , "==" , __VA_ARGS__) +#define TTS_LESS(LHS, RHS, ...) TTS_RELATION(LHS,RHS, lt , "<" , ">=" , __VA_ARGS__) +#define TTS_GREATER(LHS, RHS, ...) TTS_RELATION(LHS,RHS, gt , ">" , "<=" , __VA_ARGS__) +#define TTS_LESS_EQUAL(LHS, RHS, ...) TTS_RELATION(LHS,RHS, le , "<=" , ">" , __VA_ARGS__) +#define TTS_GREATER_EQUAL(LHS, RHS, ...) TTS_RELATION(LHS,RHS, ge , ">=" , "<=" , __VA_ARGS__) +#define TTS_CEXPR_RELATION(A, B, OP, T, F, ...) TTS_CEXPR_RELATION_ ## __VA_ARGS__ (A,B,OP,T,F) +#define TTS_CEXPR_RELATION_(A, B, OP, T, F) TTS_CEXPR_RELATION_IMPL(A,B,OP,T,F,TTS_FAIL) +#define TTS_CEXPR_RELATION_REQUIRED(A, B, OP, T, F) TTS_CEXPR_RELATION_IMPL(A,B,OP,T,F,TTS_FATAL) +#define TTS_CEXPR_RELATION_IMPL(A, B, OP, T, F, FAILURE) \ +::tts::global_logger_status = false; \ +do \ +{ \ + TTS_CEXPR_RELATION_BASE(A, B, OP, T, F, FAILURE) \ +}while(0); \ +::tts::detail::logger{::tts::global_logger_status} \ + +#define TTS_CONSTEXPR_EQUAL(LHS, RHS, ...) TTS_CEXPR_RELATION(LHS,RHS, eq , "==" , "!=", __VA_ARGS__) +#define TTS_CONSTEXPR_NOT_EQUAL(LHS, RHS, ...) TTS_CEXPR_RELATION(LHS,RHS, neq, "!=" , "==", __VA_ARGS__) +#define TTS_CONSTEXPR_LESS(LHS, RHS, ...) TTS_CEXPR_RELATION(LHS,RHS, lt , "<" , ">=", __VA_ARGS__) +#define TTS_CONSTEXPR_GREATER(LHS, RHS, ...) TTS_CEXPR_RELATION(LHS,RHS, gt , ">" , "<=", __VA_ARGS__) +#define TTS_CONSTEXPR_LESS_EQUAL(LHS, RHS, ...) TTS_CEXPR_RELATION(LHS,RHS, le , "<=" , ">" , __VA_ARGS__) +#define TTS_CONSTEXPR_GREATER_EQUAL(LHS, RHS, ...) TTS_CEXPR_RELATION(LHS,RHS, ge , ">=" , "<=", __VA_ARGS__) +#define TTS_TYPED_RELATION(A, B, OP, T, F, ...) TTS_TYPED_RELATION_ ## __VA_ARGS__ (A,B,OP,T,F) +#define TTS_TYPED_RELATION_(A, B, OP, T, F) TTS_TYPED_RELATION_IMPL(A,B,OP,T,F,TTS_FAIL) +#define TTS_TYPED_RELATION_REQUIRED(A, B, OP, T, F) TTS_TYPED_RELATION_IMPL(A,B,OP,T,F,TTS_FATAL) +#define TTS_TYPED_RELATION_IMPL(A, B, OP, T, F, FAILURE) \ +[&](auto&& local_tts_a, auto&& local_tts_b) \ +{ \ + using type_a = std::remove_cvref_t; \ + using type_b = std::remove_cvref_t; \ + \ + if ( !tts::same_as ) \ + { \ + FAILURE ( "Expression: " << TTS_STRING(A) << " " T " " << TTS_STRING(B) \ + << " is false because: " << ::tts::typename_ << " is not " \ + << ::tts::typename_ \ + ); \ + return ::tts::detail::logger{}; \ + } \ + else \ + { \ + TTS_RELATION_BASE(A, B, OP, T, F, FAILURE) \ + } \ +}(A,B) \ + +#define TTS_TYPED_EQUAL(LHS, RHS, ...) TTS_TYPED_RELATION(LHS,RHS, eq , "==" , "!=" , __VA_ARGS__) +#define TTS_TYPED_NOT_EQUAL(LHS, RHS, ...) TTS_TYPED_RELATION(LHS,RHS, neq, "!=" , "==" , __VA_ARGS__) +#define TTS_TYPED_LESS(LHS, RHS, ...) TTS_TYPED_RELATION(LHS,RHS, lt , "<" , ">=" , __VA_ARGS__) +#define TTS_TYPED_GREATER(LHS, RHS, ...) TTS_TYPED_RELATION(LHS,RHS, gt , ">" , "<=" , __VA_ARGS__) +#define TTS_TYPED_LESS_EQUAL(LHS, RHS, ...) TTS_TYPED_RELATION(LHS,RHS, le , "<=" , ">" , __VA_ARGS__) +#define TTS_TYPED_GREATER_EQUAL(LHS, RHS, ...) TTS_TYPED_RELATION(LHS,RHS, ge , ">=" , "<=" , __VA_ARGS__) +#define TTS_TYPED_CEXPR_RELATION(A, B, OP, T, F, ...) TTS_TYPED_CEXPR_RELATION_ ## __VA_ARGS__ (A,B,OP,T,F) +#define TTS_TYPED_CEXPR_RELATION_(A, B, OP, T, F) TTS_TYPED_CEXPR_RELATION_IMPL(A,B,OP,T,F,TTS_FAIL) +#define TTS_TYPED_CEXPR_RELATION_REQUIRED(A, B, OP, T, F) TTS_TYPED_CEXPR_RELATION_IMPL(A,B,OP,T,F,TTS_FATAL) +#define TTS_TYPED_CEXPR_RELATION_IMPL(A, B, OP, T, F, FAILURE) \ +::tts::global_logger_status = false; \ +do \ +{ \ + using type_a = std::remove_cvref_t; \ + using type_b = std::remove_cvref_t; \ + \ + if ( !tts::same_as ) \ + { \ + FAILURE ( "Expression: " << TTS_STRING(A) << " " T " " << TTS_STRING(B) \ + << " is false because: " << ::tts::typename_ << " is not " \ + << ::tts::typename_ \ + ); \ + \ + } \ + else \ + { \ + TTS_CEXPR_RELATION_BASE(A, B, OP, T, F, FAILURE) \ + } \ +} while(0) \ + +#define TTS_TYPED_CONSTEXPR_EQUAL(LHS, RHS, ...) TTS_TYPED_CEXPR_RELATION(LHS,RHS, eq , "==" , "!=", __VA_ARGS__) +#define TTS_TYPED_CONSTEXPR_NOT_EQUAL(LHS, RHS, ...) TTS_TYPED_CEXPR_RELATION(LHS,RHS, neq, "!=" , "==", __VA_ARGS__) +#define TTS_TYPED_CONSTEXPR_LESS(LHS, RHS, ...) TTS_TYPED_CEXPR_RELATION(LHS,RHS, lt , "<" , ">=", __VA_ARGS__) +#define TTS_TYPED_CONSTEXPR_GREATER(LHS, RHS, ...) TTS_TYPED_CEXPR_RELATION(LHS,RHS, gt , ">" , "<=", __VA_ARGS__) +#define TTS_TYPED_CONSTEXPR_LESS_EQUAL(LHS, RHS, ...) TTS_TYPED_CEXPR_RELATION(LHS,RHS, le , "<=" , ">" , __VA_ARGS__) +#define TTS_TYPED_CONSTEXPR_GREATER_EQUAL(LHS, RHS, ...) TTS_TYPED_CEXPR_RELATION(LHS,RHS, ge , ">=" , "<=", __VA_ARGS__) +#define TTS_TYPE_IS(TYPE, REF, ...) TTS_TYPE_IS_ ## __VA_ARGS__ (TYPE, REF) +#define TTS_TYPE_IS_(TYPE, REF) TTS_TYPE_IS_IMPL(TYPE, REF,TTS_FAIL) +#define TTS_TYPE_IS_REQUIRED(TYPE, REF) TTS_TYPE_IS_IMPL(TYPE, REF,TTS_FATAL) +#define TTS_TYPE_IS_IMPL(TYPE, REF, FAILURE) \ +[&](::tts::type, ::tts::type) \ +{ \ + if constexpr( std::is_same_v ) \ + { \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ + } \ + else \ + { \ + FAILURE ( "Type: " << TTS_STRING(TTS_REMOVE_PARENS(TYPE)) << " is not the same as " \ + << TTS_STRING(TTS_REMOVE_PARENS(REF)) << " because " \ + << ::tts::typename_ << " is not " << ::tts::typename_ \ + ); \ + return ::tts::detail::logger{}; \ + } \ +}(::tts::type{}, ::tts::type{}) \ + +#define TTS_EXPR_IS(EXPR, TYPE, ...) TTS_EXPR_IS_ ## __VA_ARGS__ (EXPR, TYPE) +#define TTS_EXPR_IS_(EXPR, TYPE) TTS_EXPR_IS_IMPL(EXPR, TYPE,TTS_FAIL) +#define TTS_EXPR_IS_REQUIRED(EXPR, TYPE) TTS_EXPR_IS_IMPL(EXPR, TYPE,TTS_FATAL) +#define TTS_EXPR_IS_IMPL(EXPR, TYPE, FAILURE) \ +[&](::tts::type, ::tts::type) \ +{ \ + if constexpr( std::is_same_v ) \ + { \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ + } \ + else \ + { \ + FAILURE ( "Type: " << TTS_STRING(TTS_REMOVE_PARENS(EXPR)) << " is not the same as " \ + << TTS_STRING(TTS_REMOVE_PARENS(TYPE)) << " because " \ + << ::tts::typename_ << " is not " << ::tts::typename_ \ + ); \ + return ::tts::detail::logger{}; \ + } \ +}(::tts::type{}, ::tts::type{}) \ + +#define TTS_EXPECT_COMPILES_IMPL(EXPR, ...) \ +TTS_DISABLE_WARNING_PUSH \ +TTS_DISABLE_WARNING_SHADOW \ +[&]( TTS_ARG(__VA_ARGS__) ) \ +{ \ + if constexpr( requires TTS_REMOVE_PARENS(EXPR) ) \ + { \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ + } \ + else \ + { \ + TTS_FAIL( "Expression: " << TTS_STRING(TTS_REMOVE_PARENS(EXPR)) \ + << " does not compile as expected." \ + ); \ + return ::tts::detail::logger{}; \ + } \ +TTS_DISABLE_WARNING_POP \ +}(__VA_ARGS__) \ + +#if defined(TTS_DOXYGEN_INVOKED) +#define TTS_EXPECT_COMPILES(Symbols..., Expression, ...) +#else +#define TTS_EXPECT_COMPILES(...) TTS_VAL(TTS_EXPECT_COMPILES_IMPL TTS_REVERSE(__VA_ARGS__) ) +#endif +#define TTS_EXPECT_NOT_COMPILES_IMPL(EXPR, ...) \ +TTS_DISABLE_WARNING_PUSH \ +TTS_DISABLE_WARNING_SHADOW \ +[&]( TTS_ARG(__VA_ARGS__) ) \ +{ \ + if constexpr( !(requires TTS_REMOVE_PARENS(EXPR)) ) \ + { \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ + } \ + else \ + { \ + TTS_FAIL("Expression: " << TTS_STRING(TTS_REMOVE_PARENS(EXPR)) << " compiles unexpectedly." ); \ + return ::tts::detail::logger{}; \ + } \ +TTS_DISABLE_WARNING_POP \ +}(__VA_ARGS__) \ + +#if defined(TTS_DOXYGEN_INVOKED) +#define TTS_EXPECT_NOT_COMPILES(Symbols..., Expression, ...) +#else +#define TTS_EXPECT_NOT_COMPILES(...) TTS_VAL(TTS_EXPECT_NOT_COMPILES_IMPL TTS_REVERSE(__VA_ARGS__)) +#endif +#define TTS_THROW_IMPL(EXPR, EXCEPTION, FAILURE) \ +[&]() \ +{ \ + bool tts_caught = false; \ + \ + try { EXPR; } \ + catch(EXCEPTION& ) { tts_caught = true; } \ + catch(...) { } \ + \ + if(tts_caught) \ + { \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ + } \ + else \ + { \ + FAILURE ( "Expected: " << TTS_STRING(EXPR) << " failed to throw " << TTS_STRING(EXCEPTION) ); \ + return ::tts::detail::logger{}; \ + } \ +}() +#define TTS_THROW(EXPR, EXCEPTION, ...) TTS_THROW_ ## __VA_ARGS__ ( EXPR, EXCEPTION ) +#define TTS_THROW_(EXPR, EXCEPTION) TTS_THROW_IMPL(EXPR, EXCEPTION,TTS_FAIL) +#define TTS_THROW_REQUIRED(EXPR, EXCEPTION) TTS_THROW_IMPL(EXPR, EXCEPTION,TTS_FATAL) +#define TTS_NO_THROW_IMPL(EXPR,FAILURE) \ +[&]() \ +{ \ + bool tts_caught = false; \ + \ + try { EXPR; } \ + catch(...) { tts_caught = true; } \ + \ + if(!tts_caught) \ + { \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ + } \ + else \ + { \ + FAILURE ( "Expected: " << TTS_STRING(EXPR) << " throws unexpectedly." ); \ + return ::tts::detail::logger{}; \ + } \ +}() +#define TTS_NO_THROW(EXPR, ...) TTS_NO_THROW_ ## __VA_ARGS__ ( EXPR ) +#define TTS_NO_THROW_(EXPR) TTS_NO_THROW_IMPL(EXPR,TTS_FAIL) +#define TTS_NO_THROW_REQUIRED(EXPR) TTS_NO_THROW_IMPL(EXPR,TTS_FATAL) +#include +#include +#include +#include +#include +#if !defined(__cpp_lib_bit_cast) +# include +#endif +namespace tts::detail +{ +#if !defined(__cpp_lib_bit_cast) + template + To bit_cast(const From& src) noexcept requires(sizeof(To) == sizeof(From)) + { + To dst; + std::memcpy(&dst, &src, sizeof(To)); + return dst; + } +#else + using std::bit_cast; +#endif + inline auto as_int(float a) noexcept { return bit_cast(a); } + inline auto as_int(double a) noexcept { return bit_cast(a); } + template inline auto bitinteger(T a) noexcept + { + auto ia = as_int(a); + using r_t = std::remove_cvref_t; + constexpr auto Signmask = r_t(1) << (sizeof(r_t)*8-1); + return std::signbit(a) ? Signmask-ia : ia; + } +} +#include +#include +#include +namespace tts +{ +namespace detail +{ + #if defined(__FAST_MATH__) + inline constexpr auto isinf = [](auto) { return false; }; + inline constexpr auto isnan = [](auto) { return false; }; + #else + inline constexpr auto isinf = [](auto x) { return std::isinf(x); }; + inline constexpr auto isnan = [](auto x) { return std::isnan(x); }; + #endif +} + template inline double absolute_distance(T const &a, U const &b) + { + if constexpr(std::is_same_v) + { + if constexpr(std::is_same_v) + { + return a == b ? 0. : 1.; + } + else if constexpr(std::is_floating_point_v) + { + if((a == b) || (detail::isnan(a) && detail::isnan(b))) return 0.; + if(detail::isinf(a) || detail::isinf(b) || detail::isnan(a) || detail::isnan(b)) + return std::numeric_limits::infinity(); + return std::abs(a - b); + } + else if constexpr(std::is_integral_v && !std::is_same_v) + { + auto d0 = static_cast(a), d1 = static_cast(b); + return absolute_distance(d0, d1); + } + else + { + static_assert ( std::is_floating_point_v || std::is_integral_v + , "[TTS] TTS_ABSOLUTE_EQUAL requires integral or floating points data to compare." + "Did you mean to use TTS_ALL_ABSOLUTE_EQUAL or to overload tts::absolute_distance ?" + ); + } + } + else + { + using common_t = std::common_type_t; + return absolute_distance(static_cast(a), static_cast(b)); + } + } + template inline double relative_distance(T const &a, U const &b) + { + if constexpr(std::is_same_v) + { + if constexpr(std::is_same_v) + { return a == b ? 0. : 100.; } + else if constexpr(std::is_floating_point_v) + { + if((a == b) || (detail::isnan(a) && detail::isnan(b))) return 0.; + if(detail::isinf(a) || detail::isinf(b) || detail::isnan(a) || detail::isnan(b)) + return std::numeric_limits::infinity(); + return 100. * (std::abs(a - b) / std::max(T(1), std::max(std::abs(a), std::abs(b)))); + } + else if constexpr(std::is_integral_v && !std::is_same_v) + { + auto d0 = static_cast(a), d1 = static_cast(b); + return relative_distance(d0, d1); + } + else + { + static_assert ( std::is_floating_point_v || std::is_integral_v + , "[TTS] TTS_RELATIVE_EQUAL requires integral or floating points data to compare." + "Did you mean to use TTS_ALL_RELATIVE_EQUAL or to overload tts::relative_distance ?" + ); + } + } + else + { + using common_t = std::common_type_t; + return relative_distance(static_cast(a), static_cast(b)); + } + } + template inline double ulp_distance(T const &a, U const &b) + { + if constexpr(std::is_same_v) + { + if constexpr(std::is_same_v) + { + return a == b ? 0. : std::numeric_limits::infinity(); + } + else if constexpr(std::is_floating_point_v) + { + using ui_t = std::conditional_t, std::uint32_t, std::uint64_t>; + if((a == b) || (detail::isnan(a) && detail::isnan(b))) + { + return 0.; + } + else if (std::isunordered(a, b)) + { + return std::numeric_limits::infinity(); + } + else + { + auto aa = detail::bitinteger(a); + auto bb = detail::bitinteger(b); + if(aa > bb) std::swap(aa, bb); + auto z = static_cast(bb-aa); + if( std::signbit(a) ^ std::signbit(b) ) ++z; + return z/2.; + } + } + else if constexpr(std::is_integral_v && !std::is_same_v) + { + using u_t = typename std::make_unsigned::type; + return ((a < b) ? u_t(b - a) : u_t(a - b))/2.; + } + else + { + static_assert ( std::is_floating_point_v || std::is_integral_v + , "[TTS] TTS_ULP_EQUAL requires integral or floating points data to compare." + "Did you mean to use TTS_ALL_ULP_EQUAL or to overload tts::ulp_distance ?" + ); + } + } + else + { + using common_t = std::common_type_t; + return ulp_distance(static_cast(a), static_cast(b)); + } + } + template inline bool is_ieee_equal(T const &a, U const &b) + { + if constexpr(std::is_floating_point_v) + { + return (a==b) || (detail::isnan(a) && detail::isnan(b)); + } + else + { + return a == b; + } + } +} +#define TTS_PRECISION_IMPL(LHS, RHS, N, UNIT, FUNC, PREC,FAILURE) \ +[&](auto local_tts_lhs, auto local_tts_rhs) \ +{ \ + auto r = FUNC (local_tts_lhs,local_tts_rhs); \ + \ + if(r <= N) \ + { \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ + } \ + else \ + { \ + FAILURE ( "Expected: " << TTS_STRING(LHS) << " == " << TTS_STRING(RHS) \ + << " but " \ + << ::tts::as_string(local_tts_lhs) \ + << " == " << ::tts::as_string(local_tts_rhs) \ + << " within " << std::setprecision(PREC) << std::fixed \ + << r << std::defaultfloat \ + << " " << UNIT << " when " \ + << std::setprecision(PREC) << std::fixed \ + << N << std::defaultfloat \ + << " " << UNIT << " was expected." \ + ); \ + return ::tts::detail::logger{}; \ + } \ +}(LHS,RHS) \ + +#define TTS_PRECISION(L,R,N,U,F,P,...) TTS_PRECISION_ ## __VA_ARGS__ (L,R,N,U,F,P) +#define TTS_PRECISION_(L,R,N,U,F,P) TTS_PRECISION_IMPL(L,R,N,U,F,P,TTS_FAIL) +#define TTS_PRECISION_REQUIRED(L,R,N,U,F,P) TTS_PRECISION_IMPL(L,R,N,U,F,P,TTS_FATAL) +#define TTS_ABSOLUTE_EQUAL(L,R,N,...) TTS_PRECISION(L,R,N,"unit", ::tts::absolute_distance, 8, __VA_ARGS__ ) +#define TTS_RELATIVE_EQUAL(L,R,N,...) TTS_PRECISION(L,R,N,"%" , ::tts::relative_distance, 8, __VA_ARGS__ ) +#define TTS_ULP_EQUAL(L,R,N,...) TTS_PRECISION(L,R,N,"ULP" , ::tts::ulp_distance , 2, __VA_ARGS__ ) +#define TTS_DO_IEEE_EQUAL_IMPL(LHS, RHS, FAILURE) \ +[&](auto local_tts_lhs, auto local_tts_rhs) \ +{ \ + if(::tts::is_ieee_equal(local_tts_lhs,local_tts_rhs)) \ + { \ + ::tts::global_runtime.pass(); return ::tts::detail::logger{false}; \ + } \ + else \ + { \ + FAILURE ( "Expected: " << TTS_STRING(LHS) << " == " << TTS_STRING(RHS) \ + << " but " \ + << ::tts::as_string(local_tts_lhs) << " != " << ::tts::as_string(local_tts_rhs) \ + ); \ + return ::tts::detail::logger{}; \ + } \ +}(LHS,RHS) \ + +#define TTS_DO_IEEE_EQUAL(L,R,...) TTS_DO_IEEE_EQUAL_ ## __VA_ARGS__ (L,R) +#define TTS_DO_IEEE_EQUAL_(L,R) TTS_DO_IEEE_EQUAL_IMPL(L,R,TTS_FAIL) +#define TTS_DO_IEEE_EQUAL_REQUIRED(L,R) TTS_DO_IEEE_EQUAL_IMPL(L,R,TTS_FATAL) +#define TTS_IEEE_EQUAL(L,R,...) TTS_DO_IEEE_EQUAL(L, R, __VA_ARGS__ ) +#include +#include +namespace tts +{ + template struct adapter + { + template + static void run(Base const*& src, U*& dst, Func f) noexcept { *dst++ = f(*src++); } + static auto retrieve(Base const* src) noexcept { return *src; } + static void display(Base const& v, std::ostream& os) noexcept { os << tts::as_string(v); } + }; + template + concept initializable = requires(Generator g, Args args) { g.init(args); }; +} +namespace tts::detail +{ + class text_field + { + int width_, precision_; + public: + text_field( int width, int prec = 2 ) : width_( width ), precision_(prec) {} + friend std::ostream& operator<<( std::ostream& os, text_field const& manip ) + { + os.setf( std::ios_base::left, std::ios_base::adjustfield ); + os.fill( ' ' ); + os.width( manip.width_ ); + os.precision( manip.precision_ ); + return os; + } + }; + class value_field + { + int width_, precision_; + public: + value_field( int width, int prec = 2 ) : width_( width ), precision_(prec) {} + friend std::ostream& operator<<( std::ostream& os, value_field const& manip ) + { + os.setf( std::ios_base::left , std::ios_base::adjustfield ); + os.setf( std::ios_base::fixed , std::ios_base::floatfield ); + os.fill( ' ' ); + os.precision( manip.precision_ ); + os.width( manip.width_ ); + return os; + } + }; + template void header( std::ostream& os, S const&... s) + { + ((os << std::left << detail::text_field(16) << s), ...); + os << std::endl; + } + template + void results( std::ostream& os + , U ulp, C count, R ratio, std::string const& desc, V const& v + ) + { + os << std::left << std::noshowpos; + os << detail::text_field(16,1) << ulp + << detail::text_field(16) << count + << detail::value_field(16) << ratio + << detail::value_field(16,7) << desc + << std::showpos; + adapter::display(v,os); + os << std::fixed << std::endl; + } +} +namespace tts::detail +{ + inline std::size_t next2( double x ) noexcept + { + auto v = static_cast(std::ceil(x)); + v--; + v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; + v++; + return v; + } + inline std::size_t last_bucket_less(std::size_t nb_buckets, double ulp) noexcept + { + std::size_t bucket; + if (ulp <= 1.5 ) bucket = static_cast(std::ceil(ulp*2)); + else if(detail::isinf(ulp)) bucket = nb_buckets-1; + else bucket = std::min ( nb_buckets-2 + , static_cast(std::log2(next2(ulp))+4) + ); + return bucket; + } + template + void compute(In const& inputs, Out& outputs, Func fn) + { + auto in = inputs.data(); + auto end = inputs.data() + inputs.size(); + auto out = outputs.data(); + while(in != end) + adapter::run(in,out,fn); + } +} +namespace tts +{ + template< typename RefType, typename NewType + , typename Generator, typename RefFun, typename NewFun + > + double ulp_histogram(Generator g, RefFun reference, NewFun challenger) + { + using out_type = std::decay_t() ))>; + using nout_type = std::decay_t() ))>; + std::size_t count = ::tts::arguments().value( "--block", std::size_t{4096}); + std::vector ref_out(count), new_out(count); + std::vector inputs(count); + for(std::size_t i=0;i ulp_map(nb_buckets,0); + std::vector > samples(nb_buckets, {false,{},{},{}}); + for(std::size_t r=0;r(inputs,ref_out,reference); + detail::compute(inputs,new_out,challenger); + std::vector ulpdists(count); + for(std::size_t i=0;i( samples[ idx ] ) ) + { + samples[idx] = { true + , adapter::retrieve(&inputs[i]) + , adapter::retrieve(&new_out[i]) + , adapter::retrieve(&ref_out[i]) + }; + } + } + } + detail::header(std::cout, "Max ULP", "Count (#)", "Cum. Ratio (%)", "Samples"); + std::cout << std::string(80,'-') << std::endl; + double ratio = 0.; + for(std::size_t i=0;i::infinity(); + else ulps = 1<<(i-4); + detail::results ( std::cout, ulps , ulp_map[i], ratio, "Input: ", std::get<1>(samples[i]) ); + detail::results ( std::cout, "" , "" , "", "Found: " , std::get<2>( samples[i]) ); + detail::results ( std::cout, "" , "" , "", "instead of: " , std::get<3>( samples[i]) ); + std::cout << std::string(80,'-') << std::endl << std::noshowpos; + } + } + return max_ulp; + } + template + void print_producer(P const& producer, const char* alt) + { + if constexpr( tts::detail::support_std_to_string

+ || tts::detail::streamable

+ || tts::detail::support_to_string

+ ) + { + std::cout << ::tts::as_string(producer) << "\n"; + } + else + { + std::cout << alt << "\n"; + } + } +} +#define TTS_ULP_RANGE_CHECK(Producer, RefType, NewType, RefFunc, NewFunc, Ulpmax) \ + [&]() \ + { \ + std::cout << "Comparing: " << TTS_STRING(RefFunc) \ + << "<" << TTS_STRING(TTS_REMOVE_PARENS(RefType)) << ">" \ + << " with " << TTS_STRING(NewFunc) \ + << "<" << TTS_STRING(TTS_REMOVE_PARENS(NewType)) \ + << "> using "; \ + \ + auto generator = TTS_REMOVE_PARENS(Producer); \ + tts::print_producer(generator, TTS_STRING(Producer) ); \ + \ + auto local_tts_threshold = ::tts::arguments().value( "--ulpmax", Ulpmax ); \ + auto local_tts_max_ulp = ::tts::ulp_histogram< TTS_REMOVE_PARENS(RefType) \ + , TTS_REMOVE_PARENS(NewType) \ + > \ + ( generator \ + , RefFunc, NewFunc \ + ); \ + \ + if(local_tts_max_ulp <= local_tts_threshold) \ + { \ + ::tts::global_runtime.pass(); \ + } \ + else \ + { \ + TTS_FAIL( "Expecting: " << TTS_STRING(NewFunc) \ + << " similar to " << TTS_STRING(RefFunc) \ + << " within " << std::setprecision(2) \ + << local_tts_threshold << " ULP" \ + << " but found: " << std::setprecision(2) \ + << local_tts_max_ulp << " ULP instead" \ + ); \ + } \ + }() +namespace tts +{ + template + struct prng_generator + { + using param_type = typename Distribution::param_type; + template + prng_generator(Args... args) : distribution_(static_cast(args)...) + { + seed_ = random_seed(); + generator_.seed(seed_); + auto mn = ::tts::arguments().value( "--valmin", distribution_.min() ); + auto mx = ::tts::arguments().value( "--valmax", distribution_.max() ); + distribution_.param(param_type(mn, mx)); + } + template T operator()(Idx, Count) + { + return distribution_(generator_); + } + friend std::string to_string(prng_generator const& p) + { + std::ostringstream txt; + txt << typename_ + << "(" << p.distribution_.min() << ", " << p.distribution_.max() << ")" + << "[seed = " << p.seed_ << "]"; + return txt.str(); + } + private: + Distribution distribution_; + std::mt19937 generator_; + std::mt19937::result_type seed_; + }; + template + using realistic_generator = prng_generator>; +} +#include +namespace tts::detail +{ + template struct failure + { + std::size_t index; + T original; + U other; + }; +} +#define TTS_ALL_IMPL(SEQ1,SEQ2,OP,N,UNIT,FAILURE) \ +[](auto const& local_tts_a, auto const& local_tts_b) \ +{ \ + if( std::size(local_tts_b) != std::size(local_tts_a) ) \ + { \ + FAILURE ( "Expected: " << TTS_STRING(SEQ1) << " == " << TTS_STRING(SEQ2) \ + << " but sizes does not match: " \ + << "size(" TTS_STRING(SEQ1) ") = " << std::size(local_tts_a) \ + << " while size(" TTS_STRING(SEQ2) ") = " << std::size(local_tts_b) \ + ); \ + return ::tts::detail::logger{}; \ + } \ + \ + auto ba = std::begin(local_tts_a); \ + auto bb = std::begin(local_tts_b); \ + auto ea = std::end(local_tts_a); \ + \ + std::vector < ::tts::detail::failure< std::remove_cvref_t \ + , std::remove_cvref_t \ + > \ + > failures; \ + std::size_t i = 0; \ + \ + while(ba != ea) \ + { \ + if( OP(*ba,*bb) > N ) failures.push_back({i++,*ba,*bb}); \ + ba++; \ + bb++; \ + } \ + \ + if( !failures.empty( ) ) \ + { \ + FAILURE ( "Expected: " << TTS_STRING(SEQ1) << " == " << TTS_STRING(SEQ2) \ + << " but values differ by more than " << N << " "<< UNIT \ + ); \ + \ + for(auto f : failures) \ + std::cout << " @[" << f.index << "] : " << f.original << " and " << f.other \ + << " differ by " << OP(f.original,f.other) << " " << UNIT << "\n"; \ + \ + std::cout << "\n"; \ + return ::tts::detail::logger{}; \ + } \ + \ + ::tts::global_runtime.pass(); \ + return ::tts::detail::logger{false}; \ +}(SEQ1, SEQ2) \ + +#define TTS_ALL(L,R,F,N,U, ...) TTS_ALL_ ## __VA_ARGS__ (L,R,F,N,U) +#define TTS_ALL_(L,R,F,N,U) TTS_ALL_IMPL(L,R,F,N,U,TTS_FAIL) +#define TTS_ALL_REQUIRED(L,R,F,N,U) TTS_ALL_IMPL(L,R,F,N,U,TTS_FATAL) +#define TTS_ALL_ABSOLUTE_EQUAL(L,R,N,...) TTS_ALL(L,R, ::tts::absolute_distance,N,"unit", __VA_ARGS__ ) +#define TTS_ALL_RELATIVE_EQUAL(L,R,N,...) TTS_ALL(L,R, ::tts::relative_distance,N,"%" , __VA_ARGS__ ) +#define TTS_ALL_ULP_EQUAL(L,R,N,...) TTS_ALL(L,R, ::tts::ulp_distance ,N,"ULP" , __VA_ARGS__ ) +#define TTS_ALL_IEEE_EQUAL(L,R,...) TTS_ALL_ULP_EQUAL(L,R,0, __VA_ARGS__) +#define TTS_ALL_EQUAL(L,R,...) TTS_ALL_ABSOLUTE_EQUAL(L,R, 0 __VA_ARGS__ ) +#include +namespace tts::detail +{ + struct section_guard + { + int & id; + int const & section; + section_guard(int &id_, int const §ion_, int &count) : id(id_) , section(section_) + { + if(section == 0) id = count++ - 1; + } + template bool check(Desc const& desc) + { + if(id == section) std::cout << " And then: " << desc << std::endl; + return id == section; + } + }; + struct only_once + { + bool once = true; + explicit operator bool() { bool result = once; once = false; return result; } + }; +} +#define TTS_WHEN(STORY) \ +TTS_DISABLE_WARNING_PUSH \ +TTS_DISABLE_WARNING_SHADOW \ + std::cout << "[^] - For: " << ::tts::detail::current_test << "\n"; \ + std::cout << "When : " << STORY << std::endl; \ + for(int tts_section = 0, tts_count = 1; tts_section < tts_count; tts_count -= 0==tts_section++) \ + for( tts::detail::only_once tts_only_once_setup{}; tts_only_once_setup; ) \ +TTS_DISABLE_WARNING_POP \ + +#define TTS_AND_THEN_IMPL(TTS_LOCAL_ID, ...) \ +TTS_DISABLE_WARNING_PUSH \ +TTS_DISABLE_WARNING_SHADOW \ + static int TTS_LOCAL_ID = 0; \ + std::ostringstream TTS_CAT(desc_,TTS_LOCAL_ID); \ + if(::tts::detail::section_guard(TTS_LOCAL_ID, tts_section, tts_count ) \ + .check( ((TTS_CAT(desc_,TTS_LOCAL_ID) << __VA_ARGS__) \ + , TTS_CAT(desc_,TTS_LOCAL_ID).str()) \ + ) \ + ) \ + for(int tts_section = 0, tts_count = 1; tts_section < tts_count; tts_count -= 0==tts_section++ ) \ + for(tts::detail::only_once tts__only_once_section{}; tts__only_once_section; ) \ +TTS_DISABLE_WARNING_POP \ + +#define TTS_AND_THEN(...) TTS_AND_THEN_IMPL(TTS_UNIQUE(id), __VA_ARGS__) \ No newline at end of file