123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- // -*- mode: c++; coding: utf-8 -*-
- // ra-ra - Test library.
- // (c) Daniel Llorens - 2012-2023
- // This library is free software; you can redisribute it and/or modify it under
- // the terms of the GNU Lesser General Public License as published by the Free
- // Software Foundation; either version 3 of the License, or (at your option) any
- // later version.
- #pragma once
- #include <string>
- #include <iomanip>
- #include <iostream>
- #include <ctime>
- #include "ra.hh"
- namespace ra {
- namespace esc {
- constexpr char const * bold = "\x1b[01m";
- constexpr char const * unbold = "\x1b[0m";
- constexpr char const * invert = "\x1b[07m";
- constexpr char const * underline = "\x1b[04m";
- constexpr char const * red = "\x1b[31m";
- constexpr char const * green = "\x1b[32m";
- constexpr char const * cyan = "\x1b[36m";
- constexpr char const * yellow = "\x1b[33m";
- constexpr char const * blue = "\x1b[34m";
- constexpr char const * white = "\x1b[97m"; // AIXTERM
- constexpr char const * plain = "\x1b[39m";
- constexpr char const * reset = "\x1b[39m\x1b[0m"; // plain + unbold
- constexpr char const * pink = "\x1b[38;5;225m";
- } // namespace esc
- struct TestRecorder
- {
- constexpr static double QNAN = std::numeric_limits<double>::quiet_NaN();
- constexpr static double PINF = std::numeric_limits<double>::infinity();
- // ra::amax ignores nans like fmax does, we don't want that here.
- template <class A> static auto
- amax_strict(A && a)
- {
- using T = value_t<A>;
- T c = std::numeric_limits<T>::has_infinity ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::lowest();
- return early(map([&c](auto && a) { if (c<a) { c=a; }; return isnan(a) ? std::make_optional(QNAN*a) : std::nullopt; },
- RA_FWD(a)),
- c);
- }
- enum verbose_t { QUIET, // as NOISY if failed, else no output
- ERRORS, // as NOISY if failed, else info and fp errors (default)
- NOISY }; // full output of info, test arguments, fp errors
- std::ostream & o;
- verbose_t verbose_default, verbose;
- bool willskip=false, willexpectfail=false, willstrictshape=false;
- int total=0, skipped=0, passed_good=0, passed_bad=0, failed_good=0, failed_bad=0;
- std::vector<int> bad;
- std::string info_str;
- TestRecorder(std::ostream & o_=std::cout, verbose_t verbose_default_=ERRORS)
- : o(o_), verbose_default(verbose_default_), verbose(verbose_default_) {}
- template <class ... A> void
- section(A const & ... a)
- {
- o << "\n" << esc::bold << format(a ...) << esc::unbold << std::endl;
- }
- static std::string
- format_error(double e)
- {
- return format(esc::yellow, std::setprecision(2), e, esc::reset);
- }
- template <class ... A> TestRecorder &
- info(A && ... a)
- {
- bool empty = (info_str=="");
- info_str += esc::pink;
- info_str += (empty ? "" : "; ") + format(a ...) + esc::reset;
- return *this;
- }
- TestRecorder & quiet(verbose_t v=QUIET) { verbose = v; return *this; }
- TestRecorder & noisy(verbose_t v=NOISY) { verbose = v; return *this; }
- TestRecorder & skip(bool s=true) { willskip = s; return *this; }
- TestRecorder & strictshape(bool s=true) { willstrictshape = s; return *this; }
- TestRecorder & expectfail(bool s=true) { willexpectfail = s; return *this; }
- #define RA_CURRENT_LOC std::source_location const loc = std::source_location::current()
- #define RA_LAZYINFO(...) [&] { return format(info_str, (info_str=="" ? "" : "; "), __VA_ARGS__); }
- template <class A, class B> void
- test(bool c, A && info_full, B && info_min, RA_CURRENT_LOC)
- {
- switch (verbose) {
- case QUIET: {
- if (!c) {
- o << format(esc::cyan, "[", total, ":", loc, "]", esc::reset, " ...",
- esc::bold, esc::red, " FAILED", esc::reset,
- esc::yellow, (willskip ? " skipped" : ""), (willexpectfail ? " expected" : ""), esc::reset,
- " ", info_full())
- << std::endl;
- }
- }; break;
- case NOISY: case ERRORS: {
- o << format(esc::cyan, "[", total, ":", loc, "]", esc::reset, " ...")
- << (c ? std::string(esc::green) + " ok" + esc::reset
- : std::string(esc::bold) + esc::red + " FAILED" + esc::reset)
- << esc::yellow << (willskip ? " skipped" : "")
- << (willexpectfail ? (c ? " not expected" : " expected") : "") << esc::reset
- << " " << ((verbose==NOISY || c==willexpectfail) ? info_full() : info_min())
- << std::endl;
- }; break;
- default: std::abort();
- }
- info_str = "";
- verbose = verbose_default;
- if (!willskip) {
- ++(willexpectfail? (c ? passed_bad : failed_good) : (c ? passed_good : failed_bad));
- if (c==willexpectfail) {
- bad.push_back(total);
- }
- } else {
- ++skipped;
- }
- ++total;
- willstrictshape = willskip = willexpectfail = false;
- }
- template <class A> void
- test(bool c, A && info_full, RA_CURRENT_LOC)
- {
- test(c, info_full, info_full, loc);
- }
- void
- test(bool c, RA_CURRENT_LOC)
- {
- test(c, RA_LAZYINFO(""), loc);
- }
- template <class A, class B, class Comp> bool
- test_scomp(A && a, B && b, Comp && comp, char const * msg, RA_CURRENT_LOC)
- {
- bool c = comp(a, b);
- test(c, RA_LAZYINFO(b, " (", msg, " ", a, ")"), RA_LAZYINFO(""), loc);
- return c;
- }
- template <class R, class A> bool
- test_seq(R && ref, A && a, RA_CURRENT_LOC)
- {
- return test_scomp(ref, a, [](auto && a, auto && b) { return a==b; }, "should be strictly ==", loc);
- }
- // Comp = ... is non-deduced context, so can't replace test_eq() with a default argument here.
- // where() is used to match shapes if either REF or A don't't have one.
- template <class A, class B, class Comp> bool
- test_comp(A && a, B && b, Comp && comp, char const * msg, RA_CURRENT_LOC)
- {
- if (willstrictshape
- ? [&] {
- if constexpr (ra::rank_s<decltype(a)>()==ra::rank_s<decltype(b)>()
- || ra::rank_s<decltype(a)>()==ANY || ra::rank_s<decltype(b)>()==ANY) {
- return ra::rank(a)==ra::rank(b) && every(ra::start(ra::shape(a))==ra::shape(b));
- } else {
- return false;
- } }()
- : agree_op(comp, a, b)) {
- bool c = every(ra::map(comp, a, b));
- test(c,
- RA_LAZYINFO(where(false, a, b), " (", msg, " ", where(true, a, b), ")"),
- RA_LAZYINFO(""),
- loc);
- return c;
- } else {
- test(false,
- RA_LAZYINFO("Mismatched args [", ra::noshape, ra::shape(a), "] [", ra::noshape, ra::shape(b), "]",
- willstrictshape ? " (strict shape)" : ""),
- RA_LAZYINFO("Shape mismatch", willstrictshape ? " (strict shape)" : ""),
- loc);
- return false;
- }
- }
- #define RA_TEST_COMP(NAME, OP) \
- template <class R, class A> bool \
- JOIN(test_, NAME)(R && ref, A && a, RA_CURRENT_LOC) \
- { \
- return test_comp(ra::start(ref), ra::start(a), [](auto && a, auto && b) { return every(a OP b); }, \
- "should be " STRINGIZE(OP), loc); \
- }
- RA_TEST_COMP(eq, ==)
- RA_TEST_COMP(lt, <)
- RA_TEST_COMP(le, <=)
- RA_TEST_COMP(gt, >)
- RA_TEST_COMP(ge, >=)
- #undef RA_TEST_COMP
- template <class R, class A> double
- test_rel(R && ref_, A && a_, double req, double level=0, RA_CURRENT_LOC)
- {
- decltype(auto) ref = ra::start(ref_);
- decltype(auto) a = ra::start(a_);
- double e = (level<=0)
- ? amax_strict(where(isfinite(ref),
- rel_error(ref, a),
- where(isinf(ref),
- where(ref==a, 0., PINF),
- where(isnan(a), 0., PINF))))
- : amax_strict(where(isfinite(ref),
- abs(ref-a)/level,
- where(isinf(ref),
- where(ref==a, 0., PINF),
- where(isnan(a), 0., PINF))));
- test(e<=req,
- RA_LAZYINFO("rerr (", esc::yellow, "ref", esc::reset, ": ", ref, esc::yellow, ", got", esc::reset, ": ", a,
- ") = ", format_error(e), (level<=0 ? "" : format(" (level ", level, ")")), ", req. ", req),
- RA_LAZYINFO("rerr: ", format_error(e), (level<=0 ? "" : format(" (level ", level, ")")),
- ", req. ", req),
- loc);
- return e;
- }
- template <class R, class A> double
- test_abs(R && ref_, A && a_, double req=0, RA_CURRENT_LOC)
- {
- decltype(auto) ref = ra::start(ref_);
- decltype(auto) a = ra::start(a_);
- double e = amax_strict(where(isfinite(ref),
- abs(ref-a),
- where(isinf(ref),
- where(ref==a, 0., PINF),
- where(isnan(a), 0., PINF))));
- test(e<=req,
- RA_LAZYINFO("aerr (ref: ", ref, ", got: ", a, ") = ", format_error(e), ", req. ", req),
- RA_LAZYINFO("aerr: ", format_error(e), ", req. ", req),
- loc);
- return e;
- }
- #undef RA_CURRENT_LOC
- #undef RA_LAZYINFO
- int
- summary() const
- {
- std::time_t t = std::time(nullptr);
- tm * tmp = std::localtime(&t);
- char buf[64];
- std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tmp);
- o << "--------------\nTests end " << buf << ". ";
- o << format("Of ", total, " tests passed ", (passed_good+passed_bad),
- " (", passed_bad, " unexpected), failed ", (failed_good+failed_bad),
- " (", failed_bad, " unexpected), skipped ", skipped, ".\n");
- if (bad.size()>0) {
- o << format(bad.size(), " bad tests: [", esc::bold, esc::red, ra::noshape, format_array(bad),
- esc::reset, "].\n");
- }
- return bad.size();
- }
- };
- } // namespace ra
|