You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
7.7 KiB

2 months ago
  1. #ifndef TEST_OUTPUT_TEST_H
  2. #define TEST_OUTPUT_TEST_H
  3. #undef NDEBUG
  4. #include <functional>
  5. #include <initializer_list>
  6. #include <memory>
  7. #include <sstream>
  8. #include <string>
  9. #include <utility>
  10. #include <vector>
  11. #include "../src/re.h"
  12. #include "benchmark/benchmark.h"
  13. #define CONCAT2(x, y) x##y
  14. #define CONCAT(x, y) CONCAT2(x, y)
  15. #define ADD_CASES(...) int CONCAT(dummy, __LINE__) = ::AddCases(__VA_ARGS__)
  16. #define SET_SUBSTITUTIONS(...) \
  17. int CONCAT(dummy, __LINE__) = ::SetSubstitutions(__VA_ARGS__)
  18. enum MatchRules {
  19. MR_Default, // Skip non-matching lines until a match is found.
  20. MR_Next, // Match must occur on the next line.
  21. MR_Not // No line between the current position and the next match matches
  22. // the regex
  23. };
  24. struct TestCase {
  25. TestCase(std::string re, int rule = MR_Default);
  26. std::string regex_str;
  27. int match_rule;
  28. std::string substituted_regex;
  29. std::shared_ptr<benchmark::Regex> regex;
  30. };
  31. enum TestCaseID {
  32. TC_ConsoleOut,
  33. TC_ConsoleErr,
  34. TC_JSONOut,
  35. TC_JSONErr,
  36. TC_CSVOut,
  37. TC_CSVErr,
  38. TC_NumID // PRIVATE
  39. };
  40. // Add a list of test cases to be run against the output specified by
  41. // 'ID'
  42. int AddCases(TestCaseID ID, std::initializer_list<TestCase> il);
  43. // Add or set a list of substitutions to be performed on constructed regex's
  44. // See 'output_test_helper.cc' for a list of default substitutions.
  45. int SetSubstitutions(
  46. std::initializer_list<std::pair<std::string, std::string>> il);
  47. // Run all output tests.
  48. void RunOutputTests(int argc, char* argv[]);
  49. // Count the number of 'pat' substrings in the 'haystack' string.
  50. int SubstrCnt(const std::string& haystack, const std::string& pat);
  51. // Run registered benchmarks with file reporter enabled, and return the content
  52. // outputted by the file reporter.
  53. std::string GetFileReporterOutput(int argc, char* argv[]);
  54. // ========================================================================= //
  55. // ------------------------- Results checking ------------------------------ //
  56. // ========================================================================= //
  57. // Call this macro to register a benchmark for checking its results. This
  58. // should be all that's needed. It subscribes a function to check the (CSV)
  59. // results of a benchmark. This is done only after verifying that the output
  60. // strings are really as expected.
  61. // bm_name_pattern: a name or a regex pattern which will be matched against
  62. // all the benchmark names. Matching benchmarks
  63. // will be the subject of a call to checker_function
  64. // checker_function: should be of type ResultsCheckFn (see below)
  65. #define CHECK_BENCHMARK_RESULTS(bm_name_pattern, checker_function) \
  66. size_t CONCAT(dummy, __LINE__) = AddChecker(bm_name_pattern, checker_function)
  67. struct Results;
  68. typedef std::function<void(Results const&)> ResultsCheckFn;
  69. size_t AddChecker(const std::string& bm_name_pattern, const ResultsCheckFn& fn);
  70. // Class holding the results of a benchmark.
  71. // It is passed in calls to checker functions.
  72. struct Results {
  73. // the benchmark name
  74. std::string name;
  75. // the benchmark fields
  76. std::map<std::string, std::string> values;
  77. Results(const std::string& n) : name(n) {}
  78. int NumThreads() const;
  79. double NumIterations() const;
  80. typedef enum { kCpuTime, kRealTime } BenchmarkTime;
  81. // get cpu_time or real_time in seconds
  82. double GetTime(BenchmarkTime which) const;
  83. // get the real_time duration of the benchmark in seconds.
  84. // it is better to use fuzzy float checks for this, as the float
  85. // ASCII formatting is lossy.
  86. double DurationRealTime() const {
  87. return NumIterations() * GetTime(kRealTime);
  88. }
  89. // get the cpu_time duration of the benchmark in seconds
  90. double DurationCPUTime() const { return NumIterations() * GetTime(kCpuTime); }
  91. // get the string for a result by name, or nullptr if the name
  92. // is not found
  93. const std::string* Get(const std::string& entry_name) const {
  94. auto it = values.find(entry_name);
  95. if (it == values.end()) return nullptr;
  96. return &it->second;
  97. }
  98. // get a result by name, parsed as a specific type.
  99. // NOTE: for counters, use GetCounterAs instead.
  100. template <class T>
  101. T GetAs(const std::string& entry_name) const;
  102. // counters are written as doubles, so they have to be read first
  103. // as a double, and only then converted to the asked type.
  104. template <class T>
  105. T GetCounterAs(const std::string& entry_name) const {
  106. double dval = GetAs<double>(entry_name);
  107. T tval = static_cast<T>(dval);
  108. return tval;
  109. }
  110. };
  111. template <class T>
  112. T Results::GetAs(const std::string& entry_name) const {
  113. auto* sv = Get(entry_name);
  114. BM_CHECK(sv != nullptr && !sv->empty());
  115. std::stringstream ss;
  116. ss << *sv;
  117. T out;
  118. ss >> out;
  119. BM_CHECK(!ss.fail());
  120. return out;
  121. }
  122. //----------------------------------
  123. // Macros to help in result checking. Do not use them with arguments causing
  124. // side-effects.
  125. // clang-format off
  126. #define CHECK_RESULT_VALUE_IMPL(entry, getfn, var_type, var_name, relationship, value) \
  127. CONCAT(BM_CHECK_, relationship) \
  128. (entry.getfn< var_type >(var_name), (value)) << "\n" \
  129. << __FILE__ << ":" << __LINE__ << ": " << (entry).name << ":\n" \
  130. << __FILE__ << ":" << __LINE__ << ": " \
  131. << "expected (" << #var_type << ")" << (var_name) \
  132. << "=" << (entry).getfn< var_type >(var_name) \
  133. << " to be " #relationship " to " << (value) << "\n"
  134. // check with tolerance. eps_factor is the tolerance window, which is
  135. // interpreted relative to value (eg, 0.1 means 10% of value).
  136. #define CHECK_FLOAT_RESULT_VALUE_IMPL(entry, getfn, var_type, var_name, relationship, value, eps_factor) \
  137. CONCAT(BM_CHECK_FLOAT_, relationship) \
  138. (entry.getfn< var_type >(var_name), (value), (eps_factor) * (value)) << "\n" \
  139. << __FILE__ << ":" << __LINE__ << ": " << (entry).name << ":\n" \
  140. << __FILE__ << ":" << __LINE__ << ": " \
  141. << "expected (" << #var_type << ")" << (var_name) \
  142. << "=" << (entry).getfn< var_type >(var_name) \
  143. << " to be " #relationship " to " << (value) << "\n" \
  144. << __FILE__ << ":" << __LINE__ << ": " \
  145. << "with tolerance of " << (eps_factor) * (value) \
  146. << " (" << (eps_factor)*100. << "%), " \
  147. << "but delta was " << ((entry).getfn< var_type >(var_name) - (value)) \
  148. << " (" << (((entry).getfn< var_type >(var_name) - (value)) \
  149. / \
  150. ((value) > 1.e-5 || value < -1.e-5 ? value : 1.e-5)*100.) \
  151. << "%)"
  152. #define CHECK_RESULT_VALUE(entry, var_type, var_name, relationship, value) \
  153. CHECK_RESULT_VALUE_IMPL(entry, GetAs, var_type, var_name, relationship, value)
  154. #define CHECK_COUNTER_VALUE(entry, var_type, var_name, relationship, value) \
  155. CHECK_RESULT_VALUE_IMPL(entry, GetCounterAs, var_type, var_name, relationship, value)
  156. #define CHECK_FLOAT_RESULT_VALUE(entry, var_name, relationship, value, eps_factor) \
  157. CHECK_FLOAT_RESULT_VALUE_IMPL(entry, GetAs, double, var_name, relationship, value, eps_factor)
  158. #define CHECK_FLOAT_COUNTER_VALUE(entry, var_name, relationship, value, eps_factor) \
  159. CHECK_FLOAT_RESULT_VALUE_IMPL(entry, GetCounterAs, double, var_name, relationship, value, eps_factor)
  160. // clang-format on
  161. // ========================================================================= //
  162. // --------------------------- Misc Utilities ------------------------------ //
  163. // ========================================================================= //
  164. namespace {
  165. const char* const dec_re = "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?";
  166. } // end namespace
  167. #endif // TEST_OUTPUT_TEST_H