source/tests/test.unit/omni.log/TestILog.cpp

// Copyright (c) 2020-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#include "../common/TestHelpers.h"
#include "../omni.core/ScopedTypeFactory.h"

#include "ExamplePublicHeader.h"

#include <omni/log/ILog.h>

#include <carb/dictionary/ISerializer.h>
#include <carb/settings/ISettings.h>
#include <carb/thread/Util.h>

OMNI_LOG_ADD_CHANNEL(kLogTestChannel, "test.unit.logtest", "Log test message.");
OMNI_LOG_ADD_CHANNEL(kAlphaTestChannel, "test.unit.logtest.alpha", "Log test message (alpha).");
OMNI_LOG_ADD_CHANNEL(kBetaTestChannel, "test.unit.logtest.beta", "Log test message (beta).");
OMNI_LOG_ADD_CHANNEL(kGoodTestChannel, "test.unit.logtest.alpha.good", "Log test message (alpha-good).");
OMNI_LOG_ADD_CHANNEL(kBadTestChannel, "test.unit.logtest.alpha.bad", "Log test message (alpha-bad).");
OMNI_LOG_DEFINE_CHANNEL(kExtraTestChannel, "test.unit.logtest.alpha.extra", "Log test message (alpha-extra).");
OMNI_LOG_ADD_CHANNEL(kLogChannel, "omni.log", "Log system.");

namespace omni
{
using namespace omni::core;
using namespace omni::log;
} // namespace omni

struct Message
{
    std::string msg;
    omni::log::Level level;
};

class MessageConsumer : public omni::core::Implements<omni::log::ILogMessageConsumer>
{
public:
    MessageConsumer(const char* channel, std::vector<Message> truth) : m_channel{ channel }, m_truth{ std::move(truth) }
    {
    }

    ~MessageConsumer()
    {
        CHECK(m_truth.empty());
    }

protected:
    void onMessage_abi(const char* channel,
                       omni::log::Level level,
                       const char* moduleName,
                       const char* fileName,
                       const char* functionName,
                       uint32_t lineNumber,
                       const char* msg,
                       uint32_t pid,
                       uint32_t tid,
                       uint64_t timestamp) noexcept override
    {
        if (m_channel == channel)
        {
            CHECK(!m_truth.empty());
            if (m_truth.empty())
                return;
            auto& desired = m_truth.front();
            CHECK(desired.msg == msg);
            CHECK(desired.level == level);
            m_truth.erase(m_truth.begin());
        }
    }

private:
    std::string m_channel;
    std::vector<Message> m_truth;
};

class LogSettingsSaver
{
public:
    LogSettingsSaver(omni::log::ILog* log)
    {
        m_log = log;
        m_level = log->getLevel();
        m_enabled = log->isEnabled();
    }

    ~LogSettingsSaver()
    {
        m_log->setEnabled(m_enabled);
        m_log->setLevel(m_level);
    }

private:
    omni::log::ILog* m_log;
    omni::log::Level m_level;
    bool m_enabled;
};

static uint32_t countMatchingConsumers(omni::log::ILog* log, omni::log::ILogMessageConsumer* consumer)
{
    auto consumers = log->getMessageConsumers();
    uint32_t foundCount = 0;
    for (auto& c : consumers)
    {
        if (c == consumer)
        {
            ++foundCount;
        }
    }
    return foundCount;
}

static uint32_t countMatchingChannels(omni::log::ILog* log, const char* channel)
{
    auto channels = log->getChannelNames();
    uint32_t foundCount = 0;
    for (auto& c : channels)
    {
        if (0 == std::strcmp(c->getBuffer(), channel))
        {
            ++foundCount;
        }
    }
    return foundCount;
}

