test.hh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // -*- mode: c++; coding: utf-8 -*-
  2. // ra-ra - Test library.
  3. // (c) Daniel Llorens - 2012-2023
  4. // This library is free software; you can redisribute it and/or modify it under
  5. // the terms of the GNU Lesser General Public License as published by the Free
  6. // Software Foundation; either version 3 of the License, or (at your option) any
  7. // later version.
  8. #pragma once
  9. #include <string>
  10. #include <iomanip>
  11. #include <iostream>
  12. #include <ctime>
  13. #include "ra.hh"
  14. namespace ra {
  15. namespace esc {
  16. constexpr char const * bold = "\x1b[01m";
  17. constexpr char const * unbold = "\x1b[0m";
  18. constexpr char const * invert = "\x1b[07m";
  19. constexpr char const * underline = "\x1b[04m";
  20. constexpr char const * red = "\x1b[31m";
  21. constexpr char const * green = "\x1b[32m";
  22. constexpr char const * cyan = "\x1b[36m";
  23. constexpr char const * yellow = "\x1b[33m";
  24. constexpr char const * blue = "\x1b[34m";
  25. constexpr char const * white = "\x1b[97m"; // AIXTERM
  26. constexpr char const * plain = "\x1b[39m";
  27. constexpr char const * reset = "\x1b[39m\x1b[0m"; // plain + unbold
  28. constexpr char const * pink = "\x1b[38;5;225m";
  29. } // namespace esc
  30. struct TestRecorder
  31. {
  32. constexpr static double QNAN = std::numeric_limits<double>::quiet_NaN();
  33. constexpr static double PINF = std::numeric_limits<double>::infinity();
  34. // ra::amax ignores nans like fmax does, we don't want that here.
  35. template <class A> static auto
  36. amax_strict(A && a)
  37. {
  38. using T = value_t<A>;
  39. T c = std::numeric_limits<T>::has_infinity ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::lowest();
  40. return early(map([&c](auto && a) { if (c<a) { c=a; }; return isnan(a) ? std::make_optional(QNAN*a) : std::nullopt; },
  41. RA_FWD(a)),
  42. c);
  43. }
  44. enum verbose_t { QUIET, // as NOISY if failed, else no output
  45. ERRORS, // as NOISY if failed, else info and fp errors (default)
  46. NOISY }; // full output of info, test arguments, fp errors
  47. std::ostream & o;
  48. verbose_t verbose_default, verbose;
  49. bool willskip=false, willexpectfail=false, willstrictshape=false;
  50. int total=0, skipped=0, passed_good=0, passed_bad=0, failed_good=0, failed_bad=0;
  51. std::vector<int> bad;
  52. std::string info_str;
  53. TestRecorder(std::ostream & o_=std::cout, verbose_t verbose_default_=ERRORS)
  54. : o(o_), verbose_default(verbose_default_), verbose(verbose_default_) {}
  55. template <class ... A> void
  56. section(A const & ... a)
  57. {
  58. o << "\n" << esc::bold << format(a ...) << esc::unbold << std::endl;
  59. }
  60. static std::string
  61. format_error(double e)
  62. {
  63. return format(esc::yellow, std::setprecision(2), e, esc::reset);
  64. }
  65. template <class ... A> TestRecorder &
  66. info(A && ... a)
  67. {
  68. bool empty = (info_str=="");
  69. info_str += esc::pink;
  70. info_str += (empty ? "" : "; ") + format(a ...) + esc::reset;
  71. return *this;
  72. }
  73. TestRecorder & quiet(verbose_t v=QUIET) { verbose = v; return *this; }
  74. TestRecorder & noisy(verbose_t v=NOISY) { verbose = v; return *this; }
  75. TestRecorder & skip(bool s=true) { willskip = s; return *this; }
  76. TestRecorder & strictshape(bool s=true) { willstrictshape = s; return *this; }
  77. TestRecorder & expectfail(bool s=true) { willexpectfail = s; return *this; }
  78. #define RA_CURRENT_LOC std::source_location const loc = std::source_location::current()
  79. #define RA_LAZYINFO(...) [&] { return format(info_str, (info_str=="" ? "" : "; "), __VA_ARGS__); }
  80. template <class A, class B> void
  81. test(bool c, A && info_full, B && info_min, RA_CURRENT_LOC)
  82. {
  83. switch (verbose) {
  84. case QUIET: {
  85. if (!c) {
  86. o << format(esc::cyan, "[", total, ":", loc, "]", esc::reset, " ...",
  87. esc::bold, esc::red, " FAILED", esc::reset,
  88. esc::yellow, (willskip ? " skipped" : ""), (willexpectfail ? " expected" : ""), esc::reset,
  89. " ", info_full())
  90. << std::endl;
  91. }
  92. }; break;
  93. case NOISY: case ERRORS: {
  94. o << format(esc::cyan, "[", total, ":", loc, "]", esc::reset, " ...")
  95. << (c ? std::string(esc::green) + " ok" + esc::reset
  96. : std::string(esc::bold) + esc::red + " FAILED" + esc::reset)
  97. << esc::yellow << (willskip ? " skipped" : "")
  98. << (willexpectfail ? (c ? " not expected" : " expected") : "") << esc::reset
  99. << " " << ((verbose==NOISY || c==willexpectfail) ? info_full() : info_min())
  100. << std::endl;
  101. }; break;
  102. default: std::abort();
  103. }
  104. info_str = "";
  105. verbose = verbose_default;
  106. if (!willskip) {
  107. ++(willexpectfail? (c ? passed_bad : failed_good) : (c ? passed_good : failed_bad));
  108. if (c==willexpectfail) {
  109. bad.push_back(total);
  110. }
  111. } else {
  112. ++skipped;
  113. }
  114. ++total;
  115. willstrictshape = willskip = willexpectfail = false;
  116. }
  117. template <class A> void
  118. test(bool c, A && info_full, RA_CURRENT_LOC)
  119. {
  120. test(c, info_full, info_full, loc);
  121. }
  122. void
  123. test(bool c, RA_CURRENT_LOC)
  124. {
  125. test(c, RA_LAZYINFO(""), loc);
  126. }
  127. template <class A, class B, class Comp> bool
  128. test_scomp(A && a, B && b, Comp && comp, char const * msg, RA_CURRENT_LOC)
  129. {
  130. bool c = comp(a, b);
  131. test(c, RA_LAZYINFO(b, " (", msg, " ", a, ")"), RA_LAZYINFO(""), loc);
  132. return c;
  133. }
  134. template <class R, class A> bool
  135. test_seq(R && ref, A && a, RA_CURRENT_LOC)
  136. {
  137. return test_scomp(ref, a, [](auto && a, auto && b) { return a==b; }, "should be strictly ==", loc);
  138. }
  139. // Comp = ... is non-deduced context, so can't replace test_eq() with a default argument here.
  140. // where() is used to match shapes if either REF or A don't't have one.
  141. template <class A, class B, class Comp> bool
  142. test_comp(A && a, B && b, Comp && comp, char const * msg, RA_CURRENT_LOC)
  143. {
  144. if (willstrictshape
  145. ? [&] {
  146. if constexpr (ra::rank_s<decltype(a)>()==ra::rank_s<decltype(b)>()
  147. || ra::rank_s<decltype(a)>()==ANY || ra::rank_s<decltype(b)>()==ANY) {
  148. return ra::rank(a)==ra::rank(b) && every(ra::start(ra::shape(a))==ra::shape(b));
  149. } else {
  150. return false;
  151. } }()
  152. : agree_op(comp, a, b)) {
  153. bool c = every(ra::map(comp, a, b));
  154. test(c,
  155. RA_LAZYINFO(where(false, a, b), " (", msg, " ", where(true, a, b), ")"),
  156. RA_LAZYINFO(""),
  157. loc);
  158. return c;
  159. } else {
  160. test(false,
  161. RA_LAZYINFO("Mismatched args [", ra::noshape, ra::shape(a), "] [", ra::noshape, ra::shape(b), "]",
  162. willstrictshape ? " (strict shape)" : ""),
  163. RA_LAZYINFO("Shape mismatch", willstrictshape ? " (strict shape)" : ""),
  164. loc);
  165. return false;
  166. }
  167. }
  168. #define RA_TEST_COMP(NAME, OP) \
  169. template <class R, class A> bool \
  170. JOIN(test_, NAME)(R && ref, A && a, RA_CURRENT_LOC) \
  171. { \
  172. return test_comp(ra::start(ref), ra::start(a), [](auto && a, auto && b) { return every(a OP b); }, \
  173. "should be " STRINGIZE(OP), loc); \
  174. }
  175. RA_TEST_COMP(eq, ==)
  176. RA_TEST_COMP(lt, <)
  177. RA_TEST_COMP(le, <=)
  178. RA_TEST_COMP(gt, >)
  179. RA_TEST_COMP(ge, >=)
  180. #undef RA_TEST_COMP
  181. template <class R, class A> double
  182. test_rel(R && ref_, A && a_, double req, double level=0, RA_CURRENT_LOC)
  183. {
  184. decltype(auto) ref = ra::start(ref_);
  185. decltype(auto) a = ra::start(a_);
  186. double e = (level<=0)
  187. ? amax_strict(where(isfinite(ref),
  188. rel_error(ref, a),
  189. where(isinf(ref),
  190. where(ref==a, 0., PINF),
  191. where(isnan(a), 0., PINF))))
  192. : amax_strict(where(isfinite(ref),
  193. abs(ref-a)/level,
  194. where(isinf(ref),
  195. where(ref==a, 0., PINF),
  196. where(isnan(a), 0., PINF))));
  197. test(e<=req,
  198. RA_LAZYINFO("rerr (", esc::yellow, "ref", esc::reset, ": ", ref, esc::yellow, ", got", esc::reset, ": ", a,
  199. ") = ", format_error(e), (level<=0 ? "" : format(" (level ", level, ")")), ", req. ", req),
  200. RA_LAZYINFO("rerr: ", format_error(e), (level<=0 ? "" : format(" (level ", level, ")")),
  201. ", req. ", req),
  202. loc);
  203. return e;
  204. }
  205. template <class R, class A> double
  206. test_abs(R && ref_, A && a_, double req=0, RA_CURRENT_LOC)
  207. {
  208. decltype(auto) ref = ra::start(ref_);
  209. decltype(auto) a = ra::start(a_);
  210. double e = amax_strict(where(isfinite(ref),
  211. abs(ref-a),
  212. where(isinf(ref),
  213. where(ref==a, 0., PINF),
  214. where(isnan(a), 0., PINF))));
  215. test(e<=req,
  216. RA_LAZYINFO("aerr (ref: ", ref, ", got: ", a, ") = ", format_error(e), ", req. ", req),
  217. RA_LAZYINFO("aerr: ", format_error(e), ", req. ", req),
  218. loc);
  219. return e;
  220. }
  221. #undef RA_CURRENT_LOC
  222. #undef RA_LAZYINFO
  223. int
  224. summary() const
  225. {
  226. std::time_t t = std::time(nullptr);
  227. tm * tmp = std::localtime(&t);
  228. char buf[64];
  229. std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tmp);
  230. o << "--------------\nTests end " << buf << ". ";
  231. o << format("Of ", total, " tests passed ", (passed_good+passed_bad),
  232. " (", passed_bad, " unexpected), failed ", (failed_good+failed_bad),
  233. " (", failed_bad, " unexpected), skipped ", skipped, ".\n");
  234. if (bad.size()>0) {
  235. o << format(bad.size(), " bad tests: [", esc::bold, esc::red, ra::noshape, format_array(bad),
  236. esc::reset, "].\n");
  237. }
  238. return bad.size();
  239. }
  240. };
  241. } // namespace ra