parselink-old/tests/common/logging.cpp

399 lines
13 KiB
C++

//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Tests
//
// Logging tests.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#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**) {
}