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
This commit is contained in:
Nicholas Hutchinson 2016-09-15 22:10:35 +01:00 committed by Eric
parent b826143ac2
commit 917b86e615
7 changed files with 86 additions and 26 deletions

View File

@ -24,6 +24,7 @@ Jussi Knuuttila <jussi.knuuttila@gmail.com>
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>

View File

@ -40,6 +40,7 @@ Kaito Udagawa <umireon@gmail.com>
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>

View File

@ -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_format, "json",
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 @@ size_t RunSpecifiedBenchmarks(BenchmarkReporter* console_reporter,
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 @@ void PrintUsageAndExit() {
" [--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 @@ void ParseCommandLineFlags(int* argc, char** argv) {
&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 @@ void ParseCommandLineFlags(int* argc, char** argv) {
if (*flag != "console" && *flag != "json" && *flag != "csv") {
PrintUsageAndExit();
}
if (FLAGS_benchmark_color.empty()) {
PrintUsageAndExit();
}
}
int InitializeStreams() {

View File

@ -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 @@ void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, va_list arg
}
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

View File

@ -23,6 +23,10 @@ std::string FormatString(const char* msg, ...);
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_

View File

@ -14,6 +14,7 @@
#include "commandlineflags.h"
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <iostream>
@ -74,17 +75,6 @@ bool ParseDouble(const std::string& src_text, const char* str, double* value) {
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 @@ static std::string FlagToEnvVar(const char* flag) {
// 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 @@ bool BoolFromEnv(const char* flag, bool default_value) {
// 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 @@ int32_t Int32FromEnv(const char* flag, int32_t default_value) {
// 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 @@ bool ParseBoolFlag(const char* str, const char* flag, bool* value) {
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 ParseStringFlag(const char* str, const char* flag, std::string* value) {
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

View File

@ -38,8 +38,7 @@ const char* StringFromEnv(const char* flag, const char* default_val);
// 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 @@ bool ParseStringFlag(const char* str, const char* flag, std::string* value);
// 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_