fix: Keep spdlog loggers valid between tests (#2614)

This commit is contained in:
Ayaz Salikhov
2025-09-15 14:47:35 +01:00
committed by GitHub
parent e996f2b7ab
commit 3f2ada3439
53 changed files with 353 additions and 204 deletions

View File

@@ -42,7 +42,7 @@ using namespace util;
static constexpr auto kLOG_FORMAT = "%Y-%m-%d %H:%M:%S.%f %^%3!l:%n%$ - %v";
struct BenchmarkLoggingInitializer {
static std::shared_ptr<spdlog::sinks::sink>
[[nodiscard]] static std::shared_ptr<spdlog::sinks::sink>
createFileSink(LogService::FileLoggingParams const& params)
{
return LogService::createFileSink(params, kLOG_FORMAT);
@@ -72,8 +72,6 @@ uniqueLogDir()
static void
benchmarkConcurrentFileLogging(benchmark::State& state)
{
spdlog::drop_all();
auto const numThreads = static_cast<size_t>(state.range(0));
auto const messagesPerThread = static_cast<size_t>(state.range(1));
@@ -84,7 +82,9 @@ benchmarkConcurrentFileLogging(benchmark::State& state)
state.PauseTiming();
std::filesystem::create_directories(logDir);
spdlog::init_thread_pool(8192, 1);
static constexpr size_t kQUEUE_SIZE = 8192;
static constexpr size_t kTHREAD_COUNT = 1;
spdlog::init_thread_pool(kQUEUE_SIZE, kTHREAD_COUNT);
auto fileSink = BenchmarkLoggingInitializer::createFileSink({
.logDir = logDir,

View File

@@ -54,7 +54,7 @@ OnAssert::resetAction()
void
OnAssert::defaultAction(std::string_view message)
{
if (LogService::enabled()) {
if (LogServiceState::initialized()) {
LOG(LogService::fatal()) << message;
} else {
std::cerr << message;

View File

@@ -41,12 +41,12 @@
#include <spdlog/spdlog.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
@@ -56,7 +56,10 @@
namespace util {
LogService::Data LogService::data{};
bool LogServiceState::isAsync_{true};
Severity LogServiceState::defaultSeverity_{Severity::NFO};
std::vector<spdlog::sink_ptr> LogServiceState::sinks_{};
bool LogServiceState::initialized_{false};
namespace {
@@ -238,23 +241,71 @@ getMinSeverity(config::ClioConfigDefinition const& config, Severity defaultSever
return minSeverity;
}
std::shared_ptr<spdlog::logger>
LogService::registerLogger(std::string const& channel, Severity severity)
void
LogServiceState::init(bool isAsync, Severity defaultSeverity, std::vector<spdlog::sink_ptr> const& sinks)
{
std::shared_ptr<spdlog::logger> logger;
if (data.isAsync) {
logger = std::make_shared<spdlog::async_logger>(
channel,
data.allSinks.begin(),
data.allSinks.end(),
spdlog::thread_pool(),
spdlog::async_overflow_policy::block
);
} else {
logger = std::make_shared<spdlog::logger>(channel, data.allSinks.begin(), data.allSinks.end());
if (initialized_) {
throw std::logic_error("LogServiceState is already initialized");
}
logger->set_level(toSpdlogLevel(severity));
isAsync_ = isAsync;
defaultSeverity_ = defaultSeverity;
sinks_ = sinks;
initialized_ = true;
spdlog::apply_all([](std::shared_ptr<spdlog::logger> logger) {
logger->set_level(toSpdlogLevel(defaultSeverity_));
});
if (isAsync) {
static constexpr size_t kQUEUE_SIZE = 8192;
static constexpr size_t kTHREAD_COUNT = 1;
spdlog::init_thread_pool(kQUEUE_SIZE, kTHREAD_COUNT);
}
}
bool
LogServiceState::initialized()
{
return initialized_;
}
void
LogServiceState::reset()
{
if (not initialized()) {
throw std::logic_error("LogService is not initialized");
}
isAsync_ = true;
defaultSeverity_ = Severity::NFO;
sinks_.clear();
initialized_ = false;
}
std::shared_ptr<spdlog::logger>
LogServiceState::registerLogger(std::string const& channel, std::optional<Severity> severity)
{
if (not initialized_) {
throw std::logic_error("LogService is not initialized");
}
std::shared_ptr<spdlog::logger> existingLogger = spdlog::get(channel);
if (existingLogger != nullptr) {
if (severity.has_value())
existingLogger->set_level(toSpdlogLevel(*severity));
return existingLogger;
}
std::shared_ptr<spdlog::logger> logger;
if (isAsync_) {
logger = std::make_shared<spdlog::async_logger>(
channel, sinks_.begin(), sinks_.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block
);
} else {
logger = std::make_shared<spdlog::logger>(channel, sinks_.begin(), sinks_.end());
}
logger->set_level(toSpdlogLevel(severity.value_or(defaultSeverity_)));
logger->flush_on(spdlog::level::err);
spdlog::register_logger(logger);
@@ -262,22 +313,12 @@ LogService::registerLogger(std::string const& channel, Severity severity)
return logger;
}
std::expected<void, std::string>
LogService::init(config::ClioConfigDefinition const& config)
std::expected<std::vector<spdlog::sink_ptr>, std::string>
LogService::getSinks(config::ClioConfigDefinition const& config)
{
// Drop existing loggers
spdlog::drop_all();
data.isAsync = config.get<bool>("log.is_async");
data.defaultSeverity = getSeverityLevel(config.get<std::string>("log.level"));
std::string const format = config.get<std::string>("log.format");
if (data.isAsync) {
spdlog::init_thread_pool(8192, 1);
}
data.allSinks = createConsoleSinks(config.get<bool>("log.enable_console"), format);
std::vector<spdlog::sink_ptr> allSinks = createConsoleSinks(config.get<bool>("log.enable_console"), format);
if (auto const logDir = config.maybeValue<std::string>("log.directory"); logDir.has_value()) {
std::filesystem::path const dirPath{logDir.value()};
@@ -294,11 +335,27 @@ LogService::init(config::ClioConfigDefinition const& config)
.rotationSizeMB = config.get<uint32_t>("log.rotation_size"),
.dirMaxFiles = config.get<uint32_t>("log.directory_max_files"),
};
data.allSinks.push_back(createFileSink(params, format));
allSinks.push_back(createFileSink(params, format));
}
return allSinks;
}
std::expected<void, std::string>
LogService::init(config::ClioConfigDefinition const& config)
{
auto const sinksMaybe = getSinks(config);
if (!sinksMaybe.has_value()) {
return std::unexpected{sinksMaybe.error()};
}
LogServiceState::init(
config.get<bool>("log.is_async"),
getSeverityLevel(config.get<std::string>("log.level")),
std::move(sinksMaybe).value()
);
// get min severity per channel, can be overridden using the `log.channels` array
auto const maybeMinSeverity = getMinSeverity(config, data.defaultSeverity);
auto const maybeMinSeverity = getMinSeverity(config, defaultSeverity_);
if (!maybeMinSeverity) {
return std::unexpected{maybeMinSeverity.error()};
}
@@ -307,20 +364,23 @@ LogService::init(config::ClioConfigDefinition const& config)
// Create loggers for each channel
for (auto const& channel : Logger::kCHANNELS) {
auto const it = minSeverity.find(channel);
auto const severity = (it != minSeverity.end()) ? it->second : data.defaultSeverity;
auto const severity = (it != minSeverity.end()) ? it->second : defaultSeverity_;
registerLogger(channel, severity);
}
spdlog::set_default_logger(spdlog::get("General"));
LOG(LogService::info()) << "Default log level = " << toString(data.defaultSeverity);
LOG(LogService::info()) << "Default log level = " << toString(defaultSeverity_);
return {};
}
void
LogService::shutdown()
{
spdlog::shutdown();
if (initialized_ && isAsync_) {
// We run in async mode in production, so we need to make sure all logs are flushed before shutting down
spdlog::shutdown();
}
}
Logger::Pump
@@ -359,17 +419,15 @@ LogService::fatal(SourceLocationType const& loc)
return Logger(spdlog::default_logger()).fatal(loc);
}
bool
LogService::enabled()
void
LogServiceState::replaceSinks(std::vector<std::shared_ptr<spdlog::sinks::sink>> const& sinks)
{
return spdlog::get_level() != spdlog::level::off;
sinks_ = sinks;
spdlog::apply_all([](std::shared_ptr<spdlog::logger> logger) { logger->sinks() = sinks_; });
}
Logger::Logger(std::string channel) : logger_(spdlog::get(channel))
Logger::Logger(std::string channel) : logger_(LogServiceState::registerLogger(channel))
{
if (!logger_) {
logger_ = LogService::registerLogger(channel);
}
}
Logger::Pump::Pump(std::shared_ptr<spdlog::logger> logger, Severity sev, SourceLocationType const& loc)

View File

@@ -26,6 +26,7 @@
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
@@ -43,9 +44,15 @@ class sink; // NOLINT(readability-identifier-naming)
} // namespace spdlog
struct BenchmarkLoggingInitializer;
class LoggerFixture;
struct LogServiceInitTests;
namespace util {
namespace impl {
class OnAssert;
} // namespace impl
namespace config {
class ClioConfigDefinition;
} // namespace config
@@ -228,27 +235,78 @@ private:
Logger(std::shared_ptr<spdlog::logger> logger);
};
/**
* @brief Base state management class for the logging service.
*
* This class manages the global state and core functionality for the logging system,
* including initialization, sink management, and logger registration.
*/
class LogServiceState {
protected:
friend struct ::LogServiceInitTests;
friend class ::LoggerFixture;
friend class Logger;
friend class ::util::impl::OnAssert;
/**
* @brief Initialize the logging core with specified parameters.
*
* @param isAsync Whether logging should be asynchronous
* @param defaultSeverity The default severity level for new loggers
* @param sinks Vector of spdlog sinks to use for output
*/
static void
init(bool isAsync, Severity defaultSeverity, std::vector<std::shared_ptr<spdlog::sinks::sink>> const& sinks);
/**
* @brief Whether the LogService is initialized or not
*
* @return true if the LogService is initialized
*/
[[nodiscard]] static bool
initialized();
/**
* @brief Reset the logging service to uninitialized state.
*/
static void
reset();
/**
* @brief Replace the current sinks with a new set of sinks.
*
* @param sinks Vector of new spdlog sinks to replace the current ones
*/
static void
replaceSinks(std::vector<std::shared_ptr<spdlog::sinks::sink>> const& sinks);
/**
* @brief Register a new logger for the specified channel.
*
* Creates and registers a new spdlog logger instance for the given channel
* with the specified or default severity level.
*
* @param channel The name of the logging channel
* @param severity Optional severity level override; uses default if not specified
* @return Shared pointer to the registered spdlog logger
*/
static std::shared_ptr<spdlog::logger>
registerLogger(std::string const& channel, std::optional<Severity> severity = std::nullopt);
protected:
static bool isAsync_; // NOLINT(readability-identifier-naming)
static Severity defaultSeverity_; // NOLINT(readability-identifier-naming)
static std::vector<std::shared_ptr<spdlog::sinks::sink>> sinks_; // NOLINT(readability-identifier-naming)
static bool initialized_; // NOLINT(readability-identifier-naming)
};
/**
* @brief A global logging service.
*
* Used to initialize and setup the logging core as well as a globally available
* entrypoint for logging into the `General` channel as well as raising alerts.
*/
class LogService {
struct Data {
bool isAsync;
Severity defaultSeverity;
std::vector<std::shared_ptr<spdlog::sinks::sink>> allSinks;
};
friend class Logger;
private:
static Data data;
static std::shared_ptr<spdlog::logger>
registerLogger(std::string const& channel, Severity severity = data.defaultSeverity);
class LogService : public LogServiceState {
public:
LogService() = delete;
@@ -321,15 +379,16 @@ public:
[[nodiscard]] static Logger::Pump
fatal(SourceLocationType const& loc = CURRENT_SRC_LOCATION);
/**
* @brief Whether the LogService is enabled or not
*
* @return true if the LogService is enabled, false otherwise
*/
[[nodiscard]] static bool
enabled();
private:
/**
* @brief Parses the sinks from a @ref config::ClioConfigDefinition
*
* @param config The configuration to parse sinks from
* @return A vector of sinks on success, error message on failure
*/
[[nodiscard]] static std::expected<std::vector<std::shared_ptr<spdlog::sinks::sink>>, std::string>
getSinks(config::ClioConfigDefinition const& config);
struct FileLoggingParams {
std::string logDir;

View File

@@ -40,7 +40,7 @@
*
* This is meant to be used as a base for other fixtures.
*/
struct AsyncAsioContextTest : virtual public NoLoggerFixture {
struct AsyncAsioContextTest : virtual public ::testing::Test {
AsyncAsioContextTest()
{
work_.emplace(boost::asio::make_work_guard(ctx_)); // make sure ctx does not stop on its own
@@ -79,7 +79,7 @@ private:
* Use `run_for(duration)` etc. directly on `ctx`.
* This is meant to be used as a base for other fixtures.
*/
struct SyncAsioContextTest : virtual public NoLoggerFixture {
struct SyncAsioContextTest : virtual public ::testing::Test {
template <util::CoroutineFunction F>
void
runCoroutine(F&& f, bool allowMockLeak = false)

View File

@@ -0,0 +1,43 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2025, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "util/StringBuffer.hpp"
#include <ostream>
#include <string>
class LoggerBuffer {
public:
std::string
getStrAndReset()
{
return buffer_.getStrAndReset();
}
std::ostream&
getStream()
{
return stream_;
}
private:
StringBuffer buffer_;
std::ostream stream_ = std::ostream{&buffer_};
};

View File

@@ -22,6 +22,7 @@
#include "util/log/Logger.hpp"
#include <spdlog/common.h>
#include <spdlog/logger.h>
#include <spdlog/pattern_formatter.h>
#include <spdlog/sinks/ostream_sink.h>
#include <spdlog/spdlog.h>
@@ -29,32 +30,40 @@
#include <algorithm>
#include <memory>
LoggerFixture::LoggerFixture()
void
LoggerFixture::init()
{
// Clear any existing loggers
spdlog::drop_all();
util::LogServiceState::init(false, util::Severity::FTL, {});
// Create ostream sink for testing
auto ostreamSink = std::make_shared<spdlog::sinks::ostream_sink_mt>(stream_);
ostreamSink->set_formatter(std::make_unique<spdlog::pattern_formatter>("%^%3!l:%n%$ - %v"));
// Create loggers for each channel
std::ranges::for_each(util::Logger::kCHANNELS, [&ostreamSink](char const* channel) {
auto logger = std::make_shared<spdlog::logger>(channel, ostreamSink);
logger->set_level(spdlog::level::trace);
spdlog::register_logger(logger);
std::ranges::for_each(util::Logger::kCHANNELS, [](char const* channel) {
util::LogService::registerLogger(channel);
});
spdlog::get("General")->set_level(spdlog::level::debug);
auto traceLogger = std::make_shared<spdlog::logger>("Trace", ostreamSink);
traceLogger->set_level(spdlog::level::trace);
spdlog::register_logger(traceLogger);
spdlog::set_default_logger(spdlog::get("General"));
}
NoLoggerFixture::NoLoggerFixture()
void
LoggerFixture::resetTestingLoggers()
{
spdlog::set_level(spdlog::level::off);
auto ostreamSink = std::make_shared<spdlog::sinks::ostream_sink_mt>(buffer_.getStream());
ostreamSink->set_formatter(std::make_unique<spdlog::pattern_formatter>("%^%3!l:%n%$ - %v"));
ostreamSink->set_level(spdlog::level::trace);
util::LogServiceState::replaceSinks({ostreamSink});
spdlog::apply_all([](std::shared_ptr<spdlog::logger> logger) { logger->set_level(spdlog::level::trace); });
spdlog::get("General")->set_level(spdlog::level::debug);
}
LoggerFixture::LoggerFixture()
{
util::LogServiceState::reset();
util::LogServiceState::init(false, util::Severity::TRC, {});
resetTestingLoggers();
}
LoggerFixture::~LoggerFixture()
{
util::LogServiceState::replaceSinks({});
spdlog::apply_all([](std::shared_ptr<spdlog::logger> logger) { logger->set_level(spdlog::level::critical); });
}

View File

@@ -19,37 +19,39 @@
#pragma once
#include "util/StringBuffer.hpp"
#include "util/LoggerBuffer.hpp"
#include <gtest/gtest.h>
#include <ostream>
#include <string>
/**
* @brief A fixture for testing LogService and Logger.
*/
class LoggerFixture : virtual public ::testing::Test {
StringBuffer buffer_;
std::ostream stream_ = std::ostream{&buffer_};
protected:
LoggerBuffer buffer_;
public:
// Simulates the `util::LogService::init(config)` call
LoggerFixture();
~LoggerFixture() override;
/**
* @brief Sets up spdlog loggers for each channel. Should be called once before using any loggers.
* Simulates the `util::LogService::init(config)` call
*/
static void
init();
protected:
[[nodiscard]]
std::string
getLoggerString()
{
return buffer_.getStrAndReset();
}
};
/**
* @brief Fixture with util::Logger support but completely disabled logging.
*
* This is meant to be used as a base for other fixtures.
*/
struct NoLoggerFixture : virtual LoggerFixture {
NoLoggerFixture();
private:
void
resetTestingLoggers();
};

View File

@@ -29,7 +29,7 @@
#include <memory>
template <template <typename> typename MockType = ::testing::NiceMock>
struct MockBackendTestBase : virtual public NoLoggerFixture {
struct MockBackendTestBase : virtual public ::testing::Test {
class BackendProxy {
std::shared_ptr<MockType<MockBackend>> backend_ =
std::make_shared<MockType<MockBackend>>(util::config::ClioConfigDefinition{{}});

View File

@@ -25,7 +25,7 @@
/**
* @brief Fixture with mock counters
*/
struct MockCountersTest : virtual public NoLoggerFixture {
struct MockCountersTest : virtual public ::testing::Test {
protected:
std::shared_ptr<MockCounters> mockCountersPtr_ = std::make_shared<MockCounters>();
};

View File

@@ -33,7 +33,7 @@
* @brief Fixture with a mock etl service
*/
template <template <typename> typename MockType = ::testing::NiceMock>
struct MockETLServiceTestBase : virtual public NoLoggerFixture {
struct MockETLServiceTestBase : virtual public ::testing::Test {
using Mock = MockType<MockETLService>;
protected:
@@ -63,7 +63,7 @@ using MockETLServiceTestStrict = MockETLServiceTestBase<::testing::StrictMock>;
/**
* @brief Fixture with a mock etl balancer
*/
struct MockLoadBalancerTest : virtual public NoLoggerFixture {
struct MockLoadBalancerTest : virtual public ::testing::Test {
protected:
std::shared_ptr<MockLoadBalancer> mockLoadBalancerPtr_ = std::make_shared<MockLoadBalancer>();
};
@@ -71,7 +71,7 @@ protected:
/**
* @brief Fixture with a mock NG etl balancer
*/
struct MockNgLoadBalancerTest : virtual public NoLoggerFixture {
struct MockNgLoadBalancerTest : virtual public ::testing::Test {
protected:
std::shared_ptr<MockNgLoadBalancer> mockLoadBalancerPtr_ = std::make_shared<MockNgLoadBalancer>();
};
@@ -79,7 +79,7 @@ protected:
/**
* @brief Fixture with a mock ledger fetcher
*/
struct MockLedgerFetcherTest : virtual public NoLoggerFixture {
struct MockLedgerFetcherTest : virtual public ::testing::Test {
protected:
std::shared_ptr<MockNgLedgerFetcher> mockLedgerFetcherPtr_ = std::make_shared<MockNgLedgerFetcher>();
};
@@ -87,7 +87,7 @@ protected:
/**
* @brief Fixture with a mock ledger fetcher
*/
struct MockAmendmentBlockHandlerTest : virtual public NoLoggerFixture {
struct MockAmendmentBlockHandlerTest : virtual public ::testing::Test {
protected:
std::shared_ptr<MockAmendmentBlockHandler> mockAmendmentBlockHandlerPtr_ =
std::make_shared<MockAmendmentBlockHandler>();

View File

@@ -28,7 +28,7 @@
#include <memory>
template <template <typename> typename MockType = ::testing::NiceMock>
struct MockMigrationBackendTestBase : virtual public NoLoggerFixture {
struct MockMigrationBackendTestBase : virtual public ::testing::Test {
class BackendProxy {
std::shared_ptr<MockType<MockMigrationBackend>> backend_ =
std::make_shared<MockType<MockMigrationBackend>>(util::config::ClioConfigDefinition{});

View File

@@ -30,7 +30,7 @@
* @brief Fixture with an embedded AnyExecutionContext wrapping a SyncExecutionContext
*
*/
struct SyncExecutionCtxFixture : virtual public NoLoggerFixture {
struct SyncExecutionCtxFixture : virtual public ::testing::Test {
protected:
util::async::SyncExecutionContext syncCtx_;
util::async::AnyExecutionContext ctx_{syncCtx_};

View File

@@ -41,7 +41,7 @@ using namespace std;
using namespace data::cassandra;
class BackendCassandraBaseTest : public NoLoggerFixture {
class BackendCassandraBaseTest : public virtual ::testing::Test {
protected:
static Handle
createHandle(std::string_view contactPoints, std::string_view keyspace)

View File

@@ -86,7 +86,7 @@ makeMigrationTestManagerAndBackend(ClioConfigDefinition const& config)
}
} // namespace
class MigrationCassandraSimpleTest : public WithPrometheus, public NoLoggerFixture {
class MigrationCassandraSimpleTest : public WithPrometheus {
// This function is used to prepare the database before running the tests
// It is called in the SetUp function. Different tests can override this function to prepare the database
// differently

View File

@@ -17,6 +17,7 @@
*/
//==============================================================================
#include "util/LoggerFixtures.hpp"
#include "util/TerminationHandler.hpp"
#include <gtest/gtest.h>
@@ -27,5 +28,7 @@ main(int argc, char* argv[])
util::setTerminationHandler();
testing::InitGoogleTest(&argc, argv);
LoggerFixture::init();
return RUN_ALL_TESTS();
}

View File

@@ -37,7 +37,7 @@
using namespace app;
struct StopperTest : NoLoggerFixture {
struct StopperTest : virtual public ::testing::Test {
protected:
// Order here is important, stopper_ should die before mockCallback_, otherwise UB
testing::StrictMock<testing::MockFunction<void(boost::asio::yield_context)>> mockCallback_;

View File

@@ -54,7 +54,7 @@ using namespace app;
namespace http = boost::beast::http;
using namespace util::config;
struct WebHandlersTest : virtual NoLoggerFixture {
struct WebHandlersTest : virtual public ::testing::Test {
DOSGuardStrictMock dosGuardMock;
util::TagDecoratorFactory const tagFactory{
ClioConfigDefinition{{"log.tag_style", ConfigValue{ConfigType::String}.defaultValue("uint")}}

View File

@@ -27,7 +27,7 @@
using namespace data;
struct LedgerCacheTest : util::prometheus::WithPrometheus, NoLoggerFixture {
struct LedgerCacheTest : util::prometheus::WithPrometheus {
LedgerCache cache;
};
@@ -39,7 +39,7 @@ TEST_F(LedgerCacheTest, defaultState)
EXPECT_EQ(cache.latestLedgerSequence(), 0u);
}
struct LedgerCachePrometheusMetricTest : util::prometheus::WithMockPrometheus, NoLoggerFixture {
struct LedgerCachePrometheusMetricTest : util::prometheus::WithMockPrometheus {
LedgerCache cache;
};

View File

@@ -77,7 +77,7 @@ getParseSettingsConfig(boost::json::value val)
return config;
};
class SettingsProviderTest : public NoLoggerFixture {};
class SettingsProviderTest : virtual public ::testing::Test {};
TEST_F(SettingsProviderTest, Defaults)
{

View File

@@ -30,7 +30,7 @@ using namespace data;
using namespace util::prometheus;
using namespace testing;
struct CorruptionDetectorTest : NoLoggerFixture, WithPrometheus {};
struct CorruptionDetectorTest : WithPrometheus {};
TEST_F(CorruptionDetectorTest, DisableCacheOnCorruption)
{

View File

@@ -32,7 +32,7 @@ namespace json = boost::json;
using namespace util;
using namespace testing;
struct ETLStateTest : public NoLoggerFixture {
struct ETLStateTest : public virtual ::testing::Test {
MockSource source = MockSource{};
};

View File

@@ -35,7 +35,7 @@ constexpr auto kSTART_SEQ = 1234;
} // namespace
class ETLExtractionDataPipeTest : public NoLoggerFixture {
class ETLExtractionDataPipeTest : public ::testing::Test {
protected:
etl::impl::ExtractionDataPipe<uint32_t> pipe_{kSTRIDE, kSTART_SEQ};
};

View File

@@ -34,7 +34,7 @@
using namespace testing;
using namespace etl;
struct ETLExtractorTest : util::prometheus::WithPrometheus, NoLoggerFixture {
struct ETLExtractorTest : util::prometheus::WithPrometheus {
using ExtractionDataPipeType = MockExtractionDataPipe;
using LedgerFetcherType = MockLedgerFetcher;
using ExtractorType = etl::impl::Extractor<ExtractionDataPipeType, LedgerFetcherType>;

View File

@@ -41,7 +41,7 @@
using namespace etl::impl;
using namespace util::config;
struct GrpcSourceTests : NoLoggerFixture, util::prometheus::WithPrometheus, tests::util::WithMockXrpLedgerAPIService {
struct GrpcSourceTests : util::prometheus::WithPrometheus, tests::util::WithMockXrpLedgerAPIService {
GrpcSourceTests()
: WithMockXrpLedgerAPIService("localhost:0")
, mockBackend_(std::make_shared<testing::StrictMock<MockBackend>>(ClioConfigDefinition{}))

View File

@@ -56,7 +56,7 @@ constexpr auto kOFFER_ID = "AA86CBF29770F72FA3FF4A5D9A9FA54D6F399A8E038F72393EF7
} // namespace
struct NFTHelpersTest : NoLoggerFixture {
struct NFTHelpersTest : virtual public ::testing::Test {
protected:
static void
verifyNFTTransactionsData(

View File

@@ -50,7 +50,7 @@ constinit auto const kLEDGER_HASH2 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F9965
constinit auto const kSEQ = 30;
} // namespace
struct ExtractionModelNgTests : NoLoggerFixture {};
struct ExtractionModelNgTests : virtual public ::testing::Test {};
TEST_F(ExtractionModelNgTests, LedgerDataCopyableAndEquatable)
{
@@ -178,7 +178,7 @@ TEST_F(ExtractionModelNgTests, BookSuccessorCopyableAndEquatable)
}
}
struct ExtractionNgTests : NoLoggerFixture {};
struct ExtractionNgTests : public virtual ::testing::Test {};
TEST_F(ExtractionNgTests, ModType)
{
@@ -360,7 +360,7 @@ TEST_F(ExtractionNgTests, SuccessorsWithNoNeighborsIncluded)
ASSERT_FALSE(res.has_value());
}
struct ExtractionAssertTest : common::util::WithMockAssert, NoLoggerFixture {};
struct ExtractionAssertTest : common::util::WithMockAssert {};
TEST_F(ExtractionAssertTest, InvalidModTypeAsserts)
{

View File

@@ -67,7 +67,7 @@ struct MockLoadObserver : etlng::InitialLoadObserverInterface {
);
};
struct GrpcSourceNgTests : virtual NoLoggerFixture, tests::util::WithMockXrpLedgerAPIService {
struct GrpcSourceNgTests : virtual public ::testing::Test, tests::util::WithMockXrpLedgerAPIService {
GrpcSourceNgTests()
: WithMockXrpLedgerAPIService("localhost:0"), grpcSource_("localhost", std::to_string(getXRPLMockPort()))
{

View File

@@ -32,7 +32,7 @@
using namespace etlng::impl;
struct NetworkValidatedLedgersTests : NoLoggerFixture {
struct NetworkValidatedLedgersTests : virtual public ::testing::Test {
protected:
util::async::CoroExecutionContext ctx_{2};
std::shared_ptr<etl::NetworkValidatedLedgersInterface> ledgers_ =

View File

@@ -254,7 +254,7 @@ struct MockExtNftBurnReadonly {
}
};
struct RegistryTest : NoLoggerFixture, util::prometheus::WithPrometheus {
struct RegistryTest : util::prometheus::WithPrometheus {
RegistryTest()
{
state_.isWriting = true;

View File

@@ -52,7 +52,7 @@ public:
};
} // namespace
struct ForwardSchedulerTests : NoLoggerFixture {
struct ForwardSchedulerTests : virtual public ::testing::Test {
protected:
MockNetworkValidatedLedgersPtr networkValidatedLedgers_;
};

View File

@@ -86,7 +86,7 @@ struct MockMonitor : etlng::MonitorInterface {
MOCK_METHOD(void, stop, (), (override));
};
struct TaskManagerTests : NoLoggerFixture {
struct TaskManagerTests : virtual public ::testing::Test {
using MockSchedulerType = testing::NiceMock<MockScheduler>;
using MockExtractorType = testing::NiceMock<MockExtractor>;
using MockLoaderType = testing::NiceMock<MockLoader>;

View File

@@ -59,7 +59,7 @@ createTestData()
} // namespace
struct CacheExtTests : NoLoggerFixture, util::prometheus::WithPrometheus {
struct CacheExtTests : util::prometheus::WithPrometheus {
protected:
MockLedgerCache cache_;
std::shared_ptr<etlng::impl::CacheUpdater> updater_ = std::make_shared<etlng::impl::CacheUpdater>(cache_);

View File

@@ -26,7 +26,7 @@
#include <gtest/gtest.h>
struct MigrationManagerFactoryTests : public NoLoggerFixture {};
struct MigrationManagerFactoryTests : public virtual ::testing::Test {};
TEST_F(MigrationManagerFactoryTests, InvalidDBType)
{

View File

@@ -76,7 +76,7 @@ TEST_F(FullTableScannerAssertTest, cursorsPerWorkerZero)
);
}
struct FullTableScannerTests : NoLoggerFixture {};
struct FullTableScannerTests : public virtual ::testing::Test {};
TEST_F(FullTableScannerTests, SingleThreadCtx)
{

View File

@@ -38,7 +38,7 @@ using namespace util::config;
using namespace rpc::impl;
namespace json = boost::json;
class RPCAPIVersionTest : public NoLoggerFixture {
class RPCAPIVersionTest : public virtual ::testing::Test {
protected:
ProductionAPIVersionParser parser_{kDEFAULT_API_VERSION, kMIN_API_VERSION, kMAX_API_VERSION};
};

View File

@@ -49,7 +49,7 @@ using namespace rpc::modifiers;
namespace json = boost::json;
class RPCBaseTest : public NoLoggerFixture {};
class RPCBaseTest : public virtual ::testing::Test {};
TEST_F(RPCBaseTest, CheckType)
{

View File

@@ -38,7 +38,7 @@ using util::prometheus::CounterInt;
using util::prometheus::WithMockPrometheus;
using util::prometheus::WithPrometheus;
struct RPCCountersTest : WithPrometheus, NoLoggerFixture {
struct RPCCountersTest : WithPrometheus {
WorkQueue queue{4u, 1024u}; // todo: mock instead
Counters counters{queue};
};

View File

@@ -39,7 +39,7 @@ using namespace util::config;
using namespace rpc;
using namespace util::prometheus;
struct RPCWorkQueueTestBase : NoLoggerFixture {
struct RPCWorkQueueTestBase : public virtual ::testing::Test {
ClioConfigDefinition cfg = {
{"server.max_queue_size", ConfigValue{ConfigType::Integer}.defaultValue(2)},
{"workers", ConfigValue{ConfigType::Integer}.defaultValue(4)}

View File

@@ -39,7 +39,7 @@ using namespace util::config;
using testing::MockFunction;
using testing::StrictMock;
struct SignalsHandlerTestsBase : NoLoggerFixture {
struct SignalsHandlerTestsBase : public virtual ::testing::Test {
void
allowTestToFinish()
{

View File

@@ -327,7 +327,7 @@ TEST_F(IncorrectOverrideValues, InvalidJsonErrors)
EXPECT_EQ(expectedErrors, actualErrors);
}
struct ClioConfigDefinitionParseArrayTest : NoLoggerFixture {
struct ClioConfigDefinitionParseArrayTest : public virtual ::testing::Test {
ClioConfigDefinition config{
{"array.[].int", Array{ConfigValue{ConfigType::Integer}}},
{"array.[].string", Array{ConfigValue{ConfigType::String}.optional()}}

View File

@@ -52,7 +52,8 @@ struct ConfigFileJsonParseTestBundle {
ValidationMap validationMap;
};
struct ConfigFileJsonParseTest : NoLoggerFixture, testing::WithParamInterface<ConfigFileJsonParseTestBundle> {};
struct ConfigFileJsonParseTest : public virtual ::testing::Test,
testing::WithParamInterface<ConfigFileJsonParseTestBundle> {};
TEST_P(ConfigFileJsonParseTest, parseValues)
{
@@ -309,7 +310,7 @@ INSTANTIATE_TEST_CASE_P(
tests::util::kNAME_GENERATOR
);
struct ConfigFileJsonTest : NoLoggerFixture {};
struct ConfigFileJsonTest : public virtual ::testing::Test {};
TEST_F(ConfigFileJsonTest, getValue)
{

View File

@@ -35,7 +35,7 @@
using namespace util::config;
struct ConfigValueTest : common::util::WithMockAssert, NoLoggerFixture {};
struct ConfigValueTest : common::util::WithMockAssert {};
TEST_F(ConfigValueTest, construct)
{
@@ -138,7 +138,7 @@ TEST_F(ConfigValueConstraintTest, defaultValueWithConstraintCheckError)
}
// A test for each constraint so it's easy to change in the future
struct ConstraintTest : NoLoggerFixture {};
struct ConstraintTest : public virtual ::testing::Test {};
TEST_F(ConstraintTest, PortConstraint)
{

View File

@@ -17,7 +17,8 @@
*/
//==============================================================================
#include "util/StringBuffer.hpp"
#include "util/LoggerBuffer.hpp"
#include "util/LoggerFixtures.hpp"
#include "util/config/Array.hpp"
#include "util/config/ConfigConstraints.hpp"
#include "util/config/ConfigDefinition.hpp"
@@ -39,8 +40,6 @@
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <ostream>
#include <string>
#include <string_view>
#include <vector>
@@ -51,7 +50,21 @@ using util::config::ConfigFileJson;
using util::config::ConfigType;
using util::config::ConfigValue;
struct LogServiceInitTests : virtual public ::testing::Test {
struct LogServiceInitTests : virtual public LoggerFixture {
public:
LogServiceInitTests()
{
LogServiceState::reset();
}
~LogServiceInitTests() override
{
if (LogService::initialized()) {
LogService::reset();
}
LogServiceState::init(false, Severity::FTL, {});
}
protected:
util::config::ClioConfigDefinition config_{
{"log.channels.[].channel", Array{ConfigValue{ConfigType::String}}},
@@ -80,26 +93,6 @@ protected:
{
return buffer_.getStrAndReset();
}
void
replaceSinks()
{
auto ostreamSink = std::make_shared<spdlog::sinks::ostream_sink_mt>(stream_);
ostreamSink->set_formatter(std::make_unique<spdlog::pattern_formatter>("%^%3!l:%n%$ - %v"));
for (auto const& channel : Logger::kCHANNELS) {
auto logger = spdlog::get(channel);
ASSERT_TRUE(logger != nullptr);
// It is expected that only stderrSink is present
ASSERT_EQ(logger->sinks().size(), 1);
logger->sinks().clear();
logger->sinks().push_back(ostreamSink);
}
}
private:
StringBuffer buffer_;
std::ostream stream_ = std::ostream{&buffer_};
};
TEST_F(LogServiceInitTests, DefaultLogLevel)
@@ -107,15 +100,15 @@ TEST_F(LogServiceInitTests, DefaultLogLevel)
auto const parsingErrors =
config_.parse(ConfigFileJson{boost::json::object{{"log", boost::json::object{{"level", "warn"}}}}});
ASSERT_FALSE(parsingErrors.has_value());
std::string const logString = "some log";
EXPECT_TRUE(LogService::init(config_));
replaceSinks();
std::string const logString = "some log";
for (auto const& channel : Logger::kCHANNELS) {
Logger const log{channel};
log.trace() << logString;
ASSERT_TRUE(getLoggerString().empty());
auto loggerStr = getLoggerString();
ASSERT_TRUE(loggerStr.empty()) << " channel: " << channel << " logger_str: " << loggerStr;
log.debug() << logString;
ASSERT_TRUE(getLoggerString().empty());
@@ -149,11 +142,10 @@ TEST_F(LogServiceInitTests, ChannelLogLevel)
auto const parsingErrors = config_.parse(ConfigFileJson{boost::json::parse(configStr).as_object()});
ASSERT_FALSE(parsingErrors.has_value());
std::string const logString = "some log";
EXPECT_TRUE(LogService::init(config_));
replaceSinks();
std::string const logString = "some log";
for (auto const& channel : Logger::kCHANNELS) {
Logger const log{channel};
log.trace() << logString;

View File

@@ -29,12 +29,10 @@ using namespace util;
// Used as a fixture for tests with enabled logging
class LoggerTest : public LoggerFixture {};
// Used as a fixture for tests with disabled logging
class NoLoggerTest : public NoLoggerFixture {};
TEST_F(LoggerTest, Basic)
{
Logger const log{"General"};
log.info() << "Info line logged";
ASSERT_EQ(getLoggerString(), "inf:General - Info line logged\n");
@@ -53,10 +51,6 @@ TEST_F(LoggerTest, Filtering)
log.warn() << "Warning is logged";
ASSERT_EQ(getLoggerString(), "war:General - Warning is logged\n");
Logger const tlog{"Trace"};
tlog.trace() << "Trace line logged for 'Trace' component";
ASSERT_EQ(getLoggerString(), "tra:Trace - Trace line logged for 'Trace' component\n");
}
#ifndef COVERAGE_ENABLED
@@ -77,13 +71,3 @@ TEST_F(LoggerTest, LOGMacro)
EXPECT_TRUE(computeCalled);
}
#endif
TEST_F(NoLoggerTest, Basic)
{
Logger const log{"Trace"};
log.trace() << "Nothing";
ASSERT_TRUE(getLoggerString().empty());
LogService::fatal() << "Still nothing";
ASSERT_TRUE(getLoggerString().empty());
}

View File

@@ -38,7 +38,7 @@
namespace http = boost::beast::http;
using namespace util::config;
class IPAdminVerificationStrategyTest : public NoLoggerFixture {
class IPAdminVerificationStrategyTest : public virtual ::testing::Test {
protected:
web::IPAdminVerificationStrategy strat_;
http::request<http::string_body> request_;
@@ -53,7 +53,7 @@ TEST_F(IPAdminVerificationStrategyTest, IsAdminOnlyForIP_127_0_0_1)
EXPECT_FALSE(strat_.isAdmin(request_, "localhost"));
}
class PasswordAdminVerificationStrategyTest : public NoLoggerFixture {
class PasswordAdminVerificationStrategyTest : public virtual ::testing::Test {
protected:
std::string const password_ = "secret";
std::string const passwordHash_ = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b";

View File

@@ -141,7 +141,7 @@ getParseServerConfig(boost::json::value val)
return config;
};
struct WebServerTest : NoLoggerFixture {
struct WebServerTest : public virtual ::testing::Test {
~WebServerTest() override
{
work_.reset();

View File

@@ -35,7 +35,7 @@
using namespace web;
using namespace util::config;
struct SubscriptionContextTests : NoLoggerFixture {
struct SubscriptionContextTests : public virtual ::testing::Test {
protected:
util::TagDecoratorFactory tagFactory_{ClioConfigDefinition{
{"log.tag_style", ConfigValue{ConfigType::String}.defaultValue("uint")},

View File

@@ -40,7 +40,7 @@ using namespace std;
using namespace util::config;
using namespace web::dosguard;
struct DOSGuardTest : NoLoggerFixture {
struct DOSGuardTest : public virtual ::testing::Test {
static constexpr auto kJSON_DATA = R"JSON({
"dos_guard": {
"max_fetches": 100,

View File

@@ -39,7 +39,7 @@ using namespace util;
using namespace util::config;
using namespace web::dosguard;
struct WhitelistHandlerTest : NoLoggerFixture {};
struct WhitelistHandlerTest : public virtual ::testing::Test {};
inline static ClioConfigDefinition
getParseWhitelistHandlerConfig(boost::json::value val)

View File

@@ -41,7 +41,7 @@ using namespace web::impl;
using namespace web;
using namespace util::config;
struct ErrorHandlingTests : NoLoggerFixture {
struct ErrorHandlingTests : public virtual ::testing::Test {
protected:
util::TagDecoratorFactory tagFactory_{ClioConfigDefinition{
{"log.tag_style", ConfigValue{ConfigType::String}.defaultValue("uint")},

View File

@@ -73,9 +73,7 @@ struct MakeServerTestBundle {
bool expectSuccess;
};
struct MakeServerTest : util::prometheus::WithPrometheus,
NoLoggerFixture,
testing::WithParamInterface<MakeServerTestBundle> {
struct MakeServerTest : util::prometheus::WithPrometheus, testing::WithParamInterface<MakeServerTestBundle> {
protected:
boost::asio::io_context ioContext_;
};

View File

@@ -43,7 +43,7 @@ using namespace web::ng;
namespace http = boost::beast::http;
struct NgErrorHandlingTests : NoLoggerFixture {
struct NgErrorHandlingTests : public virtual ::testing::Test {
static Request
makeRequest(bool isHttp, std::optional<std::string> body = std::nullopt)
{