TEST_CASE("omni::log::ILog::addMessageConsumer()", "ncournia", "[omni::log::ILog]")
{
    test::ScopedTypeFactory scopedFactory;

    auto log = omniGetLogWithoutAcquire();
    REQUIRE(log);

    LogSettingsSaver logSettingsSaver{ log };

    log->setEnabled(false);
    REQUIRE(!log->isEnabled());

    // set/get channel level
    log->setChannelLevel(kLogTestChannel, omni::log::Level::eInfo, omni::log::SettingBehavior::eOverride);

    constexpr omni::log::Level bogusObservedLevel = omni::log::Level(0xBADC0DE);
    constexpr omni::log::SettingBehavior bogusObservedBehavior = omni::log::SettingBehavior(0xBADC0DF);

    omni::log::Level observedLevel;
    omni::log::SettingBehavior observedBehavior;
    REQUIRE(omni::kResultSuccess == log->getChannelLevel(kLogTestChannel, &observedLevel, &observedBehavior));
    REQUIRE(omni::log::Level::eInfo == observedLevel);
    REQUIRE(omni::log::SettingBehavior::eOverride == observedBehavior);

    observedLevel = bogusObservedLevel;
    observedBehavior = bogusObservedBehavior;
    REQUIRE(omni::kResultNotFound == log->getChannelLevel("bogus.channel", &observedLevel, &observedBehavior));
    REQUIRE(bogusObservedLevel == observedLevel);
    REQUIRE(bogusObservedBehavior == observedBehavior);

    REQUIRE(omni::kResultInvalidArgument == log->getChannelLevel(nullptr, &observedLevel, &observedBehavior));
    REQUIRE(bogusObservedLevel == observedLevel);
    REQUIRE(bogusObservedBehavior == observedBehavior);

    REQUIRE(omni::kResultInvalidArgument == log->getChannelLevel("bogus.channel", nullptr, &observedBehavior));
    REQUIRE(bogusObservedLevel == observedLevel);
    REQUIRE(bogusObservedBehavior == observedBehavior);

    REQUIRE(omni::kResultInvalidArgument == log->getChannelLevel("bogus.channel", &observedLevel, nullptr));
    REQUIRE(bogusObservedLevel == observedLevel);
    REQUIRE(bogusObservedBehavior == observedBehavior);

    // set/get channel enabled
    log->setChannelEnabled(kLogTestChannel, true, omni::log::SettingBehavior::eOverride);

    bool observedEnabled = false;
    REQUIRE(omni::kResultSuccess == log->getChannelEnabled(kLogTestChannel, &observedEnabled, &observedBehavior));
    REQUIRE(observedEnabled);
    REQUIRE(omni::log::SettingBehavior::eOverride == observedBehavior);

    observedEnabled = false;
    observedBehavior = bogusObservedBehavior;
    REQUIRE(omni::kResultNotFound == log->getChannelEnabled("bogus.channel", &observedEnabled, &observedBehavior));
    REQUIRE(!observedEnabled);
    REQUIRE(bogusObservedBehavior == observedBehavior);

    REQUIRE(omni::kResultInvalidArgument == log->getChannelEnabled(nullptr, &observedEnabled, &observedBehavior));
    REQUIRE(!observedEnabled);
    REQUIRE(bogusObservedBehavior == observedBehavior);

    REQUIRE(omni::kResultInvalidArgument == log->getChannelEnabled("bogus.channel", nullptr, &observedBehavior));
    REQUIRE(!observedEnabled);
    REQUIRE(bogusObservedBehavior == observedBehavior);

    REQUIRE(omni::kResultInvalidArgument == log->getChannelEnabled("bogus.channel", &observedEnabled, nullptr));
    REQUIRE(!observedEnabled);
    REQUIRE(bogusObservedBehavior == observedBehavior);

    // add/removeMessageConsumer
    omni::ObjectPtr<omni::log::ILogMessageConsumer> consumer{
        new MessageConsumer{ kLogTestChannel.name,
                             {
                                 { "Test: Error msg", omni::log::Level::eError },
                                 { "Test: Fatal msg, 6", omni::log::Level::eFatal },
                                 { "Test: Info msg 3 hi 0x0000002A", omni::log::Level::eInfo },
                                 { "Test: This is a message.", omni::log::Level::eInfo },
                             } },
        omni::kSteal
    };
    log->addMessageConsumer(consumer);

    REQUIRE(1 == countMatchingConsumers(log, consumer.get()));

    OMNI_LOG_ERROR(kLogTestChannel, "Test: %s msg", "Error");
    OMNI_LOG_FATAL(kLogTestChannel, "Test: Fatal msg, %d", 6);
    OMNI_LOG_INFO(kLogTestChannel, "Test: Info msg %d %s 0x%08X", 3, "hi", 42);
    OMNI_LOG_VERBOSE(kLogTestChannel, "Test: I'm an ignored verbose message.");

    constexpr const char* statement = "Test: This is a message.";
    log->log(kLogTestChannel.name, omni::log::Level::eInfo, omniGetModuleFilename(), __FILE__, __func__, __LINE__,
             statement, uint32_t(std::strlen(statement)));

    log->removeMessageConsumer(consumer.get());

    REQUIRE(0 == countMatchingConsumers(log, consumer.get()));
}

