fwt_software/tests/test_logger.cpp

117 lines
3.7 KiB
C++

#include <doctest/doctest.h>
#include "fgc/Logger.h"
#include <iostream>
#include <sstream>
#include <string>
using namespace fgc;
namespace {
// Run a callable with std::cout redirected to a buffer; return what was written.
template <typename F>
std::string captureCout(F&& f) {
std::ostringstream oss;
std::streambuf* old = std::cout.rdbuf(oss.rdbuf());
f();
std::cout.rdbuf(old);
return oss.str();
}
unsigned bits(LogCat c) { return static_cast<unsigned>(c); }
} // namespace
TEST_CASE("parseCategories maps names, all/none, and flags unknown tokens") {
bool ok = true;
CHECK(Logger::parseCategories("serial,mqtt", &ok) == (bits(LogCat::Serial) | bits(LogCat::Mqtt)));
CHECK(ok);
CHECK(Logger::parseCategories("all", &ok) == bits(LogCat::All));
CHECK(ok);
CHECK(Logger::parseCategories("none", &ok) == 0u);
CHECK(ok);
CHECK(Logger::parseCategories("", &ok) == 0u);
CHECK(ok);
// Case- and whitespace-insensitive.
CHECK(Logger::parseCategories(" SeRiAl , Camera ", &ok)
== (bits(LogCat::Serial) | bits(LogCat::Camera)));
CHECK(ok);
// Unknown token: ok=false, but recognized tokens still apply.
ok = true;
CHECK(Logger::parseCategories("serial,bogus", &ok) == bits(LogCat::Serial));
CHECK_FALSE(ok);
}
TEST_CASE("catFromString round-trips and flags unknown") {
bool ok = true;
CHECK(Logger::catFromString("control", &ok) == LogCat::Control);
CHECK(ok);
CHECK(Logger::catFromString("nope", &ok) == LogCat::None);
CHECK_FALSE(ok);
}
TEST_CASE("category enable/disable/set round-trip") {
Logger::setCategories(0);
CHECK(Logger::categories() == 0u);
CHECK_FALSE(Logger::categoryEnabled(LogCat::Serial));
Logger::enableCategory(LogCat::Serial);
Logger::enableCategory(LogCat::Mqtt);
CHECK(Logger::categoryEnabled(LogCat::Serial));
CHECK(Logger::categoryEnabled(LogCat::Mqtt));
CHECK_FALSE(Logger::categoryEnabled(LogCat::Camera));
Logger::disableCategory(LogCat::Serial);
CHECK_FALSE(Logger::categoryEnabled(LogCat::Serial));
CHECK(Logger::categoryEnabled(LogCat::Mqtt));
Logger::setCategories(bits(LogCat::All));
CHECK(Logger::categoryEnabled(LogCat::Camera));
CHECK(Logger::categoryEnabled(LogCat::Control));
Logger::setCategories(0); // leave global state clean for other tests
}
TEST_CASE("LOG_TRACE_CAT gating is independent of the linear level") {
Logger::setCategories(0);
SUBCASE("suppressed when category off (even at trace level)") {
Logger::setLevel(LogLevel::Trace);
std::string out = captureCout([] { LOG_TRACE_CAT(LogCat::Serial) << "TX kd180"; });
CHECK(out.empty());
}
SUBCASE("emitted with [SERIAL] tag when category on, at default info level") {
Logger::setLevel(LogLevel::Info); // NOT trace
Logger::enableCategory(LogCat::Serial);
std::string out = captureCout([] { LOG_TRACE_CAT(LogCat::Serial) << "TX kd180"; });
CHECK(out.find("[SERIAL]") != std::string::npos);
CHECK(out.find("TX kd180") != std::string::npos);
}
SUBCASE("a different category stays silent") {
Logger::setLevel(LogLevel::Info);
Logger::enableCategory(LogCat::Serial); // serial on, mqtt off
std::string out = captureCout([] { LOG_TRACE_CAT(LogCat::Mqtt) << "PUB t p"; });
CHECK(out.empty());
}
SUBCASE("level Off silences categories too") {
Logger::setLevel(LogLevel::Off);
Logger::enableCategory(LogCat::Serial);
std::string out = captureCout([] { LOG_TRACE_CAT(LogCat::Serial) << "TX x"; });
CHECK(out.empty());
}
// Restore defaults so test ordering can't leak state.
Logger::setCategories(0);
Logger::setLevel(LogLevel::Info);
}