Auto-detect whether Benchmark should produce colorized output (#126)
* Auto-detect whether to produce colorized output
Rename --color_print to --benchmark_color for consistency with the other
flags (and Google Test). Old flag name is kept around for compatibility.
The --benchmark_color/--color_print flag takes a third option, "auto",
which is the new default. In this mode, we attempt to auto-detect
whether to produce colorized output. (The logic for deciding whether to
use colorized output was lifted from GTest:
<https://github.com/google/googletest/blob/master/googletest/src/gtest.cc#L2925>.)
* Update CONTRIBUTORS, AUTHORS
diff --git a/AUTHORS b/AUTHORS
index 0e40922..5a545fa 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -24,6 +24,7 @@
Kaito Udagawa <umireon@gmail.com>
Lei Xu <eddyxu@gmail.com>
Matt Clarkson <mattyclarkson@gmail.com>
+Nick Hutchinson <nshutchinson@gmail.com>
Oleksandr Sochka <sasha.sochka@gmail.com>
Paul Redmond <paul.redmond@gmail.com>
Radoslav Yovchev <radoslav.tm@gmail.com>
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 4bff126..33cd941 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -40,6 +40,7 @@
Kai Wolf <kai.wolf@gmail.com>
Lei Xu <eddyxu@gmail.com>
Matt Clarkson <mattyclarkson@gmail.com>
+Nick Hutchinson <nshutchinson@gmail.com>
Oleksandr Sochka <sasha.sochka@gmail.com>
Pascal Leroy <phl@google.com>
Paul Redmond <paul.redmond@gmail.com>
diff --git a/src/benchmark.cc b/src/benchmark.cc
index a5073f5..da195f9 100644
--- a/src/benchmark.cc
+++ b/src/benchmark.cc
@@ -34,6 +34,7 @@
#include <thread>
#include "check.h"
+#include "colorprint.h"
#include "commandlineflags.h"
#include "complexity.h"
#include "log.h"
@@ -82,7 +83,12 @@
DEFINE_string(benchmark_out, "", "The file to write additonal output to");
-DEFINE_bool(color_print, true, "Enables colorized logging.");
+DEFINE_string(benchmark_color, "auto",
+ "Whether to use colors in the output. Valid values: "
+ "'true'/'yes'/1, 'false'/'no'/0, and 'auto'. 'auto' means to use "
+ "colors if the output is being sent to a terminal and the TERM "
+ "environment variable is set to a terminal type that supports "
+ "colors.");
DEFINE_int32(v, 0, "The level of verbose logging to output");
@@ -546,8 +552,14 @@
std::unique_ptr<BenchmarkReporter> default_console_reporter;
std::unique_ptr<BenchmarkReporter> default_file_reporter;
if (!console_reporter) {
- auto output_opts = FLAGS_color_print ? ConsoleReporter::OO_Color
- : ConsoleReporter::OO_None;
+ auto output_opts = ConsoleReporter::OO_None;
+ if (FLAGS_benchmark_color == "auto")
+ output_opts = IsColorTerminal() ? ConsoleReporter::OO_Color
+ : ConsoleReporter::OO_None;
+ else
+ output_opts = IsTruthyFlagValue(FLAGS_benchmark_color)
+ ? ConsoleReporter::OO_Color
+ : ConsoleReporter::OO_None;
default_console_reporter = internal::CreateReporter(
FLAGS_benchmark_format, output_opts);
console_reporter = default_console_reporter.get();
@@ -602,7 +614,7 @@
" [--benchmark_format=<console|json|csv>]\n"
" [--benchmark_out=<filename>]\n"
" [--benchmark_out_format=<json|console|csv>]\n"
- " [--color_print={true|false}]\n"
+ " [--benchmark_color={auto|true|false}]\n"
" [--v=<verbosity>]\n");
exit(0);
}
@@ -627,8 +639,12 @@
&FLAGS_benchmark_out) ||
ParseStringFlag(argv[i], "benchmark_out_format",
&FLAGS_benchmark_out_format) ||
- ParseBoolFlag(argv[i], "color_print",
- &FLAGS_color_print) ||
+ ParseStringFlag(argv[i], "benchmark_color",
+ &FLAGS_benchmark_color) ||
+ // "color_print" is the deprecated name for "benchmark_color".
+ // TODO: Remove this.
+ ParseStringFlag(argv[i], "color_print",
+ &FLAGS_benchmark_color) ||
ParseInt32Flag(argv[i], "v", &FLAGS_v)) {
for (int j = i; j != *argc; ++j) argv[j] = argv[j + 1];
@@ -643,6 +659,9 @@
if (*flag != "console" && *flag != "json" && *flag != "csv") {
PrintUsageAndExit();
}
+ if (FLAGS_benchmark_color.empty()) {
+ PrintUsageAndExit();
+ }
}
int InitializeStreams() {
diff --git a/src/colorprint.cc b/src/colorprint.cc
index b7d316f..f24e6f8 100644
--- a/src/colorprint.cc
+++ b/src/colorprint.cc
@@ -16,16 +16,20 @@
#include <cstdarg>
#include <cstdio>
-#include <cstdarg>
-#include <string>
+#include <cstdlib>
+#include <cstring>
#include <memory>
+#include <string>
#include "check.h"
#include "internal_macros.h"
#ifdef BENCHMARK_OS_WINDOWS
+#include <io.h>
#include <Windows.h>
-#endif
+#else
+#include <unistd.h>
+#endif // BENCHMARK_OS_WINDOWS
namespace benchmark {
namespace {
@@ -151,4 +155,34 @@
}
+bool IsColorTerminal() {
+#if BENCHMARK_OS_WINDOWS
+ // On Windows the TERM variable is usually not set, but the
+ // console there does support colors.
+ return 0 != _isatty(_fileno(stdout));
+#else
+ // On non-Windows platforms, we rely on the TERM variable. This list of
+ // supported TERM values is copied from Google Test:
+ // <https://github.com/google/googletest/blob/master/googletest/src/gtest.cc#L2925>.
+ const char* const SUPPORTED_TERM_VALUES[] = {
+ "xterm", "xterm-color", "xterm-256color",
+ "screen", "screen-256color", "tmux",
+ "tmux-256color", "rxvt-unicode", "rxvt-unicode-256color",
+ "linux", "cygwin",
+ };
+
+ const char* const term = getenv("TERM");
+
+ bool term_supports_color = false;
+ for (const char* candidate : SUPPORTED_TERM_VALUES) {
+ if (term && 0 == strcmp(term, candidate)) {
+ term_supports_color = true;
+ break;
+ }
+ }
+
+ return 0 != isatty(fileno(stdout)) && term_supports_color;
+#endif // BENCHMARK_OS_WINDOWS
+}
+
} // end namespace benchmark
diff --git a/src/colorprint.h b/src/colorprint.h
index d924795..30d4300 100644
--- a/src/colorprint.h
+++ b/src/colorprint.h
@@ -23,6 +23,10 @@
void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, va_list args);
void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, ...);
+// Returns true if stdout appears to be a terminal that supports colored
+// output, false otherwise.
+bool IsColorTerminal();
+
} // end namespace benchmark
#endif // BENCHMARK_COLORPRINT_H_
diff --git a/src/commandlineflags.cc b/src/commandlineflags.cc
index 3e9a37a..c6fba55 100644
--- a/src/commandlineflags.cc
+++ b/src/commandlineflags.cc
@@ -14,6 +14,7 @@
#include "commandlineflags.h"
+#include <cctype>
#include <cstdlib>
#include <cstring>
#include <iostream>
@@ -74,17 +75,6 @@
return true;
}
-inline const char* GetEnv(const char* name) {
-#if defined(__BORLANDC__) || defined(__SunOS_5_8) || defined(__SunOS_5_9)
- // Environment variables which we programmatically clear will be set to the
- // empty string rather than unset (nullptr). Handle that case.
- const char* const env = getenv(name);
- return (env != nullptr && env[0] != '\0') ? env : nullptr;
-#else
- return getenv(name);
-#endif
-}
-
// Returns the name of the environment variable corresponding to the
// given flag. For example, FlagToEnvVar("foo") will return
// "BENCHMARK_FOO" in the open-source version.
@@ -104,7 +94,7 @@
// The value is considered true iff it's not "0".
bool BoolFromEnv(const char* flag, bool default_value) {
const std::string env_var = FlagToEnvVar(flag);
- const char* const string_value = GetEnv(env_var.c_str());
+ const char* const string_value = getenv(env_var.c_str());
return string_value == nullptr ? default_value : strcmp(string_value, "0") != 0;
}
@@ -113,7 +103,7 @@
// doesn't represent a valid 32-bit integer, returns default_value.
int32_t Int32FromEnv(const char* flag, int32_t default_value) {
const std::string env_var = FlagToEnvVar(flag);
- const char* const string_value = GetEnv(env_var.c_str());
+ const char* const string_value = getenv(env_var.c_str());
if (string_value == nullptr) {
// The environment variable is not set.
return default_value;
@@ -133,7 +123,7 @@
// the given flag; if it's not set, returns default_value.
const char* StringFromEnv(const char* flag, const char* default_value) {
const std::string env_var = FlagToEnvVar(flag);
- const char* const value = GetEnv(env_var.c_str());
+ const char* const value = getenv(env_var.c_str());
return value == nullptr ? default_value : value;
}
@@ -175,7 +165,7 @@
if (value_str == nullptr) return false;
// Converts the string value to a bool.
- *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F');
+ *value = IsTruthyFlagValue(value_str);
return true;
}
@@ -217,4 +207,12 @@
bool IsFlag(const char* str, const char* flag) {
return (ParseFlagValue(str, flag, true) != nullptr);
}
+
+bool IsTruthyFlagValue(const std::string& str) {
+ if (str.empty())
+ return true;
+ char ch = str[0];
+ return isalnum(ch) &&
+ !(ch == '0' || ch == 'f' || ch == 'F' || ch == 'n' || ch == 'N');
+}
} // end namespace benchmark
diff --git a/src/commandlineflags.h b/src/commandlineflags.h
index 34b9c6f..945c9a9 100644
--- a/src/commandlineflags.h
+++ b/src/commandlineflags.h
@@ -38,8 +38,7 @@
// Parses a string for a bool flag, in the form of either
// "--flag=value" or "--flag".
//
-// In the former case, the value is taken as true as long as it does
-// not start with '0', 'f', or 'F'.
+// In the former case, the value is taken as true if it passes IsTruthyValue().
//
// In the latter case, the value is taken as true.
//
@@ -71,6 +70,10 @@
// Returns true if the string matches the flag.
bool IsFlag(const char* str, const char* flag);
+// Returns true unless value starts with one of: '0', 'f', 'F', 'n' or 'N', or
+// some non-alphanumeric character. As a special case, also returns true if
+// value is the empty string.
+bool IsTruthyFlagValue(const std::string& value);
} // end namespace benchmark
#endif // BENCHMARK_COMMANDLINEFLAGS_H_