constexpr const char* kAddChannelDesc = "Channel for testing ILog::addChannel.";
OMNI_LOG_DEFINE_CHANNEL(kAddChannel, "test.unit.logtest.addChannel", kAddChannelDesc);

TEST_CASE("omni::log::ILog::addChannel()", "ncournia", "[omni::log::ILog]")
{
    test::ScopedTypeFactory scopedFactory;

    auto log = omniGetLogWithoutAcquire();
    REQUIRE(log);

    log->setEnabled(false);
    REQUIRE(!log->isEnabled());

    omni::core::ObjectPtr<omni::str::IReadOnlyCString> desc;

    // addChannel
    REQUIRE(0 == countMatchingChannels(log, kAddChannel.name));
    log->addChannel(kAddChannel.name, reinterpret_cast<omni::log::Level*>(&(kAddChannel.level)), nullptr);
    REQUIRE(1 == countMatchingChannels(log, kAddChannel.name));

    // null channel description
    REQUIRE(omni::core::kResultSuccess == log->getChannelDescription(kAddChannel.name, desc.put()));
    REQUIRE(!desc.get());

    // channel with description
    omni::log::Level level;
    log->addChannel(kAddChannel.name, &level, kAddChannelDesc);

    REQUIRE(omni::core::kResultSuccess == log->getChannelDescription(kAddChannel.name, desc.put()));
    REQUIRE(desc.get());
    REQUIRE(desc->getBuffer());
    REQUIRE(0 == std::strcmp(desc->getBuffer(), kAddChannelDesc));

    log->removeChannel(kAddChannel.name, &level);

    // get description of bogus channel
    desc = nullptr;
    REQUIRE(omni::core::kResultNotFound == log->getChannelDescription("bogus.channel", desc.put()));

    // log to channel
    log->setChannelLevel(kAddChannel, omni::log::Level::eInfo, omni::log::SettingBehavior::eOverride);
    log->setChannelEnabled(kAddChannel, true, omni::log::SettingBehavior::eOverride);
    omni::ObjectPtr<omni::log::ILogMessageConsumer> consumer{ new MessageConsumer{
                                                                  kAddChannel.name,
                                                                  {
                                                                      { "Test: Info msg", omni::log::Level::eInfo },
                                                                  } },
                                                              omni::kSteal };
    log->addMessageConsumer(consumer);

    OMNI_LOG_INFO(kAddChannel, "Test: Info msg");
    OMNI_LOG_VERBOSE(kAddChannel, "Test: I'm an ignored verbose message.");

    log->removeChannel(kAddChannel.name, reinterpret_cast<omni::log::Level*>(&(kAddChannel.level)));

    log->removeMessageConsumer(consumer.get());
}

TEST_CASE("omni::log::ILog::setChannelLevel() adds missing channel", "ncournia", "[omni::log::ILog]")
{
    test::ScopedTypeFactory scopedFactory;

    auto log = omniGetLogWithoutAcquire();
    REQUIRE(log);

    REQUIRE(0 == countMatchingChannels(log, kAddChannel.name));
    log->setChannelLevel(kAddChannel, omni::log::Level::eInfo, omni::log::SettingBehavior::eOverride);
    REQUIRE(1 == countMatchingChannels(log, kAddChannel.name));

    log->addChannel(kAddChannel.name, reinterpret_cast<omni::log::Level*>(&(kAddChannel.level)), nullptr);

    omni::log::Level level = omni::log::Level(0xBADC0DE);
    omni::log::SettingBehavior levelBehavior = omni::log::SettingBehavior(0xBADC0DF);
    REQUIRE(omni::core::kResultSuccess == log->getChannelLevel(kAddChannel, &level, &levelBehavior));
    REQUIRE(omni::log::Level::eInfo == level);
    REQUIRE(omni::log::SettingBehavior::eOverride == levelBehavior);
}

