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
diff --git a/include/benchmark/reporter.h b/include/benchmark/reporter.h
index 56fe1f9..ded01d2 100644
--- a/include/benchmark/reporter.h
+++ b/include/benchmark/reporter.h
@@ -14,6 +14,8 @@
 #ifndef BENCHMARK_REPORTER_H_
 #define BENCHMARK_REPORTER_H_
 
+#include <cassert>
+#include <iosfwd>
 #include <string>
 #include <utility>
 #include <vector>
@@ -81,6 +83,10 @@
     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.
   // The parameter "context" contains information that the
   // reporter may wish to use when generating its report, for example the
@@ -105,12 +111,38 @@
   // reported.
   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();
 protected:
   static void ComputeStats(const std::vector<Run>& reports,
                            Run* mean, Run* stddev);
   static void ComputeBigO(const std::vector<Run>& reports, Run* bigO, Run* rms);
   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
diff --git a/src/colorprint.cc b/src/colorprint.cc
index 81f917b..efb8626 100644
--- a/src/colorprint.cc
+++ b/src/colorprint.cc
@@ -16,8 +16,12 @@
 
 #include <cstdarg>
 #include <cstdio>
+#include <cstdarg>
+#include <string>
+#include <memory>
 
 #include "commandlineflags.h"
+#include "check.h"
 #include "internal_macros.h"
 
 #ifdef BENCHMARK_OS_WINDOWS
@@ -74,14 +78,51 @@
   };
 #endif
 }
+
 }  // 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_start(args, fmt);
 
   if (!FLAGS_color_print) {
-    vprintf(fmt, args);
+    out << FormatString(fmt, args);
     va_end(args);
     return;
   }
@@ -107,10 +148,11 @@
   SetConsoleTextAttribute(stdout_handle, old_color_attrs);
 #else
   const char* color_code = GetPlatformColorCode(color);
-  if (color_code) fprintf(stdout, "\033[0;3%sm", color_code);
-  vprintf(fmt, args);
-  printf("\033[m");  // Resets the terminal to default.
+  if (color_code) out << FormatString("\033[0;3%sm", color_code);
+  out << FormatString(fmt, args) << "\033[m";
 #endif
+
   va_end(args);
 }
+
 }  // end namespace benchmark
diff --git a/src/colorprint.h b/src/colorprint.h
index 54d1f66..2b3c082 100644
--- a/src/colorprint.h
+++ b/src/colorprint.h
@@ -1,6 +1,10 @@
 #ifndef BENCHMARK_COLORPRINT_H_
 #define BENCHMARK_COLORPRINT_H_
 
+#include <cstdarg>
+#include <string>
+#include <iostream>
+
 namespace benchmark {
 enum LogColor {
   COLOR_DEFAULT,
@@ -13,7 +17,11 @@
   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
 
 #endif  // BENCHMARK_COLORPRINT_H_
diff --git a/src/console_reporter.cc b/src/console_reporter.cc
index 5dd98f6..6e39ad9 100644
--- a/src/console_reporter.cc
+++ b/src/console_reporter.cc
@@ -25,6 +25,8 @@
 
 #include "check.h"
 #include "colorprint.h"
+#include "commandlineflags.h"
+#include "internal_macros.h"
 #include "string_util.h"
 #include "walltime.h"
 
@@ -33,26 +35,36 @@
 bool ConsoleReporter::ReportContext(const Context& context) {
   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";
 
-  std::cerr << LocalDateTimeString() << "\n";
+  Err << LocalDateTimeString() << "\n";
 
   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 "
                  "overhead.\n";
   }
 
 #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";
 #endif
-
-  int output_width = fprintf(stdout, "%-*s %13s %13s %10s\n",
+  std::string str = FormatString("%-*s %13s %13s %10s\n",
                              static_cast<int>(name_field_width_), "Benchmark",
                              "Time", "CPU", "Iterations");
-  std::cout << std::string(output_width - 1, '-') << "\n";
+  Out << str << std::string(str.length() - 1, '-') << "\n";
 
   return true;
 }
@@ -101,15 +113,17 @@
 }
 
 void ConsoleReporter::PrintRunData(const Run& result) {
+  auto& Out = GetOutputStream();
+
   auto name_color = (result.report_big_o || result.report_rms)
       ? COLOR_BLUE : COLOR_GREEN;
-  ColorPrintf(name_color, "%-*s ", name_field_width_,
+  ColorPrintf(Out, name_color, "%-*s ", name_field_width_,
               result.benchmark_name.c_str());
 
   if (result.error_occurred) {
-    ColorPrintf(COLOR_RED, "ERROR OCCURRED: \'%s\'",
+    ColorPrintf(Out, COLOR_RED, "ERROR OCCURRED: \'%s\'",
                 result.error_message.c_str());
-    ColorPrintf(COLOR_DEFAULT, "\n");
+    ColorPrintf(Out, COLOR_DEFAULT, "\n");
     return;
   }
   // Format bytes per second
@@ -131,24 +145,24 @@
 
   if(result.report_big_o) {
     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,
                 big_o.c_str(),
                 result.cpu_accumulated_time * multiplier,
                 big_o.c_str());
   } 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.cpu_accumulated_time * multiplier * 100);
   } 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,
                 timeLabel,
                 result.cpu_accumulated_time * multiplier,
                 timeLabel);
   } 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) /
                     (static_cast<double>(result.iterations)),
                 timeLabel,
