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