TEST_CASE("omni::log::ILog::setChannelEnabled() adds missing channel", "ncournia", "[omni::log::ILog]")
{
    test::ScopedTypeFactory scopedFactory;

    auto log = omniGetLogWithoutAcquire();
    REQUIRE(log);

    REQUIRE(0 == countMatchingChannels(log, kAddChannel.name));
    log->setChannelEnabled(kAddChannel, true, omni::log::SettingBehavior::eOverride);
    REQUIRE(1 == countMatchingChannels(log, kAddChannel.name));

    log->addChannel(kAddChannel.name, reinterpret_cast<omni::log::Level*>(&(kAddChannel.level)), nullptr);

    bool enabled = false;
    omni::log::SettingBehavior enabledBehavior = omni::log::SettingBehavior(0xBADC0DE);
    REQUIRE(omni::core::kResultSuccess == log->getChannelEnabled(kAddChannel, &enabled, &enabledBehavior));
    REQUIRE(enabled);
    REQUIRE(omni::log::SettingBehavior::eOverride == enabledBehavior);
}

TEST_CASE("add channel callback", "ncournia", "[omni::log::ILog]")
{
    test::ScopedTypeFactory scopedFactory;

    auto log = omniGetLogWithoutAcquire();
    REQUIRE(log);

    uint32_t updateCount = 0;
    auto consumer = log->addChannelUpdateConsumer(
        [&updateCount](omni::log::ILog* log, omni::str::IReadOnlyCString* name, omni::log::ChannelUpdateReason reason) {
            if (omni::log::ChannelUpdateReason::eChannelAdded != reason)
            {
                return;
            }

            CHECK(log);
            CHECK(0 == std::strcmp(name->getBuffer(), "just.added.channel"));

            ++updateCount;

            // call back into the log (this shouldn't deadlock)
            log->isEnabled();

            // spawn a thread to access the log.  this shouldn't deadlock
            auto thread = std::thread([&]() {
                log->getLevel(); // shouldn't deadlock
            });
            thread.join();
        });

    REQUIRE(0 == updateCount);
    log->setChannelEnabled("just.added.channel", true, omni::log::SettingBehavior::eOverride);
    REQUIRE(1 == updateCount);

    log->removeChannelUpdateConsumer(consumer);
}

TEST_CASE("log channel updated callback", "ncournia", "[omni::log::ILog]")
{
    test::ScopedTypeFactory scopedFactory;

    auto log = omniGetLogWithoutAcquire();
    REQUIRE(log);

    uint32_t updateCount = 0;
    auto consumer = log->addChannelUpdateConsumer(
        [&updateCount](omni::log::ILog* log, omni::str::IReadOnlyCString* name, omni::log::ChannelUpdateReason reason) {
            if ((omni::log::ChannelUpdateReason::eLevelUpdated != reason) &&
                (omni::log::ChannelUpdateReason::eEnabledUpdated != reason))
            {
                return;
            }

            CHECK(log);
            CHECK(0 == std::strcmp(name->getBuffer(), kAddChannel.name));

            bool enabled;
            omni::log::SettingBehavior enabledBehavior;
            omni::log::Level level;
            omni::log::SettingBehavior levelBehavior;

            // call back into the log (this shouldn't deadlock)
            CHECK(omni::core::kResultSuccess == log->getChannelEnabled(name->getBuffer(), &enabled, &enabledBehavior));
            CHECK(omni::core::kResultSuccess == log->getChannelLevel(name->getBuffer(), &level, &levelBehavior));

            if (0 == updateCount)
            {
                CHECK(level == omni::log::Level::eWarn);
                CHECK(levelBehavior == omni::log::SettingBehavior::eInherit);
                CHECK(enabled);
                CHECK(enabledBehavior == omni::log::SettingBehavior::eOverride);
            }
            else
            {
                CHECK(level == omni::log::Level::eVerbose);
                CHECK(levelBehavior == omni::log::SettingBehavior::eOverride);
                CHECK(enabled);
                CHECK(enabledBehavior == omni::log::SettingBehavior::eOverride);
            }

            ++updateCount;

            // spawn a thread to access the log.  this shouldn't deadlock
            auto thread = std::thread([&]() {
                log->getLevel(); // shouldn't deadlock
            });
            thread.join();
        });

    log->addChannel(kAddChannel.name, reinterpret_cast<omni::log::Level*>(&(kAddChannel.level)), nullptr);

    REQUIRE(0 == updateCount);
    log->setChannelEnabled(kAddChannel, true, omni::log::SettingBehavior::eOverride);
    REQUIRE(1 == updateCount);
    log->setChannelEnabled(kAddChannel, true, omni::log::SettingBehavior::eOverride); // no change
    REQUIRE(1 == updateCount);
    log->setChannelLevel(kAddChannel, omni::log::Level::eVerbose, omni::log::SettingBehavior::eOverride);
    REQUIRE(2 == updateCount);
    log->setChannelLevel(kAddChannel, omni::log::Level::eVerbose, omni::log::SettingBehavior::eOverride); // change
    REQUIRE(2 == updateCount);

    log->removeChannelUpdateConsumer(consumer);
}


