Commit eb34eb97 authored by Dominik Charousset's avatar Dominik Charousset Committed by Marian Triebe

Redesign terminal color API to support Windows

parent 42ab04ea
......@@ -34,7 +34,6 @@ set (LIBCAF_CORE_SRCS
src/behavior_impl.cpp
src/blocking_actor.cpp
src/blocking_behavior.cpp
src/color.cpp
src/concatenated_tuple.cpp
src/config_option.cpp
src/continue_helper.cpp
......@@ -89,6 +88,7 @@ set (LIBCAF_CORE_SRCS
src/sync_request_bouncer.cpp
src/stringification_inspector.cpp
src/test_coordinator.cpp
src/term.cpp
src/timestamp.cpp
src/try_match.cpp
src/type_erased_value.cpp
......
......@@ -27,6 +27,7 @@
#include "caf/send.hpp"
#include "caf/skip.hpp"
#include "caf/unit.hpp"
#include "caf/term.hpp"
#include "caf/actor.hpp"
#include "caf/after.hpp"
#include "caf/error.hpp"
......
......@@ -17,30 +17,55 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#ifndef CAF_COLOR_HPP
#define CAF_COLOR_HPP
#ifndef CAF_TERM_HPP
#define CAF_TERM_HPP
namespace caf {
#include <iosfwd>
enum color_face {
normal,
bold
};
namespace caf {
enum color_value {
/// Terminal color and font face options.
enum class term {
/// Resets the color to the default color and the font weight to normal.
reset,
/// Like `reset` but also prints a newline.
reset_endl,
/// Sets the terminal color to black.
black,
/// Sets the terminal color to red.
red,
/// Sets the terminal color to green.
green,
/// Sets the terminal color to yellow.
yellow,
/// Sets the terminal color to blue.
blue,
/// Sets the terminal color to magenta.
magenta,
/// Sets the terminal color to cyan.
cyan,
white
/// Sets the terminal color to white.
white,
/// Sets the terminal color to black and the font weight to bold.
bold_black,
/// Sets the terminal color to red and the font weight to bold.
bold_red,
/// Sets the terminal color to green and the font weight to bold.
bold_green,
/// Sets the terminal color to yellow and the font weight to bold.
bold_yellow,
/// Sets the terminal color to blue and the font weight to bold.
bold_blue,
/// Sets the terminal color to magenta and the font weight to bold.
bold_magenta,
/// Sets the terminal color to cyan and the font weight to bold.
bold_cyan,
/// Sets the terminal color to white and the font weight to bold.
bold_white
};
const char* color(color_value value, color_face face = normal);
std::ostream& operator<<(std::ostream& out, term x);
} // namespace caf
#endif // CAF_COLOR_HPP
#endif // CAF_TERM_HPP
......@@ -35,7 +35,7 @@
#include "caf/string_algorithms.hpp"
#include "caf/color.hpp"
#include "caf/term.hpp"
#include "caf/locks.hpp"
#include "caf/timestamp.hpp"
#include "caf/actor_proxy.hpp"
......@@ -424,22 +424,22 @@ void logger::run() {
default:
break;
case CAF_LOG_LEVEL_ERROR:
std::clog << color(red);
std::clog << term::red;;
break;
case CAF_LOG_LEVEL_WARNING:
std::clog << color(yellow);
std::clog << term::yellow;
break;
case CAF_LOG_LEVEL_INFO:
std::clog << color(green);
std::clog << term::green;
break;
case CAF_LOG_LEVEL_DEBUG:
std::clog << color(cyan);
std::clog << term::cyan;
break;
case CAF_LOG_LEVEL_TRACE:
std::clog << color(blue);
std::clog << term::blue;
break;
}
std::clog << ptr->msg << color(reset) << std::endl;
std::clog << ptr->msg << term::reset << std::endl;
}
}
if (file) {
......
......@@ -17,29 +17,126 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/color.hpp"
#include "caf/term.hpp"
#include <iostream>
#include "caf/config.hpp"
#ifdef CAF_WINDOWS
#include <io.h>
#include <windows.h>
#else
#include <stdio.h>
#include <unistd.h>
#endif
namespace caf {
const char* color(color_value value, color_face face) {
#ifdef CAF_MSVC
return "";
#else
const char* colors[9][2] = {
{"\033[0m", "\033[0m"}, // reset
{"\033[30m", "\033[1m\033[30m"}, // black
{"\033[31m", "\033[1m\033[31m"}, // red
{"\033[32m", "\033[1m\033[32m"}, // green
{"\033[33m", "\033[1m\033[33m"}, // yellow
{"\033[34m", "\033[1m\033[34m"}, // blue
{"\033[35m", "\033[1m\033[35m"}, // magenta
{"\033[36m", "\033[1m\033[36m"}, // cyan
{"\033[37m", "\033[1m\033[37m"} // white
};
return colors[static_cast<size_t>(value)][static_cast<size_t>(face)];
namespace {
#ifdef CAF_WINDOWS
// windows terminals do no support bold fonts
constexpr int win_black = 0;
constexpr int win_red = FOREGROUND_INTENSITY | FOREGROUND_RED;
constexpr int win_green = FOREGROUND_INTENSITY | FOREGROUND_GREEN;
constexpr int win_blue = FOREGROUND_INTENSITY | FOREGROUND_BLUE;
constexpr int win_yellow = win_red | win_green;
constexpr int win_magenta = win_red | win_blue;
constexpr int win_cyan = win_green | win_blue;
constexpr int win_white = win_red | win_cyan;
int tty_codes[] = {
-1, // reset
-2, // reset_endl
win_black, // black
win_red, // red
win_green, // green
win_yellow, // yellow
win_blue, // blue
win_magenta, // magenta
win_cyan, // cyan
win_white, // white
win_black, // bold black
win_red, // bold red
win_green, // bold green
win_yellow, // bold yellow
win_blue, // bold blue
win_magenta, // bold magenta
win_cyan, // bold cyan
win_white // bold white
};
void set_term_color(std::ostream& out, int c) {
static WORD default_color = 0xFFFF;
auto hdl = GetStdHandle(&out == &std::cout ? STD_OUTPUT_HANDLE
: STD_ERROR_HANDLE);
if (default_color == 0xFFFF) {
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo(hdl, &info))
return;
default_color = info.wAttributes;
}
// always pick background from the default color
auto x = c < 0 ? default_color
: static_cast<WORD>((0xF0 & default_color) | (0x0F & c));
SetConsoleTextAttribute(hdl, x);
if (c == -2)
out << '\n';
}
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
#define ISATTY_FUN ::_isatty
#else // POSIX-compatible terminals
const char* tty_codes[] = {
"\033[0m", // reset
"\033[0m\n", // reset_endl
"\033[30m", // black
"\033[31m", // red
"\033[32m", // green
"\033[33m", // yellow
"\033[34m", // blue
"\033[35m", // magenta
"\033[36m", // cyan
"\033[37m", // white
"\033[1m\033[30m", // bold_black
"\033[1m\033[31m", // bold_red
"\033[1m\033[32m", // bold_green
"\033[1m\033[33m", // bold_yellow
"\033[1m\033[34m", // bold_blue
"\033[1m\033[35m", // bold_magenta
"\033[1m\033[36m", // bold_cyan
"\033[1m\033[37m" // bold_white
};
void set_term_color(std::ostream& out, const char* x) {
out << x;
}
#define ISATTY_FUN ::isatty
#endif
bool is_tty(const std::ostream& out) {
if (&out == &std::cout)
return ISATTY_FUN(STDOUT_FILENO) != 0;
if (&out == &std::cerr || &out == &std::clog)
return ISATTY_FUN(STDERR_FILENO) != 0;
return false;
}
} // namespace <anonymous>
std::ostream& operator<<(std::ostream& out, term x) {
if (is_tty(out))
set_term_color(out, tty_codes[static_cast<size_t>(x)]);
return out;
}
} // namespace caf
......@@ -32,7 +32,7 @@
#include <iostream>
#include "caf/fwd.hpp"
#include "caf/color.hpp"
#include "caf/term.hpp"
#include "caf/optional.hpp"
#include "caf/deep_to_string.hpp"
......@@ -81,9 +81,9 @@ public:
size_t expected_failures() const;
void pass(const std::string& msg);
void pass();
void fail(const std::string& msg, bool expected);
void fail(bool expected);
const std::string& name() const;
......@@ -141,20 +141,30 @@ public:
massive = 4
};
static bool init(int lvl_cons, int lvl_file, const std::string& logfile);
static logger& instance();
template <class T>
void log(level lvl, const T& x) {
if (lvl <= level_console_) {
*console_ << x;
}
if (lvl <= level_file_) {
file_ << x;
}
}
/// Output stream for logging purposes.
class stream {
public:
stream(logger& l, level lvl);
stream(logger& parent, level lvl);
stream(stream&&);
stream(const stream&) = default;
template <class T>
typename std::enable_if<
!std::is_same<T, char*>::value,
stream&
>::type
operator<<(const T& x) {
buf_ << x;
stream& operator<<(const T& x) {
parent_.log(lvl_, x);
return *this;
}
......@@ -165,53 +175,26 @@ public:
return *this << *x;
}
stream& operator<<(const char& c);
stream& operator<<(const char* cstr);
stream& operator<<(const std::string& x);
std::string str() const;
private:
void flush();
logger& logger_;
level level_;
std::ostringstream buf_;
std::string str_;
logger& parent_;
level lvl_;
};
static bool init(int lvl_cons, int lvl_file, const std::string& logfile);
static logger& instance();
template <class T>
void log(level lvl, const T& x) {
if (lvl <= level_console_) {
std::lock_guard<std::mutex> io_guard{console_mtx_};
console_ << x;
}
if (lvl <= level_file_) {
std::lock_guard<std::mutex> io_guard{file_mtx_};
file_ << x;
}
}
stream error();
stream info();
stream verbose();
stream massive();
void disable_colors();
private:
logger();
level level_console_;
level level_file_;
std::ostream& console_;
std::ostream* console_;
std::ofstream file_;
std::mutex console_mtx_;
std::mutex file_mtx_;
std::ostringstream dummy_;
};
/// Drives unit test execution.
......@@ -270,10 +253,6 @@ public:
const std::string& tests_str,
const std::string& not_tests_str);
/// Retrieves a UNIX terminal color code or an empty string based on the
/// color configuration of the engine.
static const char* color(color_value v, color_face t = normal);
static const char* last_check_file();
static void last_check_file(const char* file);
......@@ -327,7 +306,7 @@ template <class T>
std::ostream& operator<<(std::ostream& out, const showable_base<T>& x) {
auto str = caf::deep_to_string(x.value);
if (str == "<unprintable>")
out << engine::color(blue) << "<unprintable>" << engine::color(reset);
out << term::blue << "<unprintable>" << term::reset;
else
out << str;
return out;
......@@ -360,22 +339,22 @@ template <class T, class U>
bool check(test* parent, const char *file, size_t line,
const char *expr, bool should_fail, bool result,
const T& x, const U& y) {
std::stringstream ss;
auto out = logger::instance().massive();
if (result) {
ss << engine::color(green) << "** "
<< engine::color(blue) << file << engine::color(yellow) << ":"
<< engine::color(blue) << line << fill(line) << engine::color(reset)
<< expr;
parent->pass(ss.str());
out << term::green << "** "
<< term::blue << file << term::yellow << ":"
<< term::blue << line << fill(line) << term::reset
<< expr << '\n';
parent->pass();
} else {
ss << engine::color(red) << "!!"
<< engine::color(blue) << file << engine::color(yellow) << ":"
<< engine::color(blue) << line << fill(line) << engine::color(reset)
<< expr << engine::color(magenta) << " ("
<< engine::color(red) << show(x) << engine::color(magenta)
<< " !! " << engine::color(red) << show(y) << engine::color(magenta)
<< ')' << engine::color(reset);
parent->fail(ss.str(), should_fail);
out << term::red << "!! "
<< term::blue << file << term::yellow << ":"
<< term::blue << line << fill(line) << term::reset
<< expr << term::magenta << " ("
<< term::red << show(x) << term::magenta
<< " !! " << term::red << show(y) << term::magenta
<< ')' << term::reset_endl;
parent->fail(should_fail);
}
return result;
}
......@@ -388,9 +367,8 @@ bool check(test* parent, const char *file, size_t line,
using caf_test_case_auto_fixture = caf::test::dummy_fixture;
#define CAF_TEST_PRINT(level, msg, colorcode) \
(::caf::test::logger::instance(). level () \
<< ::caf::test::engine::color(::caf:: colorcode ) \
<< " -> " << ::caf::test::engine::color(::caf::reset) << msg \
(::caf::test::logger::instance().level() \
<< ::caf::term:: colorcode << " -> " << ::caf::term::reset << msg \
<< " [line " << __LINE__ << "]\n")
#define CAF_TEST_PRINT_ERROR(msg) CAF_TEST_PRINT(info, msg, red)
......@@ -416,12 +394,11 @@ using caf_test_case_auto_fixture = caf::test::dummy_fixture;
#define CAF_ERROR(msg) \
do { \
auto CAF_UNIQUE(__str) = CAF_TEST_PRINT_ERROR(msg).str(); \
::caf::test::detail::remove_trailing_spaces(CAF_UNIQUE(__str)); \
::caf::test::engine::current_test()->fail(CAF_UNIQUE(__str), false); \
CAF_TEST_PRINT_ERROR(msg); \
::caf::test::engine::current_test()->fail(false); \
::caf::test::engine::last_check_file(__FILE__); \
::caf::test::engine::last_check_line(__LINE__); \
} while(false)
} while (false)
#define CAF_CHECK(...) \
do { \
......@@ -467,9 +444,8 @@ using caf_test_case_auto_fixture = caf::test::dummy_fixture;
#define CAF_FAIL(msg) \
do { \
auto CAF_UNIQUE(__str) = CAF_TEST_PRINT_ERROR(msg).str(); \
::caf::test::detail::remove_trailing_spaces(CAF_UNIQUE(__str)); \
::caf::test::engine::current_test()->fail(CAF_UNIQUE(__str), false); \
CAF_TEST_PRINT_ERROR(msg); \
::caf::test::engine::current_test()->fail(false); \
::caf::test::detail::requirement_failed("test failure"); \
} while (false)
......
......@@ -108,17 +108,14 @@ size_t test::expected_failures() const {
return expected_failures_;
}
void test::pass(const std::string& msg) {
void test::pass() {
++good_;
::caf::test::logger::instance().massive() << " " << msg << '\n';
}
void test::fail(const std::string& msg, bool expected) {
void test::fail(bool expected) {
++bad_;
::caf::test::logger::instance().massive() << " " << msg << '\n';
if (expected) {
if (expected)
++expected_failures_;
}
}
const std::string& test::name() const {
......@@ -129,11 +126,11 @@ namespace detail {
[[noreturn]] void requirement_failed(const std::string& msg) {
auto& log = logger::instance();
log.error() << engine::color(red) << " REQUIRED: " << msg
<< engine::color(reset) << '\n'
<< " " << engine::color(blue) << engine::last_check_file()
<< engine::color(yellow) << ":" << engine::color(cyan)
<< engine::last_check_line() << engine::color(reset)
log.error() << term::red << " REQUIRED: " << msg
<< term::reset << '\n'
<< " " << term::blue << engine::last_check_file()
<< term::yellow << ":" << term::cyan
<< engine::last_check_line() << term::reset
<< detail::fill(engine::last_check_line())
<< "had last successful check" << '\n';
abort();
......@@ -155,75 +152,31 @@ void remove_trailing_spaces(std::string& x) {
bool check(test* parent, const char *file, size_t line,
const char *expr, bool should_fail, bool result) {
std::stringstream ss;
auto out = logger::instance().massive();
if (result) {
ss << engine::color(green) << "** "
<< engine::color(blue) << file << engine::color(yellow) << ":"
<< engine::color(blue) << line << fill(line) << engine::color(reset)
out << term::green << "** "
<< term::blue << file << term::yellow << ":"
<< term::blue << line << fill(line) << term::reset
<< expr;
parent->pass(ss.str());
parent->pass();
} else {
ss << engine::color(red) << "!!"
<< engine::color(blue) << file << engine::color(yellow) << ":"
<< engine::color(blue) << line << fill(line) << engine::color(reset)
out << term::red << "!! "
<< term::blue << file << term::yellow << ":"
<< term::blue << line << fill(line) << term::reset
<< expr;
parent->fail(ss.str(), should_fail);
parent->fail(should_fail);
}
return result;
}
} // namespace detail
logger::stream::stream(logger& l, level lvl) : logger_(l), level_(lvl) {
logger::stream::stream(logger& parent, logger::level lvl)
: parent_(parent),
lvl_(lvl) {
// nop
}
logger::stream::stream(stream&& other)
: logger_(other.logger_)
, level_(other.level_) {
// ostringstream does have swap since C++11... but not in GCC 4.8.4
buf_.str(other.buf_.str());
}
logger::stream& logger::stream::operator<<(const char& c) {
buf_ << c;
if (c == '\n') {
flush();
}
return *this;
}
logger::stream& logger::stream::operator<<(const char* cstr) {
if (*cstr == '\0') {
return *this;
}
buf_ << cstr;
if (cstr[std::strlen(cstr) - 1] == '\n') {
flush();
}
return *this;
}
logger::stream& logger::stream::operator<<(const std::string& x) {
if (x.empty())
return *this;
buf_ << x;
if (x.back() == '\n')
flush();
return *this;
}
std::string logger::stream::str() const {
return str_;
}
void logger::stream::flush() {
auto x = buf_.str();
buf_.str("");
logger_.log(level_, x);
str_ += x;
}
bool logger::init(int lvl_cons, int lvl_file, const std::string& logfile) {
instance().level_console_ = static_cast<level>(lvl_cons);
instance().level_file_ = static_cast<level>(lvl_file);
......@@ -255,10 +208,19 @@ logger::stream logger::massive() {
return stream{*this, level::massive};
}
void logger::disable_colors() {
// Disable colors by piping all writes through dummy_.
// Since dummy_ is not a TTY, colors are turned off implicitly.
dummy_.copyfmt(std::cerr);
dummy_.clear(std::cerr.rdstate());
dummy_.basic_ios<char>::rdbuf(std::cerr.rdbuf());
console_ = &dummy_;
}
logger::logger()
: level_console_(level::error),
level_file_(level::error),
console_(std::cerr) {
console_(&std::cerr) {
// nop
}
......@@ -320,6 +282,8 @@ bool engine::run(bool colorize,
return false;
}
auto& log = logger::instance();
if (!colorize)
log.disable_colors();
std::chrono::microseconds runtime{0};
size_t total_suites = 0;
size_t total_tests = 0;
......@@ -385,11 +349,11 @@ bool engine::run(bool colorize,
instance().current_test_ = t.get();
++tests_ran;
if (!displayed_header) {
log.verbose() << color(yellow) << bar << '\n' << pad << suite_name
<< '\n' << bar << color(reset) << "\n\n";
log.verbose() << term::yellow << bar << '\n' << pad << suite_name
<< '\n' << bar << term::reset << "\n\n";
displayed_header = true;
}
log.verbose() << color(yellow) << "- " << color(reset) << t->name()
log.verbose() << term::yellow << "- " << term::reset << t->name()
<< '\n';
auto start = std::chrono::high_resolution_clock::now();
watchdog::start(max_runtime());
......@@ -405,17 +369,17 @@ bool engine::run(bool colorize,
total_good += good;
total_bad += bad;
total_bad_expected += t->expected_failures();
log.verbose() << color(yellow) << " -> " << color(cyan) << good + bad
<< color(reset) << " check" << (good + bad > 1 ? "s " : " ")
<< "took " << color(cyan) << render(elapsed)
<< color(reset) << '\n';
log.verbose() << term::yellow << " -> " << term::cyan << good + bad
<< term::reset << " check" << (good + bad > 1 ? "s " : " ")
<< "took " << term::cyan << render(elapsed)
<< term::reset << '\n';
if (bad > 0) {
// concat suite name + test name
failed_tests.emplace_back(p.first);
failed_tests.back() += ":";
failed_tests.back() += t->name();
log.verbose() << " (" << color(green) << good << color(reset) << '/'
<< color(red) << bad << color(reset) << ")" << '\n';
log.verbose() << " (" << term::green << good << term::reset << '/'
<< term::red << bad << term::reset << ")" << '\n';
} else {
log.verbose() << '\n';
}
......@@ -437,40 +401,36 @@ bool engine::run(bool colorize,
auto title = std::string{"summary"};
auto pad = std::string((bar.size() - title.size()) / 2, ' ');
auto indent = std::string(24, ' ');
log.info() << color(cyan) << bar << '\n' << pad << title << '\n' << bar
<< color(reset) << "\n\n" << indent << "suites: " << color(yellow)
<< total_suites << color(reset) << '\n' << indent
<< "tests: " << color(yellow) << total_tests << color(reset)
<< '\n' << indent << "checks: " << color(yellow)
<< total_good + total_bad << color(reset);
log.info() << term::cyan << bar << '\n' << pad << title << '\n' << bar
<< term::reset << "\n\n" << indent << "suites: " << term::yellow
<< total_suites << term::reset << '\n' << indent
<< "tests: " << term::yellow << total_tests << term::reset
<< '\n' << indent << "checks: " << term::yellow
<< total_good + total_bad << term::reset;
if (total_bad > 0) {
log.info() << " (" << color(green) << total_good << color(reset) << '/'
<< color(red) << total_bad << color(reset) << ")";
log.info() << " (" << term::green << total_good << term::reset << '/'
<< term::red << total_bad << term::reset << ")";
if (total_bad_expected > 0) {
log.info()
<< ' ' << color(cyan) << total_bad_expected << color(reset)
<< ' ' << term::cyan << total_bad_expected << term::reset
<< " failures expected";
}
}
log.info() << '\n' << indent << "time: " << color(yellow)
<< render(runtime) << '\n' << color(reset) << indent
log.info() << '\n' << indent << "time: " << term::yellow
<< render(runtime) << '\n' << term::reset << indent
<< "success: "
<< (total_bad == total_bad_expected ? color(green) : color(red))
<< percent_good << "%" << color(reset) << "\n\n";
<< (total_bad == total_bad_expected ? term::green : term::red)
<< percent_good << "%" << term::reset << "\n\n";
if (!failed_tests.empty()) {
log.info() << indent << "failed tests:" << '\n';
for (auto& name : failed_tests)
log.info() << indent << "- " << name << '\n';
log.info() << '\n';
}
log.info() << color(cyan) << bar << color(reset) << '\n';
log.info() << term::cyan << bar << term::reset << '\n';
return total_bad == total_bad_expected;
}
const char* engine::color(color_value v, color_face t) {
return instance().colorize_ ? caf::color(v, t) : "";
}
const char* engine::last_check_file() {
return instance().check_file_;
}
......@@ -583,20 +543,7 @@ int main(int argc, char** argv) {
<< res.helptext << std::endl;
return 1;
}
# ifdef CAF_WINDOWS
constexpr bool colorize = false;
# else
auto color_support = []() -> bool {
if (isatty(0) == 0)
return false;
char ttybuf[50];
if (ttyname_r(0, ttybuf, sizeof(ttybuf)) != 0)
return false;
char prefix[] = "/dev/tty";
return strncmp(prefix, ttybuf, sizeof(prefix) - 1) == 0;
};
auto colorize = color_support();
# endif
auto colorize = res.opts.count("no-colors") == 0;
std::vector<char*> args;
if (divider < argc) {
// make a new args vector that contains argv[0] and all remaining args
......
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