kfr

Fast, modern C++ DSP framework, FFT, Sample Rate Conversion, FIR/IIR/Biquad Filters (SSE, AVX, AVX-512, ARM NEON)
Log | Files | Refs | README

commit 2d457a951599ad878866b800a6a5ea099c793eef
parent 96671cc5421bda39f5e56646dff1aa2abb350dee
Author: d.levin256@gmail.com <d.levin256@gmail.com>
Date:   Tue,  8 Nov 2016 13:31:16 +0300

Tests refactoring

Diffstat:
Minclude/kfr/base/select.hpp | 16++++++++++++----
Minclude/kfr/base/types.hpp | 65+++++++++++++++++++++++++++++++++++++----------------------------
Ainclude/kfr/ext/console_colors.hpp | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/kfr/testo/testo.hpp | 565+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/CMakeLists.txt | 3++-
Mtests/base_test.cpp | 32++++++++++++++++++++------------
Mtests/complex_test.cpp | 3++-
Mtests/dft_test.cpp | 3++-
Mtests/dsp_test.cpp | 35+++++++++--------------------------
Mtests/expression_test.cpp | 78+++++++++++++++++++++---------------------------------------------------------
Mtests/intrinsic_test.cpp | 2+-
Mtests/multiarch.cpp | 2+-
Dtests/testo/print_colored.hpp | 153-------------------------------------------------------------------------------
Dtests/testo/testo.hpp | 565-------------------------------------------------------------------------------
Mtests/transcendental_test.cpp | 3++-
15 files changed, 835 insertions(+), 851 deletions(-)