static carb::settings::ISettings* parseConfig(const char* config)
{
    auto* f = carb::getFramework();
    REQUIRE(f);

    carb::dictionary::IDictionary* dict = f->acquireInterface<carb::dictionary::IDictionary>();
    REQUIRE(dict);

    carb::settings::ISettings* settings = f->acquireInterface<carb::settings::ISettings>();
    REQUIRE(settings);

    carb::dictionary::ISerializer* serializer =
        f->acquireInterface<carb::dictionary::ISerializer>("carb.dictionary.serializer-json.plugin");
    REQUIRE(serializer);

    carb::dictionary::Item* dictItem = serializer->createDictionaryFromStringBuffer(config);
    REQUIRE(dictItem);

    settings->initializeFromDictionary(dictItem);

    dict->destroyItem(dictItem);

    return settings;
}

TEST_CASE("channel wildcards in config files", "ncournia", "[omni::log::ILogChannelFilter]")
{
    test::ScopedTypeFactory scopedFactory;
    scopedFactory.loadCarbPlugins(
        { "carb.dictionary.plugin", "carb.settings.plugin", "carb.dictionary.serializer-json.plugin" });

    auto log = omniGetLogWithoutAcquire();
    REQUIRE(log);

    SECTION("simple config")
    {
        const char* kConfigStr = R"(
        {
            "log": {
                "channels": {
                    "test.unit.logtest" :  "verbose"
                }
            }
        })";

        parseConfig(kConfigStr);

        omni::ObjectPtr<omni::log::ILogMessageConsumer> consumer{
            new MessageConsumer{ kLogTestChannel.name,
                                 {
                                     { "Test: Error msg", omni::log::Level::eError },
                                     { "Test: Fatal msg", omni::log::Level::eFatal },
                                     { "Test: Info msg", omni::log::Level::eInfo },
                                     { "Test: Verbose msg", omni::log::Level::eVerbose },
                                 } },
            omni::kSteal
        };
        log->addMessageConsumer(consumer);

        OMNI_LOG_ERROR(kLogTestChannel, "Test: Error msg");
        OMNI_LOG_FATAL(kLogTestChannel, "Test: Fatal msg");
        OMNI_LOG_INFO(kLogTestChannel, "Test: Info msg");
        OMNI_LOG_VERBOSE(kLogTestChannel, "Test: Verbose msg");

        log->removeMessageConsumer(consumer);
    }

    SECTION("config with fatal level")
    {
        const char* kConfigStr = R"(
        {
            "log": {
                "channels": {
                    "test.unit.logtest" :  "fatal"
                }
            }
        })";

        parseConfig(kConfigStr);

        omni::ObjectPtr<omni::log::ILogMessageConsumer> consumer{
            new MessageConsumer{ kLogTestChannel.name,
                                 {
                                     { "Test: Fatal msg", omni::log::Level::eFatal },
                                 } },
            omni::kSteal
        };
        log->addMessageConsumer(consumer);

        OMNI_LOG_ERROR(kLogTestChannel, "Test: Error msg");
        OMNI_LOG_FATAL(kLogTestChannel, "Test: Fatal msg");
        OMNI_LOG_INFO(kLogTestChannel, "Test: Info msg");
        OMNI_LOG_VERBOSE(kLogTestChannel, "Test: Verbose msg");

        log->removeMessageConsumer(consumer);
    }

    SECTION("disable one subchannel")
    {
        // Order of the filters is important; they are applied in the order given
        const char* kConfigStr = R"(
        {
            "log": {
                "channels": {
                    "test.unit.logtest.alpha.*"   : "info",
                    "test.unit.logtest.alpha.bad" : "disable"
                }
            }
        })";

        parseConfig(kConfigStr);

        omni::ObjectPtr<omni::log::ILogMessageConsumer> goodConsumer{
            new MessageConsumer{ kGoodTestChannel.name,
                                 {
                                     { "Test: Error msg", omni::log::Level::eError },
                                     { "Test: Fatal msg", omni::log::Level::eFatal },
                                     { "Test: Info msg", omni::log::Level::eInfo },
                                 } },
            omni::kSteal
        };
        log->addMessageConsumer(goodConsumer);

        omni::ObjectPtr<omni::log::ILogMessageConsumer> badConsumer{ new MessageConsumer{ kBadTestChannel.name, {} },
                                                                     omni::kSteal };
        log->addMessageConsumer(badConsumer);

        omni::ObjectPtr<omni::log::ILogMessageConsumer> extraConsumer{
            new MessageConsumer{ kGoodTestChannel.name,
                                 {
                                     { "Test: Error msg", omni::log::Level::eError },
                                     { "Test: Fatal msg", omni::log::Level::eFatal },
                                     { "Test: Info msg", omni::log::Level::eInfo },
                                 } },
            omni::kSteal
        };
        log->addMessageConsumer(extraConsumer);

        // channel should pick up "Info" level from "test.unit.logtest.alpha.*"
        log->addChannel(kExtraTestChannel.name, reinterpret_cast<omni::log::Level*>(&(kExtraTestChannel.level)),
                        kExtraTestChannel.description);

        OMNI_LOG_ERROR(kGoodTestChannel, "Test: Error msg");
        OMNI_LOG_FATAL(kGoodTestChannel, "Test: Fatal msg");
        OMNI_LOG_INFO(kGoodTestChannel, "Test: Info msg");
        OMNI_LOG_VERBOSE(kGoodTestChannel, "Test: Verbose msg");

        OMNI_LOG_ERROR(kBadTestChannel, "Test: Error msg");
        OMNI_LOG_FATAL(kBadTestChannel, "Test: Fatal msg");
        OMNI_LOG_INFO(kBadTestChannel, "Test: Info msg");
        OMNI_LOG_VERBOSE(kBadTestChannel, "Test: Verbose msg");

        OMNI_LOG_ERROR(kExtraTestChannel, "Test: Error msg");
        OMNI_LOG_FATAL(kExtraTestChannel, "Test: Fatal msg");
        OMNI_LOG_INFO(kExtraTestChannel, "Test: Info msg");
        OMNI_LOG_VERBOSE(kExtraTestChannel, "Test: Verbose msg");

        log->removeMessageConsumer(extraConsumer);
        log->removeMessageConsumer(badConsumer);
        log->removeMessageConsumer(goodConsumer);
    }
}

