* Logging ported from layover project with minor tweaks.
* Logging test ported over.
* Boost libraries are available.
381 lines
13 KiB
C++
381 lines
13 KiB
C++
#include <logging.h>
|
|
|
|
#include <boost/ut.hpp>
|
|
|
|
#include <fmt/core.h>
|
|
|
|
using namespace parselink::logging;
|
|
|
|
namespace {
|
|
|
|
// Simple testing endpoint, writing to a std::string. Colors are not supported.
|
|
template <bool Colored>
|
|
struct test_endpoint_base : public endpoint {
|
|
void write(message const& msg) override {
|
|
if constexpr (Colored) {
|
|
buffer_.append(fmt::format("{} {} | {}",
|
|
themed_arg{enum_name_only{msg.lvl}}, msg.name, msg.message));
|
|
} else {
|
|
buffer_.append(fmt::format("{} {} | {}",
|
|
enum_name_only{msg.lvl}, msg.name, msg.message));
|
|
}
|
|
}
|
|
|
|
bool colored() const noexcept override { return Colored; }
|
|
|
|
void clear() { buffer_.clear(); }
|
|
std::string_view contents() const { return buffer_; }
|
|
|
|
std::string buffer_;
|
|
};
|
|
|
|
template <typename Endpoint>
|
|
auto make_test_logger(std::string_view name) {
|
|
auto ep = std::make_shared<Endpoint>();
|
|
return std::tuple{logger{name, ep}, ep};
|
|
}
|
|
|
|
using test_endpoint = test_endpoint_base<false>;
|
|
using colored_test_endpoint = test_endpoint_base<true>;
|
|
|
|
// In order to test themed (colorized) logging, we must be sure that theming
|
|
// gets correctly-styled content.
|
|
struct static_theme_test { int value; };
|
|
|
|
// In order to test themed (colorized) logging, we must be sure that theming
|
|
// gets correctly-styled content.
|
|
struct dynamic_theme_test { int value; };
|
|
|
|
template <has_theme T>
|
|
auto styled(T&& v) {
|
|
if constexpr(has_static_theme<T>) {
|
|
return fmt::styled(v, theme<std::remove_cvref_t<T>>::style);
|
|
} else if constexpr (has_dynamic_theme<T>) {
|
|
return fmt::styled(v, theme<std::remove_cvref_t<T>>::style(v));
|
|
} else {
|
|
static_assert("Could not find theme, likely broken test!");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
template <>
|
|
struct fmt::formatter<static_theme_test> : public fmt::formatter<int> {
|
|
template <typename FormatContext>
|
|
auto format(static_theme_test const& v, FormatContext& ctx) const {
|
|
return fmt::formatter<int>::format(v.value, ctx);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct fmt::formatter<dynamic_theme_test> : public fmt::formatter<int> {
|
|
template <typename FormatContext>
|
|
auto format(dynamic_theme_test const& v, FormatContext& ctx) const {
|
|
return fmt::formatter<int>::format(v.value, ctx);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct parselink::logging::theme<static_theme_test>
|
|
: static_theme<fmt::color::black> {};
|
|
|
|
template <>
|
|
struct parselink::logging::theme<dynamic_theme_test> {
|
|
static constexpr auto style(auto const& dtt) noexcept {
|
|
return fmt::fg(dtt.value % 2 ? fmt::color::red : fmt::color::green);
|
|
}
|
|
};
|
|
|
|
|
|
// Begin tests!
|
|
|
|
using namespace boost::ut;
|
|
|
|
suite logging = [] {
|
|
"log thresholds by default"_test = [] {
|
|
auto [logger, ep] = make_test_logger<test_endpoint>("log level");
|
|
// Must be handled explicitly as the level is a template parameter.
|
|
logger.log<level::critical>("test");
|
|
if (ep->threshold >= level::critical) {
|
|
expect(ep->contents() == "critical log level | test");
|
|
} else {
|
|
expect(ep->contents().empty());
|
|
}
|
|
ep->clear();
|
|
|
|
logger.log<level::error>("test");
|
|
if (ep->threshold >= level::error) {
|
|
expect(ep->contents() == "error log level | test");
|
|
} else {
|
|
expect(ep->contents().empty());
|
|
}
|
|
ep->clear();
|
|
|
|
logger.log<level::warning>("test");
|
|
if (ep->threshold >= level::warning) {
|
|
expect(ep->contents() == "warning log level | test");
|
|
} else {
|
|
expect(ep->contents().empty());
|
|
}
|
|
ep->clear();
|
|
|
|
logger.log<level::info>("test");
|
|
if (ep->threshold >= level::info) {
|
|
expect(ep->contents() == "info log level | test");
|
|
} else {
|
|
expect(ep->contents().empty());
|
|
}
|
|
ep->clear();
|
|
|
|
logger.log<level::verbose>("test");
|
|
if (ep->threshold >= level::verbose) {
|
|
expect(ep->contents() == "verbose log level | test");
|
|
} else {
|
|
expect(ep->contents().empty());
|
|
}
|
|
ep->clear();
|
|
|
|
logger.log<level::debug>("test");
|
|
if (ep->threshold >= level::debug) {
|
|
expect(ep->contents() == "debug log level | test");
|
|
} else {
|
|
expect(ep->contents().empty());
|
|
}
|
|
ep->clear();
|
|
|
|
logger.log<level::trace>("test");
|
|
if (ep->threshold >= level::trace) {
|
|
expect(ep->contents() == "trace log level | test");
|
|
} else {
|
|
expect(ep->contents().empty());
|
|
}
|
|
ep->clear();
|
|
};
|
|
|
|
"basic log formatting"_test = [] {
|
|
auto [logger, ep] = make_test_logger<test_endpoint>("formatting");
|
|
logger.log<level::info>("single int: {}", 5);
|
|
expect(ep->contents() == "info formatting | single int: 5");
|
|
ep->clear();
|
|
|
|
logger.log<level::info>("two ints: {} {}", 32, 55);
|
|
expect(ep->contents() == "info formatting | two ints: 32 55");
|
|
ep->clear();
|
|
|
|
logger.log<level::info>("string: {}", std::string{"foo"});
|
|
expect(ep->contents() == "info formatting | string: foo");
|
|
ep->clear();
|
|
|
|
logger.log<level::info>("string_view: {}", std::string_view{"bar"});
|
|
expect(ep->contents() == "info formatting | string_view: bar");
|
|
ep->clear();
|
|
|
|
logger.log<level::info>("c-string: {}", "brow");
|
|
expect(ep->contents() == "info formatting | c-string: brow");
|
|
ep->clear();
|
|
|
|
char const* cat = "cat";
|
|
logger.log<level::info>("c-string: {}", cat);
|
|
expect(ep->contents() == "info formatting | c-string: cat");
|
|
ep->clear();
|
|
};
|
|
|
|
"pointer formatting"_test = [] {
|
|
auto [logger, ep] = make_test_logger<test_endpoint>("formatting");
|
|
|
|
// Yeah, this isn't kosher, but test progression is a bit tidier.
|
|
int* ptr = new int(5);
|
|
auto uniq_ptr = std::unique_ptr<int>(ptr);
|
|
auto shr_ptr = std::shared_ptr<int>(new int(10));
|
|
|
|
{
|
|
logger.log<level::info>("int pointer: {}", ptr);
|
|
auto expected = fmt::format(
|
|
"info formatting | int pointer: {}", fmt::ptr(ptr));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
}
|
|
|
|
{
|
|
auto& ptrref = ptr;
|
|
logger.log<level::info>("int pointer&: {}", ptrref);
|
|
auto expected = fmt::format(
|
|
"info formatting | int pointer&: {}", fmt::ptr(ptrref));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
}
|
|
|
|
{
|
|
auto const& cptrref = ptr;
|
|
logger.log<level::info>("int const pointer&: {}", cptrref);
|
|
auto expected = fmt::format(
|
|
"info formatting | int const pointer&: {}",
|
|
fmt::ptr(cptrref));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
}
|
|
|
|
{
|
|
logger.log<level::info>("std::unique_ptr<int>: {}", uniq_ptr);
|
|
auto expected = fmt::format(
|
|
"info formatting | std::unique_ptr<int>: {}",
|
|
fmt::ptr(uniq_ptr));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
}
|
|
|
|
{
|
|
auto& uniq_ptrref = uniq_ptr;
|
|
logger.log<level::info>("std::unique_ptr<int>&: {}", uniq_ptrref);
|
|
auto expected = fmt::format(
|
|
"info formatting | std::unique_ptr<int>&: {}",
|
|
fmt::ptr(uniq_ptrref));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
}
|
|
|
|
{
|
|
auto const& cuniq_ptrref = uniq_ptr;
|
|
logger.log<level::info>("std::unique_ptr<int> const&: {}",
|
|
cuniq_ptrref);
|
|
auto expected = fmt::format(
|
|
"info formatting | std::unique_ptr<int> const&: {}",
|
|
fmt::ptr(cuniq_ptrref));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
}
|
|
|
|
{
|
|
logger.log<level::info>("std::shared_ptr<int>: {}", shr_ptr);
|
|
auto expected = fmt::format(
|
|
"info formatting | std::shared_ptr<int>: {}",
|
|
fmt::ptr(shr_ptr));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
}
|
|
|
|
{
|
|
auto& shr_ptrref = shr_ptr;
|
|
logger.log<level::info>("std::shared_ptr<int>&: {}", shr_ptrref);
|
|
auto expected = fmt::format(
|
|
"info formatting | std::shared_ptr<int>&: {}",
|
|
fmt::ptr(shr_ptrref));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
}
|
|
|
|
{
|
|
auto const& cshr_ptrref = shr_ptr;
|
|
logger.log<level::info>("std::shared_ptr<int> const&: {}",
|
|
cshr_ptrref);
|
|
auto expected = fmt::format(
|
|
"info formatting | std::shared_ptr<int> const&: {}",
|
|
fmt::ptr(cshr_ptrref));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
}
|
|
};
|
|
|
|
"expected<T> formatting"_test = [] {
|
|
auto [logger, ep] = make_test_logger<test_endpoint>("expected");
|
|
{
|
|
#if 0
|
|
//TODO(ksassenrath): Enable when expected is added.
|
|
layover::expected<int> x{5};
|
|
logger.log<level::info>("{}", x);
|
|
expect(ep->contents() == "info expected | 5");
|
|
x = std::errc::invalid_argument;
|
|
ep->clear();
|
|
logger.log<level::info>("{}", x);
|
|
expect(ep->contents() == "info expected | Invalid argument");
|
|
ep->clear();
|
|
#endif
|
|
}
|
|
{
|
|
#if 0
|
|
layover::expected<std::unique_ptr<int>> x{std::make_unique<int>(20)};
|
|
logger.log<level::info>("{} {}", x, *(x.value()));
|
|
auto expected = fmt::format(
|
|
"info expected | {} 20", fmt::ptr(x.value()));
|
|
expect(ep->contents() == expected);
|
|
x = std::errc::invalid_argument;
|
|
ep->clear();
|
|
logger.log<level::info>("{}", x);
|
|
expect(ep->contents() == "info expected | Invalid argument");
|
|
ep->clear();
|
|
#endif
|
|
}
|
|
};
|
|
|
|
"log theme"_test = [] {
|
|
static_theme_test stt{42};
|
|
dynamic_theme_test red{21}, green{202};
|
|
|
|
auto formatted = fmt::format("{}", styled(stt));
|
|
auto expected = fmt::format("{}",
|
|
fmt::styled(stt.value, fmt::fg(fmt::color::black)));
|
|
expect(formatted == expected);
|
|
formatted = fmt::format("{}", themed_arg{stt});
|
|
expect(formatted == expected);
|
|
|
|
formatted = fmt::format("{} {}", styled(red), styled(green));
|
|
expected = fmt::format("{} {}",
|
|
fmt::styled(red.value, fmt::fg(fmt::color::red)),
|
|
fmt::styled(green.value, fmt::fg(fmt::color::green)));
|
|
expect(formatted == expected);
|
|
formatted = fmt::format("{} {}", themed_arg{red}, themed_arg{green});
|
|
expect(formatted == expected);
|
|
};
|
|
|
|
"colored logging"_test = [] {
|
|
// This unit test should not break if log colors are changed, but
|
|
// it also relies on functionality
|
|
auto [logger, ep] = make_test_logger<colored_test_endpoint>("colored");
|
|
|
|
logger.log<level::info>("");
|
|
auto expected = fmt::format("{} colored | ",
|
|
styled(enum_name_only{level::info}));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
|
|
logger.log<level::info>("integral {}", 5);
|
|
expected = fmt::format("{} colored | integral {}",
|
|
styled(enum_name_only{level::info}), styled(5));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
|
|
std::string_view sv = "hello";
|
|
logger.log<level::info>("string_view {}", sv);
|
|
expected = fmt::format("{} colored | string_view {}",
|
|
styled(enum_name_only{level::info}), styled(sv));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
|
|
std::string str = "hello";
|
|
logger.log<level::info>("string {}", str);
|
|
expected = fmt::format("{} colored | string {}",
|
|
styled(enum_name_only{level::info}), styled(str));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
|
|
auto c_str = "hello";
|
|
logger.log<level::info>("c-string {}", c_str);
|
|
expected = fmt::format("{} colored | c-string {}",
|
|
styled(enum_name_only{level::info}), styled(c_str));
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
|
|
int int_value = 42;
|
|
auto* int_ptr = &int_value;
|
|
logger.log<level::info>("pointer {} {}", int_ptr, *int_ptr);
|
|
expected = fmt::format("{} colored | pointer {} {}",
|
|
styled(enum_name_only{level::info}),
|
|
themed_arg{int_ptr}, themed_arg{*int_ptr});
|
|
expect(ep->contents() == expected);
|
|
ep->clear();
|
|
};
|
|
};
|
|
|
|
int main(int, char**) {
|
|
}
|