This commit is contained in:
2026-09-13 13:30:21 +08:00
commit a6bbd520cf
2744 changed files with 1598795 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
cmake_minimum_required(VERSION 3.11)
project(spdlog_utests CXX)
if(NOT TARGET spdlog)
# Stand-alone build
find_package(spdlog REQUIRED)
endif()
include(../cmake/utils.cmake)
find_package(PkgConfig)
if(PkgConfig_FOUND)
pkg_check_modules(systemd libsystemd)
endif()
find_package(Catch2 3 QUIET)
if(Catch2_FOUND)
message(STATUS "Packaged version of Catch will be used.")
else()
message(STATUS "Bundled version of Catch will be downloaded and used.")
include(FetchContent)
FetchContent_Declare(
Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG 53d0d913a422d356b23dd927547febdf69ee9081 # v3.5.0
)
FetchContent_MakeAvailable(Catch2)
endif()
set(SPDLOG_UTESTS_SOURCES
test_file_helper.cpp
test_file_logging.cpp
test_daily_logger.cpp
test_misc.cpp
test_eventlog.cpp
test_pattern_formatter.cpp
test_async.cpp
test_registry.cpp
test_macros.cpp
utils.cpp
main.cpp
test_mpmc_q.cpp
test_dup_filter.cpp
test_fmt_helper.cpp
test_stdout_api.cpp
test_backtrace.cpp
test_create_dir.cpp
test_custom_callbacks.cpp
test_cfg.cpp
test_time_point.cpp
test_stopwatch.cpp
test_circular_q.cpp
test_bin_to_hex.cpp
test_ringbuffer.cpp
test_timezone.cpp
)
if(NOT SPDLOG_NO_EXCEPTIONS)
list(APPEND SPDLOG_UTESTS_SOURCES test_errors.cpp)
endif()
if(systemd_FOUND)
list(APPEND SPDLOG_UTESTS_SOURCES test_systemd.cpp)
# systemd v256 added support for namespace stream API
if(systemd_VERSION VERSION_GREATER_EQUAL "256")
list(APPEND SPDLOG_UTESTS_SOURCES test_systemd_namespace.cpp)
endif()
endif()
enable_testing()
function(spdlog_prepare_test test_target spdlog_lib)
add_executable(${test_target} ${SPDLOG_UTESTS_SOURCES})
spdlog_enable_warnings(${test_target})
target_link_libraries(${test_target} PRIVATE ${spdlog_lib})
if(systemd_FOUND)
target_link_libraries(${test_target} PRIVATE ${systemd_LIBRARIES})
endif()
target_link_libraries(${test_target} PRIVATE Catch2::Catch2WithMain)
if(SPDLOG_SANITIZE_ADDRESS)
spdlog_enable_addr_sanitizer(${test_target})
elseif(SPDLOG_SANITIZE_THREAD)
spdlog_enable_thread_sanitizer(${test_target})
endif()
add_test(NAME ${test_target} COMMAND ${test_target} --order decl)
set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON)
endfunction()
# The compiled library tests
if(SPDLOG_BUILD_TESTS OR SPDLOG_BUILD_ALL)
spdlog_prepare_test(spdlog-utests spdlog::spdlog)
endif()
# The header-only library version tests
if(SPDLOG_BUILD_TESTS_HO OR SPDLOG_BUILD_ALL)
spdlog_prepare_test(spdlog-utests-ho spdlog::spdlog_header_only)
endif()
if(SPDLOG_MASTER_PROJECT AND NOT CMAKE_CROSSCOMPILING)
add_test(
NAME spdlog-cmake-tweakme
COMMAND
${CMAKE_COMMAND} -DSPDLOG_TEST_SOURCE_DIR=${CMAKE_CURRENT_LIST_DIR}/..
-DSPDLOG_TEST_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/cmake-tweakme
-DSPDLOG_TEST_GENERATOR=${CMAKE_GENERATOR} -DSPDLOG_TEST_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}
-DSPDLOG_TEST_GENERATOR_TOOLSET=${CMAKE_GENERATOR_TOOLSET} -DSPDLOG_TEST_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DSPDLOG_TEST_CONFIG=$<CONFIG> -P ${CMAKE_CURRENT_LIST_DIR}/cmake/tweakme_test.cmake)
endif()
@@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.10)
project(spdlog_tweakme_consumer LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if(SPDLOG_TEST_INSTALLED)
find_package(spdlog CONFIG REQUIRED)
add_executable(package-compiled main.cpp)
target_link_libraries(package-compiled PRIVATE spdlog::spdlog)
add_executable(package-header-only main.cpp)
target_link_libraries(package-header-only PRIVATE spdlog::spdlog_header_only)
find_package(Threads REQUIRED)
add_executable(raw-header-only main.cpp)
target_include_directories(raw-header-only PRIVATE "${SPDLOG_TEST_PREFIX}/include")
target_link_libraries(raw-header-only PRIVATE Threads::Threads)
else()
set(SPDLOG_GENERATE_TWEAKME_H ON CACHE BOOL "" FORCE)
set(SPDLOG_NO_THREAD_ID ON CACHE BOOL "" FORCE)
set(SPDLOG_NO_TLS ON CACHE BOOL "" FORCE)
set(SPDLOG_DISABLE_DEFAULT_LOGGER ON CACHE BOOL "" FORCE)
set(SPDLOG_BUILD_SHARED ${SPDLOG_TEST_SHARED} CACHE BOOL "" FORCE)
set(SPDLOG_BUILD_EXAMPLE OFF CACHE BOOL "" FORCE)
set(SPDLOG_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(SPDLOG_INSTALL ON CACHE BOOL "" FORCE)
add_subdirectory("${SPDLOG_TEST_SOURCE_DIR}" spdlog)
add_executable(source-compiled main.cpp)
target_link_libraries(source-compiled PRIVATE spdlog::spdlog)
add_executable(source-header-only main.cpp)
target_link_libraries(source-header-only PRIVATE spdlog::spdlog_header_only)
endif()
+19
View File
@@ -0,0 +1,19 @@
#include <spdlog/spdlog.h>
#ifndef SPDLOG_TWEAKME_H_GENERATED
#error The generated tweakme.h must be selected before the source header
#endif
#ifndef SPDLOG_NO_THREAD_ID
#error SPDLOG_NO_THREAD_ID must be provided by the generated tweakme.h
#endif
#ifndef SPDLOG_NO_TLS
#error SPDLOG_NO_TLS must be provided by the generated tweakme.h
#endif
#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER
#error SPDLOG_DISABLE_DEFAULT_LOGGER must be provided by the generated tweakme.h
#endif
int main() { return 0; }
+66
View File
@@ -0,0 +1,66 @@
function(run_checked description)
execute_process(COMMAND ${ARGN} RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error)
if(NOT result EQUAL 0)
message(FATAL_ERROR "${description} failed (${result}):\n${output}\n${error}")
endif()
endfunction()
function(configure_checked description source_dir build_dir)
file(MAKE_DIRECTORY "${build_dir}")
execute_process(
COMMAND "${CMAKE_COMMAND}" ${ARGN} "${source_dir}"
WORKING_DIRECTORY "${build_dir}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE error)
if(NOT result EQUAL 0)
message(FATAL_ERROR "${description} failed (${result}):\n${output}\n${error}")
endif()
endfunction()
function(test_tweakme_configuration library_type shared)
set(build_dir "${SPDLOG_TEST_BINARY_DIR}/${library_type}/build")
set(prefix "${SPDLOG_TEST_BINARY_DIR}/${library_type}/prefix")
set(consumer_dir "${SPDLOG_TEST_BINARY_DIR}/${library_type}/consumer")
file(REMOVE_RECURSE "${SPDLOG_TEST_BINARY_DIR}/${library_type}")
set(generator_args -G "${SPDLOG_TEST_GENERATOR}")
if(SPDLOG_TEST_GENERATOR_PLATFORM)
list(APPEND generator_args -A "${SPDLOG_TEST_GENERATOR_PLATFORM}")
endif()
if(SPDLOG_TEST_GENERATOR_TOOLSET)
list(APPEND generator_args -T "${SPDLOG_TEST_GENERATOR_TOOLSET}")
endif()
set(common_args
"-DCMAKE_CXX_COMPILER=${SPDLOG_TEST_CXX_COMPILER}"
"-DCMAKE_BUILD_TYPE=${SPDLOG_TEST_CONFIG}")
configure_checked(
"Configure ${library_type} source-tree consumer"
"${CMAKE_CURRENT_LIST_DIR}/tweakme" "${build_dir}" ${generator_args} ${common_args}
"-DSPDLOG_TEST_SOURCE_DIR=${SPDLOG_TEST_SOURCE_DIR}" "-DSPDLOG_TEST_SHARED=${shared}"
"-DCMAKE_INSTALL_PREFIX=${prefix}")
run_checked("Build ${library_type} source-tree consumer" "${CMAKE_COMMAND}" --build "${build_dir}" --config
"${SPDLOG_TEST_CONFIG}")
run_checked("Install ${library_type} spdlog" "${CMAKE_COMMAND}" --build "${build_dir}" --target install --config
"${SPDLOG_TEST_CONFIG}")
set(installed_tweakme "${prefix}/include/spdlog/tweakme.h")
file(READ "${installed_tweakme}" tweakme_contents)
foreach(expected_macro SPDLOG_NO_THREAD_ID SPDLOG_NO_TLS SPDLOG_DISABLE_DEFAULT_LOGGER)
if(NOT tweakme_contents MATCHES "#define ${expected_macro}([\r\n]|$)")
message(FATAL_ERROR "${installed_tweakme} does not define ${expected_macro}")
endif()
endforeach()
configure_checked(
"Configure ${library_type} installed consumers"
"${CMAKE_CURRENT_LIST_DIR}/tweakme" "${consumer_dir}" ${generator_args} ${common_args}
-DSPDLOG_TEST_INSTALLED=ON "-DSPDLOG_TEST_PREFIX=${prefix}" "-DCMAKE_PREFIX_PATH=${prefix}")
run_checked("Build ${library_type} installed consumers" "${CMAKE_COMMAND}" --build "${consumer_dir}" --config
"${SPDLOG_TEST_CONFIG}")
endfunction()
test_tweakme_configuration(static OFF)
test_tweakme_configuration(shared ON)
+45
View File
@@ -0,0 +1,45 @@
#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
#undef SPDLOG_LEVEL_NAMES
#undef SPDLOG_SHORT_LEVEL_NAMES
#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"
+10
View File
@@ -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
+200
View File
@@ -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);
}
+73
View File
@@ -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 ********************");
}
+97
View File
@@ -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)));
}
+208
View File
@@ -0,0 +1,208 @@
#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);
}
TEST_CASE("uppercase-level-names", "[cfg]") {
spdlog::drop("l1");
spdlog::drop("l2");
#ifdef CATCH_PLATFORM_WINDOWS
_putenv_s("SPDLOG_LEVEL", "l1=DEBUG,INFO");
#else
setenv("SPDLOG_LEVEL", "l1=DEBUG,INFO", 1);
#endif
load_env_levels();
auto l1 = spdlog::create<test_sink_st>("l1");
auto l2 = spdlog::create<test_sink_st>("l2");
REQUIRE(l1->level() == spdlog::level::debug);
REQUIRE(l2->level() == spdlog::level::info);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
// Test with argv
spdlog::drop("l3");
const char *argv[] = {"ignore", "SPDLOG_LEVEL=l3=WARN,ERROR"};
load_argv_levels(2, argv);
auto l3 = spdlog::create<test_sink_st>("l3");
REQUIRE(l3->level() == spdlog::level::warn);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::err);
// Reset to info
spdlog::set_level(spdlog::level::info);
}
+50
View File
@@ -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());
}
+155
View File
@@ -0,0 +1,155 @@
/*
* 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("path_exists", "[create_dir]") {
REQUIRE(path_exists(SPDLOG_FILENAME_T("")) == false);
REQUIRE(path_exists(SPDLOG_FILENAME_T("non_existing_path_xyz")) == false);
prepare_logdir();
REQUIRE(create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir2")) == true);
REQUIRE(path_exists(SPDLOG_FILENAME_T("test_logs/dir1/dir2")) == true);
REQUIRE(create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir2")) == true); // already existing nested
REQUIRE(create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir3///")) == true); // trailing separator
REQUIRE(path_exists(SPDLOG_FILENAME_T("test_logs/dir1/dir3")) == true);
}
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
@@ -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();
}
+169
View File
@@ -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);
}
+83
View File
@@ -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..");
}
+112
View File
@@ -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);
}
+75
View File
@@ -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
+169
View File
@@ -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);
}
+244
View File
@@ -0,0 +1,244 @@
/*
* 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 get_current_size() tracks the file size, including across rotations
TEST_CASE("rotating_file_logger_get_current_size", "[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);
REQUIRE(sink->get_current_size() == 0);
logger->info("Test message");
logger->flush();
REQUIRE(sink->get_current_size() == get_filesize(ROTATING_LOG));
REQUIRE(sink->get_current_size() > 0);
sink->rotate_now();
REQUIRE(sink->get_current_size() == 0);
}
// test that a manual rotate_now() does not cause later writes to rotate the file
// prematurely, well before max_size is actually reached.
TEST_CASE("rotating_file_logger_no_premature_rotation_after_manual_rotate",
"[rotating_logger]") {
prepare_logdir();
using spdlog::details::os::default_eol;
const size_t eol_len = strlen(default_eol);
const size_t max_size = 100;
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->set_pattern("%v");
// fill the file close to (but under) max_size, then rotate it away manually.
const std::string msg1(89, 'a');
logger->info(msg1);
logger->flush();
REQUIRE(sink->get_current_size() == msg1.size() + eol_len);
sink->rotate_now();
REQUIRE(get_filesize(ROTATING_LOG ".1") == msg1.size() + eol_len);
// write two small messages whose combined real size stays well under max_size.
const std::string msg2(4, 'b');
logger->info(msg2);
logger->flush();
const std::string msg3(5, 'c');
logger->info(msg3);
logger->flush();
// combined real bytes written since rotate_now() (13ish bytes) never approached
// max_size (100), so no further rotation should have happened: ROTATING_LOG.2
// should not exist, and ROTATING_LOG.1 should still hold only msg1.
REQUIRE_THROWS(get_filesize(ROTATING_LOG ".2"));
REQUIRE(get_filesize(ROTATING_LOG ".1") == msg1.size() + eol_len);
REQUIRE(get_filesize(ROTATING_LOG) == msg2.size() + msg3.size() + 2 * eol_len);
}
// 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);
}
}
}
+86
View File
@@ -0,0 +1,86 @@
#include "includes.h"
using spdlog::memory_buf_t;
SPDLOG_CONSTEXPR_FUNC spdlog::string_view_t to_string_view(const memory_buf_t &buf)
SPDLOG_NOEXCEPT {
return spdlog::string_view_t{buf.data(), buf.size()};
}
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");
}
+53
View File
@@ -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");
}
+235
View File
@@ -0,0 +1,235 @@
#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);
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("ERR") == 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("TrAcE") == spdlog::level::trace);
REQUIRE(spdlog::level::from_str("DeBuG") == spdlog::level::debug);
}
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));
}
+114
View File
@@ -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);
}
+671
View File
@@ -0,0 +1,671 @@
#include "includes.h"
#include "test_sink.h"
#include <regex>
#include <chrono>
using spdlog::memory_buf_t;
SPDLOG_CONSTEXPR_FUNC spdlog::string_view_t to_string_view(const memory_buf_t &buf)
SPDLOG_NOEXCEPT {
return spdlog::string_view_t{buf.data(), buf.size()};
}
// 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());
}
// see test_timezone.cpp for actual UTC offset calculation tests
TEST_CASE("UTC offset", "[pattern_formatter]") {
using namespace std::chrono_literals;
const auto now = std::chrono::system_clock::now();
std::string result =
log_to_str_with_time(now, "Some message", "%z", spdlog::pattern_time_type::local, "\n");
#ifndef SPDLOG_NO_TZ_OFFSET
// Match format: +HH:MM or -HH:MM
std::regex re(R"([+-]\d{2}:[0-5]\d\n)");
REQUIRE(std::regex_match(result, re));
#else
REQUIRE(result == "+??:??\n");
#endif
}
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
+125
View File
@@ -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);
}
+78
View File
@@ -0,0 +1,78 @@
#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));
}
// Regression test for #3195: source_loc filename/funcname must be buffered by
// log_msg_buffer, otherwise they dangle once the caller's strings are gone.
TEST_CASE("ringbuffer buffers source_loc strings", "[ringbuffer]") {
spdlog::sinks::ringbuffer_sink_st sink(2);
sink.set_pattern("%g:%# %! %v");
{
// filename/funcname from scoped strings that are destroyed before retrieval
std::string filename = std::string("dynamic_") + "source.cpp";
std::string funcname = std::string("dynamic_") + "function";
spdlog::source_loc loc{filename.c_str(), 123, funcname.c_str()};
sink.log(spdlog::details::log_msg{loc, "test", spdlog::level::info, "payload"});
}
// overwrite the freed stack memory
volatile char scratch[256];
for (size_t i = 0; i < sizeof(scratch); ++i) scratch[i] = static_cast<char>(i);
auto formatted = sink.last_formatted();
REQUIRE(formatted.size() == 1);
using spdlog::details::os::default_eol;
REQUIRE(formatted[0] ==
spdlog::fmt_lib::format("dynamic_source.cpp:123 dynamic_function payload{}",
default_eol));
}
+70
View File
@@ -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
+96
View File
@@ -0,0 +1,96 @@
/*
* 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();
}
TEST_CASE("show_utc_offset", "[stdout]") {
auto l = spdlog::stdout_color_mt("test");
l->set_pattern("[%c %z] [%n] [%^%l%$] %v");
l->info("Full date");
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
+42
View File
@@ -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());
}
+14
View File
@@ -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");
}
+192
View File
@@ -0,0 +1,192 @@
#include "includes.h"
#include "spdlog/sinks/systemd_namespace_sink.h"
#include <chrono>
// Used as a helper struct to store journal entries when verifying the custom namespace journal
// output automatically.
struct JournalEntry {
std::string message;
std::string priority;
std::string syslog_identifier;
uint64_t realtime_usec = 0;
};
// Used as a helper class to test the custom namespace journal output automatically.
class JournalReader {
public:
explicit JournalReader(const std::string& name_space) {
int r = -1;
if (name_space.empty()) {
r = sd_journal_open(&j_, SD_JOURNAL_LOCAL_ONLY);
} else {
r = sd_journal_open_namespace(&j_, name_space.c_str(), SD_JOURNAL_LOCAL_ONLY);
}
if (r < 0) {
throw std::runtime_error(std::string("sd_journal_open failed: ") + strerror(-r));
}
}
~JournalReader() {
if (j_ != nullptr) {
sd_journal_close(j_);
}
}
JournalReader(const JournalReader&) = delete;
JournalReader& operator=(const JournalReader&) = delete;
// Position the cursor at "now" so we only see entries written after this point.
void seek_to_now() {
uint64_t now_usec = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
int r = sd_journal_seek_realtime_usec(j_, now_usec);
if (r < 0) {
throw std::runtime_error(std::string("seek_realtime_usec failed: ") + strerror(-r));
}
// After seek, you must call sd_journal_next/previous before reading.
// We don't call next() here because there may not be an entry yet.
}
// Add a match filter, e.g. "SYSLOG_IDENTIFIER=myapp".
// Multiple matches on the same field are OR'd; matches on different fields are AND'd.
void add_match(const std::string& match) {
int r = sd_journal_add_match(j_, match.data(), match.size());
if (r < 0) {
throw std::runtime_error(std::string("add_match failed: ") + strerror(-r));
}
}
// Read up to max_entries new entries, waiting up to timeout for each.
// Returns whatever was collected when either max_entries is reached or
// a wait times out.
std::vector<JournalEntry> wait_and_read(size_t max_entries, std::chrono::milliseconds timeout) {
std::vector<JournalEntry> out;
out.reserve(max_entries);
while (out.size() < max_entries) {
int r = sd_journal_next(j_);
if (r < 0) {
throw std::runtime_error(std::string("sd_journal_next failed: ") + strerror(-r));
}
if (r == 0) {
// No new entry available yet; wait a moment.
uint64_t timeout_usec = static_cast<uint64_t>(timeout.count()) * 1000;
int w = sd_journal_wait(j_, timeout_usec);
if (w < 0) {
throw std::runtime_error(std::string("sd_journal_wait failed: ") +
strerror(-w));
}
if (w == SD_JOURNAL_NOP) {
// Timeout
break;
}
continue;
}
out.push_back(read_current_entry());
}
return out;
}
private:
JournalEntry read_current_entry() {
JournalEntry e;
const void* data = nullptr;
size_t len = 0;
if (sd_journal_get_data(j_, "MESSAGE", &data, &len) >= 0) {
e.message = field_value(data, len);
}
if (sd_journal_get_data(j_, "PRIORITY", &data, &len) >= 0) {
e.priority = field_value(data, len);
}
if (sd_journal_get_data(j_, "SYSLOG_IDENTIFIER", &data, &len) >= 0) {
e.syslog_identifier = field_value(data, len);
}
sd_journal_get_realtime_usec(j_, &e.realtime_usec);
return e;
}
// Strip "KEY=value" pair to just value.
static std::string field_value(const void* data, size_t len) {
const char* p = static_cast<const char*>(data);
const char* eq = static_cast<const char*>(std::memchr(p, '=', len));
if (!eq) return std::string(p, len);
return std::string(eq + 1, len - (eq + 1 - p));
}
sd_journal* j_ = nullptr;
};
TEST_CASE("systemd_namespace to default namespace", "[all]") {
auto logger = spdlog::systemd_namespace_logger_st("spdlog_systemd_namespace_all_test",
"spdlog-utests", "", false);
logger->set_level(spdlog::level::trace);
logger->trace("test spdlog trace unformatted");
logger->debug("test spdlog debug unformatted");
SPDLOG_LOGGER_INFO((logger), "test spdlog info unformatted");
SPDLOG_LOGGER_WARN((logger), "test spdlog warn unformatted");
SPDLOG_LOGGER_ERROR((logger), "test spdlog error unformatted");
SPDLOG_LOGGER_CRITICAL((logger), "test spdlog critical unformatted");
logger->info("Long message\nthat spans\nmultiple lines unformatted");
auto f_logger = spdlog::systemd_namespace_logger_st("spdlog_systemd_namespace_all_test_formatted",
"spdlog-utests", "", true);
f_logger->set_level(spdlog::level::trace);
f_logger->trace("test spdlog trace formatted");
f_logger->debug("test spdlog debug formatted");
SPDLOG_LOGGER_INFO((f_logger), "test spdlog info formatted");
SPDLOG_LOGGER_WARN((f_logger), "test spdlog warn formatted");
SPDLOG_LOGGER_ERROR((f_logger), "test spdlog error formatted");
SPDLOG_LOGGER_CRITICAL((f_logger), "test spdlog critical formatted");
f_logger->info("Long message\nthat spans\nmultiple lines formatted");
}
TEST_CASE("systemd_namespace to custom namespace", "[.systemd_namespace]") {
// This test is skipped from the default set since it requires extra preparation.
// Running this test requires you to start journald on namespace 'custom', for example by
// running: sudo systemctl start systemd-journald@custom.service
//
// This test also automatically verifies the output, so the user running the test must have
// access to read the custom journal namespace.
auto logger = spdlog::systemd_namespace_logger_st("spdlog_systemd_namespace_custom_test",
"spdlog-utests", "custom", false);
JournalReader reader("custom");
reader.add_match("SYSLOG_IDENTIFIER=spdlog-utests");
reader.seek_to_now();
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");
logger->info("Long message\nthat spans\nmultiple lines");
logger->warn("");
const int expected_message_count = 9;
auto entries = reader.wait_and_read(expected_message_count, std::chrono::seconds(2));
REQUIRE(entries.size() == expected_message_count);
REQUIRE(entries[0].message == "test spdlog trace");
REQUIRE(entries[0].priority == "7");
REQUIRE(entries[1].message == "test spdlog debug");
REQUIRE(entries[1].priority == "7");
REQUIRE(entries[2].message == "test spdlog info");
REQUIRE(entries[2].priority == "6");
REQUIRE(entries[3].message == "test spdlog warn");
REQUIRE(entries[3].priority == "4");
REQUIRE(entries[4].message == "test spdlog error");
REQUIRE(entries[4].priority == "3");
REQUIRE(entries[5].message == "test spdlog critical");
REQUIRE(entries[5].priority == "2");
REQUIRE(entries[6].message == "Long message");
REQUIRE(entries[6].priority == "6");
REQUIRE(entries[7].message == "that spans");
REQUIRE(entries[7].priority == "6");
REQUIRE(entries[8].message == "multiple lines");
REQUIRE(entries[8].priority == "6");
}
+35
View File
@@ -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();
}
+193
View File
@@ -0,0 +1,193 @@
#ifndef SPDLOG_NO_TZ_OFFSET
#include "includes.h"
#include <ctime>
#include <cstdlib>
#include <cstring>
// Helper to construct a simple std::tm from components
std::tm make_tm(int year, int month, int day, int hour, int minute) {
std::tm t;
std::memset(&t, 0, sizeof(t));
t.tm_year = year - 1900;
t.tm_mon = month - 1;
t.tm_mday = day;
t.tm_hour = hour;
t.tm_min = minute;
t.tm_sec = 0;
t.tm_isdst = -1;
std::mktime(&t);
return t;
}
// Cross-platform RAII Helper to safely set/restore process timezone
class ScopedTZ {
std::string original_tz_;
bool has_original_ = false;
public:
explicit ScopedTZ(const std::string& tz_name) {
// save current TZ
#ifdef _WIN32
char* buf = nullptr;
size_t len = 0;
if (_dupenv_s(&buf, &len, "TZ") == 0 && buf != nullptr) {
original_tz_ = std::string(buf);
has_original_ = true;
free(buf);
}
#else
const char* tz = std::getenv("TZ");
if (tz) {
original_tz_ = tz;
has_original_ = true;
}
#endif
// set new TZ
#ifdef _WIN32
_putenv_s("TZ", tz_name.c_str());
_tzset();
#else
setenv("TZ", tz_name.c_str(), 1);
tzset();
#endif
}
~ScopedTZ() {
// restore original TZ
#ifdef _WIN32
if (has_original_) {
_putenv_s("TZ", original_tz_.c_str());
} else {
_putenv_s("TZ", "");
}
_tzset();
#else
if (has_original_) {
setenv("TZ", original_tz_.c_str(), 1);
} else {
unsetenv("TZ");
}
tzset();
#endif
}
};
using spdlog::details::os::utc_minutes_offset;
/*
* POSIX 2024 defines three formats for the TZ environment variable,
*
* 1. Implementation defined format which always starts with a colon:
* ":characters".
* 2. A specifier which fully describes the timezone rule in format
* "stdoffset[dst[offset][,start[/time],end[/time]]]". Note the
* offset and start/end part could be omitted, in which case one hour
* is implied, or it's considered implementation-defined when changing
* to and from Daylight Saving Time occurs.
* 3. Geographical or special timezone from an implementation-defined
* timezone database.
*
* On POSIX-compilant systems, we prefer format 2, and explicitly specify the
* DST rules to avoid implementation-defined behavior.
*
* See also IEEE 1003.1-2024 8.3 Other Environment Variables.
*/
#ifndef _WIN32
/*
* Standard time is UTC-5 ("EST"), DST time is UTC-4 ("EDT"). DST is active
* from 2:00 on the 2nd Sunday in March, to 2:00 on 1st Sunday in November.
*/
#define EST5EDT "EST5EDT,M3.2.0,M11.1.0"
/*
* Standard time is UTC+2 ("IST"), DST time is UTC+3 ("IDT"). DST is active
* from 2:00 on following day of the 4th Thursday in March, to 2:00 on the
* last Sunday in October.
*/
#define IST_MINUS2_IDT "IST-2IDT,M3.4.4/26,M10.5.0"
#else
/*
* However, Windows doesn't follow the POSIX rules and only accept a TZ
* environment variable in format
*
* tzn [+|-]hh[:mm[:ss] ][dzn]
*
* thus we couldn't specify the DST rules. Luckily, Windows C runtime library
* assumes the United State's rules for implementing the calculation of DST,
* which is fine for our test cases.
*
* See also https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset?view=msvc-170
*/
#define EST5EDT "EST5EDT"
#define IST_MINUS2_IDT "IST-2IDT"
#endif
TEST_CASE("UTC Offset - Western Hemisphere (USA - Standard Time)", "[timezone][west]") {
// EST5EDT: Eastern Standard Time (UTC-5)
ScopedTZ tz(EST5EDT);
// Jan 15th (Winter)
auto tm = make_tm(2023, 1, 15, 12, 0);
REQUIRE(utc_minutes_offset(tm) == -300);
}
TEST_CASE("UTC Offset - Eastern Hemisphere (Europe/Israel - Standard Time)", "[timezone][east]") {
// IST-2IDT: Israel Standard Time (UTC+2)
ScopedTZ tz(IST_MINUS2_IDT);
// Jan 15th (Winter)
auto tm = make_tm(2023, 1, 15, 12, 0);
REQUIRE(utc_minutes_offset(tm) == 120);
}
TEST_CASE("UTC Offset - Zero Offset (UTC/GMT)", "[timezone][utc]") {
ScopedTZ tz("GMT0");
// Check Winter
auto tm_winter = make_tm(2023, 1, 15, 12, 0);
REQUIRE(utc_minutes_offset(tm_winter) == 0);
// Check Summer (GMT never shifts, so this should also be 0)
auto tm_summer = make_tm(2023, 7, 15, 12, 0);
REQUIRE(utc_minutes_offset(tm_summer) == 0);
}
TEST_CASE("UTC Offset - Non-Integer Hour Offsets (India)", "[timezone][partial]") {
// IST-5:30: India Standard Time (UTC+5:30)
ScopedTZ tz("IST-5:30");
auto tm = make_tm(2023, 1, 15, 12, 0);
REQUIRE(utc_minutes_offset(tm) == 330);
}
TEST_CASE("UTC Offset - Edge Case: Negative Offset Crossing Midnight", "[timezone][edge]") {
ScopedTZ tz(EST5EDT);
// Late night Dec 31st, 2023
auto tm = make_tm(2023, 12, 31, 23, 59);
REQUIRE(utc_minutes_offset(tm) == -300);
}
TEST_CASE("UTC Offset - Edge Case: Leap Year", "[timezone][edge]") {
ScopedTZ tz(EST5EDT);
// Feb 29, 2024 (Leap Day) - Winter
auto tm = make_tm(2024, 2, 29, 12, 0);
REQUIRE(utc_minutes_offset(tm) == -300);
}
TEST_CASE("UTC Offset - Edge Case: Invalid Date (Pre-Epoch)", "[timezone][edge]") {
#ifdef _WIN32
// Windows mktime returns -1 for dates before 1970.
// We expect the function to safely return 0 (fallback).
auto tm = make_tm(1960, 1, 1, 12, 0);
REQUIRE(utc_minutes_offset(tm) == 0);
#else
// Unix mktime handles pre-1970 dates correctly.
// We expect the actual historical offset (EST was UTC-5 in 1960).
ScopedTZ tz(EST5EDT);
auto tm = make_tm(1960, 1, 1, 12, 0);
REQUIRE(utc_minutes_offset(tm) == -300);
#endif
}
#endif // !SPDLOG_NO_TZ_OFFSET
+102
View File
@@ -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
+18
View File
@@ -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);