EXAMPLE_ADD_CHANNELS();

TEST_CASE("redefine OMNI_LOG_DEFAULT_CHANNEL", "ncournia", "[omni::log::ILog]")
{
    test::ScopedTypeFactory scopedFactory;
    auto log = omniGetLogWithoutAcquire();

    log->setLevel(omni::log::Level::eInfo);

    carb::thread::ThreadId expectedTid = carb::this_thread::getId();
    carb::process::ProcessId expectedPid = carb::this_process::getId();

    uint64_t minTimeStamp =
        std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch())
            .count();

    uint32_t messagesSeen = 0;
    log->addMessageConsumer([&messagesSeen, expectedTid, expectedPid, minTimeStamp](
                                const char* channel, omni::log::Level level, const char* moduleName,
                                const char* fileName, const char* functionName, uint32_t lineNumber, const char* msg,
                                uint32_t pid, uint32_t tid, uint64_t timestamp) noexcept {
        // clang complains about the 'name' value for kDefaultChannel not being defined
        // immediately here in this translation unit.  It is defined elsewhere so the
        // linking will be correct still.  The other channels are defined locally here
        // so those aren't complained about.
        CARB_IGNOREWARNING_CLANG_WITH_PUSH("-Wundefined-var-template")

        if (0 == std::strcmp(channel, kAlphaTestChannel.name))
        {
            CHECK(0 == std::strcmp("i'm logged to the alpha channel", msg));
            ++messagesSeen;
        }
        else if (0 == std::strcmp(channel, kBetaTestChannel.name))
        {
            CHECK(0 == std::strcmp("i'm logged to the beta channel", msg));
            ++messagesSeen;
        }
        else if (0 == std::strcmp(channel, kDefaultChannel.name))
        {
            CHECK(0 == std::strcmp("i'm logged to the previous default channel", msg));
            ++messagesSeen;
        }
        else if (0 == std::strcmp(channel, kExampleUsefulChannel.name))
        {
            CHECK(0 == std::strcmp("i'm helping", msg));
            ++messagesSeen;
        }
        else if (0 == std::strcmp(channel, kExampleUselessChannel.name))
        {
            CHECK(0 == std::strcmp("i'm not helping", msg));
            ++messagesSeen;
        }

        CHECK(pid == expectedPid);
        CHECK(tid == expectedTid);
        CHECK(timestamp >= minTimeStamp);
        CARB_IGNOREWARNING_CLANG_POP
    });

    // example-begin redefine-default-channel
    OMNI_LOG_ERROR("i'm logged to the previous default channel");