diff --git a/include/kfr/base/select.hpp b/include/kfr/base/select.hpp @@ -129,7 +129,8 @@ KFR_SINTRIN vec<T, N> select(const mask<T, N>& a, const vec<T, N>& b, const vec< template <typename T, size_t N, KFR_ENABLE_IF(N >= platform<T>::vector_width), typename = void> KFR_SINTRIN vec<T, N> select(const mask<T, N>& a, const vec<T, N>& b, const vec<T, N>& c) { - return concat(select(low(a.asvec()).asmask(), low(b), low(c)), select(high(a.asvec()).asmask(), high(b), high(c))); + return concat(select(low(a.asvec()).asmask(), low(b), low(c)), + select(high(a.asvec()).asmask(), high(b), high(c))); } #elif defined CMT_ARCH_NEON && defined KFR_NATIVE_INTRINSICS @@ -139,8 +140,14 @@ KFR_SINTRIN f32neon select(const maskfor<f32neon>& m, const f32neon& x, const f3 return vbslq_f32(*m, *x, *y); } -KFR_SINTRIN i8neon select(const maskfor<i8neon>& m, const i8neon& x, const i8neon& y) { return vbslq_s8(*m, *x, *y); } -KFR_SINTRIN u8neon select(const maskfor<u8neon>& m, const u8neon& x, const u8neon& y) { return vbslq_u8(*m, *x, *y); } +KFR_SINTRIN i8neon select(const maskfor<i8neon>& m, const i8neon& x, const i8neon& y) +{ + return vbslq_s8(*m, *x, *y); +} +KFR_SINTRIN u8neon select(const maskfor<u8neon>& m, const u8neon& x, const u8neon& y) +{ + return vbslq_u8(*m, *x, *y); +} KFR_SINTRIN i16neon select(const maskfor<i16neon>& m, const i16neon& x, const i16neon& y) { return vbslq_s16(*m, *x, *y); @@ -218,7 +225,8 @@ template <typename T1, size_t N, typename T2, typename T3, KFR_ENABLE_IF(is_nume KFR_INTRIN vec<Tout, N> select(const mask<T1, N>& m, const T2& x, const T3& y) { static_assert(sizeof(T1) == sizeof(Tout), "select: incompatible types"); - return intrinsics::select(bitcast<Tout>(m.asvec()).asmask(), static_cast<vec<Tout, N>>(x), static_cast<vec<Tout, N>>(y)); + return intrinsics::select(bitcast<Tout>(m.asvec()).asmask(), static_cast<vec<Tout, N>>(x), + static_cast<vec<Tout, N>>(y)); } /** diff --git a/include/kfr/base/types.hpp b/include/kfr/base/types.hpp @@ -33,10 +33,17 @@ CMT_PRAGMA_GNU(GCC diagnostic push) CMT_PRAGMA_GNU(GCC diagnostic ignored "-Wshadow") +#ifdef KFR_TESTING +#include "../testo/testo.hpp" +#endif + #include "../cometa.hpp" #define KFR_ENABLE_IF CMT_ENABLE_IF +/** + * @brief Internal macro for functions + */ #define KFR_FN(FN) \ namespace fn \ { \ @@ -50,6 +57,9 @@ CMT_PRAGMA_GNU(GCC diagnostic ignored "-Wshadow") }; \ } +/** + * @brief Internal macro for functions + */ #define KFR_I_FN(FN) \ namespace fn \ { \ @@ -66,8 +76,10 @@ CMT_PRAGMA_GNU(GCC diagnostic ignored "-Wshadow") namespace kfr { +// Include all from CoMeta library using namespace cometa; +/// @brief Short names for common types using f32 = float; using f64 = double; using i8 = int8_t; @@ -84,8 +96,10 @@ using fmax = double; using f80 = long double; #if defined(KFR_BASETYPE_F32) || defined(KFR_NO_NATIVE_F64) +/// @brief Floating point type used by default using fbase = f32; #else +/// @brief Floating point type used by default using fbase = f64; #endif @@ -120,6 +134,7 @@ struct f16 u16 raw; }; +/// @brief An enumeration representing data type template <typename T1> struct range { @@ -199,12 +214,22 @@ struct typebits namespace fn { +///@copybrief cometa::pass_through using pass_through = cometa::fn_pass_through; -using noop = cometa::fn_noop; -using get_first = cometa::fn_get_first; -using get_second = cometa::fn_get_second; -using get_third = cometa::fn_get_third; +///@copybrief cometa::noop +using noop = cometa::fn_noop; + +///@copybrief cometa::get_first +using get_first = cometa::fn_get_first; + +///@copybrief cometa::get_second +using get_second = cometa::fn_get_second; + +///@copybrief cometa::get_third +using get_third = cometa::fn_get_third; + +///@copybrief cometa::returns template <typename T> using returns = cometa::fn_returns<T>; } @@ -226,25 +251,6 @@ using isubtype = itype<subtype<T>>; template <typename T> using usubtype = utype<subtype<T>>; -template <typename T, typename R = T> -using enable_if_vec = enable_if<(typebits<T>::width > 0), R>; -template <typename T, typename R = T> -using enable_if_not_vec = enable_if<(typebits<T>::width == 0), R>; - -template <typename T, typename R = T> -using enable_if_i = enable_if<typeclass<T> == datatype::i, R>; -template <typename T, typename R = T> -using enable_if_u = enable_if<typeclass<T> == datatype::u, R>; -template <typename T, typename R = T> -using enable_if_f = enable_if<typeclass<T> == datatype::f, R>; - -template <typename T, typename R = T> -using enable_if_not_i = enable_if<typeclass<T> != datatype::i, R>; -template <typename T, typename R = T> -using enable_if_not_u = enable_if<typeclass<T> != datatype::u, R>; -template <typename T, typename R = T> -using enable_if_not_f = enable_if<typeclass<T> != datatype::f, R>; - namespace internal { template <typename T> @@ -298,11 +304,6 @@ CMT_INLINE void builtin_memcpy(void* dest, const void* src, size_t size) { ::mem CMT_INLINE void builtin_memset(void* dest, int val, size_t size) { ::memset(dest, val, size); } #endif -template <typename T1> -CMT_INLINE void zeroize(T1& value) -{ - builtin_memset(static_cast<void*>(builtin_addressof(value)), 0, sizeof(T1)); -} CMT_PRAGMA_GNU(GCC diagnostic push) CMT_PRAGMA_GNU(GCC diagnostic ignored "-Wattributes") @@ -328,6 +329,14 @@ __attribute__((__packed__, __may_alias__)) // CMT_PRAGMA_GNU(GCC diagnostic pop) } +/// @brief Fills a value with zeros +template <typename T1> +CMT_INLINE void zeroize(T1& value) +{ + internal::builtin_memset(static_cast<void*>(builtin_addressof(value)), 0, sizeof(T1)); +} + +/// @brief Used to determine the initial value for reduce functions template <typename T> struct initialvalue { diff --git a/include/kfr/ext/console_colors.hpp b/include/kfr/ext/console_colors.hpp @@ -0,0 +1,161 @@ +#pragma once +#include <cstdint> +#include <cstdio> + +//#define CONSOLE_COLORS_FORCE_ASCII + +#if defined _WIN32 && !defined PRINT_COLORED_FORCE_ASCII +#define USE_WIN32_API +#endif + +#if defined(USE_WIN32_API) + +namespace win32_lite +{ +typedef void* HANDLE; +typedef uint32_t DWORD; + +#define WIN32_LITE_STD_INPUT_HANDLE ((win32_lite::DWORD)-10) +#define WIN32_LITE_STD_OUTPUT_HANDLE ((win32_lite::DWORD)-11) +#define WIN32_LITE_STD_ERROR_HANDLE ((win32_lite::DWORD)-12) + +#define WIN32_LITE_DECLSPEC_IMPORT __declspec(dllimport) + +#define WIN32_LITE_WINAPI __stdcall + +typedef short SHORT; +typedef unsigned short WORD; +typedef int WINBOOL; + +extern "C" { +WIN32_LITE_DECLSPEC_IMPORT HANDLE WIN32_LITE_WINAPI GetStdHandle(DWORD nStdHandle); +WIN32_LITE_DECLSPEC_IMPORT WINBOOL WIN32_LITE_WINAPI SetConsoleTextAttribute(HANDLE hConsoleOutput, + WORD wAttributes); +} +} + +#endif + +namespace console_colors +{ + +enum text_color : uint32_t +{ + Black = 0x00, + DarkBlue = 0x01, + DarkGreen = 0x02, + DarkCyan = 0x03, + DarkRed = 0x04, + DarkMagenta = 0x05, + DarkYellow = 0x06, + LightGrey = 0x07, + Gray = 0x08, + Blue = 0x09, + Green = 0x0A, + Cyan = 0x0B, + Red = 0x0C, + Magenta = 0x0D, + Yellow = 0x0E, + White = 0x0F, + BgBlack = 0x00, + BgDarkBlue = 0x10, + BgDarkGreen = 0x20, + BgDarkCyan = 0x30, + BgDarkRed = 0x40, + BgDarkMagenta = 0x50, + BgDarkYellow = 0x60, + BgLightGrey = 0x70, + BgGray = 0x80, + BgBlue = 0x90, + BgGreen = 0xA0, + BgCyan = 0xB0, + BgRed = 0xC0, + BgMagenta = 0xD0, + BgYellow = 0xE0, + BgWhite = 0xF0, + + Normal = BgBlack | LightGrey +}; + +enum console_buffer +{ + ConsoleStdOutput, + ConsoleStdError +}; + +struct console_color +{ +public: + console_color(text_color c, console_buffer console = ConsoleStdOutput) + : m_old(get(console)), m_console(console) + { + set(c, m_console); + } + + ~console_color() { set(m_old, m_console); } + +private: + text_color get(console_buffer console = ConsoleStdOutput) { return saved_color(); } + + void set(text_color new_color, console_buffer console = ConsoleStdOutput) + { +#ifdef USE_WIN32_API + win32_lite::SetConsoleTextAttribute(win32_lite::GetStdHandle(console == ConsoleStdOutput + ? WIN32_LITE_STD_OUTPUT_HANDLE + : WIN32_LITE_STD_ERROR_HANDLE), + static_cast<win32_lite::WORD>(new_color)); +#else + if (new_color != Normal) + { + uint8_t t = new_color & 0xF; + uint8_t b = (new_color & 0xF0) >> 4; + uint8_t tnum = 30 + ((t & 1) << 2 | (t & 2) | (t & 4) >> 2); + uint8_t bnum = 40 + ((b & 1) << 2 | (b & 2) | (b & 4) >> 2); + if (t & 8) + tnum += 60; + if (b & 8) + bnum += 60; + std::fprintf(console == ConsoleStdOutput ? stdout : stderr, "\x1B[%d;%dm", tnum, bnum); + } + else + { + std::fprintf(console == ConsoleStdOutput ? stdout : stderr, "\x1B[0m"); + } +#endif + saved_color() = new_color; + } + + text_color m_old; + console_buffer m_console; + static text_color& saved_color() + { + static text_color color = Normal; + return color; + } +}; + +template <text_color color, console_buffer console = ConsoleStdOutput> +struct console_color_tpl : public console_color +{ +public: + console_color_tpl() : console_color(color, console) {} + +private: +}; + +typedef console_color_tpl<DarkBlue> darkblue_text; +typedef console_color_tpl<DarkGreen> darkgreen_text; +typedef console_color_tpl<DarkCyan> darkcyan_text; +typedef console_color_tpl<DarkRed> darkred_text; +typedef console_color_tpl<DarkMagenta> darkmagenta_text; +typedef console_color_tpl<DarkYellow> darkyellow_text; +typedef console_color_tpl<LightGrey> lightgrey_text; +typedef console_color_tpl<Gray> gray_text; +typedef console_color_tpl<Blue> blue_text; +typedef console_color_tpl<Green> green_text; +typedef console_color_tpl<Cyan> cyan_text; +typedef console_color_tpl<Red> red_text; +typedef console_color_tpl<Magenta> magenta_text; +typedef console_color_tpl<Yellow> yellow_text; +typedef console_color_tpl<White> white_text; +} diff --git a/include/kfr/testo/testo.hpp b/include/kfr/testo/testo.hpp @@ -0,0 +1,565 @@ +#pragma once + +#include "../cometa/tuple.hpp" + +#include "../cometa.hpp" +#include "../cometa/range.hpp" +#include "../cometa/string.hpp" + +#include <algorithm> +#include <ctime> +#include <functional> +#include <sstream> +#include <utility> +#include <vector> +#ifdef TESTO_MPFR +#include <mpfr/mpfr.hpp> +#include <mpfr/mpfr_tostring.hpp> +#endif +#include "../ext/console_colors.hpp" +#include <chrono> +#include <cmath> + +#if !defined CLANG_DIAGNOSTIC_PRAGMA +#if defined __clang__ +#define TESTO_STRING(str) #str +#define CLANG_DIAGNOSTIC_PRAGMA(pragma) _Pragma(TESTO_STRING(clang diagnostic pragma)) +#else +#define CLANG_DIAGNOSTIC_PRAGMA(pragma) +#endif +#endif + +CLANG_DIAGNOSTIC_PRAGMA(push) +CLANG_DIAGNOSTIC_PRAGMA(ignored "-Wexit-time-destructors") +CLANG_DIAGNOSTIC_PRAGMA(ignored "-Wpadded") +CLANG_DIAGNOSTIC_PRAGMA(ignored "-Wshadow") + +namespace testo +{ + +using namespace cometa; + +#ifdef TESTO_MPFR +using reference_number = mpfr::number; +#else +using reference_number = long double; +#endif + +#ifdef TESTO_MPFR +template <typename T> +inline double ulp_distance(const mpfr::number& reference, T test) +{ + if (std::isnan(test) && reference.isnan()) + return 0.0; + if (std::isinf(test) && (reference.isinfinity() || mpfr::abs(reference) > std::numeric_limits<T>::max())) + { + if ((reference < 0 && test < 0) || (reference > 0 && test > 0)) + return 0.0; + else + return std::numeric_limits<double>::infinity(); + } + mpfr::number testreal = test; + T next = std::nexttoward(test, std::numeric_limits<long double>::infinity()); + mpfr::number ulp = testreal - mpfr::number(next); + return std::abs(static_cast<double>((reference - testreal) / ulp)); +} +inline std::string number_to_string(const mpfr::number& reference, int precision) +{ + return mpfr::to_string(reference, precision, 'g'); +} +#else +template <typename T> +inline double ulp_distance(long double reference, T test) +{ + if (__builtin_isnan(test) && __builtin_isnan(reference)) + return 0.0; + if (__builtin_isinf(test) && + (__builtin_isinf(reference) || std::fabs(reference) > std::numeric_limits<T>::max())) + { + if ((reference < 0 && test < 0) || (reference > 0 && test > 0)) + return 0.0; + else + return std::numeric_limits<double>::infinity(); + } + long double test80 = test; + T next = std::nexttoward(test, std::numeric_limits<long double>::infinity()); + long double ulp = test80 - static_cast<long double>(next); + return std::abs(static_cast<double>((reference - test80) / ulp)); +} +#endif + +using namespace console_colors; + +template <typename Fn, typename L, typename R> +struct comparison +{ + L left; + R right; + Fn cmp; + + comparison(L&& left, R&& right) : left(std::forward<L>(left)), right(std::forward<R>(right)) {} + + bool operator()() const { return cmp(left, right); } +}; + +template <typename Left, typename Right> +struct static_assert_type_eq +{ + static_assert(std::is_same<Left, Right>::value, "std::is_same<Left, Right>::value"); +}; + +template <typename T, T left, T right> +struct static_assert_eq +{ + static_assert(left == right, "left == right"); +}; + +template <typename L, typename R, typename = void> +struct equality_comparer +{ + bool operator()(const L& l, const R& r) const { return l == r; } +}; + +CMT_PRAGMA_GNU(GCC diagnostic push) +CMT_PRAGMA_GNU(GCC diagnostic ignored "-Wfloat-equal") + +template <typename T> +inline T& epsilon() +{ + static T value = std::numeric_limits<T>::epsilon(); + return value; +} + +template <> +struct equality_comparer<float, float> +{ + bool operator()(const float& l, const float& r) const { return !(std::abs(l - r) > epsilon<float>()); } +}; +template <> +struct equality_comparer<double, double> +{ + bool operator()(const double& l, const double& r) const { return !(std::abs(l - r) > epsilon<double>()); } +}; +template <> +struct equality_comparer<long double, long double> +{ + bool operator()(const long double& l, const long double& r) const + { + return !(std::abs(l - r) > epsilon<long double>()); + } +}; + +CMT_PRAGMA_GNU(GCC diagnostic pop) + +template <typename L, typename R> +struct equality_comparer<L, R, void_t<enable_if<!compound_type_traits<L>::is_scalar>>> +{ + using Tsubtype = subtype<L>; + constexpr static static_assert_type_eq<subtype<L>, subtype<R>> assert{}; + + bool operator()(const L& l, const R& r) const + { + if (compound_type_traits<L>::width != compound_type_traits<R>::width) + return false; + + compound_type_traits<L> itl; + compound_type_traits<R> itr; + for (size_t i = 0; i < compound_type_traits<L>::width; i++) + { + equality_comparer<Tsubtype, Tsubtype> cmp; + if (!cmp(itl.at(l, i), itr.at(r, i))) + return false; + } + return true; + } +}; + +struct cmp_eq +{ + static const char* op() { return "=="; } + + template <typename L, typename R> + bool operator()(L&& left, R&& right) const + { + equality_comparer<decay<L>, decay<R>> eq; + return eq(left, right); + } +}; + +struct cmp_ne +{ + static const char* op() { return "!="; } + + template <typename L, typename R> + bool operator()(L&& left, R&& right) const + { + return !cmp_eq()(left, right); + } +}; + +struct cmp_lt +{ + static const char* op() { return "<"; } + + template <typename L, typename R> + bool operator()(L&& left, R&& right) const + { + return left < right; + } +}; + +struct cmp_gt +{ + static const char* op() { return ">"; } + + template <typename L, typename R> + bool operator()(L&& left, R&& right) const + { + return left > right; + } +}; + +struct cmp_le +{ + static const char* op() { return "<="; } + + template <typename L, typename R> + bool operator()(L&& left, R&& right) const + { + return left <= right; + } +}; + +struct cmp_ge +{ + static const char* op() { return ">="; } + + template <typename L, typename R> + bool operator()(L&& left, R&& right) const + { + return left >= right; + } +}; + +template <typename L> +struct half_comparison +{ + half_comparison(L&& left) : left(std::forward<L>(left)) {} + + template <typename R> + comparison<cmp_eq, L, R> operator==(R&& right) + { + return comparison<cmp_eq, L, R>(std::forward<L>(left), std::forward<R>(right)); + } + + template <typename R> + comparison<cmp_ne, L, R> operator!=(R&& right) + { + return comparison<cmp_ne, L, R>(std::forward<L>(left), std::forward<R>(right)); + } + + template <typename R> + comparison<cmp_lt, L, R> operator<(R&& right) + { + return comparison<cmp_lt, L, R>(std::forward<L>(left), std::forward<R>(right)); + } + + template <typename R> + comparison<cmp_gt, L, R> operator>(R&& right) + { + return comparison<cmp_gt, L, R>(std::forward<L>(left), std::forward<R>(right)); + } + + template <typename R> + comparison<cmp_le, L, R> operator<=(R&& right) + { + return comparison<cmp_le, L, R>(std::forward<L>(left), std::forward<R>(right)); + } + + template <typename R> + comparison<cmp_ge, L, R> operator>=(R&& right) + { + return comparison<cmp_ge, L, R>(std::forward<L>(left), std::forward<R>(right)); + } + + L left; +}; + +struct make_comparison +{ + template <typename L> + half_comparison<L> operator<=(L&& left) + { + return half_comparison<L>(std::forward<L>(left)); + } +}; + +inline std::vector<std::string> split(const std::string& text, char delimeter) +{ + std::string r = text; + size_t prev_pos = 0; + size_t start_pos = 0; + std::vector<std::string> list; + while ((start_pos = r.find(delimeter, prev_pos)) != std::string::npos) + { + list.push_back(text.substr(prev_pos, start_pos - prev_pos)); + prev_pos = start_pos + 1; + } + list.push_back(text.substr(prev_pos)); + return list; +} + +struct test_case; + +inline test_case*& active_test() +{ + static test_case* instance = nullptr; + return instance; +} + +struct test_case +{ + using test_func = void (*)(); + + static std::vector<test_case*>& tests() + { + static std::vector<test_case*> list; + return list; + } + + test_case(test_func func, const char* name) + : func(func), name(name), success(0), failed(0), time(0), show_progress(false) + { + tests().push_back(this); + } + + bool run(bool show_successful) + { + using namespace std::chrono; + using time_point = high_resolution_clock::time_point; + { + console_color cc(Cyan); + printfmt("[{}]", padcenter(11, std::string("RUN"), '-')); + } + printfmt(" {}...\n", name); + time_point start = high_resolution_clock::now(); + active_test() = this; + func(); + active_test() = nullptr; + time_point stop = high_resolution_clock::now(); + time = duration_cast<duration<double>>(stop - start).count(); + + { + console_color cc(failed ? Red : Green); + printfmt("[{}] {} subtests of {}\n", padcenter(11, failed ? "ERROR" : "SUCCESS", '-'), + failed ? failed : success, success + failed); + } + if (failed) + { + for (const subtest& s : subtests) + { + if ((s.success && show_successful) || !s.success) + { + if (!s.comment.empty()) + printfmt(" {}:\n", s.comment); + { + console_color cc(s.success ? Green : Red); + printfmt(" {} ", s.success ? "[success]" : "[fail] "); + } + printfmt("{}\n", s.text); + } + } + console_color cc(White); + } + return !failed; + } + + void check(bool result, const std::string& value, const char* expr) + { + subtests.push_back(subtest{ result, as_string(padleft(22, expr), " | ", value), comment }); + result ? success++ : failed++; + if (show_progress) + { + if (result) + { + console_color cc(Green); + print("."); + } + else + { + console_color cc(Red); + print("E"); + } + } + } + + template <typename Op, typename L, typename R> + void check(const comparison<Op, L, R>& comparison, const char* expr) + { + bool result = comparison(); + check(result, as_string(comparison.left, " ", Op::op(), " ", comparison.right), expr); + } + + template <typename L> + void check(const half_comparison<L>& comparison, const char* expr) + { + bool result = comparison.left ? true : false; + check(result, as_string(comparison.left), expr); + } + + void set_comment(const std::string& text) + { + comment = text; + if (show_progress) + { + println(); + println(comment, ":"); + } + } + + struct subtest + { + bool success; + std::string text; + std::string comment; + }; + + test_func func; + const char* name; + std::vector<subtest> subtests; + std::string comment; + int success; + int failed; + double time; + bool show_progress; +}; + +template <typename Number> +struct statistics +{ + Number minimum; + Number maximum; + double sum; + unsigned long long count; + std::vector<Number> values; + void reset() { *this = statistics<Number>(); } + std::string str() + { + return format("{} ... {} (avg={}, median={})\n", minimum, maximum, cometa::fmt<'f', 2>(average()), + median()); + } + double average() const { return sum / count; } + Number median() + { + std::sort(values.begin(), values.end()); + return values.empty() ? Number() : values[values.size() / 2]; + } + statistics() + : sum(), count(), minimum(std::numeric_limits<Number>::max()), + maximum(std::numeric_limits<Number>::min()) + { + } + void operator()(Number x) + { + minimum = std::min(minimum, x); + maximum = std::max(maximum, x); + sum += x; + count++; + values.push_back(x); + } +}; + +template <typename Arg0, typename Fn> +void matrix(named_arg<Arg0>&& arg0, Fn&& fn) +{ + cforeach(std::forward<Arg0>(arg0.value), [&](auto v0) { + active_test()->set_comment(as_string(arg0.name, " = ", v0)); + fn(v0); + }); + if (active_test()->show_progress) + println(); +} + +template <typename Arg0, typename Arg1, typename Fn> +void matrix(named_arg<Arg0>&& arg0, named_arg<Arg1>&& arg1, Fn&& fn) +{ + cforeach(std::forward<Arg0>(arg0.value), std::forward<Arg1>(arg1.value), [&](auto v0, auto v1) { + active_test()->set_comment(as_string(arg0.name, " = ", v0, ", ", arg1.name, " = ", v1)); + fn(v0, v1); + }); + if (active_test()->show_progress) + println(); +} + +template <typename Arg0, typename Arg1, typename Arg2, typename Fn> +void matrix(named_arg<Arg0>&& arg0, named_arg<Arg1>&& arg1, named_arg<Arg2>&& arg2, Fn&& fn) +{ + cforeach(std::forward<Arg0>(arg0.value), std::forward<Arg1>(arg1.value), std::forward<Arg2>(arg2.value), + [&](auto v0, auto v1, auto v2) { + active_test()->set_comment( + as_string(arg0.name, " = ", v0, ", ", arg1.name, " = ", v1, ", ", arg2.name, " = ", v2)); + fn(v0, v1, v2); + }); + if (active_test()->show_progress) + println(); +} + +static int run_all(const std::string& name = std::string(), bool show_successful = false) +{ + std::vector<test_case*> success; + std::vector<test_case*> failed; + for (test_case* t : test_case::tests()) + { + if (name.empty() || t->name == name) + t->run(show_successful) ? success.push_back(t) : failed.push_back(t); + } + printfmt("{}\n", std::string(79, '=')); + if (!success.empty()) + { + console_color cc(Green); + printfmt("[{}]", padcenter(11, "SUCCESS", '-')); + printfmt(" {} tests\n", success.size()); + } + if (!failed.empty()) + { + console_color cc(Red); + printfmt("[{}]", padcenter(11, "ERROR", '-')); + printfmt(" {} tests\n", failed.size()); + } + return static_cast<int>(failed.size()); +} + +template <typename T1, typename T2> +void assert_is_same() +{ + static_assert(std::is_same<T1, T2>::value, ""); +} +template <typename T1, typename T2> +void assert_is_same_decay() +{ + static_assert(std::is_same<cometa::decay<T1>, cometa::decay<T2>>::value, ""); +} + +#define TESTO_CHECK(...) \ + do \ + { \ + ::testo::active_test()->check(::testo::make_comparison() <= __VA_ARGS__, #__VA_ARGS__); \ + } while (0) + +#define TESTO_TEST(name) \ + static void test_function_##name(); \ + ::testo::test_case test_case_##name(&test_function_##name, #name); \ + static void CMT_NOINLINE test_function_##name() + +#define TESTO_DTEST(name) \ + template <typename> \ + static void disabled_test_function_##name() + +#ifndef TESTO_NO_SHORT_MACROS +#define CHECK TESTO_CHECK +#define TEST TESTO_TEST +#define DTEST TESTO_DTEST +#endif +} + +CLANG_DIAGNOSTIC_PRAGMA(pop) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt @@ -18,9 +18,10 @@ cmake_minimum_required(VERSION 2.8) set(TEST_SRC - testo/testo.hpp ) +add_definitions(-DKFR_TESTING=1) + include_directories(../include) if (NOT ARM) diff --git a/tests/base_test.cpp b/tests/base_test.cpp @@ -4,7 +4,8 @@ * See LICENSE.txt for details */ -#include "testo/testo.hpp" +#include <kfr/testo/testo.hpp> + #include <kfr/base.hpp> #include <kfr/io.hpp> @@ -131,13 +132,13 @@ TEST(test_basic) CHECK(transpose<4>(sixteen) == vec<float, 16>(0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15)); } -TEST(vec_concat) +TEST(concat) { CHECK(concat(vec<f32, 1>{ 1 }, vec<f32, 2>{ 2, 3 }, vec<f32, 1>{ 4 }, vec<f32, 3>{ 5, 6, 7 }) // == vec<f32, 7>{ 1, 2, 3, 4, 5, 6, 7 }); } -TEST(vec_split) +TEST(split) { vec<f32, 1> a1; vec<f32, 2> a23; @@ -150,21 +151,21 @@ TEST(vec_split) CHECK(a567 == vec<f32, 3>{ 5, 6, 7 }); } -TEST(vec_broadcast) +TEST(broadcast) { CHECK(broadcast<5>(3.f) == vec<f32, 5>{ 3, 3, 3, 3, 3 }); CHECK(broadcast<6>(1.f, 2.f) == vec<f32, 6>{ 1, 2, 1, 2, 1, 2 }); CHECK(broadcast<6>(1.f, 2.f, 3.f) == vec<f32, 6>{ 1, 2, 3, 1, 2, 3 }); } -TEST(vec_resize) +TEST(resize) { CHECK(resize<5>(make_vector(3.f)) == vec<f32, 5>{ 3, 3, 3, 3, 3 }); CHECK(resize<6>(make_vector(1.f, 2.f)) == vec<f32, 6>{ 1, 2, 1, 2, 1, 2 }); CHECK(resize<6>(make_vector(1.f, 2.f, 3.f)) == vec<f32, 6>{ 1, 2, 3, 1, 2, 3 }); } -TEST(vec_make_vector) +TEST(make_vector) { const signed char ch = -1; CHECK(make_vector(1, 2, ch) == vec<i32, 3>{ 1, 2, -1 }); @@ -178,20 +179,20 @@ TEST(vec_make_vector) CHECK(make_vector(1.f, f32x2{ 10, 20 }) == vec<vec<f32, 2>, 2>{ f32x2{ 1, 1 }, f32x2{ 10, 20 } }); } -TEST(vec_apply) +TEST(apply) { CHECK(apply([](int x) { return x + 1; }, make_vector(1, 2, 3, 4, 5)) == make_vector(2, 3, 4, 5, 6)); CHECK(apply(fn::sqr(), make_vector(1, 2, 3, 4, 5)) == make_vector(1, 4, 9, 16, 25)); } -TEST(vec_zerovector) +TEST(zerovector) { CHECK(zerovector<f32, 3>() == f32x3{ 0, 0, 0 }); // CHECK(zerovector<i16, 3>() == i16x3{ 0, 0, 0 }); // clang 3.9 (trunk) crashes here CHECK(zerovector(f64x8{}) == f64x8{ 0, 0, 0, 0, 0, 0, 0, 0 }); } -TEST(vec_allonesvector) +TEST(allonesvector) { CHECK(bitcast<u32>(constants<f32>::allones()) == 0xFFFFFFFFu); CHECK(bitcast<u64>(constants<f64>::allones()) == 0xFFFFFFFFFFFFFFFFull); @@ -201,7 +202,7 @@ TEST(vec_allonesvector) CHECK(allonesvector<u8, 3>() == u8x3{ 255, 255, 255 }); } -TEST(vec_low_high) +TEST(low_high) { CHECK(low(vec<u8, 8>(1, 2, 3, 4, 5, 6, 7, 8)) == vec<u8, 4>(1, 2, 3, 4)); CHECK(high(vec<u8, 8>(1, 2, 3, 4, 5, 6, 7, 8)) == vec<u8, 4>(5, 6, 7, 8)); @@ -226,7 +227,7 @@ TEST(vec_low_high) } #ifdef CMT_COMPILER_CLANG -TEST(vec_matrix) +TEST(matrix) { using i32x2x2 = vec<vec<int, 2>, 2>; const i32x2x2 m22{ i32x2{ 1, 2 }, i32x2{ 3, 4 } }; @@ -245,7 +246,7 @@ TEST(vec_matrix) } #endif -TEST(vec_is_convertible) +TEST(is_convertible) { static_assert(std::is_convertible<float, f32x4>::value, ""); static_assert(std::is_convertible<float, f64x8>::value, ""); @@ -329,6 +330,13 @@ TEST(transcendental) CHECK(kfr::log(2.45) == 0.89608802455663561677548191074382); } +TEST(horner) +{ + CHECK(horner(pack(0, 1, 2, 3), 1, 2, 3) == pack(1, 6, 17, 34)); + CHECK(horner_odd(pack(0, 1, 2, 3), 1, 2, 3) == pack(0, 6, 114, 786)); + CHECK(horner_even(pack(0, 1, 2, 3), 1, 2, 3) == pack(1, 6, 57, 262)); +} + TEST(test_stat) { { diff --git a/tests/complex_test.cpp b/tests/complex_test.cpp @@ -4,7 +4,8 @@ * See LICENSE.txt for details */ -#include "testo/testo.hpp" +#include <kfr/testo/testo.hpp> + #include <kfr/base.hpp> #include <kfr/io.hpp> diff --git a/tests/dft_test.cpp b/tests/dft_test.cpp @@ -4,7 +4,8 @@ * See LICENSE.txt for details */ -#include "testo/testo.hpp" +#include <kfr/testo/testo.hpp> + #include <kfr/base.hpp> #include <kfr/dft.hpp> #include <kfr/dsp.hpp> diff --git a/tests/dsp_test.cpp b/tests/dsp_test.cpp @@ -4,7 +4,8 @@ * See LICENSE.txt for details */ -#include "testo/testo.hpp" +#include <kfr/testo/testo.hpp> + #include <kfr/base.hpp> #include <kfr/dsp.hpp> #include <kfr/io.hpp> @@ -14,19 +15,9 @@ using namespace kfr; TEST(delay) { const univector<float, 33> v1 = counter() + 100; - const univector<float, 33> v2 = delay(v1); - CHECK(v2[0] == 0); - CHECK(v2[1] == 100); - CHECK(v2[2] == 101); - CHECK(v2[19] == 118); + CHECK_EXPRESSION(delay(v1), 33, [](size_t i) { return i < 1 ? 0.f : (i - 1) + 100.f; }); - const univector<float, 33> v3 = delay(v1, csize_t<3>()); - CHECK(v3[0] == 0); - CHECK(v3[1] == 0); - CHECK(v3[2] == 0); - CHECK(v3[3] == 100); - CHECK(v3[4] == 101); - CHECK(v3[19] == 116); + CHECK_EXPRESSION(delay<3>(v1), 33, [](size_t i) { return i < 3 ? 0.f : (i - 3) + 100.f; }); } TEST(fracdelay) @@ -47,12 +38,8 @@ TEST(fracdelay) TEST(mixdown) { - univector<double, 20> ch1 = counter(); - univector<double, 20> ch2 = counter() * 2 + 100; - univector<double, 20> mix = mixdown(ch1, ch2); - CHECK(mix[0] == 100); - CHECK(mix[1] == 103); - CHECK(mix[19] == 157); + CHECK_EXPRESSION(mixdown(counter(), counter() * 2 + 100), infinite_size, + [](size_t i) { return i + i * 2 + 100; }); } #ifdef CMT_COMPILER_CLANG @@ -64,12 +51,8 @@ TEST(mixdown_stereo) univector<double, 21> side; unpack(mid, side) = mixdown_stereo(left, right, matrix_sum_diff()); - CHECK(mid[0] == 100); - CHECK(side[0] == -100); - CHECK(mid[1] == 103); - CHECK(side[1] == -101); - CHECK(mid[20] == 160); - CHECK(side[20] == -120); + CHECK_EXPRESSION(mid, 21, [](size_t i) { return i + i * 2.0 + 100.0; }); + CHECK_EXPRESSION(side, 21, [](size_t i) { return i - (i * 2.0 + 100.0); }); } #endif @@ -77,7 +60,7 @@ TEST(phasor) { constexpr fbase sr = 44100.0; univector<fbase, 100> v1 = sinenorm(phasor(15000, sr)); - univector<fbase, 100> v2 = sin(c_pi<fbase, 2> * counter(0, 15000 / sr)); + univector<fbase, 100> v2 = sin(constants<fbase>::pi_s(2) * counter(0, 15000 / sr)); CHECK(rms(v1 - v2) < 1.e-5); } diff --git a/tests/expression_test.cpp b/tests/expression_test.cpp @@ -4,7 +4,7 @@ * See LICENSE.txt for details */ -#include "testo/testo.hpp" +#include <kfr/testo/testo.hpp> #include <kfr/base.hpp> #include <kfr/cometa/function.hpp> @@ -17,30 +17,18 @@ TEST(pack) { const univector<float, 21> v1 = 1 + counter(); const univector<float, 21> v2 = v1 * 11; - const univector<f32x2, 21> v3 = pack(v1, v2); - CHECK(v3[0] == f32x2{ 1, 11 }); - CHECK(v3[1] == f32x2{ 2, 22 }); - CHECK(v3[18] == f32x2{ 19, 209 }); - CHECK(v3[19] == f32x2{ 20, 220 }); - CHECK(v3[20] == f32x2{ 21, 231 }); - - const univector<f32x2, 21> v4 = bind_expression(fn::reverse(), v3); - CHECK(v4[0] == f32x2{ 11, 1 }); - CHECK(v4[1] == f32x2{ 22, 2 }); - CHECK(v4[18] == f32x2{ 209, 19 }); - CHECK(v4[19] == f32x2{ 220, 20 }); - CHECK(v4[20] == f32x2{ 231, 21 }); + + CHECK_EXPRESSION(pack(v1, v2), 21, [](size_t i) { return f32x2{ 1 + i, (1 + i) * 11 }; }); + + CHECK_EXPRESSION(bind_expression(fn::reverse(), pack(v1, v2)), 21, [](size_t i) { + return f32x2{ (1 + i) * 11, 1 + i }; + }); } TEST(adjacent) { - univector<int, 20> v1 = adjacent(fn::mul(), counter()); - CHECK(v1[0] == 0); - CHECK(v1[1] == 0); - CHECK(v1[2] == 2); - CHECK(v1[3] == 6); - CHECK(v1[4] == 12); - CHECK(v1[19] == 342); + CHECK_EXPRESSION(adjacent(fn::mul(), counter()), infinite_size, + [](size_t i) { return i > 0 ? i * (i - 1) : 0; }); } TEST(padded) @@ -48,27 +36,16 @@ TEST(padded) static_assert(is_infinite<decltype(padded(counter()))>::value, ""); static_assert(is_infinite<decltype(padded(truncate(counter(), 100)))>::value, ""); - univector<int, 21> v1 = padded(truncate(counter(), 6), -1); - CHECK(v1[0] == 0); - CHECK(v1[1] == 1); - CHECK(v1[2] == 2); - CHECK(v1[3] == 3); - CHECK(v1[4] == 4); - CHECK(v1[5] == 5); - CHECK(v1[6] == -1); - CHECK(v1[20] == -1); + CHECK_EXPRESSION(padded(truncate(counter(), 6), -1), infinite_size, + [](size_t i) { return i >= 6 ? -1 : i; }); } TEST(rebind) { auto c_minus_two = counter() - 2; auto four_minus_c = rebind(c_minus_two, 4, counter()); - univector<int, 5> v1 = c_minus_two; - univector<int, 5> v2 = four_minus_c; - CHECK(v1[0] == -2); - CHECK(v1[1] == -1); - CHECK(v2[0] == 4); - CHECK(v2[1] == 3); + CHECK_EXPRESSION(c_minus_two, infinite_size, [](size_t i) { return i - 2; }); + CHECK_EXPRESSION(four_minus_c, infinite_size, [](size_t i) { return 4 - i; }); } TEST(test_arg_access) @@ -78,11 +55,8 @@ TEST(test_arg_access) auto e1 = std::move(v1) + 10; std::get<0>(e1.args)[0] = 100; std::get<1>(e1.args).val = 1; - univector<float, 10> v2 = e1; - CHECK(v2[0] == 101); - CHECK(v2[1] == 2); - CHECK(v2[2] == 3); - CHECK(v2[9] == 10); + + CHECK_EXPRESSION(e1, 10, [](size_t i) { return (i == 0 ? 100 : i) + 1; }); } TEST(test_arg_replace) @@ -91,11 +65,8 @@ TEST(test_arg_replace) univector<float, 10> v2 = -counter(); auto e1 = to_pointer(v1) * 10; std::get<0>(e1.args) = to_pointer(v2); - univector<float, 10> v3 = e1; - CHECK(v3[0] == 0); - CHECK(v3[1] == -10); - CHECK(v3[2] == -20); - CHECK(v3[9] == -90); + + CHECK_EXPRESSION(e1, 10, [](size_t i) { return i * -10.0; }); } TEST(size_calc) @@ -112,21 +83,14 @@ TEST(size_calc) TEST(reverse) { - univector<int, 21> a = reverse(truncate(counter(), 21)); - CHECK(a[0] == 20); - CHECK(a[1] == 19); - CHECK(a[20] == 0); + CHECK_EXPRESSION(reverse(truncate(counter(), 21)), 21, [](size_t i) { return 20 - i; }); } TEST(mix) { - univector<float, 21> a = mix(sequence(0, 0.5f, 1, 0.5f), counter(), counter() * 10); - CHECK(a[0] == 0); - CHECK(a[1] == 5.5); - CHECK(a[2] == 20); - CHECK(a[3] == 16.5); - CHECK(a[4] == 4); - CHECK(a[20] == 20); + CHECK_EXPRESSION(mix(sequence(0, 0.5f, 1, 0.5f), counter(), counter() * 10), infinite_size, [](size_t i) { + return mix(std::array<float, 4>{ 0, 0.5f, 1, 0.5f }[i % 4], i, i * 10); + }); } constexpr inline size_t fast_range_sum(size_t stop) { return stop * (stop + 1) / 2; } diff --git a/tests/intrinsic_test.cpp b/tests/intrinsic_test.cpp @@ -4,7 +4,7 @@ * See LICENSE.txt for details */ -#include "testo/testo.hpp" +#include <kfr/testo/testo.hpp> #include <kfr/base.hpp> #include <kfr/dsp.hpp> diff --git a/tests/multiarch.cpp b/tests/multiarch.cpp @@ -4,7 +4,7 @@ * See LICENSE.txt for details */ -#include "testo/testo.hpp" +#include <kfr/testo/testo.hpp> #include <kfr/base.hpp> #include <kfr/dsp.hpp> diff --git a/tests/testo/print_colored.hpp b/tests/testo/print_colored.hpp @@ -1,153 +0,0 @@ -#pragma once -#include <cstdint> - -#if defined(_WIN32) -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include <windows.h> -#endif - -namespace print_colored -{ - -enum text_color : uint32_t -{ - Black = 0x00, - DarkBlue = 0x01, - DarkGreen = 0x02, - DarkCyan = 0x03, - DarkRed = 0x04, - DarkMagenta = 0x05, - DarkYellow = 0x06, - LightGrey = 0x07, - Gray = 0x08, - Blue = 0x09, - Green = 0x0A, - Cyan = 0x0B, - Red = 0x0C, - Magenta = 0x0D, - Yellow = 0x0E, - White = 0x0F, - BgBlack = 0x00, - BgDarkBlue = 0x10, - BgDarkGreen = 0x20, - BgDarkCyan = 0x30, - BgDarkRed = 0x40, - BgDarkMagenta = 0x50, - BgDarkYellow = 0x60, - BgLightGrey = 0x70, - BgGray = 0x80, - BgBlue = 0x90, - BgGreen = 0xA0, - BgCyan = 0xB0, - BgRed = 0xC0, - BgMagenta = 0xD0, - BgYellow = 0xE0, - BgWhite = 0xF0, - - Normal = BgBlack | LightGrey -}; - -enum console_buffer -{ - ConsoleStdOutput, - ConsoleStdError -}; - -#if defined(_WIN32) -typedef HANDLE console_handle_t; - -inline console_handle_t console_handle(console_buffer console = ConsoleStdOutput) -{ - static HANDLE con_out = ::GetStdHandle(STD_OUTPUT_HANDLE); - static HANDLE con_err = ::GetStdHandle(STD_ERROR_HANDLE); - return console == ConsoleStdOutput ? con_out : con_err; -} - -#endif - -struct console_color -{ -public: - console_color(text_color c, console_buffer console = ConsoleStdOutput) - : m_old(get(console)), m_console(console) - { - set(c, m_console); - } - - ~console_color() { set(m_old, m_console); } - -private: - text_color get(console_buffer console = ConsoleStdOutput) - { -#ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO info; - ::GetConsoleScreenBufferInfo(console_handle(console), &info); - return static_cast<text_color>(info.wAttributes & 0xFF); -#else - return static_color(); -#endif - } - - void set(text_color new_color, console_buffer console = ConsoleStdOutput) - { -#ifdef _WIN32 - ::SetConsoleTextAttribute(console_handle(console), static_cast<WORD>(new_color)); -#else - if (new_color != Normal) - { - uint8_t t = new_color & 0xF; - uint8_t b = (new_color & 0xF0) >> 4; - uint8_t tnum = 30 + ((t & 1) << 2 | (t & 2) | (t & 4) >> 2); - uint8_t bnum = 40 + ((b & 1) << 2 | (b & 2) | (b & 4) >> 2); - if (t & 8) - tnum += 60; - if (b & 8) - bnum += 60; - printf("\x1B[%d;%dm", tnum, bnum); - } - else - { - printf("\x1B[0m"); - } - static_color() = new_color; -#endif - } - - text_color m_old; - console_buffer m_console; -#ifndef _WIN32 - static text_color& static_color() - { - static text_color color = Normal; - return color; - } -#endif -}; - -template <text_color color, console_buffer console = ConsoleStdOutput> -struct colored_text_tpl : public console_color -{ -public: - colored_text_tpl() : console_color(color, console) {} - -private: -}; - -typedef colored_text_tpl<DarkBlue> darkblue_text; -typedef colored_text_tpl<DarkGreen> darkgreen_text; -typedef colored_text_tpl<DarkCyan> darkcyan_text; -typedef colored_text_tpl<DarkRed> darkred_text; -typedef colored_text_tpl<DarkMagenta> darkmagenta_text; -typedef colored_text_tpl<DarkYellow> darkyellow_text; -typedef colored_text_tpl<LightGrey> lightgrey_text; -typedef colored_text_tpl<Gray> gray_text; -typedef colored_text_tpl<Blue> blue_text; -typedef colored_text_tpl<Green> green_text; -typedef colored_text_tpl<Cyan> cyan_text; -typedef colored_text_tpl<Red> red_text; -typedef colored_text_tpl<Magenta> magenta_text; -typedef colored_text_tpl<Yellow> yellow_text; -typedef colored_text_tpl<White> white_text; -} diff --git a/tests/testo/testo.hpp b/tests/testo/testo.hpp @@ -1,565 +0,0 @@ -#pragma once - -#include <kfr/cometa/tuple.hpp> - -#include <kfr/cometa.hpp> -#include <kfr/cometa/range.hpp> -#include <kfr/cometa/string.hpp> - -#include <algorithm> -#include <ctime> -#include <functional> -#include <sstream> -#include <utility> -#include <vector> -#ifdef TESTO_MPFR -#include <mpfr/mpfr.hpp> -#include <mpfr/mpfr_tostring.hpp> -#endif -#include "print_colored.hpp" -#include <chrono> -#include <cmath> - -#if !defined CLANG_DIAGNOSTIC_PRAGMA -#if defined __clang__ -#define TESTO_STRING(str) #str -#define CLANG_DIAGNOSTIC_PRAGMA(pragma) _Pragma(TESTO_STRING(clang diagnostic pragma)) -#else -#define CLANG_DIAGNOSTIC_PRAGMA(pragma) -#endif -#endif - -CLANG_DIAGNOSTIC_PRAGMA(push) -CLANG_DIAGNOSTIC_PRAGMA(ignored "-Wexit-time-destructors") -CLANG_DIAGNOSTIC_PRAGMA(ignored "-Wpadded") -CLANG_DIAGNOSTIC_PRAGMA(ignored "-Wshadow") - -namespace testo -{ - -using namespace cometa; - -#ifdef TESTO_MPFR -using reference_number = mpfr::number; -#else -using reference_number = long double; -#endif - -#ifdef TESTO_MPFR -template <typename T> -inline double ulp_distance(const mpfr::number& reference, T test) -{ - if (std::isnan(test) && reference.isnan()) - return 0.0; - if (std::isinf(test) && (reference.isinfinity() || mpfr::abs(reference) > std::numeric_limits<T>::max())) - { - if ((reference < 0 && test < 0) || (reference > 0 && test > 0)) - return 0.0; - else - return std::numeric_limits<double>::infinity(); - } - mpfr::number testreal = test; - T next = std::nexttoward(test, std::numeric_limits<long double>::infinity()); - mpfr::number ulp = testreal - mpfr::number(next); - return std::abs(static_cast<double>((reference - testreal) / ulp)); -} -inline std::string number_to_string(const mpfr::number& reference, int precision) -{ - return mpfr::to_string(reference, precision, 'g'); -} -#else -template <typename T> -inline double ulp_distance(long double reference, T test) -{ - if (__builtin_isnan(test) && __builtin_isnan(reference)) - return 0.0; - if (__builtin_isinf(test) && - (__builtin_isinf(reference) || std::fabs(reference) > std::numeric_limits<T>::max())) - { - if ((reference < 0 && test < 0) || (reference > 0 && test > 0)) - return 0.0; - else - return std::numeric_limits<double>::infinity(); - } - long double test80 = test; - T next = std::nexttoward(test, std::numeric_limits<long double>::infinity()); - long double ulp = test80 - static_cast<long double>(next); - return std::abs(static_cast<double>((reference - test80) / ulp)); -} -#endif - -using namespace print_colored; - -template <typename Fn, typename L, typename R> -struct comparison -{ - L left; - R right; - Fn cmp; - - comparison(L&& left, R&& right) : left(std::forward<L>(left)), right(std::forward<R>(right)) {} - - bool operator()() const { return cmp(left, right); } -}; - -template <typename Left, typename Right> -struct static_assert_type_eq -{ - static_assert(std::is_same<Left, Right>::value, "std::is_same<Left, Right>::value"); -}; - -template <typename T, T left, T right> -struct static_assert_eq -{ - static_assert(left == right, "left == right"); -}; - -template <typename L, typename R, typename = void> -struct equality_comparer -{ - bool operator()(const L& l, const R& r) const { return l == r; } -}; - -CMT_PRAGMA_GNU(GCC diagnostic push) -CMT_PRAGMA_GNU(GCC diagnostic ignored "-Wfloat-equal") - -template <typename T> -inline T& epsilon() -{ - static T value = std::numeric_limits<T>::epsilon(); - return value; -} - -template <> -struct equality_comparer<float, float> -{ - bool operator()(const float& l, const float& r) const { return !(std::abs(l - r) > epsilon<float>()); } -}; -template <> -struct equality_comparer<double, double> -{ - bool operator()(const double& l, const double& r) const { return !(std::abs(l - r) > epsilon<double>()); } -}; -template <> -struct equality_comparer<long double, long double> -{ - bool operator()(const long double& l, const long double& r) const - { - return !(std::abs(l - r) > epsilon<long double>()); - } -}; - -CMT_PRAGMA_GNU(GCC diagnostic pop) - -template <typename L, typename R> -struct equality_comparer<L, R, void_t<enable_if<!compound_type_traits<L>::is_scalar>>> -{ - using Tsubtype = subtype<L>; - constexpr static static_assert_type_eq<subtype<L>, subtype<R>> assert{}; - - bool operator()(const L& l, const R& r) const - { - if (compound_type_traits<L>::width != compound_type_traits<R>::width) - return false; - - compound_type_traits<L> itl; - compound_type_traits<R> itr; - for (size_t i = 0; i < compound_type_traits<L>::width; i++) - { - equality_comparer<Tsubtype, Tsubtype> cmp; - if (!cmp(itl.at(l, i), itr.at(r, i))) - return false; - } - return true; - } -}; - -struct cmp_eq -{ - static const char* op() { return "=="; } - - template <typename L, typename R> - bool operator()(L&& left, R&& right) const - { - equality_comparer<decay<L>, decay<R>> eq; - return eq(left, right); - } -}; - -struct cmp_ne -{ - static const char* op() { return "!="; } - - template <typename L, typename R> - bool operator()(L&& left, R&& right) const - { - return !cmp_eq()(left, right); - } -}; - -struct cmp_lt -{ - static const char* op() { return "<"; } - - template <typename L, typename R> - bool operator()(L&& left, R&& right) const - { - return left < right; - } -}; - -struct cmp_gt -{ - static const char* op() { return ">"; } - - template <typename L, typename R> - bool operator()(L&& left, R&& right) const - { - return left > right; - } -}; - -struct cmp_le -{ - static const char* op() { return "<="; } - - template <typename L, typename R> - bool operator()(L&& left, R&& right) const - { - return left <= right; - } -}; - -struct cmp_ge -{ - static const char* op() { return ">="; } - - template <typename L, typename R> - bool operator()(L&& left, R&& right) const - { - return left >= right; - } -}; - -template <typename L> -struct half_comparison -{ - half_comparison(L&& left) : left(std::forward<L>(left)) {} - - template <typename R> - comparison<cmp_eq, L, R> operator==(R&& right) - { - return comparison<cmp_eq, L, R>(std::forward<L>(left), std::forward<R>(right)); - } - - template <typename R> - comparison<cmp_ne, L, R> operator!=(R&& right) - { - return comparison<cmp_ne, L, R>(std::forward<L>(left), std::forward<R>(right)); - } - - template <typename R> - comparison<cmp_lt, L, R> operator<(R&& right) - { - return comparison<cmp_lt, L, R>(std::forward<L>(left), std::forward<R>(right)); - } - - template <typename R> - comparison<cmp_gt, L, R> operator>(R&& right) - { - return comparison<cmp_gt, L, R>(std::forward<L>(left), std::forward<R>(right)); - } - - template <typename R> - comparison<cmp_le, L, R> operator<=(R&& right) - { - return comparison<cmp_le, L, R>(std::forward<L>(left), std::forward<R>(right)); - } - - template <typename R> - comparison<cmp_ge, L, R> operator>=(R&& right) - { - return comparison<cmp_ge, L, R>(std::forward<L>(left), std::forward<R>(right)); - } - - L left; -}; - -struct make_comparison -{ - template <typename L> - half_comparison<L> operator<=(L&& left) - { - return half_comparison<L>(std::forward<L>(left)); - } -}; - -inline std::vector<std::string> split(const std::string& text, char delimeter) -{ - std::string r = text; - size_t prev_pos = 0; - size_t start_pos = 0; - std::vector<std::string> list; - while ((start_pos = r.find(delimeter, prev_pos)) != std::string::npos) - { - list.push_back(text.substr(prev_pos, start_pos - prev_pos)); - prev_pos = start_pos + 1; - } - list.push_back(text.substr(prev_pos)); - return list; -} - -struct test_case; - -inline test_case*& active_test() -{ - static test_case* instance = nullptr; - return instance; -} - -struct test_case -{ - using test_func = void (*)(); - - static std::vector<test_case*>& tests() - { - static std::vector<test_case*> list; - return list; - } - - test_case(test_func func, const char* name) - : func(func), name(name), success(0), failed(0), time(0), show_progress(false) - { - tests().push_back(this); - } - - bool run(bool show_successful) - { - using namespace std::chrono; - using time_point = high_resolution_clock::time_point; - { - console_color cc(Cyan); - printfmt("[{}]", padcenter(11, std::string("RUN"), '-')); - } - printfmt(" {}...\n", name); - time_point start = high_resolution_clock::now(); - active_test() = this; - func(); - active_test() = nullptr; - time_point stop = high_resolution_clock::now(); - time = duration_cast<duration<double>>(stop - start).count(); - - { - console_color cc(failed ? Red : Green); - printfmt("[{}] {} subtests of {}\n", padcenter(11, failed ? "ERROR" : "SUCCESS", '-'), - failed ? failed : success, success + failed); - } - if (failed) - { - for (const subtest& s : subtests) - { - if ((s.success && show_successful) || !s.success) - { - if (!s.comment.empty()) - printfmt(" {}:\n", s.comment); - { - console_color cc(s.success ? Green : Red); - printfmt(" {} ", s.success ? "[success]" : "[fail] "); - } - printfmt("{}\n", s.text); - } - } - console_color cc(White); - } - return !failed; - } - - void check(bool result, const std::string& value, const char* expr) - { - subtests.push_back(subtest{ result, as_string(padleft(22, expr), " | ", value), comment }); - result ? success++ : failed++; - if (show_progress) - { - if (result) - { - console_color cc(Green); - print("."); - } - else - { - console_color cc(Red); - print("E"); - } - } - } - - template <typename Op, typename L, typename R> - void check(const comparison<Op, L, R>& comparison, const char* expr) - { - bool result = comparison(); - check(result, as_string(comparison.left, " ", Op::op(), " ", comparison.right), expr); - } - - template <typename L> - void check(const half_comparison<L>& comparison, const char* expr) - { - bool result = comparison.left ? true : false; - check(result, as_string(comparison.left), expr); - } - - void set_comment(const std::string& text) - { - comment = text; - if (show_progress) - { - println(); - println(comment, ":"); - } - } - - struct subtest - { - bool success; - std::string text; - std::string comment; - }; - - test_func func; - const char* name; - std::vector<subtest> subtests; - std::string comment; - int success; - int failed; - double time; - bool show_progress; -}; - -template <typename Number> -struct statistics -{ - Number minimum; - Number maximum; - double sum; - unsigned long long count; - std::vector<Number> values; - void reset() { *this = statistics<Number>(); } - std::string str() - { - return format("{} ... {} (avg={}, median={})\n", minimum, maximum, cometa::fmt<'f', 2>(average()), - median()); - } - double average() const { return sum / count; } - Number median() - { - std::sort(values.begin(), values.end()); - return values.empty() ? Number() : values[values.size() / 2]; - } - statistics() - : sum(), count(), minimum(std::numeric_limits<Number>::max()), - maximum(std::numeric_limits<Number>::min()) - { - } - void operator()(Number x) - { - minimum = std::min(minimum, x); - maximum = std::max(maximum, x); - sum += x; - count++; - values.push_back(x); - } -}; - -template <typename Arg0, typename Fn> -void matrix(named_arg<Arg0>&& arg0, Fn&& fn) -{ - cforeach(std::forward<Arg0>(arg0.value), [&](auto v0) { - active_test()->set_comment(as_string(arg0.name, " = ", v0)); - fn(v0); - }); - if (active_test()->show_progress) - println(); -} - -template <typename Arg0, typename Arg1, typename Fn> -void matrix(named_arg<Arg0>&& arg0, named_arg<Arg1>&& arg1, Fn&& fn) -{ - cforeach(std::forward<Arg0>(arg0.value), std::forward<Arg1>(arg1.value), [&](auto v0, auto v1) { - active_test()->set_comment(as_string(arg0.name, " = ", v0, ", ", arg1.name, " = ", v1)); - fn(v0, v1); - }); - if (active_test()->show_progress) - println(); -} - -template <typename Arg0, typename Arg1, typename Arg2, typename Fn> -void matrix(named_arg<Arg0>&& arg0, named_arg<Arg1>&& arg1, named_arg<Arg2>&& arg2, Fn&& fn) -{ - cforeach(std::forward<Arg0>(arg0.value), std::forward<Arg1>(arg1.value), std::forward<Arg2>(arg2.value), - [&](auto v0, auto v1, auto v2) { - active_test()->set_comment( - as_string(arg0.name, " = ", v0, ", ", arg1.name, " = ", v1, ", ", arg2.name, " = ", v2)); - fn(v0, v1, v2); - }); - if (active_test()->show_progress) - println(); -} - -static int run_all(const std::string& name = std::string(), bool show_successful = false) -{ - std::vector<test_case*> success; - std::vector<test_case*> failed; - for (test_case* t : test_case::tests()) - { - if (name.empty() || t->name == name) - t->run(show_successful) ? success.push_back(t) : failed.push_back(t); - } - printfmt("{}\n", std::string(79, '=')); - if (!success.empty()) - { - console_color cc(Green); - printfmt("[{}]", padcenter(11, "SUCCESS", '-')); - printfmt(" {} tests\n", success.size()); - } - if (!failed.empty()) - { - console_color cc(Red); - printfmt("[{}]", padcenter(11, "ERROR", '-')); - printfmt(" {} tests\n", failed.size()); - } - return static_cast<int>(failed.size()); -} - -template <typename T1, typename T2> -void assert_is_same() -{ - static_assert(std::is_same<T1, T2>::value, ""); -} -template <typename T1, typename T2> -void assert_is_same_decay() -{ - static_assert(std::is_same<cometa::decay<T1>, cometa::decay<T2>>::value, ""); -} - -#define TESTO_CHECK(...) \ - do \ - { \ - ::testo::active_test()->check(::testo::make_comparison() <= __VA_ARGS__, #__VA_ARGS__); \ - } while (0) - -#define TESTO_TEST(name) \ - void test_function_##name(); \ - ::testo::test_case test_case_##name(&test_function_##name, #name); \ - void CMT_NOINLINE test_function_##name() - -#define TESTO_DTEST(name) \ - template <typename> \ - void disabled_test_function_##name() - -#ifndef TESTO_NO_SHORT_MACROS -#define CHECK TESTO_CHECK -#define TEST TESTO_TEST -#define DTEST TESTO_DTEST -#endif -} - -CLANG_DIAGNOSTIC_PRAGMA(pop) diff --git a/tests/transcendental_test.cpp b/tests/transcendental_test.cpp @@ -4,7 +4,8 @@ * See LICENSE.txt for details */ -#include "testo/testo.hpp" +#include <kfr/testo/testo.hpp> + #include <kfr/base.hpp> #include <kfr/io.hpp>