#include "vdm/util/log.hpp" #include #include #include #include "vtest.hpp" using vdm::CallbackSink; using vdm::LogLevel; using vdm::LogRecord; namespace { struct Captured { LogLevel level; std::string category; std::string message; }; // Restores the null sink when it goes out of scope, so tests don't leak a sink. struct SinkGuard { ~SinkGuard() { vdm::set_log_sink(nullptr); } }; } // namespace VT_TEST(log_discards_when_no_sink) { SinkGuard g; vdm::set_log_sink(nullptr); // Must not crash and must not format: this just has to be a no-op. VDM_LOG_INFO("test", "value {}", 123); VT_CHECK(!vdm::detail::log_wants(LogLevel::error)); } VT_TEST(log_forwards_to_sink_with_format) { SinkGuard g; auto hits = std::make_shared>(); vdm::set_log_sink(std::make_shared([hits](const LogRecord &r) { hits->push_back({r.level, std::string(r.category), r.message}); })); VDM_LOG_WARN("probe", "HEAD {} -> {}", "https://x/y", 405); VT_REQUIRE(hits->size() == 1); VT_CHECK_EQ((*hits)[0].level, LogLevel::warn); VT_CHECK_EQ((*hits)[0].category, std::string("probe")); VT_CHECK_EQ((*hits)[0].message, std::string("HEAD https://x/y -> 405")); } VT_TEST(log_level_filter_skips_below_min) { SinkGuard g; auto count = std::make_shared(0); vdm::set_log_sink( std::make_shared([count](const LogRecord &) { ++*count; }, LogLevel::warn)); VDM_LOG_DEBUG("x", "no"); VDM_LOG_INFO("x", "no"); VDM_LOG_WARN("x", "yes"); VDM_LOG_ERROR("x", "yes"); VT_CHECK_EQ(*count, 2); } VT_TEST(log_level_name_is_stable) { VT_CHECK_EQ(vdm::log_level_name(LogLevel::trace), std::string_view("trace")); VT_CHECK_EQ(vdm::log_level_name(LogLevel::error), std::string_view("error")); }