#include #include "fgc/Logger.h" #include #include #include using namespace fgc; namespace { // Run a callable with std::cout redirected to a buffer; return what was written. template 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(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); }