test.H 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // (c) Daniel Llorens - 2012, 2014-2016
  2. // This library is free software; you can redistribute it and/or modify it under
  3. // the terms of the GNU Lesser General Public License as published by the Free
  4. // Software Foundation; either version 3 of the License, or (at your option) any
  5. // later version.
  6. /// @file test.H
  7. /// @brief Minimal test library.
  8. #pragma once
  9. #include <string>
  10. #include <iostream>
  11. #include <iomanip>
  12. #include <limits>
  13. #include "ra/format.H"
  14. #include "ra/operators.H"
  15. #include "ra/io.H"
  16. char const * esc_bold = "\x1b[01m";
  17. char const * esc_unbold = "\x1b[0m";
  18. char const * esc_red = "\x1b[31m";
  19. char const * esc_green = "\x1b[32m";
  20. char const * esc_cyan = "\x1b[36m";
  21. char const * esc_yellow = "\x1b[33m";
  22. char const * esc_blue = "\x1b[34m";
  23. char const * esc_white = "\x1b[97m"; // an AIXTERM sequence
  24. char const * esc_plain = "\x1b[39m";
  25. char const * esc_reset = "\x1b[39m\x1b[0m"; // plain + unbold
  26. struct TestRecorder
  27. {
  28. enum verbose_t { QUIET, // as NOISY if failed, else no output
  29. ERRORS, // as NOISY if failed, else info and fp errors (default)
  30. NOISY }; // full output of info, test arguments, fp errors
  31. std::ostream & o;
  32. int total = 0, skipped = 0;
  33. std::vector<int> failed;
  34. std::string info_str;
  35. verbose_t verbose_default, verbose;
  36. bool willskip;
  37. TestRecorder(std::ostream & o_=std::cout, verbose_t verbose_default_=ERRORS)
  38. : o(o_), verbose_default(verbose_default_), verbose(verbose_default_), willskip(false) {}
  39. template <class ... A> void section(A const & ... a)
  40. {
  41. o << "\n" << esc_bold << format(a ...) << esc_unbold << std::endl;
  42. }
  43. static std::string format_error(double e)
  44. {
  45. return format(esc_yellow, std::setprecision(2), e, esc_plain);
  46. }
  47. template <class ... A> TestRecorder & info(A && ... a)
  48. {
  49. bool empty = (info_str=="");
  50. info_str += esc_plain;
  51. info_str += (empty ? "" : "; ");
  52. info_str += format(a ...);
  53. info_str += esc_plain;
  54. return *this;
  55. }
  56. TestRecorder & quiet(verbose_t v=QUIET) { verbose = v; return *this; }
  57. TestRecorder & noisy(verbose_t v=NOISY) { verbose = v; return *this; }
  58. TestRecorder & skip(bool s=true) { willskip = s; return *this; }
  59. template <class A, class B>
  60. void test(bool c, A && info_full, B && info_min)
  61. {
  62. switch (verbose) {
  63. case QUIET: {
  64. if (!c) {
  65. o << esc_cyan << "[" << (willskip ? std::string("skipped") : format(total)) << "] " << esc_plain
  66. << esc_bold << esc_red << "FAILED" << esc_reset
  67. << "... " << info_full() << std::endl;
  68. }
  69. }; break;
  70. case NOISY: case ERRORS: {
  71. o << esc_cyan << "[" << (willskip ? std::string("skipped") : format(total)) << "] " << esc_plain
  72. << (c ? std::string(esc_green)+"ok"+esc_plain
  73. : std::string(esc_bold)+esc_red+"FAILED"+esc_reset)
  74. << "... " << ((verbose==NOISY || !c) ? info_full() : info_min()) << std::endl;
  75. }; break;
  76. default: assert(0);
  77. }
  78. info_str = "";
  79. verbose = verbose_default;
  80. if (!willskip) {
  81. if (!c) {
  82. failed.push_back(total);
  83. }
  84. ++total;
  85. } else {
  86. ++skipped;
  87. }
  88. willskip = false;
  89. }
  90. #define LAZYINFO(...) [&]() { return format(info_str, (info_str=="" ? "" : "; "), __VA_ARGS__); }
  91. template <class A> void test(bool c, A && info_full) { test(c, info_full, info_full); }
  92. void test(bool c) { test(c, LAZYINFO("")); }
  93. // Comp = ... is non-deduced context, so can't replace test_eq() with a default argument here.
  94. // where() is used to match shapes if either ref or a don't't have one.
  95. template <class R, class A, class Comp>
  96. bool test_comp(R && ref, A && a, Comp && comp)
  97. {
  98. bool c = every(ra::map(comp, ref, a));
  99. test(c, LAZYINFO("comp(ref: ", where(true, ref, a), ", got: ", where(false, ref, a), ")"), LAZYINFO(""));
  100. return c;
  101. }
  102. template <class R, class A>
  103. bool test_eq(R && ref, A && a)
  104. {
  105. return test_comp(std::forward<R>(ref), std::forward<A>(a), [](auto && a, auto && b) { return every(a==b); });
  106. }
  107. template <class A, class B>
  108. bool test_le(A && a, B && b)
  109. {
  110. bool c = every(a<=b);
  111. test(c, LAZYINFO("comp(", where(true, a, b), " should be <= ", where(false, a, b), ")"), LAZYINFO(""));
  112. return c;
  113. }
  114. template <class R, class A>
  115. double test_rel_error(R && ref, A && a, double req_err, double level=0)
  116. {
  117. double e = (level<=0)
  118. ? amax(where(isnan(ref),
  119. where(isnan(a), 0., std::numeric_limits<double>::infinity()),
  120. rel_error(ref, a)))
  121. : amax(where(isnan(ref),
  122. where(isnan(a), 0., std::numeric_limits<double>::infinity()),
  123. abs(ref-a)/level));
  124. test(e<=req_err,
  125. LAZYINFO("rel_error(", esc_yellow, "ref", esc_plain, ": ", ref, esc_yellow, ", got", esc_plain, ": ", a,
  126. ") = ", format_error(e), (level<=0 ? "" : format(" (level ", level, ")")), ", req. ", req_err),
  127. LAZYINFO("rel_error: ", format_error(e), (level<=0 ? "" : format(" (level ", level, ")")),
  128. ", req. ", req_err));
  129. return e;
  130. }
  131. template <class R, class A>
  132. double test_abs_error(R && ref, A && a, double req_err=0)
  133. {
  134. double e = amax(where(isnan(ref),
  135. where(isnan(a), 0., std::numeric_limits<double>::infinity()),
  136. abs(ref-a)));
  137. test(e<=req_err,
  138. LAZYINFO((verbose!=QUIET), "abs_error(ref: ", ref, ", got: ", a, ") = ", format_error(e), ", req. ", req_err),
  139. LAZYINFO("abs_error: ", format_error(e), ", req. ", req_err));
  140. return e;
  141. }
  142. #undef LAZYINFO
  143. int summary() const
  144. {
  145. o << "Of " << total << " tests " << esc_bold << esc_green << "passed " << (total-failed.size());
  146. if (skipped>0) {
  147. o << esc_reset << ", " << esc_bold << esc_yellow << "skipped " << skipped;
  148. }
  149. if (!failed.empty()) {
  150. o << esc_reset << ", " << esc_bold << esc_red << " failed " << failed.size()
  151. << " (" << ra::format_array(failed, false) << ")";
  152. }
  153. o << esc_reset << std::endl;
  154. return failed.size();
  155. }
  156. };