#undef OMNI_LOG_DEFAULT_CHANNEL
#define OMNI_LOG_DEFAULT_CHANNEL kAlphaTestChannel
    OMNI_LOG_ERROR("i'm logged to the alpha %s", "channel");

#undef OMNI_LOG_DEFAULT_CHANNEL
#define OMNI_LOG_DEFAULT_CHANNEL kBetaTestChannel
    OMNI_LOG_ERROR("i'm logged to the beta channel");
    // example-end redefine-default-channel

    example::myUsefulFunction();
    example::myUselessFunction();

    REQUIRE(5 == messagesSeen);
}

static uint32_t sConstructionCounterCount = 0;

struct ConstructionCounter
{
    ConstructionCounter()
    {
        ++sConstructionCounterCount;
    }

    const char* txt()
    {
        return "i'm expensive";
    }
};

TEST_CASE("lazy evaluation of log macro arguments", "ncournia", "[omni::log::ILog]")
{
    test::ScopedTypeFactory scopedFactory;
    auto log = omniGetLogWithoutAcquire();

    sConstructionCounterCount = 0;
    OMNI_LOG_ERROR("%s", ConstructionCounter().txt());
    REQUIRE(1 == sConstructionCounterCount);

    sConstructionCounterCount = 0;
    OMNI_LOG_VERBOSE("%s", ConstructionCounter().txt());
    REQUIRE(0 == sConstructionCounterCount);

    log->setChannelLevel(kAlphaTestChannel, omni::log::Level::eWarn, omni::log::SettingBehavior::eInherit);

    sConstructionCounterCount = 0;
    OMNI_LOG_ERROR(kAlphaTestChannel, "%s", ConstructionCounter().txt());
    REQUIRE(1 == sConstructionCounterCount);

    sConstructionCounterCount = 0;
    OMNI_LOG_VERBOSE(kAlphaTestChannel, "%s", ConstructionCounter().txt());
    REQUIRE(0 == sConstructionCounterCount);
}

TEST_CASE("log macro scoping", "evanbeurden", "[omni::log::ILog][scoping]")
{
    test::ScopedTypeFactory scopedFactory;
    auto log = omniGetLogWithoutAcquire();
    int32_t value = rand();


    log->setLevel(omni::log::Level::eVerbose);
    omni::ObjectPtr<omni::log::ILogMessageConsumer> consumer{ new MessageConsumer{ kLogTestChannel.name, {} },
                                                              omni::kSteal };
    log->addMessageConsumer(consumer);

    CHECK(countMatchingConsumers(log, consumer.get()) == 1);

    if (value < 0)
        OMNI_LOG_ERROR(kLogTestChannel, "Test: %s msg", "Error");

    if (value < 0)
        OMNI_LOG_FATAL(kLogTestChannel, "Test: Fatal msg, %d", 6);

    if (value < 0)
        OMNI_LOG_WARN(kLogTestChannel, "Test: Warning msg, %d", 2);

    if (value < 0)
        OMNI_LOG_INFO(kLogTestChannel, "Test: Info msg %d %s 0x%08X", 3, "hi", 42);

    if (value < 0)
        OMNI_LOG_VERBOSE(kLogTestChannel, "Test: I'm an ignored verbose message.");

    log->removeMessageConsumer(consumer.get());
    CHECK(0 == countMatchingConsumers(log, consumer.get()));
}