util/ carries no wire surface, so it lands before the contract freeze. - error: enum class Error, the engine-wide failure taxonomy; is_retryable enumerates every value (no default:) so -Wswitch forces the retry decision on each future addition. ErrorInfo carries context/http_status. - result: Result<T> over std::expected<T, ErrorInfo>, Result<void>, VDM_TRY / VDM_TRY_ASSIGN. Errors returned, never thrown, on the transfer path. - bytes: span aliases, LE load_le/store_le (debug-asserted precondition, not input validation), and a bounds-checked latching ByteReader for the .veloxpart.meta reader. - event_bus: typed thread-safe pub/sub; header states plainly that unsubscribe is not a quiesce point and download_task will need its own drain. - thread_pool: std::jthread pool; dtor joins in the body before members die (fixed a use-after-destruction on cv_/mu_). Header notes shutdown is drain-only and DAEMON will need a cancel mode. - log: sink interface (core does no I/O); DAEMON installs one. Tested: -Werror clean, 6 binaries green under plain / ASan+UBSan / TSan. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
65 lines
1.8 KiB
C++
65 lines
1.8 KiB
C++
#include "vdm/util/log.hpp"
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#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<std::vector<Captured>>();
|
|
vdm::set_log_sink(std::make_shared<CallbackSink>([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<int>(0);
|
|
vdm::set_log_sink(std::make_shared<CallbackSink>(
|
|
[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"));
|
|
}
|