test.hh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. // -*- mode: c++; coding: utf-8 -*-
  2. // ra-ra - Test/benchmarking library.
  3. // (c) Daniel Llorens - 2012-2024
  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 <chrono>
  13. #include <ctime>
  14. #include "ra.hh"
  15. namespace ra {
  16. namespace esc {
  17. constexpr char const * bold = "\x1b[01m";
  18. constexpr char const * unbold = "\x1b[0m";
  19. constexpr char const * invert = "\x1b[07m";
  20. constexpr char const * underline = "\x1b[04m";
  21. constexpr char const * red = "\x1b[31m";
  22. constexpr char const * green = "\x1b[32m";
  23. constexpr char const * cyan = "\x1b[36m";
  24. constexpr char const * yellow = "\x1b[33m";
  25. constexpr char const * blue = "\x1b[34m";
  26. constexpr char const * white = "\x1b[97m"; // AIXTERM
  27. constexpr char const * plain = "\x1b[39m";
  28. constexpr char const * reset = "\x1b[39m\x1b[0m"; // plain + unbold
  29. constexpr char const * pink = "\x1b[38;5;225m";
  30. } // namespace esc
  31. struct TestRecorder
  32. {
  33. constexpr static double QNAN = std::numeric_limits<double>::quiet_NaN();
  34. constexpr static double PINF = std::numeric_limits<double>::infinity();
  35. // ra::amax ignores nans like fmax does, we don't want that here.
  36. __attribute__((optimize("-fno-finite-math-only")))
  37. static auto
  38. amax_strict(auto && a)
  39. {
  40. using T = ncvalue_t<decltype(a)>;
  41. T c = std::numeric_limits<T>::has_infinity ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::lowest();
  42. return early(map([&c](auto && a) { if (c<a) { c=a; }; return isnan(a) ? std::make_optional(QNAN*a) : std::nullopt; },
  43. RA_FWD(a)),
  44. c);
  45. }
  46. enum verbose_t { QUIET, // as NOISY if failed, else no output
  47. ERRORS, // as NOISY if failed, else info and fp errors (default)
  48. NOISY }; // full output of info, test arguments, fp errors
  49. std::ostream & o;
  50. verbose_t verbose_default, verbose;
  51. bool willskip=false, willexpectfail=false, willstrictshape=false;
  52. int total=0, skipped=0, passed_good=0, passed_bad=0, failed_good=0, failed_bad=0;
  53. std::vector<int> bad;
  54. std::string info_str;
  55. TestRecorder(std::ostream & o_=std::cout, verbose_t verbose_default_=ERRORS)
  56. : o(o_), verbose_default(verbose_default_), verbose(verbose_default_) {}
  57. void
  58. section(auto const & ... a)
  59. {
  60. o << "\n" << esc::bold << format(a ...) << esc::unbold << std::endl;
  61. }
  62. static std::string
  63. format_error(double e)
  64. {
  65. return format(esc::yellow, std::setprecision(2), e, esc::reset);
  66. }
  67. TestRecorder &
  68. info(auto && ... a)
  69. {
  70. bool empty = (info_str=="");
  71. info_str += esc::pink;
  72. info_str += (empty ? "" : "; ") + format(a ...) + esc::reset;
  73. return *this;
  74. }
  75. TestRecorder & quiet(verbose_t v=QUIET) { verbose = v; return *this; }
  76. TestRecorder & noisy(verbose_t v=NOISY) { verbose = v; return *this; }
  77. TestRecorder & skip(bool s=true) { willskip = s; return *this; }
  78. TestRecorder & strictshape(bool s=true) { willstrictshape = s; return *this; }
  79. TestRecorder & expectfail(bool s=true) { willexpectfail = s; return *this; }
  80. #define RA_CURRENT_LOC std::source_location const loc = std::source_location::current()
  81. #define RA_LAZYINFO(...) [&] { return format(info_str, (info_str=="" ? "" : "; "), __VA_ARGS__); }
  82. void
  83. test(bool c, auto && info_full, auto && info_min, RA_CURRENT_LOC)
  84. {
  85. switch (verbose) {
  86. case QUIET: {
  87. if (!c) {
  88. o << format(esc::cyan, "[", total, ":", loc, "]", esc::reset, " ...",
  89. esc::bold, esc::red, " FAILED", esc::reset,
  90. esc::yellow, (willskip ? " skipped" : ""), (willexpectfail ? " expected" : ""), esc::reset,
  91. " ", info_full())
  92. << std::endl;
  93. }
  94. }; break;
  95. case NOISY: case ERRORS: {
  96. o << format(esc::cyan, "[", total, ":", loc, "]", esc::reset, " ...")
  97. << (c ? std::string(esc::green) + " ok" + esc::reset
  98. : std::string(esc::bold) + esc::red + " FAILED" + esc::reset)
  99. << esc::yellow << (willskip ? " skipped" : "")
  100. << (willexpectfail ? (c ? " not expected" : " expected") : "") << esc::reset
  101. << " " << ((verbose==NOISY || c==willexpectfail) ? info_full() : info_min())
  102. << std::endl;
  103. }; break;
  104. default: std::abort();
  105. }
  106. info_str = "";
  107. verbose = verbose_default;
  108. if (!willskip) {
  109. ++(willexpectfail? (c ? passed_bad : failed_good) : (c ? passed_good : failed_bad));
  110. if (c==willexpectfail) {
  111. bad.push_back(total);
  112. }
  113. } else {
  114. ++skipped;
  115. }
  116. ++total;
  117. willstrictshape = willskip = willexpectfail = false;
  118. }
  119. void
  120. test(bool c, auto && info_full, RA_CURRENT_LOC)
  121. {
  122. test(c, info_full, info_full, loc);
  123. }
  124. void
  125. test(bool c, RA_CURRENT_LOC)
  126. {
  127. test(c, RA_LAZYINFO(""), loc);
  128. }
  129. bool
  130. test_scomp(auto && a, auto && b, auto && comp, char const * msg, RA_CURRENT_LOC)
  131. {
  132. bool c = comp(a, b);
  133. test(c, RA_LAZYINFO(b, " (", msg, " ", a, ")"), RA_LAZYINFO(""), loc);
  134. return c;
  135. }
  136. bool
  137. test_seq(auto && ref, auto && a, RA_CURRENT_LOC)
  138. {
  139. return test_scomp(ref, a, [](auto && a, auto && b) { return a==b; }, "should be strictly ==", loc);
  140. }
  141. // Comp = ... is non-deduced context, so can't replace test_eq() with a default argument here.
  142. // where() is used to match shapes if either REF or A don't't have one.
  143. bool
  144. test_comp(auto && a, auto && b, auto && comp, char const * msg, RA_CURRENT_LOC)
  145. {
  146. if (willstrictshape
  147. ? [&] {
  148. if constexpr (ra::rank_s(a)==ra::rank_s(b) || ra::rank_s(a)==ANY || ra::rank_s(b)==ANY) {
  149. return ra::rank(a)==ra::rank(b) && every(ra::start(ra::shape(a))==ra::shape(b));
  150. } else {
  151. return false;
  152. } }()
  153. : agree_op(comp, a, b)) {
  154. bool c = every(ra::map(comp, a, b));
  155. test(c,
  156. RA_LAZYINFO(where(false, a, b), " (", where(true, a, b), " ", msg, ")"),
  157. RA_LAZYINFO(""),
  158. loc);
  159. return c;
  160. } else {
  161. test(false,
  162. RA_LAZYINFO("Mismatched shapes [", ra::noshape, ra::shape(a), "] [", ra::noshape, ra::shape(b), "]",
  163. willstrictshape ? " (strict shape)" : ""),
  164. RA_LAZYINFO("Mismatched shapes", willstrictshape ? " (strict shape)" : ""),
  165. loc);
  166. return false;
  167. }
  168. }
  169. #define RA_TEST_COMP(NAME, OP) \
  170. bool \
  171. JOIN(test_, NAME)(auto && ref, auto && a, RA_CURRENT_LOC) \
  172. { \
  173. return test_comp(ra::start(ref), ra::start(a), [](auto && a, auto && b) { return every(a OP b); }, \
  174. "should be " STRINGIZE(OP), loc); \
  175. }
  176. RA_TEST_COMP(eq, ==)
  177. RA_TEST_COMP(lt, <)
  178. RA_TEST_COMP(le, <=)
  179. RA_TEST_COMP(gt, >)
  180. RA_TEST_COMP(ge, >=)
  181. #undef RA_TEST_COMP
  182. __attribute__((optimize("-fno-finite-math-only")))
  183. double
  184. test_rel(auto && ref_, auto && a_, double req, double level=0, RA_CURRENT_LOC)
  185. {
  186. decltype(auto) ref = ra::start(ref_);
  187. decltype(auto) a = ra::start(a_);
  188. double e = (level<=0)
  189. ? amax_strict(where(isfinite(ref),
  190. rel_error(ref, a),
  191. where(isinf(ref),
  192. where(ref==a, 0., PINF),
  193. where(isnan(a), 0., PINF))))
  194. : amax_strict(where(isfinite(ref),
  195. abs(ref-a)/level,
  196. where(isinf(ref),
  197. where(ref==a, 0., PINF),
  198. where(isnan(a), 0., PINF))));
  199. test(e<=req,
  200. RA_LAZYINFO("rerr (", esc::yellow, "ref", esc::reset, ": ", ref, esc::yellow, ", got", esc::reset, ": ", a,
  201. ") = ", format_error(e), (level<=0 ? "" : format(" (level ", level, ")")), ", req. ", req),
  202. RA_LAZYINFO("rerr: ", format_error(e), (level<=0 ? "" : format(" (level ", level, ")")),
  203. ", req. ", req),
  204. loc);
  205. return e;
  206. }
  207. __attribute__((optimize("-fno-finite-math-only")))
  208. double
  209. test_abs(auto && ref_, auto && a_, double req=0, RA_CURRENT_LOC)
  210. {
  211. decltype(auto) ref = ra::start(ref_);
  212. decltype(auto) a = ra::start(a_);
  213. double e = amax_strict(where(isfinite(ref),
  214. abs(ref-a),
  215. where(isinf(ref),
  216. where(ref==a, 0., PINF),
  217. where(isnan(a), 0., PINF))));
  218. test(e<=req,
  219. RA_LAZYINFO("aerr (ref: ", ref, ", got: ", a, ") = ", format_error(e), ", req. ", req),
  220. RA_LAZYINFO("aerr: ", format_error(e), ", req. ", req),
  221. loc);
  222. return e;
  223. }
  224. #undef RA_CURRENT_LOC
  225. #undef RA_LAZYINFO
  226. int
  227. summary() const
  228. {
  229. std::time_t t = std::time(nullptr);
  230. tm * tmp = std::localtime(&t);
  231. char buf[64];
  232. std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tmp);
  233. o << "--------------\nTests end " << buf << ". ";
  234. o << format("Of ", total, " tests passed ", (passed_good+passed_bad),
  235. " (", passed_bad, " unexpected), failed ", (failed_good+failed_bad),
  236. " (", failed_bad, " unexpected), skipped ", skipped, ".\n");
  237. if (bad.size()>0) {
  238. o << format(bad.size(), " bad tests: [", esc::bold, esc::red, ra::noshape, format_array(bad),
  239. esc::reset, "].\n");
  240. }
  241. return bad.size();
  242. }
  243. };
  244. // TODO measure empty loops, better reporting
  245. // TODO let benchmarked functions to return results
  246. struct Benchmark
  247. {
  248. using clock = std::conditional_t<std::chrono::high_resolution_clock::is_steady,
  249. std::chrono::high_resolution_clock,
  250. std::chrono::steady_clock>;
  251. static clock::duration
  252. lapse(clock::duration empty, clock::duration full)
  253. {
  254. return (full>empty) ? full-empty : full;
  255. }
  256. static double
  257. toseconds(clock::duration const & t)
  258. {
  259. return std::chrono::duration<float, std::ratio<1, 1>>(t).count();
  260. }
  261. struct Value
  262. {
  263. std::string name;
  264. int repeats;
  265. clock::duration empty;
  266. ra::Big<clock::duration, 1> times;
  267. };
  268. static double
  269. avg(Value const & bv)
  270. {
  271. return toseconds(sum(bv.times))/bv.repeats/bv.times.size();
  272. }
  273. static double
  274. stddev(Value const & bv)
  275. {
  276. double m = avg(bv);
  277. return sqrt(sum(sqr(ra::map(toseconds, bv.times)/bv.repeats-m))/bv.times.size());
  278. }
  279. void
  280. report(std::ostream & o, auto const & b, double frac)
  281. {
  282. o << (info_str=="" ? "" : info_str + " : ") << ra::map([](auto && bv) { return avg(bv); }, b)/frac << std::endl;
  283. o << (info_str=="" ? "" : info_str + " : ") << ra::map([](auto && bv) { return stddev(bv); }, b)/frac << std::endl;
  284. info_str = "";
  285. }
  286. int const repeats_ = 1;
  287. int const runs_ = 1;
  288. std::string const name_ = "";
  289. std::string info_str = "";
  290. Benchmark &
  291. info(auto && ... a)
  292. {
  293. bool empty = (info_str=="");
  294. info_str += ra::esc::plain;
  295. info_str += (empty ? "" : "; ");
  296. info_str += ra::format(a ...);
  297. info_str += ra::esc::plain;
  298. return *this;
  299. }
  300. Benchmark name(std::string name_) { return Benchmark { repeats_, runs_, name_, "" }; }
  301. Benchmark repeats(int repeats_) { return Benchmark { repeats_, runs_, name_, "" }; }
  302. Benchmark runs(int runs_) { return Benchmark { repeats_, runs_, name_, "" }; }
  303. auto
  304. once(auto && f, auto && ... a)
  305. {
  306. auto t0 = clock::now();
  307. clock::duration empty = clock::now()-t0;
  308. ra::Big<clock::duration, 1> times;
  309. for (int k=0; k<runs_; ++k) {
  310. auto t0 = clock::now();
  311. for (int i=0; i<repeats_; ++i) {
  312. f(RA_FWD(a) ...);
  313. }
  314. clock::duration full = clock::now()-t0;
  315. times.push_back(lapse(empty, full));
  316. }
  317. return Value { name_, repeats_, empty, std::move(times) };
  318. }
  319. auto
  320. once_f(auto && g, auto && ... a)
  321. {
  322. clock::duration empty;
  323. g([&](auto && f)
  324. {
  325. auto t0 = clock::now();
  326. empty = clock::now()-t0;
  327. }, RA_FWD(a) ...);
  328. ra::Big<clock::duration, 1> times;
  329. for (int k=0; k<runs_; ++k) {
  330. g([&](auto && f)
  331. {
  332. auto t0 = clock::now();
  333. for (int i=0; i<repeats_; ++i) {
  334. f();
  335. }
  336. clock::duration full = clock::now()-t0;
  337. times.push_back(lapse(empty, full));
  338. }, RA_FWD(a) ...);
  339. }
  340. return Value { name_, repeats_, empty, std::move(times) };
  341. }
  342. auto
  343. run(auto && f, auto && ... a)
  344. {
  345. return ra::concrete(ra::from([this, &f](auto && ... b) { return this->once(f, b ...); }, a ...));
  346. }
  347. auto
  348. run_f(auto && f, auto && ... a)
  349. {
  350. return ra::concrete(ra::from([this, &f](auto && ... b) { return this->once_f(f, b ...); }, a ...));
  351. }
  352. };
  353. template <> constexpr bool is_scalar_def<Benchmark::Value> = true;
  354. } // namespace ra