icinga2/test/base-testloggerfixture.hpp

128 lines
3.6 KiB
C++

/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
#ifndef TEST_LOGGER_FIXTURE_H
#define TEST_LOGGER_FIXTURE_H
#include <BoostTestTargetConfig.h>
#include "base/logger.hpp"
#include <boost/regex.hpp>
#include <boost/test/test_tools.hpp>
#include <future>
namespace icinga {
class TestLogger : public Logger
{
public:
DECLARE_PTR_TYPEDEFS(TestLogger);
struct Expect
{
std::string pattern;
std::promise<bool> prom;
};
auto ExpectLogPattern(const std::string& pattern,
const std::chrono::milliseconds& timeout = std::chrono::seconds(0))
{
std::unique_lock lock(m_Mutex);
for (const auto& logEntry : m_LogEntries) {
if (boost::regex_match(logEntry.Message.GetData(), boost::regex(pattern))) {
return boost::test_tools::assertion_result{true};
}
}
if (timeout == std::chrono::seconds(0)) {
return boost::test_tools::assertion_result{false};
}
auto expect = std::make_shared<Expect>(Expect{pattern, std::promise<bool>()});
m_Expects.emplace_back(expect);
lock.unlock();
auto future = expect->prom.get_future();
auto status = future.wait_for(timeout);
boost::test_tools::assertion_result ret{status == std::future_status::ready && future.get()};
ret.message() << "Pattern \"" << pattern << "\" in log within " << timeout.count() << "ms";
return ret;
}
private:
void ProcessLogEntry(const LogEntry& entry) override
{
std::unique_lock lock(m_Mutex);
m_LogEntries.push_back(entry);
auto it = std::remove_if(m_Expects.begin(), m_Expects.end(), [&entry](std::weak_ptr<Expect> expectsEntry) {
if (expectsEntry.expired()) {
return true;
}
auto expect = expectsEntry.lock();
if (boost::regex_match(entry.Message.GetData(), boost::regex(expect->pattern))) {
expect->prom.set_value(true);
return true;
}
return false;
});
m_Expects.erase(it, m_Expects.end());
}
void Flush() override {}
std::mutex m_Mutex;
std::vector<std::weak_ptr<Expect>> m_Expects;
std::vector<LogEntry> m_LogEntries;
};
/**
* A fixture to capture log entries and assert their presence in tests.
*
* Currently, this only supports checking existing entries and waiting for new ones
* using ExpectLogPattern(), but more functionality can easily be added in the future,
* like only asserting on past log messages, only waiting for new ones, asserting log
* entry metadata (severity etc.) and so on.
*/
struct TestLoggerFixture
{
TestLoggerFixture()
{
testLogger->SetSeverity(testLogger->SeverityToString(LogDebug));
testLogger->Activate(true);
testLogger->SetActive(true);
}
~TestLoggerFixture()
{
testLogger->SetActive(false);
testLogger->Deactivate(true);
}
/**
* Asserts the presence of a log entry that matches the given regex pattern.
*
* First, the existing log entries are searched for the pattern. If the pattern isn't found,
* until the timeout is reached, the function will wait if a new log message is added that
* matches the pattern.
*
* A boost assertion result object is returned, that evaluates to bool, but contains an
* error message that is printed by the testing framework when the assert failed.
*
* @param pattern The regex pattern the log message needs to match
* @param timeout The maximum amount of time to wait for the log message to arrive
*
* @return A @c boost::test_tools::assertion_result object that can be used in BOOST_REQUIRE
*/
auto ExpectLogPattern(const std::string& pattern,
const std::chrono::milliseconds& timeout = std::chrono::seconds(0))
{
return testLogger->ExpectLogPattern(pattern, timeout);
}
TestLogger::Ptr testLogger = new TestLogger;
};
} // namespace icinga
#endif // TEST_LOGGER_FIXTURE_H