| Eric Fiselier | 4d5e91d | 2016-08-29 19:12:01 +0000 | [diff] [blame] | 1 | #ifndef TEST_OUTPUT_TEST_H |
| 2 | #define TEST_OUTPUT_TEST_H |
| 3 | |
| 4 | #undef NDEBUG |
| Eric Fiselier | 4d5e91d | 2016-08-29 19:12:01 +0000 | [diff] [blame] | 5 | #include <initializer_list> |
| 6 | #include <memory> |
| Eric Fiselier | fbc9ff2 | 2016-11-05 00:30:27 +0000 | [diff] [blame] | 7 | #include <string> |
| Eric Fiselier | 4d5e91d | 2016-08-29 19:12:01 +0000 | [diff] [blame] | 8 | #include <utility> |
| Eric Fiselier | fbc9ff2 | 2016-11-05 00:30:27 +0000 | [diff] [blame] | 9 | #include <vector> |
| Eric Fiselier | 1903976 | 2018-01-18 04:23:01 +0000 | [diff] [blame] | 10 | #include <functional> |
| 11 | #include <sstream> |
| Eric Fiselier | fbc9ff2 | 2016-11-05 00:30:27 +0000 | [diff] [blame] | 12 | |
| 13 | #include "../src/re.h" |
| 14 | #include "benchmark/benchmark.h" |
| Eric Fiselier | 4d5e91d | 2016-08-29 19:12:01 +0000 | [diff] [blame] | 15 | |
| 16 | #define CONCAT2(x, y) x##y |
| 17 | #define CONCAT(x, y) CONCAT2(x, y) |
| 18 | |
| Eric Fiselier | fbc9ff2 | 2016-11-05 00:30:27 +0000 | [diff] [blame] | 19 | #define ADD_CASES(...) int CONCAT(dummy, __LINE__) = ::AddCases(__VA_ARGS__) |
| Eric Fiselier | 4d5e91d | 2016-08-29 19:12:01 +0000 | [diff] [blame] | 20 | |
| 21 | #define SET_SUBSTITUTIONS(...) \ |
| Eric Fiselier | fbc9ff2 | 2016-11-05 00:30:27 +0000 | [diff] [blame] | 22 | int CONCAT(dummy, __LINE__) = ::SetSubstitutions(__VA_ARGS__) |
| Eric Fiselier | 4d5e91d | 2016-08-29 19:12:01 +0000 | [diff] [blame] | 23 | |
| 24 | enum MatchRules { |
| Eric Fiselier | fbc9ff2 | 2016-11-05 00:30:27 +0000 | [diff] [blame] | 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 |
| Eric Fiselier | 4d5e91d | 2016-08-29 19:12:01 +0000 | [diff] [blame] | 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 | |
| Eric Fiselier | fbc9ff2 | 2016-11-05 00:30:27 +0000 | [diff] [blame] | 48 | TC_NumID // PRIVATE |
| Eric Fiselier | 4d5e91d | 2016-08-29 19:12:01 +0000 | [diff] [blame] | 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 | // ========================================================================= // |
| Eric Fiselier | 1903976 | 2018-01-18 04:23:01 +0000 | [diff] [blame] | 64 | // ------------------------- Results checking ------------------------------ // |
| 65 | // ========================================================================= // |
| 66 | |
| 67 | // Call this macro to register a benchmark for checking its results. This |
| 68 | // should be all that's needed. It subscribes a function to check the (CSV) |
| 69 | // results of a benchmark. This is done only after verifying that the output |
| 70 | // strings are really as expected. |
| 71 | // bm_name_pattern: a name or a regex pattern which will be matched against |
| 72 | // all the benchmark names. Matching benchmarks |
| 73 | // will be the subject of a call to checker_function |
| 74 | // checker_function: should be of type ResultsCheckFn (see below) |
| 75 | #define CHECK_BENCHMARK_RESULTS(bm_name_pattern, checker_function) \ |
| 76 | size_t CONCAT(dummy, __LINE__) = AddChecker(bm_name_pattern, checker_function) |
| 77 | |
| 78 | struct Results; |
| 79 | typedef std::function< void(Results const&) > ResultsCheckFn; |
| 80 | |
| 81 | size_t AddChecker(const char* bm_name_pattern, ResultsCheckFn fn); |
| 82 | |
| 83 | // Class holding the results of a benchmark. |
| 84 | // It is passed in calls to checker functions. |
| 85 | struct Results { |
| 86 | |
| 87 | // the benchmark name |
| 88 | std::string name; |
| 89 | // the benchmark fields |
| 90 | std::map< std::string, std::string > values; |
| 91 | |
| 92 | Results(const std::string& n) : name(n) {} |
| 93 | |
| 94 | int NumThreads() const; |
| 95 | |
| 96 | typedef enum { kCpuTime, kRealTime } BenchmarkTime; |
| 97 | |
| 98 | // get cpu_time or real_time in seconds |
| 99 | double GetTime(BenchmarkTime which) const; |
| 100 | |
| 101 | // get the real_time duration of the benchmark in seconds. |
| 102 | // it is better to use fuzzy float checks for this, as the float |
| 103 | // ASCII formatting is lossy. |
| 104 | double DurationRealTime() const { |
| 105 | return GetAs< double >("iterations") * GetTime(kRealTime); |
| 106 | } |
| 107 | // get the cpu_time duration of the benchmark in seconds |
| 108 | double DurationCPUTime() const { |
| 109 | return GetAs< double >("iterations") * GetTime(kCpuTime); |
| 110 | } |
| 111 | |
| 112 | // get the string for a result by name, or nullptr if the name |
| 113 | // is not found |
| 114 | const std::string* Get(const char* entry_name) const { |
| 115 | auto it = values.find(entry_name); |
| 116 | if(it == values.end()) return nullptr; |
| 117 | return &it->second; |
| 118 | } |
| 119 | |
| 120 | // get a result by name, parsed as a specific type. |
| 121 | // NOTE: for counters, use GetCounterAs instead. |
| 122 | template <class T> |
| 123 | T GetAs(const char* entry_name) const; |
| 124 | |
| 125 | // counters are written as doubles, so they have to be read first |
| 126 | // as a double, and only then converted to the asked type. |
| 127 | template <class T> |
| 128 | T GetCounterAs(const char* entry_name) const { |
| 129 | double dval = GetAs< double >(entry_name); |
| 130 | T tval = static_cast< T >(dval); |
| 131 | return tval; |
| 132 | } |
| 133 | }; |
| 134 | |
| 135 | template <class T> |
| 136 | T Results::GetAs(const char* entry_name) const { |
| 137 | auto *sv = Get(entry_name); |
| 138 | CHECK(sv != nullptr && !sv->empty()); |
| 139 | std::stringstream ss; |
| 140 | ss << *sv; |
| 141 | T out; |
| 142 | ss >> out; |
| 143 | CHECK(!ss.fail()); |
| 144 | return out; |
| 145 | } |
| 146 | |
| 147 | //---------------------------------- |
| 148 | // Macros to help in result checking. Do not use them with arguments causing |
| 149 | // side-effects. |
| 150 | |
| 151 | #define _CHECK_RESULT_VALUE(entry, getfn, var_type, var_name, relationship, value) \ |
| 152 | CONCAT(CHECK_, relationship) \ |
| 153 | (entry.getfn< var_type >(var_name), (value)) << "\n" \ |
| 154 | << __FILE__ << ":" << __LINE__ << ": " << (entry).name << ":\n" \ |
| 155 | << __FILE__ << ":" << __LINE__ << ": " \ |
| 156 | << "expected (" << #var_type << ")" << (var_name) \ |
| 157 | << "=" << (entry).getfn< var_type >(var_name) \ |
| 158 | << " to be " #relationship " to " << (value) << "\n" |
| 159 | |
| 160 | // check with tolerance. eps_factor is the tolerance window, which is |
| 161 | // interpreted relative to value (eg, 0.1 means 10% of value). |
| 162 | #define _CHECK_FLOAT_RESULT_VALUE(entry, getfn, var_type, var_name, relationship, value, eps_factor) \ |
| 163 | CONCAT(CHECK_FLOAT_, relationship) \ |
| 164 | (entry.getfn< var_type >(var_name), (value), (eps_factor) * (value)) << "\n" \ |
| 165 | << __FILE__ << ":" << __LINE__ << ": " << (entry).name << ":\n" \ |
| 166 | << __FILE__ << ":" << __LINE__ << ": " \ |
| 167 | << "expected (" << #var_type << ")" << (var_name) \ |
| 168 | << "=" << (entry).getfn< var_type >(var_name) \ |
| 169 | << " to be " #relationship " to " << (value) << "\n" \ |
| 170 | << __FILE__ << ":" << __LINE__ << ": " \ |
| 171 | << "with tolerance of " << (eps_factor) * (value) \ |
| 172 | << " (" << (eps_factor)*100. << "%), " \ |
| 173 | << "but delta was " << ((entry).getfn< var_type >(var_name) - (value)) \ |
| 174 | << " (" << (((entry).getfn< var_type >(var_name) - (value)) \ |
| 175 | / \ |
| 176 | ((value) > 1.e-5 || value < -1.e-5 ? value : 1.e-5)*100.) \ |
| 177 | << "%)" |
| 178 | |
| 179 | #define CHECK_RESULT_VALUE(entry, var_type, var_name, relationship, value) \ |
| 180 | _CHECK_RESULT_VALUE(entry, GetAs, var_type, var_name, relationship, value) |
| 181 | |
| 182 | #define CHECK_COUNTER_VALUE(entry, var_type, var_name, relationship, value) \ |
| 183 | _CHECK_RESULT_VALUE(entry, GetCounterAs, var_type, var_name, relationship, value) |
| 184 | |
| 185 | #define CHECK_FLOAT_RESULT_VALUE(entry, var_name, relationship, value, eps_factor) \ |
| 186 | _CHECK_FLOAT_RESULT_VALUE(entry, GetAs, double, var_name, relationship, value, eps_factor) |
| 187 | |
| 188 | #define CHECK_FLOAT_COUNTER_VALUE(entry, var_name, relationship, value, eps_factor) \ |
| 189 | _CHECK_FLOAT_RESULT_VALUE(entry, GetCounterAs, double, var_name, relationship, value, eps_factor) |
| 190 | |
| 191 | // ========================================================================= // |
| Eric Fiselier | 4d5e91d | 2016-08-29 19:12:01 +0000 | [diff] [blame] | 192 | // --------------------------- Misc Utilities ------------------------------ // |
| 193 | // ========================================================================= // |
| 194 | |
| 195 | namespace { |
| 196 | |
| 197 | const char* const dec_re = "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?"; |
| 198 | |
| Eric Fiselier | fbc9ff2 | 2016-11-05 00:30:27 +0000 | [diff] [blame] | 199 | } // end namespace |
| Eric Fiselier | 4d5e91d | 2016-08-29 19:12:01 +0000 | [diff] [blame] | 200 | |
| Eric Fiselier | fbc9ff2 | 2016-11-05 00:30:27 +0000 | [diff] [blame] | 201 | #endif // TEST_OUTPUT_TEST_H |