diff options
Diffstat (limited to 'thirdparty/spdlog/tests')
29 files changed, 3307 insertions, 0 deletions
diff --git a/thirdparty/spdlog/tests/includes.h b/thirdparty/spdlog/tests/includes.h new file mode 100644 index 000000000..2e49a5cb7 --- /dev/null +++ b/thirdparty/spdlog/tests/includes.h @@ -0,0 +1,42 @@ +#pragma once + +#if defined(__GNUC__) && __GNUC__ == 12 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // Workaround for GCC 12 +#endif +#include <catch2/catch_all.hpp> +#if defined(__GNUC__) && __GNUC__ == 12 + #pragma GCC diagnostic pop +#endif + +#include "utils.h" +#include <chrono> +#include <cstdio> +#include <exception> +#include <fstream> +#include <iostream> +#include <ostream> +#include <sstream> +#include <string> +#include <iomanip> +#include <stdlib.h> + +#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG + +#include "spdlog/spdlog.h" +#include "spdlog/async.h" +#include "spdlog/details/fmt_helper.h" +#include "spdlog/details/os.h" + +#ifndef SPDLOG_NO_TLS + #include "spdlog/mdc.h" +#endif + +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/daily_file_sink.h" +#include "spdlog/sinks/null_sink.h" +#include "spdlog/sinks/ostream_sink.h" +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/sinks/msvc_sink.h" +#include "spdlog/pattern_formatter.h" diff --git a/thirdparty/spdlog/tests/main.cpp b/thirdparty/spdlog/tests/main.cpp new file mode 100644 index 000000000..a4a4ff154 --- /dev/null +++ b/thirdparty/spdlog/tests/main.cpp @@ -0,0 +1,10 @@ +#if defined(__GNUC__) && __GNUC__ == 12 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // Workaround for GCC 12 +#endif + +#include <catch2/catch_all.hpp> + +#if defined(__GNUC__) && __GNUC__ == 12 + #pragma GCC diagnostic pop +#endif diff --git a/thirdparty/spdlog/tests/test_async.cpp b/thirdparty/spdlog/tests/test_async.cpp new file mode 100644 index 000000000..76fdd7c6b --- /dev/null +++ b/thirdparty/spdlog/tests/test_async.cpp @@ -0,0 +1,200 @@ +#include "includes.h" +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +#include "test_sink.h" + +#define TEST_FILENAME "test_logs/async_test.log" + +TEST_CASE("basic async test ", "[async]") { + auto test_sink = std::make_shared<spdlog::sinks::test_sink_mt>(); + size_t overrun_counter = 0; + size_t queue_size = 128; + size_t messages = 256; + { + auto tp = std::make_shared<spdlog::details::thread_pool>(queue_size, 1); + auto logger = std::make_shared<spdlog::async_logger>("as", test_sink, tp, + spdlog::async_overflow_policy::block); + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message #{}", i); + } + logger->flush(); + overrun_counter = tp->overrun_counter(); + } + REQUIRE(test_sink->msg_counter() == messages); + REQUIRE(test_sink->flush_counter() == 1); + REQUIRE(overrun_counter == 0); +} + +TEST_CASE("discard policy ", "[async]") { + auto test_sink = std::make_shared<spdlog::sinks::test_sink_mt>(); + test_sink->set_delay(std::chrono::milliseconds(1)); + size_t queue_size = 4; + size_t messages = 1024; + + auto tp = std::make_shared<spdlog::details::thread_pool>(queue_size, 1); + auto logger = std::make_shared<spdlog::async_logger>( + "as", test_sink, tp, spdlog::async_overflow_policy::overrun_oldest); + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message"); + } + REQUIRE(test_sink->msg_counter() < messages); + REQUIRE(tp->overrun_counter() > 0); +} + +TEST_CASE("discard policy discard_new ", "[async]") { + auto test_sink = std::make_shared<spdlog::sinks::test_sink_mt>(); + test_sink->set_delay(std::chrono::milliseconds(1)); + size_t queue_size = 4; + size_t messages = 1024; + + auto tp = std::make_shared<spdlog::details::thread_pool>(queue_size, 1); + auto logger = std::make_shared<spdlog::async_logger>( + "as", test_sink, tp, spdlog::async_overflow_policy::discard_new); + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message"); + } + REQUIRE(test_sink->msg_counter() < messages); + REQUIRE(tp->discard_counter() > 0); +} + +TEST_CASE("discard policy using factory ", "[async]") { + size_t queue_size = 4; + size_t messages = 1024; + spdlog::init_thread_pool(queue_size, 1); + + auto logger = spdlog::create_async_nb<spdlog::sinks::test_sink_mt>("as2"); + auto test_sink = std::static_pointer_cast<spdlog::sinks::test_sink_mt>(logger->sinks()[0]); + test_sink->set_delay(std::chrono::milliseconds(3)); + + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message"); + } + + REQUIRE(test_sink->msg_counter() < messages); + spdlog::drop_all(); +} + +TEST_CASE("flush", "[async]") { + auto test_sink = std::make_shared<spdlog::sinks::test_sink_mt>(); + size_t queue_size = 256; + size_t messages = 256; + { + auto tp = std::make_shared<spdlog::details::thread_pool>(queue_size, 1); + auto logger = std::make_shared<spdlog::async_logger>("as", test_sink, tp, + spdlog::async_overflow_policy::block); + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message #{}", i); + } + + logger->flush(); + } + // std::this_thread::sleep_for(std::chrono::milliseconds(250)); + REQUIRE(test_sink->msg_counter() == messages); + REQUIRE(test_sink->flush_counter() == 1); +} + +TEST_CASE("async periodic flush", "[async]") { + auto logger = spdlog::create_async<spdlog::sinks::test_sink_mt>("as"); + auto test_sink = std::static_pointer_cast<spdlog::sinks::test_sink_mt>(logger->sinks()[0]); + + spdlog::flush_every(std::chrono::seconds(1)); + std::this_thread::sleep_for(std::chrono::milliseconds(1700)); + REQUIRE(test_sink->flush_counter() == 1); + spdlog::flush_every(std::chrono::seconds(0)); + spdlog::drop_all(); +} + +TEST_CASE("tp->wait_empty() ", "[async]") { + auto test_sink = std::make_shared<spdlog::sinks::test_sink_mt>(); + test_sink->set_delay(std::chrono::milliseconds(5)); + size_t messages = 100; + + auto tp = std::make_shared<spdlog::details::thread_pool>(messages, 2); + auto logger = std::make_shared<spdlog::async_logger>("as", test_sink, tp, + spdlog::async_overflow_policy::block); + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message #{}", i); + } + logger->flush(); + tp.reset(); + + REQUIRE(test_sink->msg_counter() == messages); + REQUIRE(test_sink->flush_counter() == 1); +} + +TEST_CASE("multi threads", "[async]") { + auto test_sink = std::make_shared<spdlog::sinks::test_sink_mt>(); + size_t queue_size = 128; + size_t messages = 256; + size_t n_threads = 10; + { + auto tp = std::make_shared<spdlog::details::thread_pool>(queue_size, 1); + auto logger = std::make_shared<spdlog::async_logger>("as", test_sink, tp, + spdlog::async_overflow_policy::block); + + std::vector<std::thread> threads; + for (size_t i = 0; i < n_threads; i++) { + threads.emplace_back([logger, messages] { + for (size_t j = 0; j < messages; j++) { + logger->info("Hello message #{}", j); + } + }); + logger->flush(); + } + + for (auto &t : threads) { + t.join(); + } + } + + REQUIRE(test_sink->msg_counter() == messages * n_threads); + REQUIRE(test_sink->flush_counter() == n_threads); +} + +TEST_CASE("to_file", "[async]") { + prepare_logdir(); + size_t messages = 1024; + size_t tp_threads = 1; + spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME); + { + auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, true); + auto tp = std::make_shared<spdlog::details::thread_pool>(messages, tp_threads); + auto logger = + std::make_shared<spdlog::async_logger>("as", std::move(file_sink), std::move(tp)); + + for (size_t j = 0; j < messages; j++) { + logger->info("Hello message #{}", j); + } + } + + require_message_count(TEST_FILENAME, messages); + auto contents = file_contents(TEST_FILENAME); + using spdlog::details::os::default_eol; + REQUIRE(ends_with(contents, spdlog::fmt_lib::format("Hello message #1023{}", default_eol))); +} + +TEST_CASE("to_file multi-workers", "[async]") { + prepare_logdir(); + size_t messages = 1024 * 10; + size_t tp_threads = 10; + spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME); + { + auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, true); + auto tp = std::make_shared<spdlog::details::thread_pool>(messages, tp_threads); + auto logger = + std::make_shared<spdlog::async_logger>("as", std::move(file_sink), std::move(tp)); + + for (size_t j = 0; j < messages; j++) { + logger->info("Hello message #{}", j); + } + } + require_message_count(TEST_FILENAME, messages); +} + +TEST_CASE("bad_tp", "[async]") { + auto test_sink = std::make_shared<spdlog::sinks::test_sink_mt>(); + std::shared_ptr<spdlog::details::thread_pool> const empty_tp; + auto logger = std::make_shared<spdlog::async_logger>("as", test_sink, empty_tp); + logger->info("Please throw an exception"); + REQUIRE(test_sink->msg_counter() == 0); +} diff --git a/thirdparty/spdlog/tests/test_backtrace.cpp b/thirdparty/spdlog/tests/test_backtrace.cpp new file mode 100644 index 000000000..4d78c0c21 --- /dev/null +++ b/thirdparty/spdlog/tests/test_backtrace.cpp @@ -0,0 +1,73 @@ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/async.h" + +TEST_CASE("bactrace1", "[bactrace]") { + using spdlog::sinks::test_sink_st; + auto test_sink = std::make_shared<test_sink_st>(); + size_t backtrace_size = 5; + + spdlog::logger logger("test-backtrace", test_sink); + logger.set_pattern("%v"); + logger.enable_backtrace(backtrace_size); + + logger.info("info message"); + for (int i = 0; i < 100; i++) logger.debug("debug message {}", i); + + REQUIRE(test_sink->lines().size() == 1); + REQUIRE(test_sink->lines()[0] == "info message"); + + logger.dump_backtrace(); + REQUIRE(test_sink->lines().size() == backtrace_size + 3); + REQUIRE(test_sink->lines()[1] == "****************** Backtrace Start ******************"); + REQUIRE(test_sink->lines()[2] == "debug message 95"); + REQUIRE(test_sink->lines()[3] == "debug message 96"); + REQUIRE(test_sink->lines()[4] == "debug message 97"); + REQUIRE(test_sink->lines()[5] == "debug message 98"); + REQUIRE(test_sink->lines()[6] == "debug message 99"); + REQUIRE(test_sink->lines()[7] == "****************** Backtrace End ********************"); +} + +TEST_CASE("bactrace-empty", "[bactrace]") { + using spdlog::sinks::test_sink_st; + auto test_sink = std::make_shared<test_sink_st>(); + size_t backtrace_size = 5; + + spdlog::logger logger("test-backtrace", test_sink); + logger.set_pattern("%v"); + logger.enable_backtrace(backtrace_size); + logger.dump_backtrace(); + REQUIRE(test_sink->lines().size() == 0); +} + +TEST_CASE("bactrace-async", "[bactrace]") { + using spdlog::sinks::test_sink_mt; + auto test_sink = std::make_shared<test_sink_mt>(); + using spdlog::details::os::sleep_for_millis; + + size_t backtrace_size = 5; + + spdlog::init_thread_pool(120, 1); + auto logger = std::make_shared<spdlog::async_logger>("test-bactrace-async", test_sink, + spdlog::thread_pool()); + logger->set_pattern("%v"); + logger->enable_backtrace(backtrace_size); + + logger->info("info message"); + for (int i = 0; i < 100; i++) logger->debug("debug message {}", i); + + sleep_for_millis(100); + REQUIRE(test_sink->lines().size() == 1); + REQUIRE(test_sink->lines()[0] == "info message"); + + logger->dump_backtrace(); + sleep_for_millis(100); // give time for the async dump to complete + REQUIRE(test_sink->lines().size() == backtrace_size + 3); + REQUIRE(test_sink->lines()[1] == "****************** Backtrace Start ******************"); + REQUIRE(test_sink->lines()[2] == "debug message 95"); + REQUIRE(test_sink->lines()[3] == "debug message 96"); + REQUIRE(test_sink->lines()[4] == "debug message 97"); + REQUIRE(test_sink->lines()[5] == "debug message 98"); + REQUIRE(test_sink->lines()[6] == "debug message 99"); + REQUIRE(test_sink->lines()[7] == "****************** Backtrace End ********************"); +} diff --git a/thirdparty/spdlog/tests/test_bin_to_hex.cpp b/thirdparty/spdlog/tests/test_bin_to_hex.cpp new file mode 100644 index 000000000..45fc9fa96 --- /dev/null +++ b/thirdparty/spdlog/tests/test_bin_to_hex.cpp @@ -0,0 +1,97 @@ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/fmt/bin_to_hex.h" + +TEST_CASE("to_hex", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector<unsigned char> v{9, 0xa, 0xb, 0xc, 0xff, 0xff}; + oss_logger.info("{}", spdlog::to_hex(v)); + + auto output = oss.str(); + REQUIRE(ends_with(output, + "0000: 09 0a 0b 0c ff ff" + std::string(spdlog::details::os::default_eol))); +} + +TEST_CASE("to_hex_upper", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector<unsigned char> v{9, 0xa, 0xb, 0xc, 0xff, 0xff}; + oss_logger.info("{:X}", spdlog::to_hex(v)); + + auto output = oss.str(); + REQUIRE(ends_with(output, + "0000: 09 0A 0B 0C FF FF" + std::string(spdlog::details::os::default_eol))); +} + +TEST_CASE("to_hex_no_delimiter", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector<unsigned char> v{9, 0xa, 0xb, 0xc, 0xff, 0xff}; + oss_logger.info("{:sX}", spdlog::to_hex(v)); + + auto output = oss.str(); + REQUIRE( + ends_with(output, "0000: 090A0B0CFFFF" + std::string(spdlog::details::os::default_eol))); +} + +TEST_CASE("to_hex_show_ascii", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector<unsigned char> v{9, 0xa, 0xb, 0x41, 0xc, 0x4b, 0xff, 0xff}; + oss_logger.info("{:Xsa}", spdlog::to_hex(v, 8)); + + REQUIRE(ends_with(oss.str(), "0000: 090A0B410C4BFFFF ...A.K.." + + std::string(spdlog::details::os::default_eol))); +} + +TEST_CASE("to_hex_different_size_per_line", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector<unsigned char> v{9, 0xa, 0xb, 0x41, 0xc, 0x4b, 0xff, 0xff}; + + oss_logger.info("{:Xsa}", spdlog::to_hex(v, 10)); + REQUIRE(ends_with(oss.str(), "0000: 090A0B410C4BFFFF ...A.K.." + + std::string(spdlog::details::os::default_eol))); + + oss_logger.info("{:Xs}", spdlog::to_hex(v, 10)); + REQUIRE(ends_with(oss.str(), + "0000: 090A0B410C4BFFFF" + std::string(spdlog::details::os::default_eol))); + + oss_logger.info("{:Xsa}", spdlog::to_hex(v, 6)); + REQUIRE(ends_with( + oss.str(), "0000: 090A0B410C4B ...A.K" + std::string(spdlog::details::os::default_eol) + + "0006: FFFF .." + std::string(spdlog::details::os::default_eol))); + + oss_logger.info("{:Xs}", spdlog::to_hex(v, 6)); + REQUIRE(ends_with(oss.str(), "0000: 090A0B410C4B" + + std::string(spdlog::details::os::default_eol) + "0006: FFFF" + + std::string(spdlog::details::os::default_eol))); +} + +TEST_CASE("to_hex_no_ascii", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector<unsigned char> v{9, 0xa, 0xb, 0x41, 0xc, 0x4b, 0xff, 0xff}; + oss_logger.info("{:Xs}", spdlog::to_hex(v, 8)); + + REQUIRE(ends_with(oss.str(), + "0000: 090A0B410C4BFFFF" + std::string(spdlog::details::os::default_eol))); + + oss_logger.info("{:Xsna}", spdlog::to_hex(v, 8)); + + REQUIRE( + ends_with(oss.str(), "090A0B410C4BFFFF" + std::string(spdlog::details::os::default_eol))); +} diff --git a/thirdparty/spdlog/tests/test_cfg.cpp b/thirdparty/spdlog/tests/test_cfg.cpp new file mode 100644 index 000000000..7dec94b44 --- /dev/null +++ b/thirdparty/spdlog/tests/test_cfg.cpp @@ -0,0 +1,178 @@ + +#include "includes.h" +#include "test_sink.h" + +#include <spdlog/cfg/env.h> +#include <spdlog/cfg/argv.h> + +using spdlog::cfg::load_argv_levels; +using spdlog::cfg::load_env_levels; +using spdlog::sinks::test_sink_st; + +TEST_CASE("env", "[cfg]") { + spdlog::drop("l1"); + auto l1 = spdlog::create<test_sink_st>("l1"); +#ifdef CATCH_PLATFORM_WINDOWS + _putenv_s("SPDLOG_LEVEL", "l1=warn"); +#else + setenv("SPDLOG_LEVEL", "l1=warn", 1); +#endif + load_env_levels(); + REQUIRE(l1->level() == spdlog::level::warn); + +#ifdef CATCH_PLATFORM_WINDOWS + _putenv_s("MYAPP_LEVEL", "l1=trace"); +#else + setenv("MYAPP_LEVEL", "l1=trace", 1); +#endif + load_env_levels("MYAPP_LEVEL"); + REQUIRE(l1->level() == spdlog::level::trace); + + spdlog::set_default_logger(spdlog::create<test_sink_st>("cfg-default")); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::info); +} + +TEST_CASE("argv1", "[cfg]") { + spdlog::drop("l1"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=warn"}; + load_argv_levels(2, argv); + auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1"); + REQUIRE(l1->level() == spdlog::level::warn); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::info); +} + +TEST_CASE("argv2", "[cfg]") { + spdlog::drop("l1"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=warn,trace"}; + load_argv_levels(2, argv); + auto l1 = spdlog::create<test_sink_st>("l1"); + REQUIRE(l1->level() == spdlog::level::warn); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace); +} + +TEST_CASE("argv3", "[cfg]") { + spdlog::set_level(spdlog::level::trace); + + spdlog::drop("l1"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=junk_name=warn"}; + load_argv_levels(2, argv); + auto l1 = spdlog::create<test_sink_st>("l1"); + REQUIRE(l1->level() == spdlog::level::trace); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace); +} + +TEST_CASE("argv4", "[cfg]") { + spdlog::set_level(spdlog::level::info); + spdlog::drop("l1"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=junk"}; + load_argv_levels(2, argv); + auto l1 = spdlog::create<test_sink_st>("l1"); + REQUIRE(l1->level() == spdlog::level::info); +} + +TEST_CASE("argv5", "[cfg]") { + spdlog::set_level(spdlog::level::info); + spdlog::drop("l1"); + const char *argv[] = {"ignore", "ignore", "SPDLOG_LEVEL=l1=warn,trace"}; + load_argv_levels(3, argv); + auto l1 = spdlog::create<test_sink_st>("l1"); + REQUIRE(l1->level() == spdlog::level::warn); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace); + spdlog::set_level(spdlog::level::info); +} + +TEST_CASE("argv6", "[cfg]") { + spdlog::set_level(spdlog::level::err); + const char *argv[] = {""}; + load_argv_levels(1, argv); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::err); + spdlog::set_level(spdlog::level::info); +} + +TEST_CASE("argv7", "[cfg]") { + spdlog::set_level(spdlog::level::err); + const char *argv[] = {""}; + load_argv_levels(0, argv); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::err); + spdlog::set_level(spdlog::level::info); +} + +TEST_CASE("level-not-set-test1", "[cfg]") { + spdlog::drop("l1"); + const char *argv[] = {"ignore", ""}; + load_argv_levels(2, argv); + auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1"); + l1->set_level(spdlog::level::trace); + REQUIRE(l1->level() == spdlog::level::trace); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::info); +} + +TEST_CASE("level-not-set-test2", "[cfg]") { + spdlog::drop("l1"); + spdlog::drop("l2"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=trace"}; + + auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1"); + l1->set_level(spdlog::level::warn); + auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2"); + l2->set_level(spdlog::level::warn); + + load_argv_levels(2, argv); + + REQUIRE(l1->level() == spdlog::level::trace); + REQUIRE(l2->level() == spdlog::level::warn); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::info); +} + +TEST_CASE("level-not-set-test3", "[cfg]") { + spdlog::drop("l1"); + spdlog::drop("l2"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=trace"}; + + load_argv_levels(2, argv); + + auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1"); + auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2"); + + REQUIRE(l1->level() == spdlog::level::trace); + REQUIRE(l2->level() == spdlog::level::info); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::info); +} + +TEST_CASE("level-not-set-test4", "[cfg]") { + spdlog::drop("l1"); + spdlog::drop("l2"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=trace,warn"}; + + load_argv_levels(2, argv); + + auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1"); + auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2"); + + REQUIRE(l1->level() == spdlog::level::trace); + REQUIRE(l2->level() == spdlog::level::warn); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::warn); +} + +TEST_CASE("level-not-set-test5", "[cfg]") { + spdlog::drop("l1"); + spdlog::drop("l2"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=junk,warn"}; + + load_argv_levels(2, argv); + + auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1"); + auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2"); + + REQUIRE(l1->level() == spdlog::level::warn); + REQUIRE(l2->level() == spdlog::level::warn); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::warn); +} + +TEST_CASE("restore-to-default", "[cfg]") { + spdlog::drop("l1"); + spdlog::drop("l2"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=info"}; + load_argv_levels(2, argv); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::info); +} diff --git a/thirdparty/spdlog/tests/test_circular_q.cpp b/thirdparty/spdlog/tests/test_circular_q.cpp new file mode 100644 index 000000000..c8b02d363 --- /dev/null +++ b/thirdparty/spdlog/tests/test_circular_q.cpp @@ -0,0 +1,50 @@ +#include "includes.h" +#include "spdlog/details/circular_q.h" + +using q_type = spdlog::details::circular_q<size_t>; +TEST_CASE("test_size", "[circular_q]") { + const size_t q_size = 4; + q_type q(q_size); + REQUIRE(q.size() == 0); + REQUIRE(q.empty() == true); + for (size_t i = 0; i < q_size; i++) { + q.push_back(std::move(i)); + } + REQUIRE(q.size() == q_size); + q.push_back(999); + REQUIRE(q.size() == q_size); +} + +TEST_CASE("test_rolling", "[circular_q]") { + const size_t q_size = 4; + q_type q(q_size); + + for (size_t i = 0; i < q_size + 2; i++) { + q.push_back(std::move(i)); + } + + REQUIRE(q.size() == q_size); + + REQUIRE(q.front() == 2); + q.pop_front(); + + REQUIRE(q.front() == 3); + q.pop_front(); + + REQUIRE(q.front() == 4); + q.pop_front(); + + REQUIRE(q.front() == 5); + q.pop_front(); + + REQUIRE(q.empty()); + + q.push_back(6); + REQUIRE(q.front() == 6); +} + +TEST_CASE("test_empty", "[circular_q]") { + q_type q(0); + q.push_back(1); + REQUIRE(q.empty()); +}
\ No newline at end of file diff --git a/thirdparty/spdlog/tests/test_create_dir.cpp b/thirdparty/spdlog/tests/test_create_dir.cpp new file mode 100644 index 000000000..fd040339c --- /dev/null +++ b/thirdparty/spdlog/tests/test_create_dir.cpp @@ -0,0 +1,144 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +using spdlog::details::os::create_dir; +using spdlog::details::os::path_exists; + +bool try_create_dir(const spdlog::filename_t &path, const spdlog::filename_t &normalized_path) { + auto rv = create_dir(path); + REQUIRE(rv == true); + return path_exists(normalized_path); +} + +TEST_CASE("create_dir", "[create_dir]") { + prepare_logdir(); + + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir1"), + SPDLOG_FILENAME_T("test_logs/dir1/dir1"))); + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir1"), + SPDLOG_FILENAME_T("test_logs/dir1/dir1"))); // test existing + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1///dir2//"), + SPDLOG_FILENAME_T("test_logs/dir1/dir2"))); + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("./test_logs/dir1/dir3"), + SPDLOG_FILENAME_T("test_logs/dir1/dir3"))); + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/../test_logs/dir1/dir4"), + SPDLOG_FILENAME_T("test_logs/dir1/dir4"))); + +#ifdef WIN32 + // test backslash folder separator + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\dir1\\dir222"), + SPDLOG_FILENAME_T("test_logs\\dir1\\dir222"))); + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\dir1\\dir223\\"), + SPDLOG_FILENAME_T("test_logs\\dir1\\dir223\\"))); + REQUIRE(try_create_dir(SPDLOG_FILENAME_T(".\\test_logs\\dir1\\dir2\\dir99\\..\\dir23"), + SPDLOG_FILENAME_T("test_logs\\dir1\\dir2\\dir23"))); + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\..\\test_logs\\dir1\\dir5"), + SPDLOG_FILENAME_T("test_logs\\dir1\\dir5"))); +#endif +} + +TEST_CASE("create_invalid_dir", "[create_dir]") { + REQUIRE(create_dir(SPDLOG_FILENAME_T("")) == false); + REQUIRE(create_dir(spdlog::filename_t{}) == false); +#ifdef __linux__ + REQUIRE(create_dir("/proc/spdlog-utest") == false); +#endif +} + +TEST_CASE("dir_name", "[create_dir]") { + using spdlog::details::os::dir_name; + REQUIRE(dir_name(SPDLOG_FILENAME_T("")).empty()); + REQUIRE(dir_name(SPDLOG_FILENAME_T("dir")).empty()); + +#ifdef WIN32 + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\)")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\\\)")) == SPDLOG_FILENAME_T(R"(dir\\)")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file)")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir/file)")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file.txt)")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir/file)")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file.txt\)")) == + SPDLOG_FILENAME_T(R"(dir\file.txt)")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(\dir\file.txt)")) == SPDLOG_FILENAME_T(R"(\dir)")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(\\dir\file.txt)")) == SPDLOG_FILENAME_T(R"(\\dir)")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(..\file.txt)")) == SPDLOG_FILENAME_T("..")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(.\file.txt)")) == SPDLOG_FILENAME_T(".")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(c:\\a\b\c\d\file.txt)")) == + SPDLOG_FILENAME_T(R"(c:\\a\b\c\d)")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(c://a/b/c/d/file.txt)")) == + SPDLOG_FILENAME_T(R"(c://a/b/c/d)")); +#endif + REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T("dir///")) == SPDLOG_FILENAME_T("dir//")); + REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/file")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/file.txt")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/file.txt/")) == SPDLOG_FILENAME_T("dir/file.txt")); + REQUIRE(dir_name(SPDLOG_FILENAME_T("/dir/file.txt")) == SPDLOG_FILENAME_T("/dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T("//dir/file.txt")) == SPDLOG_FILENAME_T("//dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T("../file.txt")) == SPDLOG_FILENAME_T("..")); + REQUIRE(dir_name(SPDLOG_FILENAME_T("./file.txt")) == SPDLOG_FILENAME_T(".")); +} + +#ifdef _WIN32 + + // + // test windows cases when drive letter is given e.g. C:\\some-folder + // + #include <windows.h> + #include <fileapi.h> + +std::string get_full_path(const std::string &relative_folder_path) { + char full_path[MAX_PATH]; + + DWORD result = ::GetFullPathNameA(relative_folder_path.c_str(), MAX_PATH, full_path, nullptr); + // Return an empty string if failed to get full path + return result > 0 && result < MAX_PATH ? std::string(full_path) : std::string(); +} + +std::wstring get_full_path(const std::wstring &relative_folder_path) { + wchar_t full_path[MAX_PATH]; + DWORD result = ::GetFullPathNameW(relative_folder_path.c_str(), MAX_PATH, full_path, nullptr); + return result > 0 && result < MAX_PATH ? std::wstring(full_path) : std::wstring(); +} + +spdlog::filename_t::value_type find_non_existing_drive() { + for (char drive = 'A'; drive <= 'Z'; ++drive) { + std::string root_path = std::string(1, drive) + ":\\"; + UINT drive_type = GetDriveTypeA(root_path.c_str()); + if (drive_type == DRIVE_NO_ROOT_DIR) { + return static_cast<spdlog::filename_t::value_type>(drive); + } + } + return '\0'; // No available drive found +} + +TEST_CASE("create_abs_path1", "[create_dir]") { + prepare_logdir(); + auto abs_path = get_full_path(SPDLOG_FILENAME_T("test_logs\\logdir1")); + REQUIRE(!abs_path.empty()); + REQUIRE(create_dir(abs_path) == true); +} + +TEST_CASE("create_abs_path2", "[create_dir]") { + prepare_logdir(); + auto abs_path = get_full_path(SPDLOG_FILENAME_T("test_logs/logdir2")); + REQUIRE(!abs_path.empty()); + REQUIRE(create_dir(abs_path) == true); +} + +TEST_CASE("non_existing_drive", "[create_dir]") { + prepare_logdir(); + spdlog::filename_t path; + + auto non_existing_drive = find_non_existing_drive(); + path += non_existing_drive; + path += SPDLOG_FILENAME_T(":\\"); + REQUIRE(create_dir(path) == false); + path += SPDLOG_FILENAME_T("subdir"); + REQUIRE(create_dir(path) == false); +} +// #endif // SPDLOG_WCHAR_FILENAMES +#endif // _WIN32 diff --git a/thirdparty/spdlog/tests/test_custom_callbacks.cpp b/thirdparty/spdlog/tests/test_custom_callbacks.cpp new file mode 100644 index 000000000..f14572115 --- /dev/null +++ b/thirdparty/spdlog/tests/test_custom_callbacks.cpp @@ -0,0 +1,37 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/sinks/callback_sink.h" +#include "spdlog/async.h" +#include "spdlog/common.h" + +TEST_CASE("custom_callback_logger", "[custom_callback_logger]") { + std::vector<std::string> lines; + spdlog::pattern_formatter formatter; + auto callback_logger = + std::make_shared<spdlog::sinks::callback_sink_st>([&](const spdlog::details::log_msg &msg) { + spdlog::memory_buf_t formatted; + formatter.format(msg, formatted); + auto eol_len = strlen(spdlog::details::os::default_eol); + using diff_t = + typename std::iterator_traits<decltype(formatted.end())>::difference_type; + lines.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len)); + }); + std::shared_ptr<spdlog::sinks::test_sink_st> test_sink(new spdlog::sinks::test_sink_st); + + spdlog::logger logger("test-callback", {callback_logger, test_sink}); + + logger.info("test message 1"); + logger.info("test message 2"); + logger.info("test message 3"); + + std::vector<std::string> ref_lines = test_sink->lines(); + + REQUIRE(lines[0] == ref_lines[0]); + REQUIRE(lines[1] == ref_lines[1]); + REQUIRE(lines[2] == ref_lines[2]); + spdlog::drop_all(); +} diff --git a/thirdparty/spdlog/tests/test_daily_logger.cpp b/thirdparty/spdlog/tests/test_daily_logger.cpp new file mode 100644 index 000000000..8a00c024a --- /dev/null +++ b/thirdparty/spdlog/tests/test_daily_logger.cpp @@ -0,0 +1,169 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +#ifdef SPDLOG_USE_STD_FORMAT +using filename_memory_buf_t = std::basic_string<spdlog::filename_t::value_type>; +#else +using filename_memory_buf_t = fmt::basic_memory_buffer<spdlog::filename_t::value_type, 250>; +#endif + +#ifdef SPDLOG_WCHAR_FILENAMES +std::string filename_buf_to_utf8string(const filename_memory_buf_t &w) { + spdlog::memory_buf_t buf; + spdlog::details::os::wstr_to_utf8buf(spdlog::wstring_view_t(w.data(), w.size()), buf); + return SPDLOG_BUF_TO_STRING(buf); +} +#else +std::string filename_buf_to_utf8string(const filename_memory_buf_t &w) { + return SPDLOG_BUF_TO_STRING(w); +} +#endif + +TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]") { + using sink_type = + spdlog::sinks::daily_file_sink<std::mutex, spdlog::sinks::daily_filename_calculator>; + + prepare_logdir(); + + // calculate filename (time based) + spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly"); + std::tm tm = spdlog::details::os::localtime(); + filename_memory_buf_t w; + spdlog::fmt_lib::format_to(std::back_inserter(w), SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}"), + basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + + auto logger = spdlog::create<sink_type>("logger", basename, 0, 0); + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + logger->flush(); + + require_message_count(filename_buf_to_utf8string(w), 10); +} + +struct custom_daily_file_name_calculator { + static spdlog::filename_t calc_filename(const spdlog::filename_t &basename, const tm &now_tm) { + return spdlog::fmt_lib::format(SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename, + now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday); + } +}; + +TEST_CASE("daily_logger with custom calculator", "[daily_logger]") { + using sink_type = spdlog::sinks::daily_file_sink<std::mutex, custom_daily_file_name_calculator>; + + prepare_logdir(); + + // calculate filename (time based) + spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly"); + std::tm tm = spdlog::details::os::localtime(); + filename_memory_buf_t w; + spdlog::fmt_lib::format_to(std::back_inserter(w), SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), + basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + + auto logger = spdlog::create<sink_type>("logger", basename, 0, 0); + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + + logger->flush(); + + require_message_count(filename_buf_to_utf8string(w), 10); +} + +/* + * File name calculations + */ + +TEST_CASE("rotating_file_sink::calc_filename1", "[rotating_file_sink]") { + auto filename = + spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated.txt"), 3); + REQUIRE(filename == SPDLOG_FILENAME_T("rotated.3.txt")); +} + +TEST_CASE("rotating_file_sink::calc_filename2", "[rotating_file_sink]") { + auto filename = + spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated"), 3); + REQUIRE(filename == SPDLOG_FILENAME_T("rotated.3")); +} + +TEST_CASE("rotating_file_sink::calc_filename3", "[rotating_file_sink]") { + auto filename = + spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated.txt"), 0); + REQUIRE(filename == SPDLOG_FILENAME_T("rotated.txt")); +} + +// regex supported only from gcc 4.9 and above +#if defined(_MSC_VER) || !(__GNUC__ <= 4 && __GNUC_MINOR__ < 9) + + #include <regex> + +TEST_CASE("daily_file_sink::daily_filename_calculator", "[daily_file_sink]") { + // daily_YYYY-MM-DD_hh-mm.txt + auto filename = spdlog::sinks::daily_filename_calculator::calc_filename( + SPDLOG_FILENAME_T("daily.txt"), spdlog::details::os::localtime()); + // date regex based on https://www.regular-expressions.info/dates.html + std::basic_regex<spdlog::filename_t::value_type> re( + SPDLOG_FILENAME_T(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\.txt$)")); + std::match_results<spdlog::filename_t::const_iterator> match; + REQUIRE(std::regex_match(filename, match, re)); +} +#endif + +TEST_CASE("daily_file_sink::daily_filename_format_calculator", "[daily_file_sink]") { + std::tm tm = spdlog::details::os::localtime(); + // example-YYYY-MM-DD.log + auto filename = spdlog::sinks::daily_filename_format_calculator::calc_filename( + SPDLOG_FILENAME_T("example-%Y-%m-%d.log"), tm); + + REQUIRE(filename == + spdlog::fmt_lib::format(SPDLOG_FILENAME_T("example-{:04d}-{:02d}-{:02d}.log"), + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday)); +} + +/* Test removal of old files */ +static spdlog::details::log_msg create_msg(std::chrono::seconds offset) { + using spdlog::log_clock; + spdlog::details::log_msg msg{"test", spdlog::level::info, "Hello Message"}; + msg.time = log_clock::now() + offset; + return msg; +} + +static void test_rotate(int days_to_run, uint16_t max_days, uint16_t expected_n_files) { + using spdlog::log_clock; + using spdlog::details::log_msg; + using spdlog::sinks::daily_file_sink_st; + + prepare_logdir(); + + spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_rotate.txt"); + daily_file_sink_st sink{basename, 2, 30, true, max_days}; + + // simulate messages with 24 intervals + + for (int i = 0; i < days_to_run; i++) { + auto offset = std::chrono::seconds{24 * 3600 * i}; + sink.log(create_msg(offset)); + } + + REQUIRE(count_files("test_logs") == static_cast<size_t>(expected_n_files)); +} + +TEST_CASE("daily_logger rotate", "[daily_file_sink]") { + int days_to_run = 1; + test_rotate(days_to_run, 0, 1); + test_rotate(days_to_run, 1, 1); + test_rotate(days_to_run, 3, 1); + test_rotate(days_to_run, 10, 1); + + days_to_run = 10; + test_rotate(days_to_run, 0, 10); + test_rotate(days_to_run, 1, 1); + test_rotate(days_to_run, 3, 3); + test_rotate(days_to_run, 9, 9); + test_rotate(days_to_run, 10, 10); + test_rotate(days_to_run, 11, 10); + test_rotate(days_to_run, 20, 10); +} diff --git a/thirdparty/spdlog/tests/test_dup_filter.cpp b/thirdparty/spdlog/tests/test_dup_filter.cpp new file mode 100644 index 000000000..78e22be3b --- /dev/null +++ b/thirdparty/spdlog/tests/test_dup_filter.cpp @@ -0,0 +1,83 @@ +#include "includes.h" +#include "spdlog/sinks/dup_filter_sink.h" +#include "test_sink.h" + +TEST_CASE("dup_filter_test1", "[dup_filter_sink]") { + using spdlog::sinks::dup_filter_sink_st; + using spdlog::sinks::test_sink_mt; + + dup_filter_sink_st dup_sink{std::chrono::seconds{5}}; + auto test_sink = std::make_shared<test_sink_mt>(); + dup_sink.add_sink(test_sink); + + for (int i = 0; i < 10; i++) { + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"}); + } + + REQUIRE(test_sink->msg_counter() == 1); +} + +TEST_CASE("dup_filter_test2", "[dup_filter_sink]") { + using spdlog::sinks::dup_filter_sink_st; + using spdlog::sinks::test_sink_mt; + + dup_filter_sink_st dup_sink{std::chrono::seconds{0}}; + auto test_sink = std::make_shared<test_sink_mt>(); + dup_sink.add_sink(test_sink); + + for (int i = 0; i < 10; i++) { + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"}); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + REQUIRE(test_sink->msg_counter() == 10); +} + +TEST_CASE("dup_filter_test3", "[dup_filter_sink]") { + using spdlog::sinks::dup_filter_sink_st; + using spdlog::sinks::test_sink_mt; + + dup_filter_sink_st dup_sink{std::chrono::seconds{1}}; + auto test_sink = std::make_shared<test_sink_mt>(); + dup_sink.add_sink(test_sink); + + for (int i = 0; i < 10; i++) { + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"}); + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message2"}); + } + + REQUIRE(test_sink->msg_counter() == 20); +} + +TEST_CASE("dup_filter_test4", "[dup_filter_sink]") { + using spdlog::sinks::dup_filter_sink_mt; + using spdlog::sinks::test_sink_mt; + + dup_filter_sink_mt dup_sink{std::chrono::milliseconds{10}}; + auto test_sink = std::make_shared<test_sink_mt>(); + dup_sink.add_sink(test_sink); + + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message"}); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message"}); + REQUIRE(test_sink->msg_counter() == 2); +} + +TEST_CASE("dup_filter_test5", "[dup_filter_sink]") { + using spdlog::sinks::dup_filter_sink_mt; + using spdlog::sinks::test_sink_mt; + + dup_filter_sink_mt dup_sink{std::chrono::seconds{5}}; + auto test_sink = std::make_shared<test_sink_mt>(); + test_sink->set_pattern("%v"); + dup_sink.add_sink(test_sink); + + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"}); + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"}); + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"}); + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message2"}); + + REQUIRE(test_sink->msg_counter() == + 3); // skip 2 messages but log the "skipped.." message before message2 + REQUIRE(test_sink->lines()[1] == "Skipped 2 duplicate messages.."); +} diff --git a/thirdparty/spdlog/tests/test_errors.cpp b/thirdparty/spdlog/tests/test_errors.cpp new file mode 100644 index 000000000..1c24cabc2 --- /dev/null +++ b/thirdparty/spdlog/tests/test_errors.cpp @@ -0,0 +1,112 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +#include <iostream> + +#define SIMPLE_LOG "test_logs/simple_log.txt" +#define SIMPLE_ASYNC_LOG "test_logs/simple_async_log.txt" + +class failing_sink : public spdlog::sinks::base_sink<std::mutex> { +protected: + void sink_it_(const spdlog::details::log_msg &) final { + throw std::runtime_error("some error happened during log"); + } + + void flush_() final { throw std::runtime_error("some error happened during flush"); } +}; +struct custom_ex {}; + +#if !defined(SPDLOG_USE_STD_FORMAT) // std format doesn't fully support runtime strings +TEST_CASE("default_error_handler", "[errors]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + + auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("test-error", filename, true); + logger->set_pattern("%v"); + logger->info(SPDLOG_FMT_RUNTIME("Test message {} {}"), 1); + logger->info("Test message {}", 2); + logger->flush(); + using spdlog::details::os::default_eol; + REQUIRE(file_contents(SIMPLE_LOG) == spdlog::fmt_lib::format("Test message 2{}", default_eol)); + REQUIRE(count_lines(SIMPLE_LOG) == 1); +} + +TEST_CASE("custom_error_handler", "[errors]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename, true); + logger->flush_on(spdlog::level::info); + logger->set_error_handler([=](const std::string &) { throw custom_ex(); }); + logger->info("Good message #1"); + + REQUIRE_THROWS_AS(logger->info(SPDLOG_FMT_RUNTIME("Bad format msg {} {}"), "xxx"), custom_ex); + logger->info("Good message #2"); + require_message_count(SIMPLE_LOG, 2); +} +#endif + +TEST_CASE("default_error_handler2", "[errors]") { + spdlog::drop_all(); + auto logger = spdlog::create<failing_sink>("failed_logger"); + logger->set_error_handler([=](const std::string &) { throw custom_ex(); }); + REQUIRE_THROWS_AS(logger->info("Some message"), custom_ex); +} + +TEST_CASE("flush_error_handler", "[errors]") { + spdlog::drop_all(); + auto logger = spdlog::create<failing_sink>("failed_logger"); + logger->set_error_handler([=](const std::string &) { throw custom_ex(); }); + REQUIRE_THROWS_AS(logger->flush(), custom_ex); +} + +#if !defined(SPDLOG_USE_STD_FORMAT) +TEST_CASE("async_error_handler", "[errors]") { + prepare_logdir(); + std::string err_msg("log failed with some msg"); + + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_ASYNC_LOG); + { + spdlog::init_thread_pool(128, 1); + auto logger = + spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("logger", filename, true); + logger->set_error_handler([=](const std::string &) { + std::ofstream ofs("test_logs/custom_err.txt"); + if (!ofs) { + throw std::runtime_error("Failed open test_logs/custom_err.txt"); + } + ofs << err_msg; + }); + logger->info("Good message #1"); + logger->info(SPDLOG_FMT_RUNTIME("Bad format msg {} {}"), "xxx"); + logger->info("Good message #2"); + spdlog::drop("logger"); // force logger to drain the queue and shutdown + } + spdlog::init_thread_pool(128, 1); + require_message_count(SIMPLE_ASYNC_LOG, 2); + REQUIRE(file_contents("test_logs/custom_err.txt") == err_msg); +} +#endif + +// Make sure async error handler is executed +TEST_CASE("async_error_handler2", "[errors]") { + prepare_logdir(); + std::string err_msg("This is async handler error message"); + { + spdlog::details::os::create_dir(SPDLOG_FILENAME_T("test_logs")); + spdlog::init_thread_pool(128, 1); + auto logger = spdlog::create_async<failing_sink>("failed_logger"); + logger->set_error_handler([=](const std::string &) { + std::ofstream ofs("test_logs/custom_err2.txt"); + if (!ofs) throw std::runtime_error("Failed open test_logs/custom_err2.txt"); + ofs << err_msg; + }); + logger->info("Hello failure"); + spdlog::drop("failed_logger"); // force logger to drain the queue and shutdown + } + + spdlog::init_thread_pool(128, 1); + REQUIRE(file_contents("test_logs/custom_err2.txt") == err_msg); +} diff --git a/thirdparty/spdlog/tests/test_eventlog.cpp b/thirdparty/spdlog/tests/test_eventlog.cpp new file mode 100644 index 000000000..702eabea1 --- /dev/null +++ b/thirdparty/spdlog/tests/test_eventlog.cpp @@ -0,0 +1,75 @@ +#if _WIN32 + + #include "includes.h" + #include "test_sink.h" + + #include "spdlog/sinks/win_eventlog_sink.h" + +static const LPCSTR TEST_SOURCE = "spdlog_test"; + +static void test_single_print(std::function<void(std::string const &)> do_log, + std::string const &expected_contents, + WORD expected_ev_type) { + using namespace std::chrono; + do_log(expected_contents); + const auto expected_time_generated = + duration_cast<seconds>(system_clock::now().time_since_epoch()).count(); + + struct handle_t { + HANDLE handle_; + + ~handle_t() { + if (handle_) { + REQUIRE(CloseEventLog(handle_)); + } + } + } event_log{::OpenEventLogA(nullptr, TEST_SOURCE)}; + + REQUIRE(event_log.handle_); + + DWORD read_bytes{}, size_needed{}; + auto ok = ::ReadEventLogA(event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, + 0, &read_bytes, 0, &read_bytes, &size_needed); + REQUIRE(!ok); + REQUIRE(::GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + std::vector<char> record_buffer(size_needed); + PEVENTLOGRECORD record = (PEVENTLOGRECORD)record_buffer.data(); + + ok = ::ReadEventLogA(event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, + record, size_needed, &read_bytes, &size_needed); + REQUIRE(ok); + + REQUIRE(record->NumStrings == 1); + REQUIRE(record->EventType == expected_ev_type); + REQUIRE((expected_time_generated - record->TimeGenerated) <= 3u); + + std::string message_in_log(((char *)record + record->StringOffset)); + REQUIRE(message_in_log == expected_contents + spdlog::details::os::default_eol); +} + +TEST_CASE("eventlog", "[eventlog]") { + using namespace spdlog; + + auto test_sink = std::make_shared<sinks::win_eventlog_sink_mt>(TEST_SOURCE); + + spdlog::logger test_logger("eventlog", test_sink); + test_logger.set_level(level::trace); + + test_sink->set_pattern("%v"); + + test_single_print([&test_logger](std::string const &msg) { test_logger.trace(msg); }, + "my trace message", EVENTLOG_SUCCESS); + test_single_print([&test_logger](std::string const &msg) { test_logger.debug(msg); }, + "my debug message", EVENTLOG_SUCCESS); + test_single_print([&test_logger](std::string const &msg) { test_logger.info(msg); }, + "my info message", EVENTLOG_INFORMATION_TYPE); + test_single_print([&test_logger](std::string const &msg) { test_logger.warn(msg); }, + "my warn message", EVENTLOG_WARNING_TYPE); + test_single_print([&test_logger](std::string const &msg) { test_logger.error(msg); }, + "my error message", EVENTLOG_ERROR_TYPE); + test_single_print([&test_logger](std::string const &msg) { test_logger.critical(msg); }, + "my critical message", EVENTLOG_ERROR_TYPE); +} + +#endif //_WIN32 diff --git a/thirdparty/spdlog/tests/test_file_helper.cpp b/thirdparty/spdlog/tests/test_file_helper.cpp new file mode 100644 index 000000000..56ee75e3e --- /dev/null +++ b/thirdparty/spdlog/tests/test_file_helper.cpp @@ -0,0 +1,169 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +#define TEST_FILENAME "test_logs/file_helper_test.txt" + +using spdlog::details::file_helper; + +static void write_with_helper(file_helper &helper, size_t howmany) { + spdlog::memory_buf_t formatted; + spdlog::fmt_lib::format_to(std::back_inserter(formatted), "{}", std::string(howmany, '1')); + helper.write(formatted); + helper.flush(); +} + +TEST_CASE("file_helper_filename", "[file_helper::filename()]") { + prepare_logdir(); + + file_helper helper; + spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + helper.open(target_filename); + REQUIRE(helper.filename() == target_filename); +} + +TEST_CASE("file_helper_size", "[file_helper::size()]") { + prepare_logdir(); + spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + size_t expected_size = 123; + { + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, expected_size); + REQUIRE(static_cast<size_t>(helper.size()) == expected_size); + } + REQUIRE(get_filesize(TEST_FILENAME) == expected_size); +} + +TEST_CASE("file_helper_reopen", "[file_helper::reopen()]") { + prepare_logdir(); + spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, 12); + REQUIRE(helper.size() == 12); + helper.reopen(true); + REQUIRE(helper.size() == 0); +} + +TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]") { + prepare_logdir(); + spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + size_t expected_size = 14; + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, expected_size); + REQUIRE(helper.size() == expected_size); + helper.reopen(false); + REQUIRE(helper.size() == expected_size); +} + +static void test_split_ext(const spdlog::filename_t::value_type *fname, + const spdlog::filename_t::value_type *expect_base, + const spdlog::filename_t::value_type *expect_ext) { + spdlog::filename_t filename(fname); + spdlog::filename_t expected_base(expect_base); + spdlog::filename_t expected_ext(expect_ext); + + spdlog::filename_t basename; + spdlog::filename_t ext; + std::tie(basename, ext) = file_helper::split_by_extension(filename); + REQUIRE(basename == expected_base); + REQUIRE(ext == expected_ext); +} + +TEST_CASE("file_helper_split_by_extension", "[file_helper::split_by_extension()]") { + test_split_ext(SPDLOG_FILENAME_T("mylog.txt"), SPDLOG_FILENAME_T("mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T(".mylog.txt"), SPDLOG_FILENAME_T(".mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T(".mylog"), SPDLOG_FILENAME_T(".mylog"), SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), + SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("/aaa/bb.d/mylog.txt"), SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog.txt"), + SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog"), SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog."), SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog."), + SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("aaa/bbb/ccc/.mylog.txt"), + SPDLOG_FILENAME_T("aaa/bbb/ccc/.mylog"), SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T("/aaa/bbb/ccc/mylog.txt"), + SPDLOG_FILENAME_T("/aaa/bbb/ccc/mylog"), SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T("/aaa/bbb/ccc/.mylog"), + SPDLOG_FILENAME_T("/aaa/bbb/ccc/.mylog"), SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("../mylog.txt"), SPDLOG_FILENAME_T("../mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T(".././mylog.txt"), SPDLOG_FILENAME_T(".././mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T(".././mylog.txt/xxx"), SPDLOG_FILENAME_T(".././mylog.txt/xxx"), + SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("/mylog.txt"), SPDLOG_FILENAME_T("/mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T("//mylog.txt"), SPDLOG_FILENAME_T("//mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T(""), SPDLOG_FILENAME_T(""), SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("..txt"), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T(".txt")); +} + +TEST_CASE("file_event_handlers", "[file_helper]") { + enum class flags { before_open, after_open, before_close, after_close }; + prepare_logdir(); + + spdlog::filename_t test_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + // define event handles that update vector of flags when called + std::vector<flags> events; + spdlog::file_event_handlers handlers; + handlers.before_open = [&](spdlog::filename_t filename) { + REQUIRE(filename == test_filename); + events.push_back(flags::before_open); + }; + handlers.after_open = [&](spdlog::filename_t filename, std::FILE *fstream) { + REQUIRE(filename == test_filename); + REQUIRE(fstream); + fputs("after_open\n", fstream); + events.push_back(flags::after_open); + }; + handlers.before_close = [&](spdlog::filename_t filename, std::FILE *fstream) { + REQUIRE(filename == test_filename); + REQUIRE(fstream); + fputs("before_close\n", fstream); + events.push_back(flags::before_close); + }; + handlers.after_close = [&](spdlog::filename_t filename) { + REQUIRE(filename == test_filename); + events.push_back(flags::after_close); + }; + { + spdlog::details::file_helper helper{handlers}; + REQUIRE(events.empty()); + + helper.open(test_filename); + REQUIRE(events == std::vector<flags>{flags::before_open, flags::after_open}); + + events.clear(); + helper.close(); + REQUIRE(events == std::vector<flags>{flags::before_close, flags::after_close}); + REQUIRE(file_contents(TEST_FILENAME) == "after_open\nbefore_close\n"); + + helper.reopen(true); + events.clear(); + } + // make sure that the file_helper destructor calls the close callbacks if needed + REQUIRE(events == std::vector<flags>{flags::before_close, flags::after_close}); + REQUIRE(file_contents(TEST_FILENAME) == "after_open\nbefore_close\n"); +} + +TEST_CASE("file_helper_open", "[file_helper]") { + prepare_logdir(); + spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + file_helper helper; + helper.open(target_filename); + helper.close(); + + target_filename += SPDLOG_FILENAME_T("/invalid"); + REQUIRE_THROWS_AS(helper.open(target_filename), spdlog::spdlog_ex); +} diff --git a/thirdparty/spdlog/tests/test_file_logging.cpp b/thirdparty/spdlog/tests/test_file_logging.cpp new file mode 100644 index 000000000..e3155effd --- /dev/null +++ b/thirdparty/spdlog/tests/test_file_logging.cpp @@ -0,0 +1,187 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +#define SIMPLE_LOG "test_logs/simple_log" +#define ROTATING_LOG "test_logs/rotating_log" + +TEST_CASE("simple_file_logger", "[simple_logger]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + + auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename); + logger->set_pattern("%v"); + + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); + + logger->flush(); + require_message_count(SIMPLE_LOG, 2); + using spdlog::details::os::default_eol; + REQUIRE(file_contents(SIMPLE_LOG) == + spdlog::fmt_lib::format("Test message 1{}Test message 2{}", default_eol, default_eol)); +} + +TEST_CASE("flush_on", "[flush_on]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + + auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::info); + logger->trace("Should not be flushed"); + REQUIRE(count_lines(SIMPLE_LOG) == 0); + + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); + + require_message_count(SIMPLE_LOG, 3); + using spdlog::details::os::default_eol; + REQUIRE(file_contents(SIMPLE_LOG) == + spdlog::fmt_lib::format("Should not be flushed{}Test message 1{}Test message 2{}", + default_eol, default_eol, default_eol)); +} + +TEST_CASE("simple_file_logger", "[truncate]") { + prepare_logdir(); + const spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + const bool truncate = true; + const auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, truncate); + const auto logger = std::make_shared<spdlog::logger>("simple_file_logger", sink); + + logger->info("Test message {}", 3.14); + logger->info("Test message {}", 2.71); + logger->flush(); + REQUIRE(count_lines(SIMPLE_LOG) == 2); + + sink->truncate(); + REQUIRE(count_lines(SIMPLE_LOG) == 0); + + logger->info("Test message {}", 6.28); + logger->flush(); + REQUIRE(count_lines(SIMPLE_LOG) == 1); +} + +TEST_CASE("rotating_file_logger1", "[rotating_logger]") { + prepare_logdir(); + size_t max_size = 1024 * 10; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 0); + + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + + logger->flush(); + require_message_count(ROTATING_LOG, 10); +} + +TEST_CASE("rotating_file_logger2", "[rotating_logger]") { + prepare_logdir(); + size_t max_size = 1024 * 10; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + + { + // make an initial logger to create the first output file + auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 2, true); + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + // drop causes the logger destructor to be called, which is required so the + // next logger can rename the first output file. + spdlog::drop(logger->name()); + } + auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 2, true); + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + logger->flush(); + require_message_count(ROTATING_LOG, 10); + + for (int i = 0; i < 1000; i++) { + logger->info("Test message {}", i); + } + + logger->flush(); + REQUIRE(get_filesize(ROTATING_LOG) <= max_size); + REQUIRE(get_filesize(ROTATING_LOG ".1") <= max_size); +} + +// test that passing max_size=0 throws +TEST_CASE("rotating_file_logger3", "[rotating_logger]") { + prepare_logdir(); + size_t max_size = 0; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + REQUIRE_THROWS_AS(spdlog::rotating_logger_mt("logger", basename, max_size, 0), + spdlog::spdlog_ex); +} + +// test on-demand rotation of logs +TEST_CASE("rotating_file_logger4", "[rotating_logger]") { + prepare_logdir(); + size_t max_size = 1024 * 10; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + auto sink = std::make_shared<spdlog::sinks::rotating_file_sink_st>(basename, max_size, 2); + auto logger = std::make_shared<spdlog::logger>("rotating_sink_logger", sink); + + logger->info("Test message - pre-rotation"); + logger->flush(); + + sink->rotate_now(); + + logger->info("Test message - post-rotation"); + logger->flush(); + + REQUIRE(get_filesize(ROTATING_LOG) > 0); + REQUIRE(get_filesize(ROTATING_LOG ".1") > 0); +} + +// test changing the max size of the rotating file sink +TEST_CASE("rotating_file_logger5", "[rotating_logger]") { + prepare_logdir(); + size_t max_size = 5 * 1024; + size_t max_files = 2; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + auto sink = + std::make_shared<spdlog::sinks::rotating_file_sink_st>(basename, max_size, max_files); + auto logger = std::make_shared<spdlog::logger>("rotating_sink_logger", sink); + logger->set_pattern("%v"); + + REQUIRE(sink->get_max_size() == max_size); + REQUIRE(sink->get_max_files() == max_files); + max_size = 7 * 1024; + max_files = 3; + + sink->set_max_size(max_size); + sink->set_max_files(max_files); + REQUIRE(sink->get_max_size() == max_size); + REQUIRE(sink->get_max_files() == max_files); + + const auto message = std::string(200, 'x'); + assert(message.size() < max_size); + const auto n_messages = max_files * max_size / message.size(); + for (size_t i = 0; i < n_messages; ++i) { + logger->info(message); + } + logger.reset(); // force flush and close the file + + // validate that the files were rotated correctly with the new max size and max files + for (size_t i = 0; i <= max_files; i++) { + // calc filenames + // e.g. rotating_log, rotating_log.0 rotating_log.1, rotating_log.2, etc. + std::ostringstream oss; + oss << ROTATING_LOG; + if (i > 0) { + oss << '.' << i; + } + const auto filename = oss.str(); + const auto filesize = get_filesize(filename); + REQUIRE(filesize <= max_size); + if (i > 0) { + REQUIRE(filesize >= max_size - message.size() - 2); + } + } +} diff --git a/thirdparty/spdlog/tests/test_fmt_helper.cpp b/thirdparty/spdlog/tests/test_fmt_helper.cpp new file mode 100644 index 000000000..31b930672 --- /dev/null +++ b/thirdparty/spdlog/tests/test_fmt_helper.cpp @@ -0,0 +1,82 @@ + +#include "includes.h" + +using spdlog::memory_buf_t; +using spdlog::details::to_string_view; + +void test_pad2(int n, const char *expected) { + memory_buf_t buf; + spdlog::details::fmt_helper::pad2(n, buf); + + REQUIRE(to_string_view(buf) == expected); +} + +void test_pad3(uint32_t n, const char *expected) { + memory_buf_t buf; + spdlog::details::fmt_helper::pad3(n, buf); + + REQUIRE(to_string_view(buf) == expected); +} + +void test_pad6(std::size_t n, const char *expected) { + memory_buf_t buf; + spdlog::details::fmt_helper::pad6(n, buf); + + REQUIRE(to_string_view(buf) == expected); +} + +void test_pad9(std::size_t n, const char *expected) { + memory_buf_t buf; + spdlog::details::fmt_helper::pad9(n, buf); + + REQUIRE(to_string_view(buf) == expected); +} + +TEST_CASE("pad2", "[fmt_helper]") { + test_pad2(0, "00"); + test_pad2(3, "03"); + test_pad2(10, "10"); + test_pad2(23, "23"); + test_pad2(99, "99"); + test_pad2(100, "100"); + test_pad2(123, "123"); + test_pad2(1234, "1234"); + test_pad2(-5, "-5"); +} + +TEST_CASE("pad3", "[fmt_helper]") { + test_pad3(0, "000"); + test_pad3(3, "003"); + test_pad3(10, "010"); + test_pad3(23, "023"); + test_pad3(99, "099"); + test_pad3(100, "100"); + test_pad3(123, "123"); + test_pad3(999, "999"); + test_pad3(1000, "1000"); + test_pad3(1234, "1234"); +} + +TEST_CASE("pad6", "[fmt_helper]") { + test_pad6(0, "000000"); + test_pad6(3, "000003"); + test_pad6(23, "000023"); + test_pad6(123, "000123"); + test_pad6(1234, "001234"); + test_pad6(12345, "012345"); + test_pad6(123456, "123456"); +} + +TEST_CASE("pad9", "[fmt_helper]") { + test_pad9(0, "000000000"); + test_pad9(3, "000000003"); + test_pad9(23, "000000023"); + test_pad9(123, "000000123"); + test_pad9(1234, "000001234"); + test_pad9(12345, "000012345"); + test_pad9(123456, "000123456"); + test_pad9(1234567, "001234567"); + test_pad9(12345678, "012345678"); + test_pad9(123456789, "123456789"); + test_pad9(1234567891, "1234567891"); +} diff --git a/thirdparty/spdlog/tests/test_macros.cpp b/thirdparty/spdlog/tests/test_macros.cpp new file mode 100644 index 000000000..132706f13 --- /dev/null +++ b/thirdparty/spdlog/tests/test_macros.cpp @@ -0,0 +1,53 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ + +#include "includes.h" + +#if SPDLOG_ACTIVE_LEVEL != SPDLOG_LEVEL_DEBUG + #error "Invalid SPDLOG_ACTIVE_LEVEL in test. Should be SPDLOG_LEVEL_DEBUG" +#endif + +#define TEST_FILENAME "test_logs/simple_log" + +TEST_CASE("debug and trace w/o format string", "[macros]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME); + + auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + + SPDLOG_LOGGER_TRACE(logger, "Test message 1"); + SPDLOG_LOGGER_DEBUG(logger, "Test message 2"); + logger->flush(); + + using spdlog::details::os::default_eol; + REQUIRE(ends_with(file_contents(TEST_FILENAME), + spdlog::fmt_lib::format("Test message 2{}", default_eol))); + REQUIRE(count_lines(TEST_FILENAME) == 1); + + auto orig_default_logger = spdlog::default_logger(); + spdlog::set_default_logger(logger); + + SPDLOG_TRACE("Test message 3"); + SPDLOG_DEBUG("Test message {}", 4); + logger->flush(); + + require_message_count(TEST_FILENAME, 2); + REQUIRE(ends_with(file_contents(TEST_FILENAME), + spdlog::fmt_lib::format("Test message 4{}", default_eol))); + spdlog::set_default_logger(std::move(orig_default_logger)); +} + +TEST_CASE("disable param evaluation", "[macros]") { + SPDLOG_TRACE("Test message {}", throw std::runtime_error("Should not be evaluated")); +} + +TEST_CASE("pass logger pointer", "[macros]") { + auto logger = spdlog::create<spdlog::sinks::null_sink_mt>("refmacro"); + auto &ref = *logger; + SPDLOG_LOGGER_TRACE(&ref, "Test message 1"); + SPDLOG_LOGGER_DEBUG(&ref, "Test message 2"); +} diff --git a/thirdparty/spdlog/tests/test_misc.cpp b/thirdparty/spdlog/tests/test_misc.cpp new file mode 100644 index 000000000..deb18e745 --- /dev/null +++ b/thirdparty/spdlog/tests/test_misc.cpp @@ -0,0 +1,224 @@ +#ifdef _WIN32 // to prevent fopen warning on windows + #define _CRT_SECURE_NO_WARNINGS +#endif + +#include "includes.h" +#include "test_sink.h" + +template <class T> +std::string log_info(const T& what, spdlog::level::level_enum logger_level = spdlog::level::info) { + std::ostringstream oss; + auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); + + spdlog::logger oss_logger("oss", oss_sink); + oss_logger.set_level(logger_level); + oss_logger.set_pattern("%v"); + oss_logger.info(what); + + return oss.str().substr(0, oss.str().length() - strlen(spdlog::details::os::default_eol)); +} + +TEST_CASE("basic_logging ", "[basic_logging]") { + // const char + REQUIRE(log_info("Hello") == "Hello"); + REQUIRE(log_info("").empty()); + + // std::string + REQUIRE(log_info(std::string("Hello")) == "Hello"); + REQUIRE(log_info(std::string()).empty()); + + // Numbers + REQUIRE(log_info(5) == "5"); + REQUIRE(log_info(5.6) == "5.6"); + + // User defined class + // REQUIRE(log_info(some_logged_class("some_val")) == "some_val"); +} + +TEST_CASE("log_levels", "[log_levels]") { + REQUIRE(log_info("Hello", spdlog::level::err).empty()); + REQUIRE(log_info("Hello", spdlog::level::critical).empty()); + REQUIRE(log_info("Hello", spdlog::level::info) == "Hello"); + REQUIRE(log_info("Hello", spdlog::level::debug) == "Hello"); + REQUIRE(log_info("Hello", spdlog::level::trace) == "Hello"); +} + +TEST_CASE("level_to_string_view", "[convert_to_string_view]") { + REQUIRE(spdlog::level::to_string_view(spdlog::level::trace) == "trace"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::debug) == "debug"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::info) == "info"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::warn) == "warning"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::err) == "error"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::critical) == "critical"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::off) == "off"); +} + +TEST_CASE("to_short_c_str", "[convert_to_short_c_str]") { + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::trace)) == "T"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::debug)) == "D"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::info)) == "I"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::warn)) == "W"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::err)) == "E"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::critical)) == "C"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::off)) == "O"); +} + +TEST_CASE("to_level_enum", "[convert_to_level_enum]") { + REQUIRE(spdlog::level::from_str("trace") == spdlog::level::trace); + REQUIRE(spdlog::level::from_str("debug") == spdlog::level::debug); + REQUIRE(spdlog::level::from_str("info") == spdlog::level::info); + REQUIRE(spdlog::level::from_str("warning") == spdlog::level::warn); + REQUIRE(spdlog::level::from_str("warn") == spdlog::level::warn); + REQUIRE(spdlog::level::from_str("error") == spdlog::level::err); + REQUIRE(spdlog::level::from_str("critical") == spdlog::level::critical); + REQUIRE(spdlog::level::from_str("off") == spdlog::level::off); + REQUIRE(spdlog::level::from_str("null") == spdlog::level::off); +} + +TEST_CASE("periodic flush", "[periodic_flush]") { + using spdlog::sinks::test_sink_mt; + auto logger = spdlog::create<test_sink_mt>("periodic_flush"); + auto test_sink = std::static_pointer_cast<test_sink_mt>(logger->sinks()[0]); + + spdlog::flush_every(std::chrono::seconds(1)); + std::this_thread::sleep_for(std::chrono::milliseconds(1250)); + REQUIRE(test_sink->flush_counter() == 1); + spdlog::flush_every(std::chrono::seconds(0)); + spdlog::drop_all(); +} + +TEST_CASE("clone-logger", "[clone]") { + using spdlog::sinks::test_sink_mt; + auto test_sink = std::make_shared<test_sink_mt>(); + auto logger = std::make_shared<spdlog::logger>("orig", test_sink); + logger->set_pattern("%v"); + auto cloned = logger->clone("clone"); + + REQUIRE(cloned->name() == "clone"); + REQUIRE(logger->sinks() == cloned->sinks()); + REQUIRE(logger->level() == cloned->level()); + REQUIRE(logger->flush_level() == cloned->flush_level()); + logger->info("Some message 1"); + cloned->info("Some message 2"); + + REQUIRE(test_sink->lines().size() == 2); + REQUIRE(test_sink->lines()[0] == "Some message 1"); + REQUIRE(test_sink->lines()[1] == "Some message 2"); + + spdlog::drop_all(); +} + +TEST_CASE("clone async", "[clone]") { + using spdlog::sinks::test_sink_mt; + spdlog::init_thread_pool(4, 1); + auto test_sink = std::make_shared<test_sink_mt>(); + auto logger = std::make_shared<spdlog::async_logger>("orig", test_sink, spdlog::thread_pool()); + logger->set_pattern("%v"); + auto cloned = logger->clone("clone"); + + REQUIRE(cloned->name() == "clone"); + REQUIRE(logger->sinks() == cloned->sinks()); + REQUIRE(logger->level() == cloned->level()); + REQUIRE(logger->flush_level() == cloned->flush_level()); + + logger->info("Some message 1"); + cloned->info("Some message 2"); + + spdlog::details::os::sleep_for_millis(100); + + REQUIRE(test_sink->lines().size() == 2); + REQUIRE(test_sink->lines()[0] == "Some message 1"); + REQUIRE(test_sink->lines()[1] == "Some message 2"); + + spdlog::drop_all(); +} + +TEST_CASE("default logger API", "[default logger]") { + std::ostringstream oss; + auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); + + spdlog::set_default_logger(std::make_shared<spdlog::logger>("oss", oss_sink)); + spdlog::set_pattern("*** %v"); + + spdlog::default_logger()->set_level(spdlog::level::trace); + spdlog::trace("hello trace"); + REQUIRE(oss.str() == "*** hello trace" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::debug("hello debug"); + REQUIRE(oss.str() == "*** hello debug" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::info("Hello"); + REQUIRE(oss.str() == "*** Hello" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::warn("Hello again {}", 2); + REQUIRE(oss.str() == "*** Hello again 2" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::error(123); + REQUIRE(oss.str() == "*** 123" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::critical(std::string("some string")); + REQUIRE(oss.str() == "*** some string" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::set_level(spdlog::level::info); + spdlog::debug("should not be logged"); + REQUIRE(oss.str().empty()); + spdlog::drop_all(); + spdlog::set_pattern("%v"); +} + +#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) +TEST_CASE("utf8 to utf16 conversion using windows api", "[windows utf]") { + spdlog::wmemory_buf_t buffer; + + spdlog::details::os::utf8_to_wstrbuf("", buffer); + REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"")); + + spdlog::details::os::utf8_to_wstrbuf("abc", buffer); + REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"abc")); + + spdlog::details::os::utf8_to_wstrbuf("\xc3\x28", buffer); // Invalid UTF-8 sequence. + REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\xfffd(")); + + spdlog::details::os::utf8_to_wstrbuf("\xe3\x81\xad\xe3\x81\x93", + buffer); // "Neko" in hiragana. + REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\x306d\x3053")); +} +#endif + +struct auto_closer { + FILE* fp = nullptr; + explicit auto_closer(FILE* f) + : fp(f) {} + auto_closer(const auto_closer&) = delete; + auto_closer& operator=(const auto_closer&) = delete; + ~auto_closer() { + if (fp != nullptr) (void)std::fclose(fp); + } +}; + +TEST_CASE("os::fwrite_bytes", "[os]") { + using spdlog::details::os::create_dir; + using spdlog::details::os::fwrite_bytes; + const char* filename = "log_tests/test_fwrite_bytes.txt"; + const char* msg = "hello"; + prepare_logdir(); + REQUIRE(create_dir(SPDLOG_FILENAME_T("log_tests")) == true); + { + auto_closer closer(std::fopen(filename, "wb")); + REQUIRE(closer.fp != nullptr); + REQUIRE(fwrite_bytes(msg, std::strlen(msg), closer.fp) == true); + REQUIRE(fwrite_bytes(msg, 0, closer.fp) == true); + std::fflush(closer.fp); + REQUIRE(spdlog::details::os::filesize(closer.fp) == 5); + } + // fwrite_bytes should return false on write failure + auto_closer closer(std::fopen(filename, "r")); + REQUIRE(closer.fp != nullptr); + REQUIRE_FALSE(fwrite_bytes("Hello", 5, closer.fp)); +} diff --git a/thirdparty/spdlog/tests/test_mpmc_q.cpp b/thirdparty/spdlog/tests/test_mpmc_q.cpp new file mode 100644 index 000000000..bc7a37d9c --- /dev/null +++ b/thirdparty/spdlog/tests/test_mpmc_q.cpp @@ -0,0 +1,114 @@ +#include "includes.h" + +using std::chrono::milliseconds; +using test_clock = std::chrono::high_resolution_clock; + +static milliseconds millis_from(const test_clock::time_point &tp0) { + return std::chrono::duration_cast<milliseconds>(test_clock::now() - tp0); +} +TEST_CASE("dequeue-empty-nowait", "[mpmc_blocking_q]") { + size_t q_size = 100; + milliseconds tolerance_wait(20); + spdlog::details::mpmc_blocking_queue<int> q(q_size); + int popped_item = 0; + + auto start = test_clock::now(); + auto rv = q.dequeue_for(popped_item, milliseconds::zero()); + auto delta_ms = millis_from(start); + + REQUIRE(rv == false); + INFO("Delta " << delta_ms.count() << " millis"); + REQUIRE(delta_ms <= tolerance_wait); +} + +TEST_CASE("dequeue-empty-wait", "[mpmc_blocking_q]") { + size_t q_size = 100; + milliseconds wait_ms(250); + milliseconds tolerance_wait(250); + + spdlog::details::mpmc_blocking_queue<int> q(q_size); + int popped_item = 0; + auto start = test_clock::now(); + auto rv = q.dequeue_for(popped_item, wait_ms); + auto delta_ms = millis_from(start); + + REQUIRE(rv == false); + + INFO("Delta " << delta_ms.count() << " millis"); + REQUIRE(delta_ms >= wait_ms - tolerance_wait); + REQUIRE(delta_ms <= wait_ms + tolerance_wait); +} + +TEST_CASE("dequeue-full-nowait", "[mpmc_blocking_q]") { + spdlog::details::mpmc_blocking_queue<int> q(1); + q.enqueue(42); + + int item = 0; + q.dequeue_for(item, milliseconds::zero()); + REQUIRE(item == 42); +} + +TEST_CASE("dequeue-full-wait", "[mpmc_blocking_q]") { + spdlog::details::mpmc_blocking_queue<int> q(1); + q.enqueue(42); + + int item = 0; + q.dequeue(item); + REQUIRE(item == 42); +} + +TEST_CASE("enqueue_nowait", "[mpmc_blocking_q]") { + size_t q_size = 1; + spdlog::details::mpmc_blocking_queue<int> q(q_size); + milliseconds tolerance_wait(10); + + q.enqueue(1); + REQUIRE(q.overrun_counter() == 0); + + auto start = test_clock::now(); + q.enqueue_nowait(2); + auto delta_ms = millis_from(start); + + INFO("Delta " << delta_ms.count() << " millis"); + REQUIRE(delta_ms <= tolerance_wait); + REQUIRE(q.overrun_counter() == 1); +} + +TEST_CASE("bad_queue", "[mpmc_blocking_q]") { + size_t q_size = 0; + spdlog::details::mpmc_blocking_queue<int> q(q_size); + q.enqueue_nowait(1); + REQUIRE(q.overrun_counter() == 1); + int i = 0; + REQUIRE(q.dequeue_for(i, milliseconds(0)) == false); +} + +TEST_CASE("empty_queue", "[mpmc_blocking_q]") { + size_t q_size = 10; + spdlog::details::mpmc_blocking_queue<int> q(q_size); + int i = 0; + REQUIRE(q.dequeue_for(i, milliseconds(10)) == false); +} + +TEST_CASE("full_queue", "[mpmc_blocking_q]") { + size_t q_size = 100; + spdlog::details::mpmc_blocking_queue<int> q(q_size); + for (int i = 0; i < static_cast<int>(q_size); i++) { + q.enqueue(i + 0); // i+0 to force rvalue and avoid tidy warnings on the same time if we + // std::move(i) instead + } + + q.enqueue_nowait(123456); + REQUIRE(q.overrun_counter() == 1); + + for (int i = 1; i < static_cast<int>(q_size); i++) { + int item = -1; + q.dequeue(item); + REQUIRE(item == i); + } + + // last item pushed has overridden the oldest. + int item = -1; + q.dequeue(item); + REQUIRE(item == 123456); +} diff --git a/thirdparty/spdlog/tests/test_pattern_formatter.cpp b/thirdparty/spdlog/tests/test_pattern_formatter.cpp new file mode 100644 index 000000000..17a1bbcb5 --- /dev/null +++ b/thirdparty/spdlog/tests/test_pattern_formatter.cpp @@ -0,0 +1,660 @@ +#include "includes.h" +#include "test_sink.h" + +#include <chrono> + +using spdlog::memory_buf_t; +using spdlog::details::to_string_view; + +// log to str and return it +template <typename... Args> +static std::string log_to_str(const std::string &msg, const Args &...args) { + std::ostringstream oss; + auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); + spdlog::logger oss_logger("pattern_tester", oss_sink); + oss_logger.set_level(spdlog::level::info); + + oss_logger.set_formatter( + std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter(args...))); + + oss_logger.info(msg); + return oss.str(); +} + +// log to str and return it with time +template <typename... Args> +static std::string log_to_str_with_time(spdlog::log_clock::time_point log_time, + const std::string &msg, + const Args &...args) { + std::ostringstream oss; + auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); + spdlog::logger oss_logger("pattern_tester", oss_sink); + oss_logger.set_level(spdlog::level::info); + + oss_logger.set_formatter( + std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter(args...))); + + oss_logger.log(log_time, {}, spdlog::level::info, msg); + return oss.str(); +} + +TEST_CASE("custom eol", "[pattern_formatter]") { + std::string msg = "Hello custom eol test"; + std::string eol = ";)"; + REQUIRE(log_to_str(msg, "%v", spdlog::pattern_time_type::local, ";)") == msg + eol); +} + +TEST_CASE("empty format", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "", spdlog::pattern_time_type::local, "").empty()); +} + +TEST_CASE("empty format2", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "", spdlog::pattern_time_type::local, "\n") == "\n"); +} + +TEST_CASE("level", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%l] %v", spdlog::pattern_time_type::local, "\n") == + "[info] Some message\n"); +} + +TEST_CASE("short level", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%L] %v", spdlog::pattern_time_type::local, "\n") == + "[I] Some message\n"); +} + +TEST_CASE("name", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester] Some message\n"); +} + +TEST_CASE("date MM/DD/YY ", "[pattern_formatter]") { + auto now_tm = spdlog::details::os::localtime(); + std::stringstream oss; + oss << std::setfill('0') << std::setw(2) << now_tm.tm_mon + 1 << "/" << std::setw(2) + << now_tm.tm_mday << "/" << std::setw(2) << (now_tm.tm_year + 1900) % 1000 + << " Some message\n"; + REQUIRE(log_to_str("Some message", "%D %v", spdlog::pattern_time_type::local, "\n") == + oss.str()); +} + +TEST_CASE("GMT offset ", "[pattern_formatter]") { + using namespace std::chrono_literals; + const auto now = std::chrono::system_clock::now(); + const auto yesterday = now - 24h; + + REQUIRE(log_to_str_with_time(yesterday, "Some message", "%z", spdlog::pattern_time_type::utc, + "\n") == "+00:00\n"); +} + +TEST_CASE("color range test1", "[pattern_formatter]") { + auto formatter = std::make_shared<spdlog::pattern_formatter>( + "%^%v%$", spdlog::pattern_time_type::local, "\n"); + + memory_buf_t buf; + spdlog::fmt_lib::format_to(std::back_inserter(buf), "Hello"); + memory_buf_t formatted; + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, + spdlog::string_view_t(buf.data(), buf.size())); + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 5); + REQUIRE(log_to_str("hello", "%^%v%$", spdlog::pattern_time_type::local, "\n") == "hello\n"); +} + +TEST_CASE("color range test2", "[pattern_formatter]") { + auto formatter = + std::make_shared<spdlog::pattern_formatter>("%^%$", spdlog::pattern_time_type::local, "\n"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, ""); + memory_buf_t formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 0); + REQUIRE(log_to_str("", "%^%$", spdlog::pattern_time_type::local, "\n") == "\n"); +} + +TEST_CASE("color range test3", "[pattern_formatter]") { + auto formatter = std::make_shared<spdlog::pattern_formatter>("%^***%$"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored"); + memory_buf_t formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 3); +} + +TEST_CASE("color range test4", "[pattern_formatter]") { + auto formatter = std::make_shared<spdlog::pattern_formatter>( + "XX%^YYY%$", spdlog::pattern_time_type::local, "\n"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored"); + + memory_buf_t formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 2); + REQUIRE(msg.color_range_end == 5); + REQUIRE(log_to_str("ignored", "XX%^YYY%$", spdlog::pattern_time_type::local, "\n") == + "XXYYY\n"); +} + +TEST_CASE("color range test5", "[pattern_formatter]") { + auto formatter = std::make_shared<spdlog::pattern_formatter>("**%^"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored"); + memory_buf_t formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 2); + REQUIRE(msg.color_range_end == 0); +} + +TEST_CASE("color range test6", "[pattern_formatter]") { + auto formatter = std::make_shared<spdlog::pattern_formatter>("**%$"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored"); + memory_buf_t formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 2); +} + +// +// Test padding +// + +TEST_CASE("level_left_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%8l] %v", spdlog::pattern_time_type::local, "\n") == + "[ info] Some message\n"); + REQUIRE(log_to_str("Some message", "[%8!l] %v", spdlog::pattern_time_type::local, "\n") == + "[ info] Some message\n"); +} + +TEST_CASE("level_right_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%-8l] %v", spdlog::pattern_time_type::local, "\n") == + "[info ] Some message\n"); + REQUIRE(log_to_str("Some message", "[%-8!l] %v", spdlog::pattern_time_type::local, "\n") == + "[info ] Some message\n"); +} + +TEST_CASE("level_center_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%=8l] %v", spdlog::pattern_time_type::local, "\n") == + "[ info ] Some message\n"); + REQUIRE(log_to_str("Some message", "[%=8!l] %v", spdlog::pattern_time_type::local, "\n") == + "[ info ] Some message\n"); +} + +TEST_CASE("short level_left_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%3L] %v", spdlog::pattern_time_type::local, "\n") == + "[ I] Some message\n"); + REQUIRE(log_to_str("Some message", "[%3!L] %v", spdlog::pattern_time_type::local, "\n") == + "[ I] Some message\n"); +} + +TEST_CASE("short level_right_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%-3L] %v", spdlog::pattern_time_type::local, "\n") == + "[I ] Some message\n"); + REQUIRE(log_to_str("Some message", "[%-3!L] %v", spdlog::pattern_time_type::local, "\n") == + "[I ] Some message\n"); +} + +TEST_CASE("short level_center_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%=3L] %v", spdlog::pattern_time_type::local, "\n") == + "[ I ] Some message\n"); + REQUIRE(log_to_str("Some message", "[%=3!L] %v", spdlog::pattern_time_type::local, "\n") == + "[ I ] Some message\n"); +} + +TEST_CASE("left_padded_short", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%3n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester] Some message\n"); + REQUIRE(log_to_str("Some message", "[%3!n] %v", spdlog::pattern_time_type::local, "\n") == + "[pat] Some message\n"); +} + +TEST_CASE("right_padded_short", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%-3n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester] Some message\n"); + REQUIRE(log_to_str("Some message", "[%-3!n] %v", spdlog::pattern_time_type::local, "\n") == + "[pat] Some message\n"); +} + +TEST_CASE("center_padded_short", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%=3n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester] Some message\n"); + REQUIRE(log_to_str("Some message", "[%=3!n] %v", spdlog::pattern_time_type::local, "\n") == + "[pat] Some message\n"); +} + +TEST_CASE("left_padded_huge", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%-300n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester ] Some message\n"); + + REQUIRE(log_to_str("Some message", "[%-300!n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester ] Some message\n"); +} + +TEST_CASE("left_padded_max", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%-64n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester ] Some message\n"); + + REQUIRE(log_to_str("Some message", "[%-64!n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester ] Some message\n"); +} + +// Test padding + truncate flag + +TEST_CASE("paddinng_truncate", "[pattern_formatter]") { + REQUIRE(log_to_str("123456", "%6!v", spdlog::pattern_time_type::local, "\n") == "123456\n"); + REQUIRE(log_to_str("123456", "%5!v", spdlog::pattern_time_type::local, "\n") == "12345\n"); + REQUIRE(log_to_str("123456", "%7!v", spdlog::pattern_time_type::local, "\n") == " 123456\n"); + + REQUIRE(log_to_str("123456", "%-6!v", spdlog::pattern_time_type::local, "\n") == "123456\n"); + REQUIRE(log_to_str("123456", "%-5!v", spdlog::pattern_time_type::local, "\n") == "12345\n"); + REQUIRE(log_to_str("123456", "%-7!v", spdlog::pattern_time_type::local, "\n") == "123456 \n"); + + REQUIRE(log_to_str("123456", "%=6!v", spdlog::pattern_time_type::local, "\n") == "123456\n"); + REQUIRE(log_to_str("123456", "%=5!v", spdlog::pattern_time_type::local, "\n") == "12345\n"); + REQUIRE(log_to_str("123456", "%=7!v", spdlog::pattern_time_type::local, "\n") == "123456 \n"); + + REQUIRE(log_to_str("123456", "%0!v", spdlog::pattern_time_type::local, "\n") == "\n"); +} + +TEST_CASE("padding_truncate_funcname", "[pattern_formatter]") { + spdlog::sinks::test_sink_st test_sink; + + const char *pattern = "%v [%5!!]"; + auto formatter = std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter(pattern)); + test_sink.set_formatter(std::move(formatter)); + + spdlog::details::log_msg msg1{spdlog::source_loc{"ignored", 1, "func"}, "test_logger", + spdlog::level::info, "message"}; + test_sink.log(msg1); + REQUIRE(test_sink.lines()[0] == "message [ func]"); + + spdlog::details::log_msg msg2{spdlog::source_loc{"ignored", 1, "function"}, "test_logger", + spdlog::level::info, "message"}; + test_sink.log(msg2); + REQUIRE(test_sink.lines()[1] == "message [funct]"); +} + +TEST_CASE("padding_funcname", "[pattern_formatter]") { + spdlog::sinks::test_sink_st test_sink; + + const char *pattern = "%v [%10!]"; + auto formatter = std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter(pattern)); + test_sink.set_formatter(std::move(formatter)); + + spdlog::details::log_msg msg1{spdlog::source_loc{"ignored", 1, "func"}, "test_logger", + spdlog::level::info, "message"}; + test_sink.log(msg1); + REQUIRE(test_sink.lines()[0] == "message [ func]"); + + spdlog::details::log_msg msg2{spdlog::source_loc{"ignored", 1, "func567890123"}, "test_logger", + spdlog::level::info, "message"}; + test_sink.log(msg2); + REQUIRE(test_sink.lines()[1] == "message [func567890123]"); +} + +TEST_CASE("clone-default-formatter", "[pattern_formatter]") { + auto formatter_1 = std::make_shared<spdlog::pattern_formatter>(); + auto formatter_2 = formatter_1->clone(); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message"); + + memory_buf_t formatted_1; + memory_buf_t formatted_2; + formatter_1->format(msg, formatted_1); + formatter_2->format(msg, formatted_2); + + REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2)); +} + +TEST_CASE("clone-default-formatter2", "[pattern_formatter]") { + auto formatter_1 = std::make_shared<spdlog::pattern_formatter>("%+"); + auto formatter_2 = formatter_1->clone(); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message"); + + memory_buf_t formatted_1; + memory_buf_t formatted_2; + formatter_1->format(msg, formatted_1); + formatter_2->format(msg, formatted_2); + + REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2)); +} + +TEST_CASE("clone-formatter", "[pattern_formatter]") { + auto formatter_1 = std::make_shared<spdlog::pattern_formatter>("%D %X [%] [%n] %v"); + auto formatter_2 = formatter_1->clone(); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message"); + + memory_buf_t formatted_1; + memory_buf_t formatted_2; + formatter_1->format(msg, formatted_1); + formatter_2->format(msg, formatted_2); + + REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2)); +} + +TEST_CASE("clone-formatter-2", "[pattern_formatter]") { + using spdlog::pattern_time_type; + auto formatter_1 = std::make_shared<spdlog::pattern_formatter>( + "%D %X [%] [%n] %v", pattern_time_type::utc, "xxxxxx\n"); + auto formatter_2 = formatter_1->clone(); + std::string logger_name = "test2"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message"); + + memory_buf_t formatted_1; + memory_buf_t formatted_2; + formatter_1->format(msg, formatted_1); + formatter_2->format(msg, formatted_2); + + REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2)); +} + +class custom_test_flag : public spdlog::custom_flag_formatter { +public: + explicit custom_test_flag(std::string txt) + : some_txt{std::move(txt)} {} + + void format(const spdlog::details::log_msg &, + const std::tm &tm, + spdlog::memory_buf_t &dest) override { + if (some_txt == "throw_me") { + throw spdlog::spdlog_ex("custom_flag_exception_test"); + } else if (some_txt == "time") { + auto formatted = spdlog::fmt_lib::format("{:d}:{:02d}{:s}", tm.tm_hour % 12, tm.tm_min, + tm.tm_hour / 12 ? "PM" : "AM"); + dest.append(formatted.data(), formatted.data() + formatted.size()); + return; + } + some_txt = std::string(padinfo_.width_, ' ') + some_txt; + dest.append(some_txt.data(), some_txt.data() + some_txt.size()); + } + spdlog::details::padding_info get_padding_info() { return padinfo_; } + + std::string some_txt; + + std::unique_ptr<custom_flag_formatter> clone() const override { + return spdlog::details::make_unique<custom_test_flag>(some_txt); + } +}; +// test clone with custom flag formatters +TEST_CASE("clone-custom_formatter", "[pattern_formatter]") { + auto formatter_1 = std::make_shared<spdlog::pattern_formatter>(); + formatter_1->add_flag<custom_test_flag>('t', "custom_output").set_pattern("[%n] [%t] %v"); + auto formatter_2 = formatter_1->clone(); + std::string logger_name = "logger-name"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message"); + + memory_buf_t formatted_1; + memory_buf_t formatted_2; + formatter_1->format(msg, formatted_1); + formatter_2->format(msg, formatted_2); + + auto expected = spdlog::fmt_lib::format("[logger-name] [custom_output] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted_1) == expected); + REQUIRE(to_string_view(formatted_2) == expected); +} + +// +// Test source location formatting +// + +#ifdef _WIN32 +static const char *const test_path = "\\a\\b\\c/myfile.cpp"; +#else +static const char *const test_path = "/a/b//myfile.cpp"; +#endif + +TEST_CASE("short filename formatter-1", "[pattern_formatter]") { + spdlog::pattern_formatter formatter("%s", spdlog::pattern_time_type::local, ""); + memory_buf_t formatted; + std::string logger_name = "logger-name"; + spdlog::source_loc source_loc{test_path, 123, "some_func()"}; + spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); + formatter.format(msg, formatted); + + REQUIRE(to_string_view(formatted) == "myfile.cpp"); +} + +TEST_CASE("short filename formatter-2", "[pattern_formatter]") { + spdlog::pattern_formatter formatter("%s:%#", spdlog::pattern_time_type::local, ""); + memory_buf_t formatted; + std::string logger_name = "logger-name"; + spdlog::source_loc source_loc{"myfile.cpp", 123, "some_func()"}; + spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); + formatter.format(msg, formatted); + + REQUIRE(to_string_view(formatted) == "myfile.cpp:123"); +} + +TEST_CASE("short filename formatter-3", "[pattern_formatter]") { + spdlog::pattern_formatter formatter("%s %v", spdlog::pattern_time_type::local, ""); + memory_buf_t formatted; + std::string logger_name = "logger-name"; + spdlog::source_loc source_loc{"", 123, "some_func()"}; + spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); + formatter.format(msg, formatted); + + REQUIRE(to_string_view(formatted) == " Hello"); +} + +TEST_CASE("full filename formatter", "[pattern_formatter]") { + spdlog::pattern_formatter formatter("%g", spdlog::pattern_time_type::local, ""); + memory_buf_t formatted; + std::string logger_name = "logger-name"; + spdlog::source_loc source_loc{test_path, 123, "some_func()"}; + spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); + formatter.format(msg, formatted); + + REQUIRE(to_string_view(formatted) == test_path); +} + +TEST_CASE("custom flags", "[pattern_formatter]") { + auto formatter = std::make_shared<spdlog::pattern_formatter>(); + formatter->add_flag<custom_test_flag>('t', "custom1") + .add_flag<custom_test_flag>('u', "custom2") + .set_pattern("[%n] [%t] [%u] %v"); + + memory_buf_t formatted; + + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + auto expected = spdlog::fmt_lib::format("[logger-name] [custom1] [custom2] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted) == expected); +} + +TEST_CASE("custom flags-padding", "[pattern_formatter]") { + auto formatter = std::make_shared<spdlog::pattern_formatter>(); + formatter->add_flag<custom_test_flag>('t', "custom1") + .add_flag<custom_test_flag>('u', "custom2") + .set_pattern("[%n] [%t] [%5u] %v"); + + memory_buf_t formatted; + + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + auto expected = spdlog::fmt_lib::format("[logger-name] [custom1] [ custom2] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted) == expected); +} + +TEST_CASE("custom flags-exception", "[pattern_formatter]") { + auto formatter = std::make_shared<spdlog::pattern_formatter>(); + formatter->add_flag<custom_test_flag>('t', "throw_me") + .add_flag<custom_test_flag>('u', "custom2") + .set_pattern("[%n] [%t] [%u] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + CHECK_THROWS_AS(formatter->format(msg, formatted), spdlog::spdlog_ex); +} + +TEST_CASE("override need_localtime", "[pattern_formatter]") { + auto formatter = + std::make_shared<spdlog::pattern_formatter>(spdlog::pattern_time_type::local, "\n"); + formatter->add_flag<custom_test_flag>('t', "time").set_pattern("%t> %v"); + + { + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + REQUIRE(to_string_view(formatted) == "0:00AM> some message\n"); + } + + { + formatter->need_localtime(); + + auto now_tm = spdlog::details::os::localtime(); + std::stringstream oss; + oss << (now_tm.tm_hour % 12) << ":" << std::setfill('0') << std::setw(2) << now_tm.tm_min + << (now_tm.tm_hour / 12 ? "PM" : "AM") << "> some message\n"; + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + REQUIRE(to_string_view(formatted) == oss.str()); + } +} + +#ifndef SPDLOG_NO_TLS +TEST_CASE("mdc formatter test-1", "[pattern_formatter]") { + spdlog::mdc::put("mdc_key_1", "mdc_value_1"); + spdlog::mdc::put("mdc_key_2", "mdc_value_2"); + + auto formatter = std::make_shared<spdlog::pattern_formatter>(); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + + auto expected = spdlog::fmt_lib::format( + "[logger-name] [info] [mdc_key_1:mdc_value_1 mdc_key_2:mdc_value_2] some message{}", + spdlog::details::os::default_eol); + REQUIRE(to_string_view(formatted) == expected); + + SECTION("Tear down") { spdlog::mdc::clear(); } +} + +TEST_CASE("mdc formatter value update", "[pattern_formatter]") { + spdlog::mdc::put("mdc_key_1", "mdc_value_1"); + spdlog::mdc::put("mdc_key_2", "mdc_value_2"); + + auto formatter = std::make_shared<spdlog::pattern_formatter>(); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted_1; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted_1); + + auto expected = spdlog::fmt_lib::format( + "[logger-name] [info] [mdc_key_1:mdc_value_1 mdc_key_2:mdc_value_2] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted_1) == expected); + + spdlog::mdc::put("mdc_key_1", "new_mdc_value_1"); + memory_buf_t formatted_2; + formatter->format(msg, formatted_2); + expected = spdlog::fmt_lib::format( + "[logger-name] [info] [mdc_key_1:new_mdc_value_1 mdc_key_2:mdc_value_2] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted_2) == expected); + + SECTION("Tear down") { spdlog::mdc::clear(); } +} + +TEST_CASE("mdc different threads", "[pattern_formatter]") { + auto formatter = std::make_shared<spdlog::pattern_formatter>(); + formatter->set_pattern("[%n] [%l] [%&] %v"); + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + + memory_buf_t formatted_2; + + auto lambda_1 = [formatter, msg]() { + spdlog::mdc::put("mdc_key", "thread_1_id"); + memory_buf_t formatted; + formatter->format(msg, formatted); + + auto expected = + spdlog::fmt_lib::format("[logger-name] [info] [mdc_key:thread_1_id] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted) == expected); + }; + + auto lambda_2 = [formatter, msg]() { + spdlog::mdc::put("mdc_key", "thread_2_id"); + memory_buf_t formatted; + formatter->format(msg, formatted); + + auto expected = + spdlog::fmt_lib::format("[logger-name] [info] [mdc_key:thread_2_id] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted) == expected); + }; + + std::thread thread_1(lambda_1); + std::thread thread_2(lambda_2); + + thread_1.join(); + thread_2.join(); + + SECTION("Tear down") { spdlog::mdc::clear(); } +} + +TEST_CASE("mdc remove key", "[pattern_formatter]") { + spdlog::mdc::put("mdc_key_1", "mdc_value_1"); + spdlog::mdc::put("mdc_key_2", "mdc_value_2"); + spdlog::mdc::remove("mdc_key_1"); + + auto formatter = std::make_shared<spdlog::pattern_formatter>(); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + + auto expected = + spdlog::fmt_lib::format("[logger-name] [info] [mdc_key_2:mdc_value_2] some message{}", + spdlog::details::os::default_eol); + REQUIRE(to_string_view(formatted) == expected); + + SECTION("Tear down") { spdlog::mdc::clear(); } +} + +TEST_CASE("mdc empty", "[pattern_formatter]") { + auto formatter = std::make_shared<spdlog::pattern_formatter>(); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + + auto expected = spdlog::fmt_lib::format("[logger-name] [info] [] some message{}", + spdlog::details::os::default_eol); + REQUIRE(to_string_view(formatted) == expected); + + SECTION("Tear down") { spdlog::mdc::clear(); } +} +#endif diff --git a/thirdparty/spdlog/tests/test_registry.cpp b/thirdparty/spdlog/tests/test_registry.cpp new file mode 100644 index 000000000..1805ae7f8 --- /dev/null +++ b/thirdparty/spdlog/tests/test_registry.cpp @@ -0,0 +1,125 @@ +#include "includes.h" + +static const char *const tested_logger_name = "null_logger"; +static const char *const tested_logger_name2 = "null_logger2"; + +#ifndef SPDLOG_NO_EXCEPTIONS +TEST_CASE("register_drop", "[registry]") { + spdlog::drop_all(); + spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name); + REQUIRE(spdlog::get(tested_logger_name) != nullptr); + // Throw if registering existing name + REQUIRE_THROWS_AS(spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name), + spdlog::spdlog_ex); +} + +TEST_CASE("explicit register", "[registry]") { + spdlog::drop_all(); + auto logger = std::make_shared<spdlog::logger>(tested_logger_name, + std::make_shared<spdlog::sinks::null_sink_st>()); + spdlog::register_logger(logger); + REQUIRE(spdlog::get(tested_logger_name) != nullptr); + // Throw if registering existing name + REQUIRE_THROWS_AS(spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name), + spdlog::spdlog_ex); +} +#endif + +TEST_CASE("register_or_replace", "[registry]") { + spdlog::drop_all(); + auto logger1 = std::make_shared<spdlog::logger>( + tested_logger_name, std::make_shared<spdlog::sinks::null_sink_st>()); + spdlog::register_logger(logger1); + REQUIRE(spdlog::get(tested_logger_name) == logger1); + + auto logger2 = std::make_shared<spdlog::logger>( + tested_logger_name, std::make_shared<spdlog::sinks::null_sink_st>()); + spdlog::register_or_replace(logger2); + REQUIRE(spdlog::get(tested_logger_name) == logger2); +} + +TEST_CASE("apply_all", "[registry]") { + spdlog::drop_all(); + auto logger = std::make_shared<spdlog::logger>(tested_logger_name, + std::make_shared<spdlog::sinks::null_sink_st>()); + spdlog::register_logger(logger); + auto logger2 = std::make_shared<spdlog::logger>( + tested_logger_name2, std::make_shared<spdlog::sinks::null_sink_st>()); + spdlog::register_logger(logger2); + + int counter = 0; + spdlog::apply_all([&counter](std::shared_ptr<spdlog::logger>) { counter++; }); + REQUIRE(counter == 2); + + counter = 0; + spdlog::drop(tested_logger_name2); + spdlog::apply_all([&counter](std::shared_ptr<spdlog::logger> l) { + REQUIRE(l->name() == tested_logger_name); + counter++; + }); + REQUIRE(counter == 1); +} + +TEST_CASE("drop", "[registry]") { + spdlog::drop_all(); + spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name); + spdlog::drop(tested_logger_name); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); +} + +TEST_CASE("drop-default", "[registry]") { + spdlog::set_default_logger(spdlog::null_logger_st(tested_logger_name)); + spdlog::drop(tested_logger_name); + REQUIRE_FALSE(spdlog::default_logger()); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); +} + +TEST_CASE("drop_all", "[registry]") { + spdlog::drop_all(); + spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name); + spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name2); + spdlog::drop_all(); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); + REQUIRE_FALSE(spdlog::get(tested_logger_name2)); + REQUIRE_FALSE(spdlog::default_logger()); +} + +TEST_CASE("drop non existing", "[registry]") { + spdlog::drop_all(); + spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name); + spdlog::drop("some_name"); + REQUIRE_FALSE(spdlog::get("some_name")); + REQUIRE(spdlog::get(tested_logger_name)); + spdlog::drop_all(); +} + +TEST_CASE("default logger", "[registry]") { + spdlog::drop_all(); + spdlog::set_default_logger(spdlog::null_logger_st(tested_logger_name)); + REQUIRE(spdlog::get(tested_logger_name) == spdlog::default_logger()); + spdlog::drop_all(); +} + +TEST_CASE("set_default_logger(nullptr)", "[registry]") { + spdlog::set_default_logger(nullptr); + REQUIRE_FALSE(spdlog::default_logger()); +} + +TEST_CASE("disable automatic registration", "[registry]") { + // set some global parameters + spdlog::level::level_enum log_level = spdlog::level::level_enum::warn; + spdlog::set_level(log_level); + // but disable automatic registration + spdlog::set_automatic_registration(false); + auto logger1 = spdlog::create<spdlog::sinks::daily_file_sink_st>( + tested_logger_name, SPDLOG_FILENAME_T("filename"), 11, 59); + auto logger2 = spdlog::create_async<spdlog::sinks::stdout_color_sink_mt>(tested_logger_name2); + // loggers should not be part of the registry + REQUIRE_FALSE(spdlog::get(tested_logger_name)); + REQUIRE_FALSE(spdlog::get(tested_logger_name2)); + // but make sure they are still initialized according to global defaults + REQUIRE(logger1->level() == log_level); + REQUIRE(logger2->level() == log_level); + spdlog::set_level(spdlog::level::info); + spdlog::set_automatic_registration(true); +} diff --git a/thirdparty/spdlog/tests/test_ringbuffer.cpp b/thirdparty/spdlog/tests/test_ringbuffer.cpp new file mode 100644 index 000000000..81d791656 --- /dev/null +++ b/thirdparty/spdlog/tests/test_ringbuffer.cpp @@ -0,0 +1,52 @@ +#include "includes.h" +#include "spdlog/sinks/ringbuffer_sink.h" + +TEST_CASE("ringbuffer invalid size", "[ringbuffer]") { + REQUIRE_THROWS_AS(spdlog::sinks::ringbuffer_sink_mt(0), spdlog::spdlog_ex); +} + +TEST_CASE("ringbuffer stores formatted messages", "[ringbuffer]") { + spdlog::sinks::ringbuffer_sink_st sink(3); + sink.set_pattern("%v"); + + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg1"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg2"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg3"}); + + auto formatted = sink.last_formatted(); + REQUIRE(formatted.size() == 3); + using spdlog::details::os::default_eol; + REQUIRE(formatted[0] == spdlog::fmt_lib::format("msg1{}", default_eol)); + REQUIRE(formatted[1] == spdlog::fmt_lib::format("msg2{}", default_eol)); + REQUIRE(formatted[2] == spdlog::fmt_lib::format("msg3{}", default_eol)); +} + +TEST_CASE("ringbuffer overrun keeps last items", "[ringbuffer]") { + spdlog::sinks::ringbuffer_sink_st sink(2); + sink.set_pattern("%v"); + + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "first"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "second"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "third"}); + + auto formatted = sink.last_formatted(); + REQUIRE(formatted.size() == 2); + using spdlog::details::os::default_eol; + REQUIRE(formatted[0] == spdlog::fmt_lib::format("second{}", default_eol)); + REQUIRE(formatted[1] == spdlog::fmt_lib::format("third{}", default_eol)); +} + +TEST_CASE("ringbuffer retrieval limit", "[ringbuffer]") { + spdlog::sinks::ringbuffer_sink_st sink(3); + sink.set_pattern("%v"); + + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "A"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "B"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "C"}); + + auto formatted = sink.last_formatted(2); + REQUIRE(formatted.size() == 2); + using spdlog::details::os::default_eol; + REQUIRE(formatted[0] == spdlog::fmt_lib::format("B{}", default_eol)); + REQUIRE(formatted[1] == spdlog::fmt_lib::format("C{}", default_eol)); +} diff --git a/thirdparty/spdlog/tests/test_sink.h b/thirdparty/spdlog/tests/test_sink.h new file mode 100644 index 000000000..9c0945232 --- /dev/null +++ b/thirdparty/spdlog/tests/test_sink.h @@ -0,0 +1,70 @@ +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" +#include "spdlog/fmt/fmt.h" +#include <chrono> +#include <mutex> +#include <thread> + +namespace spdlog { +namespace sinks { + +template <class Mutex> +class test_sink : public base_sink<Mutex> { + const size_t lines_to_save = 100; + +public: + size_t msg_counter() { + std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); + return msg_counter_; + } + + size_t flush_counter() { + std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); + return flush_counter_; + } + + void set_delay(std::chrono::milliseconds delay) { + std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); + delay_ = delay; + } + + // return last output without the eol + std::vector<std::string> lines() { + std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); + return lines_; + } + +protected: + void sink_it_(const details::log_msg &msg) override { + memory_buf_t formatted; + base_sink<Mutex>::formatter_->format(msg, formatted); + // save the line without the eol + auto eol_len = strlen(details::os::default_eol); + using diff_t = typename std::iterator_traits<decltype(formatted.end())>::difference_type; + if (lines_.size() < lines_to_save) { + lines_.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len)); + } + msg_counter_++; + std::this_thread::sleep_for(delay_); + } + + void flush_() override { flush_counter_++; } + + size_t msg_counter_{0}; + size_t flush_counter_{0}; + std::chrono::milliseconds delay_{std::chrono::milliseconds::zero()}; + std::vector<std::string> lines_; +}; + +using test_sink_mt = test_sink<std::mutex>; +using test_sink_st = test_sink<details::null_mutex>; + +} // namespace sinks +} // namespace spdlog diff --git a/thirdparty/spdlog/tests/test_stdout_api.cpp b/thirdparty/spdlog/tests/test_stdout_api.cpp new file mode 100644 index 000000000..67659b84d --- /dev/null +++ b/thirdparty/spdlog/tests/test_stdout_api.cpp @@ -0,0 +1,90 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" +#include "spdlog/sinks/stdout_sinks.h" +#include "spdlog/sinks/stdout_color_sinks.h" +TEST_CASE("stdout_st", "[stdout]") { + auto l = spdlog::stdout_logger_st("test"); + l->set_pattern("%+"); + l->set_level(spdlog::level::trace); + l->trace("Test stdout_st"); + spdlog::drop_all(); +} + +TEST_CASE("stdout_mt", "[stdout]") { + auto l = spdlog::stdout_logger_mt("test"); + l->set_pattern("%+"); + l->set_level(spdlog::level::debug); + l->debug("Test stdout_mt"); + spdlog::drop_all(); +} + +TEST_CASE("stderr_st", "[stderr]") { + auto l = spdlog::stderr_logger_st("test"); + l->set_pattern("%+"); + l->info("Test stderr_st"); + spdlog::drop_all(); +} + +TEST_CASE("stderr_mt", "[stderr]") { + auto l = spdlog::stderr_logger_mt("test"); + l->set_pattern("%+"); + l->info("Test stderr_mt"); + l->warn("Test stderr_mt"); + l->error("Test stderr_mt"); + l->critical("Test stderr_mt"); + spdlog::drop_all(); +} + +// color loggers +TEST_CASE("stdout_color_st", "[stdout]") { + auto l = spdlog::stdout_color_st("test"); + l->set_pattern("%+"); + l->info("Test stdout_color_st"); + spdlog::drop_all(); +} + +TEST_CASE("stdout_color_mt", "[stdout]") { + auto l = spdlog::stdout_color_mt("test"); + l->set_pattern("%+"); + l->set_level(spdlog::level::trace); + l->trace("Test stdout_color_mt"); + spdlog::drop_all(); +} + +TEST_CASE("stderr_color_st", "[stderr]") { + auto l = spdlog::stderr_color_st("test"); + l->set_pattern("%+"); + l->set_level(spdlog::level::debug); + l->debug("Test stderr_color_st"); + spdlog::drop_all(); +} + +TEST_CASE("stderr_color_mt", "[stderr]") { + auto l = spdlog::stderr_color_mt("test"); + l->set_pattern("%+"); + l->info("Test stderr_color_mt"); + l->warn("Test stderr_color_mt"); + l->error("Test stderr_color_mt"); + l->critical("Test stderr_color_mt"); + spdlog::drop_all(); +} + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT + +TEST_CASE("wchar_api", "[stdout]") { + auto l = spdlog::stdout_logger_st("wchar_logger"); + l->set_pattern("%+"); + l->set_level(spdlog::level::trace); + l->trace(L"Test wchar_api"); + l->trace(L"Test wchar_api {}", L"param"); + l->trace(L"Test wchar_api {}", 1); + l->trace(L"Test wchar_api {}", std::wstring{L"wstring param"}); + l->trace(std::wstring{L"Test wchar_api wstring"}); + SPDLOG_LOGGER_DEBUG(l, L"Test SPDLOG_LOGGER_DEBUG {}", L"param"); + spdlog::drop_all(); +} + +#endif diff --git a/thirdparty/spdlog/tests/test_stopwatch.cpp b/thirdparty/spdlog/tests/test_stopwatch.cpp new file mode 100644 index 000000000..b1b4b191c --- /dev/null +++ b/thirdparty/spdlog/tests/test_stopwatch.cpp @@ -0,0 +1,42 @@ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/stopwatch.h" + +TEST_CASE("stopwatch1", "[stopwatch]") { + using std::chrono::milliseconds; + using clock = std::chrono::steady_clock; + milliseconds wait_ms(500); + milliseconds tolerance_ms(250); + auto start = clock::now(); + spdlog::stopwatch sw; + std::this_thread::sleep_for(wait_ms); + auto stop = clock::now(); + auto diff_ms = std::chrono::duration_cast<milliseconds>(stop - start); + REQUIRE(sw.elapsed() >= diff_ms); + REQUIRE(sw.elapsed() <= diff_ms + tolerance_ms); +} + +TEST_CASE("stopwatch2", "[stopwatch]") { + using spdlog::sinks::test_sink_st; + using std::chrono::duration_cast; + using std::chrono::milliseconds; + using clock = std::chrono::steady_clock; + + clock::duration wait_duration(milliseconds(500)); + clock::duration tolerance_duration(milliseconds(250)); + + auto test_sink = std::make_shared<test_sink_st>(); + + auto start = clock::now(); + spdlog::stopwatch sw; + spdlog::logger logger("test-stopwatch", test_sink); + logger.set_pattern("%v"); + std::this_thread::sleep_for(wait_duration); + auto stop = clock::now(); + logger.info("{}", sw); + auto val = std::stod(test_sink->lines()[0]); + auto diff_duration = duration_cast<std::chrono::duration<double>>(stop - start); + + REQUIRE(val >= (diff_duration).count() - 0.001); + REQUIRE(val <= (diff_duration + tolerance_duration).count()); +} diff --git a/thirdparty/spdlog/tests/test_systemd.cpp b/thirdparty/spdlog/tests/test_systemd.cpp new file mode 100644 index 000000000..e36365741 --- /dev/null +++ b/thirdparty/spdlog/tests/test_systemd.cpp @@ -0,0 +1,14 @@ +#include "includes.h" +#include "spdlog/sinks/systemd_sink.h" + +TEST_CASE("systemd", "[all]") { + auto systemd_sink = std::make_shared<spdlog::sinks::systemd_sink_st>(); + spdlog::logger logger("spdlog_systemd_test", systemd_sink); + logger.set_level(spdlog::level::trace); + logger.trace("test spdlog trace"); + logger.debug("test spdlog debug"); + SPDLOG_LOGGER_INFO((&logger), "test spdlog info"); + SPDLOG_LOGGER_WARN((&logger), "test spdlog warn"); + SPDLOG_LOGGER_ERROR((&logger), "test spdlog error"); + SPDLOG_LOGGER_CRITICAL((&logger), "test spdlog critical"); +} diff --git a/thirdparty/spdlog/tests/test_time_point.cpp b/thirdparty/spdlog/tests/test_time_point.cpp new file mode 100644 index 000000000..b7b1b2323 --- /dev/null +++ b/thirdparty/spdlog/tests/test_time_point.cpp @@ -0,0 +1,35 @@ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/async.h" + +TEST_CASE("time_point1", "[time_point log_msg]") { + std::shared_ptr<spdlog::sinks::test_sink_st> test_sink(new spdlog::sinks::test_sink_st); + spdlog::logger logger("test-time_point", test_sink); + + spdlog::source_loc source{}; + std::chrono::system_clock::time_point tp{std::chrono::system_clock::now()}; + test_sink->set_pattern("%T.%F"); // interested in the time_point + + // all the following should have the same time + test_sink->set_delay(std::chrono::milliseconds(10)); + for (int i = 0; i < 5; i++) { + spdlog::details::log_msg msg{tp, source, "test_logger", spdlog::level::info, "message"}; + test_sink->log(msg); + } + + logger.log(tp, source, spdlog::level::info, "formatted message"); + logger.log(tp, source, spdlog::level::info, "formatted message"); + logger.log(tp, source, spdlog::level::info, "formatted message"); + logger.log(tp, source, spdlog::level::info, "formatted message"); + logger.log(source, spdlog::level::info, + "formatted message"); // last line has different time_point + + // now the real test... that the times are the same. + std::vector<std::string> lines = test_sink->lines(); + REQUIRE(lines[0] == lines[1]); + REQUIRE(lines[2] == lines[3]); + REQUIRE(lines[4] == lines[5]); + REQUIRE(lines[6] == lines[7]); + REQUIRE(lines[8] != lines[9]); + spdlog::drop_all(); +} diff --git a/thirdparty/spdlog/tests/utils.cpp b/thirdparty/spdlog/tests/utils.cpp new file mode 100644 index 000000000..405b5e5a2 --- /dev/null +++ b/thirdparty/spdlog/tests/utils.cpp @@ -0,0 +1,102 @@ +#include "includes.h" + +#ifdef _WIN32 + #include <windows.h> +#else + #include <sys/types.h> + #include <dirent.h> +#endif + +void prepare_logdir() { + spdlog::drop_all(); +#ifdef _WIN32 + system("rmdir /S /Q test_logs"); +#else + auto rv = system("rm -rf test_logs"); + if (rv != 0) { + throw std::runtime_error("Failed to rm -rf test_logs"); + } +#endif +} + +std::string file_contents(const std::string &filename) { + std::ifstream ifs(filename, std::ios_base::binary); + if (!ifs) { + throw std::runtime_error("Failed open file "); + } + return std::string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); +} + +std::size_t count_lines(const std::string &filename) { + std::ifstream ifs(filename); + if (!ifs) { + throw std::runtime_error("Failed open file "); + } + + std::string line; + size_t counter = 0; + while (std::getline(ifs, line)) counter++; + return counter; +} + +void require_message_count(const std::string &filename, const std::size_t messages) { + if (strlen(spdlog::details::os::default_eol) == 0) { + REQUIRE(count_lines(filename) == 1); + } else { + REQUIRE(count_lines(filename) == messages); + } +} + +std::size_t get_filesize(const std::string &filename) { + std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary); + if (!ifs) { + throw std::runtime_error("Failed open file " + filename); + } + return static_cast<std::size_t>(ifs.tellg()); +} + +// source: https://stackoverflow.com/a/2072890/192001 +bool ends_with(std::string const &value, std::string const &ending) { + if (ending.size() > value.size()) { + return false; + } + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +#ifdef _WIN32 +// Based on: https://stackoverflow.com/a/37416569/192001 +std::size_t count_files(const std::string &folder) { + size_t counter = 0; + WIN32_FIND_DATAA ffd; + + // Start iterating over the files in the folder directory. + HANDLE hFind = ::FindFirstFileA((folder + "\\*").c_str(), &ffd); + if (hFind != INVALID_HANDLE_VALUE) { + do // Managed to locate and create an handle to that folder. + { + if (ffd.cFileName[0] != '.') counter++; + } while (::FindNextFileA(hFind, &ffd) != 0); + ::FindClose(hFind); + } else { + throw std::runtime_error("Failed open folder " + folder); + } + + return counter; +} +#else +// Based on: https://stackoverflow.com/a/2802255/192001 +std::size_t count_files(const std::string &folder) { + size_t counter = 0; + DIR *dp = opendir(folder.c_str()); + if (dp == nullptr) { + throw std::runtime_error("Failed open folder " + folder); + } + + struct dirent *ep = nullptr; + while ((ep = readdir(dp)) != nullptr) { + if (ep->d_name[0] != '.') counter++; + } + (void)closedir(dp); + return counter; +} +#endif diff --git a/thirdparty/spdlog/tests/utils.h b/thirdparty/spdlog/tests/utils.h new file mode 100644 index 000000000..53c09b469 --- /dev/null +++ b/thirdparty/spdlog/tests/utils.h @@ -0,0 +1,18 @@ +#pragma once + +#include <cstddef> +#include <string> + +std::size_t count_files(const std::string &folder); + +void prepare_logdir(); + +std::string file_contents(const std::string &filename); + +std::size_t count_lines(const std::string &filename); + +void require_message_count(const std::string &filename, const std::size_t messages); + +std::size_t get_filesize(const std::string &filename); + +bool ends_with(std::string const &value, std::string const &ending);
\ No newline at end of file |