@@ -158,22 +172,22 @@
   }
 
   if(!result.report_big_o && !result.report_rms) {
-    ColorPrintf(COLOR_CYAN, "%10lld", result.iterations);
+    ColorPrintf(Out, COLOR_CYAN, "%10lld", result.iterations);
   }
 
   if (!rate.empty()) {
-    ColorPrintf(COLOR_DEFAULT, " %*s", 13, rate.c_str());
+    ColorPrintf(Out, COLOR_DEFAULT, " %*s", 13, rate.c_str());
   }
 
   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()) {
-    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
diff --git a/src/csv_reporter.cc b/src/csv_reporter.cc
index ac4bd3d..36f149b 100644
--- a/src/csv_reporter.cc
+++ b/src/csv_reporter.cc
@@ -44,27 +44,30 @@
 }
 
 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";
 
-  std::cerr << LocalDateTimeString() << "\n";
+  Err << LocalDateTimeString() << "\n";
 
   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 "
                  "overhead.\n";
   }
 
 #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";
 #endif
   for (auto B = elements.begin(); B != elements.end(); ) {
-    std::cout << *B++;
+    Out << *B++;
     if (B != elements.end())
-      std::cout << ",";
+      Out << ",";
   }
-  std::cout << "\n";
+  Out << "\n";
   return true;
 }
 
@@ -106,19 +109,19 @@
 }
 
 void CSVReporter::PrintRunData(const Run & run) {
-
+  std::ostream& Out = GetOutputStream();
 
   // Field with embedded double-quote characters must be doubled and the field
   // delimited with double-quotes.
   std::string name = run.benchmark_name;
   ReplaceAll(&name, "\"", "\"\"");
-  std::cout << '"' << name << "\",";
+  Out << '"' << name << "\",";
   if (run.error_occurred) {
-    std::cout << std::string(elements.size() - 3, ',');
-    std::cout << "true,";
+    Out << std::string(elements.size() - 3, ',');
+    Out << "true,";
     std::string msg = run.error_message;
     ReplaceAll(&msg, "\"", "\"\"");
-    std::cout << '"' << msg << "\"\n";
+    Out << '"' << msg << "\"\n";
     return;
   }
 
@@ -135,36 +138,36 @@
 
   // Do not print iteration on bigO and RMS report
   if(!run.report_big_o && !run.report_rms) {
-    std::cout << run.iterations;
+    Out << run.iterations;
   }
-  std::cout << ",";
+  Out << ",";
 
-  std::cout << real_time << ",";
-  std::cout << cpu_time << ",";
+  Out << real_time << ",";
+  Out << cpu_time << ",";
 
   // Do not print timeLabel on RMS report
   if(!run.report_rms) {
-    std::cout << timeLabel;
+    Out << timeLabel;
   }
-  std::cout << ",";
+  Out << ",";
 
   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) {
-    std::cout << run.items_per_second;
+    Out << run.items_per_second;
   }
-  std::cout << ",";
+  Out << ",";
   if (!run.report_label.empty()) {
     // Field with embedded double-quote characters must be doubled and the field
     // delimited with double-quotes.
     std::string label = run.report_label;
     ReplaceAll(&label, "\"", "\"\"");
-    std::cout << "\"" << label << "\"";
+    Out << "\"" << label << "\"";
   }
-  std::cout << ",,"; // for error_occurred and error_message
-  std::cout << '\n';
+  Out << ",,"; // for error_occurred and error_message
+  Out << '\n';
 }
 
 }  // end namespace benchmark
diff --git a/src/json_reporter.cc b/src/json_reporter.cc
index cab527c..d3effe1 100644
--- a/src/json_reporter.cc
+++ b/src/json_reporter.cc
@@ -53,7 +53,7 @@
 } // end namespace
 
 bool JSONReporter::ReportContext(const Context& context) {
-  std::ostream& out = std::cout;
+  std::ostream& out = GetOutputStream();
 
   out << "{\n";
   std::string inner_indent(2, ' ');
@@ -92,7 +92,7 @@
     return;
   }
   std::string indent(4, ' ');
-  std::ostream& out = std::cout;
+  std::ostream& out = GetOutputStream();
   if (!first_report_) {
     out << ",\n";
   }
@@ -128,7 +128,7 @@
   }
 
   std::string indent(4, ' ');
-  std::ostream& out = std::cout;
+  std::ostream& out = GetOutputStream();
   if (!first_report_) {
     out << ",\n";
   }
@@ -148,7 +148,7 @@
 
 void JSONReporter::Finalize() {
     // 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) {
@@ -164,7 +164,7 @@
     }
 
     std::string indent(6, ' ');
-    std::ostream& out = std::cout;
+    std::ostream& out = GetOutputStream();
     out << indent
         << FormatKV("name", run.benchmark_name)
         << ",\n";
diff --git a/src/reporter.cc b/src/reporter.cc
index d0a80e6..5b303f7 100644
--- a/src/reporter.cc
+++ b/src/reporter.cc
@@ -16,6 +16,8 @@
 #include "complexity.h"
 
 #include <cstdlib>
+
+#include <iostream>
 #include <vector>
 #include <tuple>
 
@@ -24,6 +26,11 @@
 
 namespace benchmark {
 
+BenchmarkReporter::BenchmarkReporter()
+    : output_stream_(&std::cout), error_stream_(&std::cerr)
+{
+}
+
 void BenchmarkReporter::ComputeStats(
     const std::vector<Run>& reports,
     Run* mean_data, Run* stddev_data) {
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 247c630..aeb720a 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -48,6 +48,8 @@
 compile_benchmark_test(map_test)
 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)
 if (BENCHMARK_HAS_CXX03_FLAG)
diff --git a/test/reporter_output_test.cc b/test/reporter_output_test.cc
new file mode 100644
index 0000000..acfbf68
--- /dev/null
+++ b/test/reporter_output_test.cc
@@ -0,0 +1,264 @@
+
+#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;
+}