Commit 5686bf1b by Eric Committed by Dominic Hamon

Change reporters to use a specified output and error stream. Add tests for output. (#219)

* Add test for reporter output. * setup err_stream tests * Fix warnings in tests * whitespace * Fix build errors caused by super pedantic compilers * Pass streams by pointer not non-const reference
parent 3f7a9c76
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
#ifndef BENCHMARK_REPORTER_H_ #ifndef BENCHMARK_REPORTER_H_
#define BENCHMARK_REPORTER_H_ #define BENCHMARK_REPORTER_H_
#include <cassert>
#include <iosfwd>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
...@@ -81,6 +83,10 @@ class BenchmarkReporter { ...@@ -81,6 +83,10 @@ class BenchmarkReporter {
bool report_rms; bool report_rms;
}; };
// Construct a BenchmarkReporter with the output stream set to 'std::cout'
// and the error stream set to 'std::cerr'
BenchmarkReporter();
// Called once for every suite of benchmarks run. // Called once for every suite of benchmarks run.
// The parameter "context" contains information that the // The parameter "context" contains information that the
// reporter may wish to use when generating its report, for example the // reporter may wish to use when generating its report, for example the
...@@ -105,12 +111,38 @@ class BenchmarkReporter { ...@@ -105,12 +111,38 @@ class BenchmarkReporter {
// reported. // reported.
virtual void Finalize(); virtual void Finalize();
// REQUIRES: The object referenced by 'out' is valid for the lifetime
// of the reporter.
void SetOutputStream(std::ostream* out) {
assert(out);
output_stream_ = out;
}
// REQUIRES: The object referenced by 'err' is valid for the lifetime
// of the reporter.
void SetErrorStream(std::ostream* err) {
assert(err);
error_stream_ = err;
}
std::ostream& GetOutputStream() const {
return *output_stream_;
}
std::ostream& GetErrorStream() const {
return *error_stream_;
}
virtual ~BenchmarkReporter(); virtual ~BenchmarkReporter();
protected: protected:
static void ComputeStats(const std::vector<Run>& reports, static void ComputeStats(const std::vector<Run>& reports,
Run* mean, Run* stddev); Run* mean, Run* stddev);
static void ComputeBigO(const std::vector<Run>& reports, Run* bigO, Run* rms); static void ComputeBigO(const std::vector<Run>& reports, Run* bigO, Run* rms);
static TimeUnitMultiplier GetTimeUnitAndMultiplier(TimeUnit unit); static TimeUnitMultiplier GetTimeUnitAndMultiplier(TimeUnit unit);
private:
std::ostream* output_stream_;
std::ostream* error_stream_;
}; };
// Simple reporter that outputs benchmark data to the console. This is the // Simple reporter that outputs benchmark data to the console. This is the
......
...@@ -16,8 +16,12 @@ ...@@ -16,8 +16,12 @@
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <cstdarg>
#include <string>
#include <memory>
#include "commandlineflags.h" #include "commandlineflags.h"
#include "check.h"
#include "internal_macros.h" #include "internal_macros.h"
#ifdef BENCHMARK_OS_WINDOWS #ifdef BENCHMARK_OS_WINDOWS
...@@ -74,14 +78,51 @@ PlatformColorCode GetPlatformColorCode(LogColor color) { ...@@ -74,14 +78,51 @@ PlatformColorCode GetPlatformColorCode(LogColor color) {
}; };
#endif #endif
} }
} // end namespace } // end namespace
void ColorPrintf(LogColor color, const char* fmt, ...) { std::string FormatString(const char *msg, va_list args) {
// we might need a second shot at this, so pre-emptivly make a copy
va_list args_cp;
va_copy(args_cp, args);
std::size_t size = 256;
char local_buff[256];
auto ret = std::vsnprintf(local_buff, size, msg, args_cp);
va_end(args_cp);
// currently there is no error handling for failure, so this is hack.
CHECK(ret >= 0);
if (ret == 0) // handle empty expansion
return {};
else if (static_cast<size_t>(ret) < size)
return local_buff;
else {
// we did not provide a long enough buffer on our first attempt.
size = (size_t)ret + 1; // + 1 for the null byte
std::unique_ptr<char[]> buff(new char[size]);
ret = std::vsnprintf(buff.get(), size, msg, args);
CHECK(ret > 0 && ((size_t)ret) < size);
return buff.get();
}
}
std::string FormatString(const char *msg, ...) {
va_list args;
va_start(args, msg);
auto tmp = FormatString(msg, args);
va_end(args);
return tmp;
}
void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
if (!FLAGS_color_print) { if (!FLAGS_color_print) {
vprintf(fmt, args); out << FormatString(fmt, args);
va_end(args); va_end(args);
return; return;
} }
...@@ -107,10 +148,11 @@ void ColorPrintf(LogColor color, const char* fmt, ...) { ...@@ -107,10 +148,11 @@ void ColorPrintf(LogColor color, const char* fmt, ...) {
SetConsoleTextAttribute(stdout_handle, old_color_attrs); SetConsoleTextAttribute(stdout_handle, old_color_attrs);
#else #else
const char* color_code = GetPlatformColorCode(color); const char* color_code = GetPlatformColorCode(color);
if (color_code) fprintf(stdout, "\033[0;3%sm", color_code); if (color_code) out << FormatString("\033[0;3%sm", color_code);
vprintf(fmt, args); out << FormatString(fmt, args) << "\033[m";
printf("\033[m"); // Resets the terminal to default.
#endif #endif
va_end(args); va_end(args);
} }
} // end namespace benchmark } // end namespace benchmark
#ifndef BENCHMARK_COLORPRINT_H_ #ifndef BENCHMARK_COLORPRINT_H_
#define BENCHMARK_COLORPRINT_H_ #define BENCHMARK_COLORPRINT_H_
#include <cstdarg>
#include <string>
#include <iostream>
namespace benchmark { namespace benchmark {
enum LogColor { enum LogColor {
COLOR_DEFAULT, COLOR_DEFAULT,
...@@ -13,7 +17,11 @@ enum LogColor { ...@@ -13,7 +17,11 @@ enum LogColor {
COLOR_WHITE COLOR_WHITE
}; };
void ColorPrintf(LogColor color, const char* fmt, ...); std::string FormatString(const char* msg, va_list args);
std::string FormatString(const char* msg, ...);
void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, ...);
} // end namespace benchmark } // end namespace benchmark
#endif // BENCHMARK_COLORPRINT_H_ #endif // BENCHMARK_COLORPRINT_H_
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
#include "check.h" #include "check.h"
#include "colorprint.h" #include "colorprint.h"
#include "commandlineflags.h"
#include "internal_macros.h"
#include "string_util.h" #include "string_util.h"
#include "walltime.h" #include "walltime.h"
...@@ -33,26 +35,36 @@ namespace benchmark { ...@@ -33,26 +35,36 @@ namespace benchmark {
bool ConsoleReporter::ReportContext(const Context& context) { bool ConsoleReporter::ReportContext(const Context& context) {
name_field_width_ = context.name_field_width; name_field_width_ = context.name_field_width;
std::cerr << "Run on (" << context.num_cpus << " X " << context.mhz_per_cpu auto& Out = GetOutputStream();
auto& Err = GetErrorStream();
#ifdef BENCHMARK_OS_WINDOWS
if (FLAGS_color_print && &Out != &std::cout) {
Err << "Color printing is only supported for stdout on windows. "
"Disabling color printing\n";
FLAGS_color_print = false;
}
#endif
Err << "Run on (" << context.num_cpus << " X " << context.mhz_per_cpu
<< " MHz CPU " << ((context.num_cpus > 1) ? "s" : "") << ")\n"; << " MHz CPU " << ((context.num_cpus > 1) ? "s" : "") << ")\n";
std::cerr << LocalDateTimeString() << "\n"; Err << LocalDateTimeString() << "\n";
if (context.cpu_scaling_enabled) { if (context.cpu_scaling_enabled) {
std::cerr << "***WARNING*** CPU scaling is enabled, the benchmark " Err << "***WARNING*** CPU scaling is enabled, the benchmark "
"real time measurements may be noisy and will incur extra " "real time measurements may be noisy and will incur extra "
"overhead.\n"; "overhead.\n";
} }
#ifndef NDEBUG #ifndef NDEBUG
std::cerr << "***WARNING*** Library was built as DEBUG. Timings may be " Err << "***WARNING*** Library was built as DEBUG. Timings may be "
"affected.\n"; "affected.\n";
#endif #endif
std::string str = FormatString("%-*s %13s %13s %10s\n",
int output_width = fprintf(stdout, "%-*s %13s %13s %10s\n",
static_cast<int>(name_field_width_), "Benchmark", static_cast<int>(name_field_width_), "Benchmark",
"Time", "CPU", "Iterations"); "Time", "CPU", "Iterations");
std::cout << std::string(output_width - 1, '-') << "\n"; Out << str << std::string(str.length() - 1, '-') << "\n";
return true; return true;
} }
...@@ -101,15 +113,17 @@ void ConsoleReporter::ReportComplexity(const std::vector<Run> & complexity_repor ...@@ -101,15 +113,17 @@ void ConsoleReporter::ReportComplexity(const std::vector<Run> & complexity_repor
} }
void ConsoleReporter::PrintRunData(const Run& result) { void ConsoleReporter::PrintRunData(const Run& result) {
auto& Out = GetOutputStream();
auto name_color = (result.report_big_o || result.report_rms) auto name_color = (result.report_big_o || result.report_rms)
? COLOR_BLUE : COLOR_GREEN; ? COLOR_BLUE : COLOR_GREEN;
ColorPrintf(name_color, "%-*s ", name_field_width_, ColorPrintf(Out, name_color, "%-*s ", name_field_width_,
result.benchmark_name.c_str()); result.benchmark_name.c_str());
if (result.error_occurred) { if (result.error_occurred) {
ColorPrintf(COLOR_RED, "ERROR OCCURRED: \'%s\'", ColorPrintf(Out, COLOR_RED, "ERROR OCCURRED: \'%s\'",
result.error_message.c_str()); result.error_message.c_str());
ColorPrintf(COLOR_DEFAULT, "\n"); ColorPrintf(Out, COLOR_DEFAULT, "\n");
return; return;
} }
// Format bytes per second // Format bytes per second
...@@ -131,24 +145,24 @@ void ConsoleReporter::PrintRunData(const Run& result) { ...@@ -131,24 +145,24 @@ void ConsoleReporter::PrintRunData(const Run& result) {
if(result.report_big_o) { if(result.report_big_o) {
std::string big_o = result.report_big_o ? GetBigOString(result.complexity) : ""; std::string big_o = result.report_big_o ? GetBigOString(result.complexity) : "";
ColorPrintf(COLOR_YELLOW, "%10.4f %s %10.4f %s ", ColorPrintf(Out, COLOR_YELLOW, "%10.4f %s %10.4f %s ",
result.real_accumulated_time * multiplier, result.real_accumulated_time * multiplier,
big_o.c_str(), big_o.c_str(),
result.cpu_accumulated_time * multiplier, result.cpu_accumulated_time * multiplier,
big_o.c_str()); big_o.c_str());
} else if(result.report_rms) { } else if(result.report_rms) {
ColorPrintf(COLOR_YELLOW, "%10.0f %% %10.0f %% ", ColorPrintf(Out, COLOR_YELLOW, "%10.0f %% %10.0f %% ",
result.real_accumulated_time * multiplier * 100, result.real_accumulated_time * multiplier * 100,
result.cpu_accumulated_time * multiplier * 100); result.cpu_accumulated_time * multiplier * 100);
} else if (result.iterations == 0) { } else if (result.iterations == 0) {
ColorPrintf(COLOR_YELLOW, "%10.0f %s %10.0f %s ", ColorPrintf(Out, COLOR_YELLOW, "%10.0f %s %10.0f %s ",
result.real_accumulated_time * multiplier, result.real_accumulated_time * multiplier,
timeLabel, timeLabel,
result.cpu_accumulated_time * multiplier, result.cpu_accumulated_time * multiplier,
timeLabel); timeLabel);
} else { } else {
ColorPrintf(COLOR_YELLOW, "%10.0f %s %10.0f %s ", ColorPrintf(Out, COLOR_YELLOW, "%10.0f %s %10.0f %s ",
(result.real_accumulated_time * multiplier) / (result.real_accumulated_time * multiplier) /
(static_cast<double>(result.iterations)), (static_cast<double>(result.iterations)),
timeLabel, timeLabel,
...@@ -158,22 +172,22 @@ void ConsoleReporter::PrintRunData(const Run& result) { ...@@ -158,22 +172,22 @@ void ConsoleReporter::PrintRunData(const Run& result) {
} }
if(!result.report_big_o && !result.report_rms) { if(!result.report_big_o && !result.report_rms) {
ColorPrintf(COLOR_CYAN, "%10lld", result.iterations); ColorPrintf(Out, COLOR_CYAN, "%10lld", result.iterations);
} }
if (!rate.empty()) { if (!rate.empty()) {
ColorPrintf(COLOR_DEFAULT, " %*s", 13, rate.c_str()); ColorPrintf(Out, COLOR_DEFAULT, " %*s", 13, rate.c_str());
} }
if (!items.empty()) { if (!items.empty()) {
ColorPrintf(COLOR_DEFAULT, " %*s", 18, items.c_str()); ColorPrintf(Out, COLOR_DEFAULT, " %*s", 18, items.c_str());
} }
if (!result.report_label.empty()) { if (!result.report_label.empty()) {
ColorPrintf(COLOR_DEFAULT, " %s", result.report_label.c_str()); ColorPrintf(Out, COLOR_DEFAULT, " %s", result.report_label.c_str());
} }
ColorPrintf(COLOR_DEFAULT, "\n"); ColorPrintf(Out, COLOR_DEFAULT, "\n");
} }
} // end namespace benchmark } // end namespace benchmark
...@@ -44,27 +44,30 @@ std::vector<std::string> elements = { ...@@ -44,27 +44,30 @@ std::vector<std::string> elements = {
} }
bool CSVReporter::ReportContext(const Context& context) { bool CSVReporter::ReportContext(const Context& context) {
std::cerr << "Run on (" << context.num_cpus << " X " << context.mhz_per_cpu std::ostream& Err = GetErrorStream();
std::ostream& Out = GetOutputStream();
Err << "Run on (" << context.num_cpus << " X " << context.mhz_per_cpu
<< " MHz CPU " << ((context.num_cpus > 1) ? "s" : "") << ")\n"; << " MHz CPU " << ((context.num_cpus > 1) ? "s" : "") << ")\n";
std::cerr << LocalDateTimeString() << "\n"; Err << LocalDateTimeString() << "\n";
if (context.cpu_scaling_enabled) { if (context.cpu_scaling_enabled) {
std::cerr << "***WARNING*** CPU scaling is enabled, the benchmark " Err << "***WARNING*** CPU scaling is enabled, the benchmark "
"real time measurements may be noisy and will incur extra " "real time measurements may be noisy and will incur extra "
"overhead.\n"; "overhead.\n";
} }
#ifndef NDEBUG #ifndef NDEBUG
std::cerr << "***WARNING*** Library was built as DEBUG. Timings may be " Err << "***WARNING*** Library was built as DEBUG. Timings may be "
"affected.\n"; "affected.\n";
#endif #endif
for (auto B = elements.begin(); B != elements.end(); ) { for (auto B = elements.begin(); B != elements.end(); ) {
std::cout << *B++; Out << *B++;
if (B != elements.end()) if (B != elements.end())
std::cout << ","; Out << ",";
} }
std::cout << "\n"; Out << "\n";
return true; return true;
} }
...@@ -106,19 +109,19 @@ void CSVReporter::ReportComplexity(const std::vector<Run>& complexity_reports) { ...@@ -106,19 +109,19 @@ void CSVReporter::ReportComplexity(const std::vector<Run>& complexity_reports) {
} }
void CSVReporter::PrintRunData(const Run & run) { void CSVReporter::PrintRunData(const Run & run) {
std::ostream& Out = GetOutputStream();
// Field with embedded double-quote characters must be doubled and the field // Field with embedded double-quote characters must be doubled and the field
// delimited with double-quotes. // delimited with double-quotes.
std::string name = run.benchmark_name; std::string name = run.benchmark_name;
ReplaceAll(&name, "\"", "\"\""); ReplaceAll(&name, "\"", "\"\"");
std::cout << '"' << name << "\","; Out << '"' << name << "\",";
if (run.error_occurred) { if (run.error_occurred) {
std::cout << std::string(elements.size() - 3, ','); Out << std::string(elements.size() - 3, ',');
std::cout << "true,"; Out << "true,";
std::string msg = run.error_message; std::string msg = run.error_message;
ReplaceAll(&msg, "\"", "\"\""); ReplaceAll(&msg, "\"", "\"\"");
std::cout << '"' << msg << "\"\n"; Out << '"' << msg << "\"\n";
return; return;
} }
...@@ -135,36 +138,36 @@ void CSVReporter::PrintRunData(const Run & run) { ...@@ -135,36 +138,36 @@ void CSVReporter::PrintRunData(const Run & run) {
// Do not print iteration on bigO and RMS report // Do not print iteration on bigO and RMS report
if(!run.report_big_o && !run.report_rms) { if(!run.report_big_o && !run.report_rms) {
std::cout << run.iterations; Out << run.iterations;
} }
std::cout << ","; Out << ",";
std::cout << real_time << ","; Out << real_time << ",";
std::cout << cpu_time << ","; Out << cpu_time << ",";
// Do not print timeLabel on RMS report // Do not print timeLabel on RMS report
if(!run.report_rms) { if(!run.report_rms) {
std::cout << timeLabel; Out << timeLabel;
} }
std::cout << ","; Out << ",";
if (run.bytes_per_second > 0.0) { if (run.bytes_per_second > 0.0) {
std::cout << run.bytes_per_second; Out << run.bytes_per_second;
} }
std::cout << ","; Out << ",";
if (run.items_per_second > 0.0) { if (run.items_per_second > 0.0) {
std::cout << run.items_per_second; Out << run.items_per_second;
} }
std::cout << ","; Out << ",";
if (!run.report_label.empty()) { if (!run.report_label.empty()) {
// Field with embedded double-quote characters must be doubled and the field // Field with embedded double-quote characters must be doubled and the field
// delimited with double-quotes. // delimited with double-quotes.
std::string label = run.report_label; std::string label = run.report_label;
ReplaceAll(&label, "\"", "\"\""); ReplaceAll(&label, "\"", "\"\"");
std::cout << "\"" << label << "\""; Out << "\"" << label << "\"";
} }
std::cout << ",,"; // for error_occurred and error_message Out << ",,"; // for error_occurred and error_message
std::cout << '\n'; Out << '\n';
} }
} // end namespace benchmark } // end namespace benchmark
...@@ -53,7 +53,7 @@ int64_t RoundDouble(double v) { ...@@ -53,7 +53,7 @@ int64_t RoundDouble(double v) {
} // end namespace } // end namespace
bool JSONReporter::ReportContext(const Context& context) { bool JSONReporter::ReportContext(const Context& context) {
std::ostream& out = std::cout; std::ostream& out = GetOutputStream();
out << "{\n"; out << "{\n";
std::string inner_indent(2, ' '); std::string inner_indent(2, ' ');
...@@ -92,7 +92,7 @@ void JSONReporter::ReportRuns(std::vector<Run> const& reports) { ...@@ -92,7 +92,7 @@ void JSONReporter::ReportRuns(std::vector<Run> const& reports) {
return; return;
} }
std::string indent(4, ' '); std::string indent(4, ' ');
std::ostream& out = std::cout; std::ostream& out = GetOutputStream();
if (!first_report_) { if (!first_report_) {
out << ",\n"; out << ",\n";
} }
...@@ -128,7 +128,7 @@ void JSONReporter::ReportComplexity(const std::vector<Run> & complexity_reports) ...@@ -128,7 +128,7 @@ void JSONReporter::ReportComplexity(const std::vector<Run> & complexity_reports)
} }
std::string indent(4, ' '); std::string indent(4, ' ');
std::ostream& out = std::cout; std::ostream& out = GetOutputStream();
if (!first_report_) { if (!first_report_) {
out << ",\n"; out << ",\n";
} }
...@@ -148,7 +148,7 @@ void JSONReporter::ReportComplexity(const std::vector<Run> & complexity_reports) ...@@ -148,7 +148,7 @@ void JSONReporter::ReportComplexity(const std::vector<Run> & complexity_reports)
void JSONReporter::Finalize() { void JSONReporter::Finalize() {
// Close the list of benchmarks and the top level object. // Close the list of benchmarks and the top level object.
std::cout << "\n ]\n}\n"; GetOutputStream() << "\n ]\n}\n";
} }
void JSONReporter::PrintRunData(Run const& run) { void JSONReporter::PrintRunData(Run const& run) {
...@@ -164,7 +164,7 @@ void JSONReporter::PrintRunData(Run const& run) { ...@@ -164,7 +164,7 @@ void JSONReporter::PrintRunData(Run const& run) {
} }
std::string indent(6, ' '); std::string indent(6, ' ');
std::ostream& out = std::cout; std::ostream& out = GetOutputStream();
out << indent out << indent
<< FormatKV("name", run.benchmark_name) << FormatKV("name", run.benchmark_name)
<< ",\n"; << ",\n";
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
#include "complexity.h" #include "complexity.h"
#include <cstdlib> #include <cstdlib>
#include <iostream>
#include <vector> #include <vector>
#include <tuple> #include <tuple>
...@@ -24,6 +26,11 @@ ...@@ -24,6 +26,11 @@
namespace benchmark { namespace benchmark {
BenchmarkReporter::BenchmarkReporter()
: output_stream_(&std::cout), error_stream_(&std::cerr)
{
}
void BenchmarkReporter::ComputeStats( void BenchmarkReporter::ComputeStats(
const std::vector<Run>& reports, const std::vector<Run>& reports,
Run* mean_data, Run* stddev_data) { Run* mean_data, Run* stddev_data) {
......
...@@ -48,6 +48,8 @@ add_test(fixture_test fixture_test --benchmark_min_time=0.01) ...@@ -48,6 +48,8 @@ add_test(fixture_test fixture_test --benchmark_min_time=0.01)
compile_benchmark_test(map_test) compile_benchmark_test(map_test)
add_test(map_test map_test --benchmark_min_time=0.01) add_test(map_test map_test --benchmark_min_time=0.01)
compile_benchmark_test(reporter_output_test)
add_test(reporter_output_test reporter_output_test --benchmark_min_time=0.01)
check_cxx_compiler_flag(-std=c++03 BENCHMARK_HAS_CXX03_FLAG) check_cxx_compiler_flag(-std=c++03 BENCHMARK_HAS_CXX03_FLAG)
if (BENCHMARK_HAS_CXX03_FLAG) if (BENCHMARK_HAS_CXX03_FLAG)
......
#undef NDEBUG
#include "benchmark/benchmark.h"
#include "../src/check.h" // NOTE: check.h is for internal use only!
#include "../src/re.h" // NOTE: re.h is for internal use only
#include <cassert>
#include <cstring>
#include <iostream>
#include <sstream>
#include <vector>
#include <utility>
namespace {
// ========================================================================= //
// -------------------------- Testing Case --------------------------------- //
// ========================================================================= //
enum MatchRules {
MR_Default, // Skip non-matching lines until a match is found.
MR_Next // Match must occur on the next line.
};
struct TestCase {
std::string regex;
int match_rule;
TestCase(std::string re, int rule = MR_Default) : regex(re), match_rule(rule) {}
void Check(std::stringstream& remaining_output) const {
benchmark::Regex r;
std::string err_str;
r.Init(regex, &err_str);
CHECK(err_str.empty()) << "Could not construct regex \"" << regex << "\""
<< " got Error: " << err_str;
std::string line;
while (remaining_output.eof() == false) {
CHECK(remaining_output.good());
std::getline(remaining_output, line);
if (r.Match(line)) return;
CHECK(match_rule != MR_Next) << "Expected line \"" << line
<< "\" to match regex \"" << regex << "\"";
}
CHECK(remaining_output.eof() == false)
<< "End of output reached before match for regex \"" << regex
<< "\" was found";
}
};
std::vector<TestCase> ConsoleOutputTests;
std::vector<TestCase> JSONOutputTests;
std::vector<TestCase> CSVOutputTests;
std::vector<TestCase> ConsoleErrorTests;
std::vector<TestCase> JSONErrorTests;
std::vector<TestCase> CSVErrorTests;
// ========================================================================= //
// -------------------------- Test Helpers --------------------------------- //
// ========================================================================= //
class TestReporter : public benchmark::BenchmarkReporter {
public:
TestReporter(std::vector<benchmark::BenchmarkReporter*> reps)
: reporters_(reps) {}
virtual bool ReportContext(const Context& context) {
bool last_ret = false;
bool first = true;
for (auto rep : reporters_) {
bool new_ret = rep->ReportContext(context);
CHECK(first || new_ret == last_ret)
<< "Reports return different values for ReportContext";
first = false;
last_ret = new_ret;
}
return last_ret;
}
virtual void ReportRuns(const std::vector<Run>& report) {
for (auto rep : reporters_)
rep->ReportRuns(report);
}
virtual void ReportComplexity(const std::vector<Run>& complexity_reports) {
for (auto rep : reporters_)
rep->ReportComplexity(complexity_reports);
}
virtual void Finalize() {
for (auto rep : reporters_)
rep->Finalize();
}
private:
std::vector<benchmark::BenchmarkReporter*> reporters_;
};
#define CONCAT2(x, y) x##y
#define CONCAT(x, y) CONCAT2(x, y)
#define ADD_CASES(...) \
int CONCAT(dummy, __LINE__) = AddCases(__VA_ARGS__)
int AddCases(std::vector<TestCase>* out, std::initializer_list<TestCase> const& v) {
for (auto const& TC : v)
out->push_back(TC);
return 0;
}
template <class First>
std::string join(First f) { return f; }
template <class First, class ...Args>
std::string join(First f, Args&&... args) {
return std::string(std::move(f)) + "[ ]+" + join(std::forward<Args>(args)...);
}
std::string dec_re = "[0-9]+\\.[0-9]+";
} // end namespace
// ========================================================================= //
// ---------------------- Testing Prologue Output -------------------------- //
// ========================================================================= //
ADD_CASES(&ConsoleOutputTests, {
{join("^Benchmark", "Time", "CPU", "Iterations$"), MR_Next},
{"^[-]+$", MR_Next}
});
ADD_CASES(&CSVOutputTests, {
{"name,iterations,real_time,cpu_time,time_unit,bytes_per_second,items_per_second,"
"label,error_occurred,error_message"}
});
// ========================================================================= //
// ------------------------ Testing Basic Output --------------------------- //
// ========================================================================= //
void BM_basic(benchmark::State& state) {
while (state.KeepRunning()) {}
}
BENCHMARK(BM_basic);
ADD_CASES(&ConsoleOutputTests, {
{"^BM_basic[ ]+[0-9]{1,5} ns[ ]+[0-9]{1,5} ns[ ]+[0-9]+$"}
});
ADD_CASES(&JSONOutputTests, {
{"\"name\": \"BM_basic\",$"},
{"\"iterations\": [0-9]+,$", MR_Next},
{"\"real_time\": [0-9],$", MR_Next},
{"\"cpu_time\": [0-9],$", MR_Next},
{"\"time_unit\": \"ns\"$", MR_Next},
{"}", MR_Next}
});
ADD_CASES(&CSVOutputTests, {
{"^\"BM_basic\",[0-9]+," + dec_re + "," + dec_re + ",ns,,,,,$"}
});
// ========================================================================= //
// ------------------------ Testing Error Output --------------------------- //
// ========================================================================= //
void BM_error(benchmark::State& state) {
state.SkipWithError("message");
while(state.KeepRunning()) {}
}
BENCHMARK(BM_error);
ADD_CASES(&ConsoleOutputTests, {
{"^BM_error[ ]+ERROR OCCURRED: 'message'$"}
});
ADD_CASES(&JSONOutputTests, {
{"\"name\": \"BM_error\",$"},
{"\"error_occurred\": true,$", MR_Next},
{"\"error_message\": \"message\",$", MR_Next}
});
ADD_CASES(&CSVOutputTests, {
{"^\"BM_error\",,,,,,,,true,\"message\"$"}
});
// ========================================================================= //
// ----------------------- Testing Complexity Output ----------------------- //
// ========================================================================= //
void BM_Complexity_O1(benchmark::State& state) {
while (state.KeepRunning()) {
}
state.SetComplexityN(state.range_x());
}
BENCHMARK(BM_Complexity_O1)->Range(1, 1<<18)->Complexity(benchmark::o1);
std::string bigOStr = "[0-9]+\\.[0-9]+ \\* [0-9]+";
ADD_CASES(&ConsoleOutputTests, {
{join("^BM_Complexity_O1_BigO", bigOStr, bigOStr) + "[ ]*$"},
{join("^BM_Complexity_O1_RMS", "[0-9]+ %", "[0-9]+ %") + "[ ]*$"}
});
// ========================================================================= //
// --------------------------- TEST CASES END ------------------------------ //
// ========================================================================= //
int main(int argc, char* argv[]) {
// Add --color_print=false to argv since we don't want to match color codes.
char new_arg[64];
char* new_argv[64];
std::copy(argv, argv + argc, new_argv);
new_argv[argc++] = std::strcpy(new_arg, "--color_print=false");
benchmark::Initialize(&argc, new_argv);
benchmark::ConsoleReporter CR;
benchmark::JSONReporter JR;
benchmark::CSVReporter CSVR;
struct ReporterTest {
const char* name;
std::vector<TestCase>& output_cases;
std::vector<TestCase>& error_cases;
benchmark::BenchmarkReporter& reporter;
std::stringstream out_stream;
std::stringstream err_stream;
ReporterTest(const char* n,
std::vector<TestCase>& out_tc,
std::vector<TestCase>& err_tc,
benchmark::BenchmarkReporter& br)
: name(n), output_cases(out_tc), error_cases(err_tc), reporter(br) {
reporter.SetOutputStream(&out_stream);
reporter.SetErrorStream(&err_stream);
}
} TestCases[] = {
{"ConsoleReporter", ConsoleOutputTests, ConsoleErrorTests, CR},
{"JSONReporter", JSONOutputTests, JSONErrorTests, JR},
{"CSVReporter", CSVOutputTests, CSVErrorTests, CSVR}
};
// Create the test reporter and run the benchmarks.
std::cout << "Running benchmarks...\n";
TestReporter test_rep({&CR, &JR, &CSVR});
benchmark::RunSpecifiedBenchmarks(&test_rep);
for (auto& rep_test : TestCases) {
std::string msg = std::string("\nTesting ") + rep_test.name + " Output\n";
std::string banner(msg.size() - 1, '-');
std::cout << banner << msg << banner << "\n";
std::cerr << rep_test.err_stream.str();
std::cout << rep_test.out_stream.str();
for (const auto& TC : rep_test.error_cases)
TC.Check(rep_test.err_stream);
for (const auto& TC : rep_test.output_cases)
TC.Check(rep_test.out_stream);
std::cout << "\n";
}
return 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment