//----------------------------------------------------------------------------- // ___ __ _ _ // / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ // / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / // / ___/ (_| | | \__ \ __/ /__| | | | | < // \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . // //----------------------------------------------------------------------------- // Author: Kurt Sassenrath // Module: Tests // // Logging tests. // // Copyright (c) 2023 Kurt Sassenrath. // // License TBD. //----------------------------------------------------------------------------- #include #include #include using namespace parselink::logging; namespace { // Simple testing endpoint, writing to a std::string. Colors are not supported. template 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 auto make_test_logger(std::string_view name) { auto ep = std::make_shared(); return std::tuple{logger{name, ep}, ep}; } using test_endpoint = test_endpoint_base; using colored_test_endpoint = test_endpoint_base; // 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 auto styled(T&& v) { if constexpr(has_static_theme) { return fmt::styled(v, theme>::style); } else if constexpr (has_dynamic_theme) { return fmt::styled(v, theme>::style(v)); } else { static_assert("Could not find theme, likely broken test!"); } } } template <> struct fmt::formatter : public fmt::formatter { template auto format(static_theme_test const& v, FormatContext& ctx) const { return fmt::formatter::format(v.value, ctx); } }; template <> struct fmt::formatter : public fmt::formatter { template auto format(dynamic_theme_test const& v, FormatContext& ctx) const { return fmt::formatter::format(v.value, ctx); } }; template <> struct parselink::logging::theme : static_theme {}; template <> struct parselink::logging::theme { 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("log level"); // Must be handled explicitly as the level is a template parameter. logger.log("test"); if (ep->threshold >= level::critical) { expect(ep->contents() == "critical log level | test"); } else { expect(ep->contents().empty()); } ep->clear(); logger.log("test"); if (ep->threshold >= level::error) { expect(ep->contents() == "error log level | test"); } else { expect(ep->contents().empty()); } ep->clear(); logger.log("test"); if (ep->threshold >= level::warning) { expect(ep->contents() == "warning log level | test"); } else { expect(ep->contents().empty()); } ep->clear(); logger.log("test"); if (ep->threshold >= level::info) { expect(ep->contents() == "info log level | test"); } else { expect(ep->contents().empty()); } ep->clear(); logger.log("test"); if (ep->threshold >= level::verbose) { expect(ep->contents() == "verbose log level | test"); } else { expect(ep->contents().empty()); } ep->clear(); logger.log("test"); if (ep->threshold >= level::debug) { expect(ep->contents() == "debug log level | test"); } else { expect(ep->contents().empty()); } ep->clear(); logger.log("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("formatting"); logger.log("single int: {}", 5); expect(ep->contents() == "info formatting | single int: 5"); ep->clear(); logger.log("two ints: {} {}", 32, 55); expect(ep->contents() == "info formatting | two ints: 32 55"); ep->clear(); logger.log("string: {}", std::string{"foo"}); expect(ep->contents() == "info formatting | string: foo"); ep->clear(); logger.log("string_view: {}", std::string_view{"bar"}); expect(ep->contents() == "info formatting | string_view: bar"); ep->clear(); logger.log("c-string: {}", "brow"); expect(ep->contents() == "info formatting | c-string: brow"); ep->clear(); char const* cat = "cat"; logger.log("c-string: {}", cat); expect(ep->contents() == "info formatting | c-string: cat"); ep->clear(); }; "pointer formatting"_test = [] { auto [logger, ep] = make_test_logger("formatting"); // Yeah, this isn't kosher, but test progression is a bit tidier. int* ptr = new int(5); auto uniq_ptr = std::unique_ptr(ptr); auto shr_ptr = std::shared_ptr(new int(10)); { logger.log("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("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("int const pointer&: {}", cptrref); auto expected = fmt::format( "info formatting | int const pointer&: {}", fmt::ptr(cptrref)); expect(ep->contents() == expected); ep->clear(); } { logger.log("std::unique_ptr: {}", uniq_ptr); auto expected = fmt::format( "info formatting | std::unique_ptr: {}", fmt::ptr(uniq_ptr)); expect(ep->contents() == expected); ep->clear(); } { auto& uniq_ptrref = uniq_ptr; logger.log("std::unique_ptr&: {}", uniq_ptrref); auto expected = fmt::format( "info formatting | std::unique_ptr&: {}", fmt::ptr(uniq_ptrref)); expect(ep->contents() == expected); ep->clear(); } { auto const& cuniq_ptrref = uniq_ptr; logger.log("std::unique_ptr const&: {}", cuniq_ptrref); auto expected = fmt::format( "info formatting | std::unique_ptr const&: {}", fmt::ptr(cuniq_ptrref)); expect(ep->contents() == expected); ep->clear(); } { logger.log("std::shared_ptr: {}", shr_ptr); auto expected = fmt::format( "info formatting | std::shared_ptr: {}", fmt::ptr(shr_ptr)); expect(ep->contents() == expected); ep->clear(); } { auto& shr_ptrref = shr_ptr; logger.log("std::shared_ptr&: {}", shr_ptrref); auto expected = fmt::format( "info formatting | std::shared_ptr&: {}", fmt::ptr(shr_ptrref)); expect(ep->contents() == expected); ep->clear(); } { auto const& cshr_ptrref = shr_ptr; logger.log("std::shared_ptr const&: {}", cshr_ptrref); auto expected = fmt::format( "info formatting | std::shared_ptr const&: {}", fmt::ptr(cshr_ptrref)); expect(ep->contents() == expected); ep->clear(); } }; "expected formatting"_test = [] { auto [logger, ep] = make_test_logger("expected"); { #if 0 //TODO(ksassenrath): Enable when expected is added. layover::expected x{5}; logger.log("{}", x); expect(ep->contents() == "info expected | 5"); x = std::errc::invalid_argument; ep->clear(); logger.log("{}", x); expect(ep->contents() == "info expected | Invalid argument"); ep->clear(); #endif } { #if 0 layover::expected> x{std::make_unique(20)}; logger.log("{} {}", 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("{}", 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"); logger.log(""); auto expected = fmt::format("{} colored | ", styled(enum_name_only{level::info})); expect(ep->contents() == expected); ep->clear(); logger.log("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("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("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("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("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**) { }