| #ifndef TEST_OUTPUT_TEST_H | |
| #define TEST_OUTPUT_TEST_H | |
| #undef NDEBUG | |
| #include <functional> | |
| #include <initializer_list> | |
| #include <memory> | |
| #include <sstream> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "../src/re.h" | |
| #include "benchmark/benchmark.h" | |
| #define CONCAT2(x, y) x##y | |
| #define CONCAT(x, y) CONCAT2(x, y) | |
| #define ADD_CASES(...) \ | |
| const int CONCAT(dummy, __LINE__) = ::AddCases(__VA_ARGS__) | |
| #define SET_SUBSTITUTIONS(...) \ | |
| const int CONCAT(dummy, __LINE__) = ::SetSubstitutions(__VA_ARGS__) | |
| enum MatchRules : uint8_t { | |
| MR_Default, // Skip non-matching lines until a match is found. | |
| MR_Next, // Match must occur on the next line. | |
| MR_Not // No line between the current position and the next match matches | |
| // the regex | |
| }; | |
| struct TestCase { | |
| TestCase(std::string re, int rule = MR_Default); | |
| std::string regex_str; | |
| int match_rule; | |
| std::string substituted_regex; | |
| std::shared_ptr<benchmark::Regex> regex; | |
| }; | |
| enum TestCaseID : uint8_t { | |
| TC_ConsoleOut, | |
| TC_ConsoleErr, | |
| TC_JSONOut, | |
| TC_JSONErr, | |
| TC_CSVOut, | |
| TC_CSVErr, | |
| TC_NumID // PRIVATE | |
| }; | |
| // Add a list of test cases to be run against the output specified by | |
| // 'ID' | |
| int AddCases(TestCaseID ID, std::initializer_list<TestCase> il); | |
| // Add or set a list of substitutions to be performed on constructed regex's | |
| // See 'output_test_helper.cc' for a list of default substitutions. | |
| int SetSubstitutions( | |
| std::initializer_list<std::pair<std::string, std::string>> il); | |
| // Run all output tests. | |
| void RunOutputTests(int argc, char* argv[]); | |
| // Count the number of 'pat' substrings in the 'haystack' string. | |
| int SubstrCnt(const std::string& haystack, const std::string& pat); | |
| // Run registered benchmarks with file reporter enabled, and return the content | |
| // outputted by the file reporter. | |
| std::string GetFileReporterOutput(int argc, char* argv[]); | |
| // ========================================================================= // | |
| // ------------------------- Results checking ------------------------------ // | |
| // ========================================================================= // | |
| // Call this macro to register a benchmark for checking its results. This | |
| // should be all that's needed. It subscribes a function to check the (CSV) | |
| // results of a benchmark. This is done only after verifying that the output | |
| // strings are really as expected. | |
| // bm_name_pattern: a name or a regex pattern which will be matched against | |
| // all the benchmark names. Matching benchmarks | |
| // will be the subject of a call to checker_function | |
| // checker_function: should be of type ResultsCheckFn (see below) | |
| #define CHECK_BENCHMARK_RESULTS(bm_name_pattern, checker_function) \ | |
| const size_t CONCAT(dummy, __LINE__) = \ | |
| AddChecker(bm_name_pattern, checker_function) | |
| struct Results; | |
| typedef std::function<void(Results const&)> ResultsCheckFn; | |
| size_t AddChecker(const std::string& bm_name_pattern, const ResultsCheckFn& fn); | |
| // Class holding the results of a benchmark. | |
| // It is passed in calls to checker functions. | |
| struct Results { | |
| // the benchmark name | |
| std::string name; | |
| // the benchmark fields | |
| std::map<std::string, std::string> values; | |
| Results(const std::string& n) : name(n) {} | |
| int NumThreads() const; | |
| double NumIterations() const; | |
| typedef enum : uint8_t { kCpuTime, kRealTime } BenchmarkTime; | |
| // get cpu_time or real_time in seconds | |
| double GetTime(BenchmarkTime which) const; | |
| // get the real_time duration of the benchmark in seconds. | |
| // it is better to use fuzzy float checks for this, as the float | |
| // ASCII formatting is lossy. | |
| double DurationRealTime() const { | |
| return NumIterations() * GetTime(kRealTime); | |
| } | |
| // get the cpu_time duration of the benchmark in seconds | |
| double DurationCPUTime() const { return NumIterations() * GetTime(kCpuTime); } | |
| // get the string for a result by name, or nullptr if the name | |
| // is not found | |
| const std::string* Get(const std::string& entry_name) const { | |
| auto it = values.find(entry_name); | |
| if (it == values.end()) return nullptr; | |
| return &it->second; | |
| } | |
| // get a result by name, parsed as a specific type. | |
| // NOTE: for counters, use GetCounterAs instead. | |
| template <class T> | |
| T GetAs(const std::string& entry_name) const; | |
| // counters are written as doubles, so they have to be read first | |
| // as a double, and only then converted to the asked type. | |
| template <class T> | |
| T GetCounterAs(const std::string& entry_name) const { | |
| double dval = GetAs<double>(entry_name); | |
| T tval = static_cast<T>(dval); | |
| return tval; | |
| } | |
| }; | |
| template <class T> | |
| T Results::GetAs(const std::string& entry_name) const { | |
| auto* sv = Get(entry_name); | |
| BM_CHECK(sv != nullptr && !sv->empty()); | |
| std::stringstream ss; | |
| ss << *sv; | |
| T out; | |
| ss >> out; | |
| BM_CHECK(!ss.fail()); | |
| return out; | |
| } | |
| //---------------------------------- | |
| // Macros to help in result checking. Do not use them with arguments causing | |
| // side-effects. | |
| // clang-format off | |
| #define CHECK_RESULT_VALUE_IMPL(entry, getfn, var_type, var_name, relationship, value) \ | |
| CONCAT(BM_CHECK_, relationship) \ | |
| (entry.getfn< var_type >(var_name), (value)) << "\n" \ | |
| << __FILE__ << ":" << __LINE__ << ": " << (entry).name << ":\n" \ | |
| << __FILE__ << ":" << __LINE__ << ": " \ | |
| << "expected (" << #var_type << ")" << (var_name) \ | |
| << "=" << (entry).getfn< var_type >(var_name) \ | |
| << " to be " #relationship " to " << (value) << "\n" | |
| // check with tolerance. eps_factor is the tolerance window, which is | |
| // interpreted relative to value (eg, 0.1 means 10% of value). | |
| #define CHECK_FLOAT_RESULT_VALUE_IMPL(entry, getfn, var_type, var_name, relationship, value, eps_factor) \ | |
| CONCAT(BM_CHECK_FLOAT_, relationship) \ | |
| (entry.getfn< var_type >(var_name), (value), (eps_factor) * (value)) << "\n" \ | |
| << __FILE__ << ":" << __LINE__ << ": " << (entry).name << ":\n" \ | |
| << __FILE__ << ":" << __LINE__ << ": " \ | |
| << "expected (" << #var_type << ")" << (var_name) \ | |
| << "=" << (entry).getfn< var_type >(var_name) \ | |
| << " to be " #relationship " to " << (value) << "\n" \ | |
| << __FILE__ << ":" << __LINE__ << ": " \ | |
| << "with tolerance of " << (eps_factor) * (value) \ | |
| << " (" << (eps_factor)*100. << "%), " \ | |
| << "but delta was " << ((entry).getfn< var_type >(var_name) - (value)) \ | |
| << " (" << (((entry).getfn< var_type >(var_name) - (value)) \ | |
| / \ | |
| ((value) > 1.e-5 || value < -1.e-5 ? value : 1.e-5)*100.) \ | |
| << "%)" | |
| #define CHECK_RESULT_VALUE(entry, var_type, var_name, relationship, value) \ | |
| CHECK_RESULT_VALUE_IMPL(entry, GetAs, var_type, var_name, relationship, value) | |
| #define CHECK_COUNTER_VALUE(entry, var_type, var_name, relationship, value) \ | |
| CHECK_RESULT_VALUE_IMPL(entry, GetCounterAs, var_type, var_name, relationship, value) | |
| #define CHECK_FLOAT_RESULT_VALUE(entry, var_name, relationship, value, eps_factor) \ | |
| CHECK_FLOAT_RESULT_VALUE_IMPL(entry, GetAs, double, var_name, relationship, value, eps_factor) | |
| #define CHECK_FLOAT_COUNTER_VALUE(entry, var_name, relationship, value, eps_factor) \ | |
| CHECK_FLOAT_RESULT_VALUE_IMPL(entry, GetCounterAs, double, var_name, relationship, value, eps_factor) | |
| // clang-format on | |
| // ========================================================================= // | |
| // --------------------------- Misc Utilities ------------------------------ // | |
| // ========================================================================= // | |
| namespace { | |
| const char* const dec_re = "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?"; | |
| } // end namespace | |
| #endif // TEST_OUTPUT_TEST_H |