This commit is contained in:
Mars 2024-06-02 06:03:21 -04:00
parent 6e5045f1f4
commit 693fa17d10
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
266 changed files with 60543 additions and 1000 deletions

View file

@ -0,0 +1,137 @@
#include "doctest/doctest.h"
#include "misc/DocTestExtensions.h"
#include "quill/core/BoundedSPSCQueue.h"
#include <cstring>
#include <thread>
#include <vector>
TEST_SUITE_BEGIN("BoundedQueue");
using namespace quill::detail;
#if !defined(QUILL_X86ARCH)
// QUILL_X86ARCH requires at least a queue capacity of 1024 and those tests are using a smaller number
TEST_CASE("read_write_buffer")
{
BoundedSPSCQueue buffer{64u};
for (uint32_t i = 0; i < 128; ++i)
{
{
std::byte* write_buf = buffer.prepare_write(32u);
REQUIRE_NE(write_buf, nullptr);
buffer.finish_write(32u);
buffer.commit_write();
}
{
std::byte* write_buf = buffer.prepare_write(32u);
REQUIRE_NE(write_buf, nullptr);
buffer.finish_write(32u);
buffer.commit_write();
}
{
std::byte* res = buffer.prepare_read();
REQUIRE(res);
buffer.finish_read(32u);
buffer.commit_read();
}
{
std::byte* res = buffer.prepare_read();
REQUIRE(res);
buffer.finish_read(32u);
buffer.commit_read();
res = buffer.prepare_read();
REQUIRE_FALSE(res);
}
}
std::byte* res = buffer.prepare_read();
REQUIRE_FALSE(res);
}
TEST_CASE("bounded_queue_integer_overflow")
{
BoundedSPSCQueueImpl<uint8_t> buffer{128, false, 0};
size_t constexpr iterations = static_cast<size_t>(std::numeric_limits<uint8_t>::max()) * 8ull;
for (size_t i = 0; i < iterations; ++i)
{
std::string to_write{"test"};
to_write += std::to_string(i);
std::byte* r = buffer.prepare_write(static_cast<uint8_t>(to_write.length()) + 1);
std::strncpy(reinterpret_cast<char*>(r), to_write.data(), to_write.length() + 1);
buffer.finish_write(static_cast<uint8_t>(to_write.length()) + 1);
buffer.commit_write();
// now read
std::byte* w = buffer.prepare_read();
REQUIRE(w);
char result[256];
std::memcpy(&result[0], w, static_cast<uint8_t>(to_write.length()) + 1);
REQUIRE_STREQ(result, to_write.data());
buffer.finish_read(static_cast<uint8_t>(to_write.length()) + 1);
buffer.commit_read();
}
}
#endif
TEST_CASE("bounded_queue_read_write_multithreaded_plain_ints")
{
BoundedSPSCQueue buffer{131'072};
std::thread producer_thread(
[&buffer]()
{
for (uint32_t wrap_cnt = 0; wrap_cnt < 20; ++wrap_cnt)
{
for (uint32_t i = 0; i < 8192; ++i)
{
std::byte* write_buffer = buffer.prepare_write(sizeof(uint32_t));
while (!write_buffer)
{
std::this_thread::sleep_for(std::chrono::microseconds{2});
write_buffer = buffer.prepare_write(sizeof(uint32_t));
}
std::memcpy(write_buffer, &i, sizeof(uint32_t));
buffer.finish_write(sizeof(uint32_t));
buffer.commit_write();
}
}
});
std::thread consumer_thread(
[&buffer]()
{
for (uint32_t wrap_cnt = 0; wrap_cnt < 20; ++wrap_cnt)
{
for (uint32_t i = 0; i < 8192; ++i)
{
std::byte const* read_buffer = buffer.prepare_read();
while (!read_buffer)
{
std::this_thread::sleep_for(std::chrono::microseconds{2});
read_buffer = buffer.prepare_read();
}
auto const value = reinterpret_cast<uint32_t const*>(read_buffer);
REQUIRE_EQ(*value, i);
buffer.finish_read(sizeof(uint32_t));
buffer.commit_read();
}
}
});
producer_thread.join();
consumer_thread.join();
}
TEST_SUITE_END();

View file

@ -0,0 +1,66 @@
function(quill_add_test TEST_NAME SOURCES)
set(HEADER_FILES
${PROJECT_SOURCE_DIR}/quill/test/bundled/doctest/doctest.h
${PROJECT_SOURCE_DIR}/quill/test/misc/TestUtilities.h
)
set(ADD_SOURCE_FILES
${PROJECT_SOURCE_DIR}/quill/test/misc/TestMain.cpp
${PROJECT_SOURCE_DIR}/quill/test/misc/TestUtilities.cpp
${PROJECT_SOURCE_DIR}/quill/test/misc/DocTestExtensions.cpp)
list(APPEND SOURCES ${ADD_SOURCE_FILES})
# Create a test executable
add_executable(${TEST_NAME} "")
set_common_compile_options(${TEST_NAME})
# Add sources
target_sources(${TEST_NAME} PRIVATE ${SOURCES} ${HEADER_FILES})
# include dirs
target_include_directories(${TEST_NAME}
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/quill/test/misc>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/quill/test/bundled>
$<INSTALL_INTERFACE:include>
PRIVATE
${PROJECT_SOURCE_DIR}/quill/test)
# Link dependencies
target_link_libraries(${TEST_NAME} quill)
# Do not decay cxx standard if not specified
set_property(TARGET ${TEST_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
# Set output test directory
set_target_properties(${TEST_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build/test)
# Add this target to the post build unit tests
doctest_discover_tests(${TEST_NAME})
endfunction()
include(${PROJECT_SOURCE_DIR}/cmake/doctest.cmake)
quill_add_test(TEST_BoundedQueueTest BoundedQueueTest.cpp)
quill_add_test(TEST_DynamicFormatArgStore DynamicFormatArgStoreTest.cpp)
quill_add_test(TEST_FileUtilities FileUtilitiesTest.cpp)
quill_add_test(TEST_LoggerManager LoggerManagerTest.cpp)
quill_add_test(TEST_Logger LoggerTest.cpp)
quill_add_test(TEST_LogLevel LogLevelTest.cpp)
quill_add_test(TEST_MacroMetadata MacroMetadataTest.cpp)
quill_add_test(TEST_MathUtils MathUtilsTest.cpp)
quill_add_test(TEST_PatternFormatter PatternFormatterTest.cpp)
quill_add_test(TEST_RotatingFileSink RotatingFileSinkTest.cpp)
quill_add_test(TEST_SinkManager SinkManagerTest.cpp)
quill_add_test(TEST_StringFromTime StringFromTimeTest.cpp)
quill_add_test(TEST_ThreadContextManager ThreadContextManagerTest.cpp)
quill_add_test(TEST_TimestampFormatter TimestampFormatterTest.cpp)
quill_add_test(TEST_TransitEventBuffer TransitEventBufferTest.cpp)
quill_add_test(TEST_UnboundedQueue.cpp UnboundedQueueTest.cpp)
quill_add_test(TEST_Utility UtilityTest.cpp)
if (NOT QUILL_USE_VALGRIND)
quill_add_test(TEST_RdtscClock RdtscClockTest.cpp)
endif ()

View file

@ -0,0 +1,33 @@
#include "doctest/doctest.h"
#include "misc/DocTestExtensions.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/format.h"
TEST_SUITE_BEGIN("DynamicFormatArgStore");
using namespace quill;
using namespace quill::detail;
/***/
TEST_CASE("dynamic_format_arg_store")
{
// DynamicFormatArgStore store;
DynamicFormatArgStore store;
store.push_back(42);
store.push_back(std::string_view{"abc"});
store.push_back(1.5f);
// c style string allocates
store.push_back("efg");
std::string result = fmtquill::vformat(
"{} and {} and {} and {}",
fmtquill::basic_format_args<fmtquill::format_context>{store.get_types(), store.data()});
REQUIRE_EQ(result, std::string{"42 and abc and 1.5 and efg"});
}
TEST_SUITE_END();

View file

@ -0,0 +1,166 @@
#include "doctest/doctest.h"
#include "misc/DocTestExtensions.h"
#include "quill/core/Common.h"
#include "quill/sinks/FileSink.h"
TEST_SUITE_BEGIN("FileUtilities");
using namespace quill;
using namespace quill::detail;
class FileSinkMock : public quill::FileSink
{
public:
static std::pair<std::string, std::string> extract_stem_and_extension(
fs::path const& filename) noexcept
{
return quill::FileSink::extract_stem_and_extension(filename);
}
static fs::path append_datetime_to_filename(
fs::path const& filename, bool with_time = false, Timezone timezone = Timezone::LocalTime,
std::chrono::system_clock::time_point timestamp = {}) noexcept
{
return quill::FileSink::append_datetime_to_filename(filename, with_time, timezone, timestamp);
}
};
/***/
TEST_CASE("extract_stem_and_extension")
{
{
// simple file
fs::path fname = "logfile";
auto const res = FileSinkMock::extract_stem_and_extension(fname);
REQUIRE_STREQ(res.first.data(), fname.string().data());
REQUIRE_STREQ(res.second.data(), "");
}
{
// simple directory
fs::path fname = "etc";
fname /= "eng";
fname /= "logfile";
auto const res = FileSinkMock::extract_stem_and_extension(fname);
REQUIRE_STREQ(res.first.data(), fname.string().data());
REQUIRE_STREQ(res.second.data(), "");
}
{
// no file extension
fs::path fname = "logfile.";
auto const res = FileSinkMock::extract_stem_and_extension(fname);
REQUIRE_STREQ(res.first.data(), "logfile");
REQUIRE_STREQ(res.second.data(), ".");
}
{
// no file extension - directory
fs::path fname = "etc";
fname /= "eng";
fname /= "logfile.";
fs::path fname_expected = "etc";
fname_expected /= "eng";
fname_expected /= "logfile";
auto const res = FileSinkMock::extract_stem_and_extension(fname);
REQUIRE_STREQ(res.first.data(), fname_expected.string().data());
REQUIRE_STREQ(res.second.data(), ".");
}
{
// hidden file
fs::path fname = ".logfile.";
auto const res = FileSinkMock::extract_stem_and_extension(fname);
REQUIRE_STREQ(res.first.data(), ".logfile");
REQUIRE_STREQ(res.second.data(), ".");
}
#ifndef QUILL_HAS_EXPERIMENTAL_FILESYSTEM
{
// hidden file - directory
fs::path fname = "etc";
fname /= "eng";
fname /= ".logfile";
auto const res = FileSinkMock::extract_stem_and_extension(fname);
// in gcc 7.3.1 with experimental filesystem this line fails with str1: etc/eng != str2: /etc/eng/.logfile
REQUIRE_STREQ(res.first.data(), fname.string().data());
REQUIRE_STREQ(res.second.data(), "");
}
#endif
{
// valid stem and extension
fs::path fname = "logfile.log";
fs::path fname_expected = "logfile";
fs::path extension_expected = ".log";
auto const res = FileSinkMock::extract_stem_and_extension(fname);
REQUIRE_STREQ(res.first.data(), fname_expected.string().data());
REQUIRE_STREQ(res.second.data(), extension_expected.string().data());
}
{
// valid stem and extension - directory
fs::path fname = "etc";
fname /= "eng";
fname /= "logfile.log";
fs::path fname_expected = "etc";
fname_expected /= "eng";
fname_expected /= "logfile";
fs::path extension_expected = ".log";
auto const res = FileSinkMock::extract_stem_and_extension(fname);
REQUIRE_STREQ(res.first.data(), fname_expected.string().data());
REQUIRE_STREQ(res.second.data(), extension_expected.string().data());
}
{
// valid stem and extension - directory
fs::path fname = "/etc";
fname /= "eng";
fname /= "logfile.log";
fs::path fname_expected = "/etc";
fname_expected /= "eng";
fname_expected /= "logfile";
fs::path extension_expected = ".log";
auto const res = FileSinkMock::extract_stem_and_extension(fname);
REQUIRE_STREQ(res.first.data(), fname_expected.string().data());
REQUIRE_STREQ(res.second.data(), extension_expected.string().data());
}
}
/***/
TEST_CASE("append_date_to_filename")
{
std::chrono::system_clock::time_point const ts =
std::chrono::system_clock::time_point{std::chrono::seconds{1583376945}};
fs::path const expected_fname = "logfile_20200305.log";
fs::path const base_fname = "logfile.log";
REQUIRE_STREQ(
FileSinkMock::append_datetime_to_filename(base_fname, false, quill::Timezone::GmtTime, ts).string().data(),
expected_fname.string().data());
}
/***/
TEST_CASE("append_datetime_to_filename")
{
std::chrono::system_clock::time_point const ts =
std::chrono::system_clock::time_point{std::chrono::seconds{1583376945}};
fs::path const expected_fname = "logfile_20200305_025545.log";
fs::path const base_fname = "logfile.log";
REQUIRE_STREQ(
FileSinkMock::append_datetime_to_filename(base_fname, true, quill::Timezone::GmtTime, ts).string().data(),
expected_fname.string().data());
}
TEST_SUITE_END();

View file

@ -0,0 +1,146 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/core/LogLevel.h"
#include "quill/core/QuillError.h"
TEST_SUITE_BEGIN("LogLevel");
using namespace quill;
using namespace std::literals;
/***/
TEST_CASE("loglevel_to_string")
{
{
LogLevel log_level{LogLevel::Dynamic};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "DYNAMIC");
}
{
LogLevel log_level{LogLevel::None};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "NONE");
}
{
LogLevel log_level{LogLevel::Backtrace};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "BACKTRACE");
}
{
LogLevel log_level{LogLevel::Critical};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "CRITICAL");
}
{
LogLevel log_level{LogLevel::Error};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "ERROR");
}
{
LogLevel log_level{LogLevel::Warning};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "WARNING");
}
{
LogLevel log_level{LogLevel::Info};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "INFO");
}
{
LogLevel log_level{LogLevel::Debug};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "DEBUG");
}
{
LogLevel log_level{LogLevel::TraceL1};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "TRACE_L1");
}
{
LogLevel log_level{LogLevel::TraceL2};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "TRACE_L2");
}
{
LogLevel log_level{LogLevel::TraceL3};
REQUIRE_STREQ(loglevel_to_string(log_level).data(), "TRACE_L3");
}
{
#ifndef QUILL_NO_EXCEPTIONS
LogLevel log_level;
log_level = static_cast<LogLevel>(-1);
REQUIRE_THROWS_AS(QUILL_MAYBE_UNUSED auto s = loglevel_to_string(log_level).data(), quill::QuillError);
REQUIRE_THROWS_AS(QUILL_MAYBE_UNUSED auto s = loglevel_to_string_id(log_level).data(), quill::QuillError);
#endif
}
}
/***/
TEST_CASE("loglevel_from_string")
{
{
std::string log_level{"Dynamic"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::Dynamic);
}
{
std::string log_level{"None"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::None);
}
{
std::string log_level{"Backtrace"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::Backtrace);
}
{
std::string log_level{"Critical"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::Critical);
}
{
std::string log_level{"Error"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::Error);
}
{
std::string log_level{"Warning"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::Warning);
}
{
std::string log_level{"Info"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::Info);
}
{
std::string log_level{"Debug"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::Debug);
}
{
std::string log_level{"TraceL1"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::TraceL1);
}
{
std::string log_level{"TraceL2"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::TraceL2);
}
{
std::string log_level{"TraceL3"};
REQUIRE_EQ(loglevel_from_string(log_level), LogLevel::TraceL3);
}
{
#ifndef QUILL_NO_EXCEPTIONS
std::string log_level{"dummy"};
REQUIRE_THROWS_AS(QUILL_MAYBE_UNUSED auto res = loglevel_from_string(log_level), quill::QuillError);
#endif
}
}
TEST_SUITE_END();

View file

@ -0,0 +1,125 @@
#include "doctest/doctest.h"
#include "quill/Logger.h"
#include "quill/core/LoggerManager.h"
#include "quill/sinks/ConsoleSink.h"
TEST_SUITE_BEGIN("LoggerManager");
using namespace quill;
using namespace quill::detail;
/***/
TEST_CASE("create_get_remove_logger")
{
std::shared_ptr<ConsoleSink> sink = std::make_unique<ConsoleSink>();
LoggerManager& lm = LoggerManager::instance();
std::vector<std::shared_ptr<Sink>> sinks;
sinks.push_back(sink);
LoggerBase* logger_1 = lm.create_or_get_logger<Logger>(
"logger_1", std::move(sinks),
"%(time) [%(thread_id)] %(short_source_location:<28) "
"LOG_%(log_level:<9) %(logger:<12) %(message)",
"%H:%M:%S.%Qns", quill::Timezone::GmtTime, ClockSourceType::Tsc, nullptr);
LoggerBase* logger_2 = lm.create_or_get_logger<Logger>(
"logger_2", std::initializer_list<std::shared_ptr<Sink>>{sink},
"[%(thread_id)] %(short_source_location:<28) "
"LOG_%(log_level:<9) %(logger:<12) %(message)",
"%H:%M:%S.%Qns", quill::Timezone::GmtTime, ClockSourceType::Tsc, nullptr);
REQUIRE_EQ(logger_1->get_logger_name(), "logger_1");
REQUIRE_EQ(logger_2->get_logger_name(), "logger_2");
REQUIRE_EQ(lm.get_logger("logger_1")->get_logger_name(), "logger_1");
REQUIRE_EQ(lm.get_logger("logger_2")->get_logger_name(), "logger_2");
REQUIRE_EQ(lm.get_logger("logger_3"), nullptr);
REQUIRE_EQ(lm.get_all_loggers().size(), 2);
REQUIRE_EQ(lm.get_all_loggers()[0]->get_logger_name(), "logger_1");
REQUIRE_EQ(lm.get_all_loggers()[1]->get_logger_name(), "logger_2");
// try_remove_logger_1_queue_has_pending_messages
{
lm.remove_logger(logger_1);
// Logger is only marked invalid at this stage
REQUIRE_EQ(lm.get_all_loggers().size(), 1);
REQUIRE_EQ(lm.get_all_loggers()[0]->get_logger_name(), "logger_2");
// count will include also the invalidated loggers
REQUIRE_EQ(lm.get_number_of_loggers(), 2);
// Logger is not removed yet because we have pending records
std::vector<std::string> const removed_loggers =
lm.cleanup_invalidated_loggers([]() { return false; });
REQUIRE_EQ(removed_loggers.size(), 0);
REQUIRE_EQ(lm.get_number_of_loggers(), 2);
}
// try_remove_logger_1_queue_is_empty
{
lm.remove_logger(logger_1);
// Logger is only marked invalid at this stage
REQUIRE_EQ(lm.get_all_loggers().size(), 1);
REQUIRE_EQ(lm.get_all_loggers()[0]->get_logger_name(), "logger_2");
// count will include also the invalidated loggers
REQUIRE_EQ(lm.get_number_of_loggers(), 2);
// Logger is removed since we pass true meaning the queue is empty
std::vector<std::string> const removed_loggers =
lm.cleanup_invalidated_loggers([]() { return true; });
REQUIRE_EQ(removed_loggers.size(), 1);
REQUIRE_EQ(removed_loggers[0], "logger_1");
REQUIRE_EQ(lm.get_all_loggers().size(), 1);
REQUIRE_EQ(lm.get_all_loggers()[0]->get_logger_name(), "logger_2");
// Number of loggers is also updated after removal
REQUIRE_EQ(lm.get_number_of_loggers(), 1);
}
// try_remove_logger_2_queue_has_pending_messages
{
lm.remove_logger(logger_2);
// Logger is only marked invalid at this stage
REQUIRE_EQ(lm.get_all_loggers().size(), 0);
// Logger is not removed yet because we have pending records
std::vector<std::string> const removed_loggers =
lm.cleanup_invalidated_loggers([]() { return false; });
REQUIRE_EQ(removed_loggers.size(), 0);
REQUIRE_EQ(lm.get_all_loggers().size(), 0);
REQUIRE_EQ(lm.get_number_of_loggers(), 1);
}
// try_remove_logger_2_queue_is_empty
{
lm.remove_logger(logger_2);
// Logger is only marked invalid at this stage
REQUIRE_EQ(lm.get_all_loggers().size(), 0);
// Logger is removed since we pass true meaning the queue is empty
std::vector<std::string> const removed_loggers =
lm.cleanup_invalidated_loggers([]() { return true; });
REQUIRE_EQ(removed_loggers.size(), 1);
REQUIRE_EQ(removed_loggers[0], "logger_2");
REQUIRE_EQ(lm.get_all_loggers().size(), 0);
REQUIRE_EQ(lm.get_number_of_loggers(), 0);
}
}
TEST_SUITE_END();

View file

@ -0,0 +1,70 @@
#include "doctest/doctest.h"
#include "quill/Logger.h"
#include "quill/core/LoggerManager.h"
#include "quill/sinks/ConsoleSink.h"
TEST_SUITE_BEGIN("Logger");
using namespace quill;
using namespace quill::detail;
/***/
TEST_CASE("check_logger")
{
std::shared_ptr<ConsoleSink> sink = std::make_unique<ConsoleSink>();
LoggerManager& lm = LoggerManager::instance();
std::vector<std::shared_ptr<Sink>> sinks;
sinks.push_back(std::move(sink));
Logger* logger_1 = static_cast<Logger*>(lm.create_or_get_logger<Logger>(
"logger_1", std::move(sinks),
"%(time) [%(thread_id)] %(short_source_location:<28) "
"LOG_%(log_level:<9) %(logger:<12) %(message)",
"%H:%M:%S.%Qns", quill::Timezone::GmtTime, ClockSourceType::Tsc, nullptr));
// Check default log level
REQUIRE_EQ(logger_1->get_log_level(), LogLevel::Info);
REQUIRE_EQ(logger_1->get_logger_name(), "logger_1");
#ifndef QUILL_NO_EXCEPTIONS
// throw if backtrace log level is used
REQUIRE_THROWS_AS(logger_1->set_log_level(LogLevel::Backtrace), quill::QuillError);
#endif
}
/***/
TEST_CASE("logger_should_log")
{
std::shared_ptr<ConsoleSink> sink = std::make_unique<ConsoleSink>();
LoggerManager& lm = LoggerManager::instance();
std::vector<std::shared_ptr<Sink>> sinks;
sinks.push_back(std::move(sink));
Logger* logger_1 = static_cast<Logger*>(lm.create_or_get_logger<Logger>(
"logger_1", std::move(sinks),
"%(time) [%(thread_id)] %(short_source_location:<28) "
"LOG_%(log_level:<9) %(logger:<12) %(message)",
"%H:%M:%S.%Qns", quill::Timezone::GmtTime, ClockSourceType::Tsc, nullptr));
REQUIRE_UNARY_FALSE(logger_1->should_log_message<LogLevel::Debug>());
REQUIRE(logger_1->should_log_message<LogLevel::Info>());
REQUIRE(logger_1->should_log_message<LogLevel::Error>());
// change log level
logger_1->set_log_level(LogLevel::TraceL3);
REQUIRE(logger_1->should_log_message<LogLevel::TraceL3>());
REQUIRE(logger_1->should_log_message<LogLevel::Critical>());
// change log level
logger_1->set_log_level(LogLevel::None);
REQUIRE_UNARY_FALSE(logger_1->should_log_message<LogLevel::TraceL3>());
REQUIRE_UNARY_FALSE(logger_1->should_log_message<LogLevel::Critical>());
}
TEST_SUITE_END();

View file

@ -0,0 +1,88 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/core/Common.h"
#include "quill/core/MacroMetadata.h"
TEST_SUITE_BEGIN("MacroMetadata");
using namespace quill::detail;
using namespace quill;
TEST_CASE("construct")
{
{
constexpr MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__FUNCTION__,
"Test fmt {}",
nullptr,
quill::LogLevel::Debug,
MacroMetadata::Event::Log};
REQUIRE_STREQ(macro_metadata.message_format(), "Test fmt {}");
REQUIRE_EQ(macro_metadata.log_level(), quill::LogLevel::Debug);
REQUIRE_STREQ(macro_metadata.line(), "15");
REQUIRE_EQ(macro_metadata.has_named_args(), false);
}
{
constexpr MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__FUNCTION__,
"Test another fmt {name}",
nullptr,
quill::LogLevel::Info,
MacroMetadata::Event::Flush};
REQUIRE_STREQ(macro_metadata.message_format(), "Test another fmt {name}");
REQUIRE_EQ(macro_metadata.log_level(), quill::LogLevel::Info);
REQUIRE_STREQ(macro_metadata.line(), "29");
REQUIRE_EQ(macro_metadata.file_name(), std::string_view{"MacroMetadataTest.cpp"});
REQUIRE_STREQ(macro_metadata.short_source_location(), "MacroMetadataTest.cpp:29");
REQUIRE_STREQ(macro_metadata.caller_function(), "DOCTEST_ANON_FUNC_3");
REQUIRE_EQ(macro_metadata.event(), MacroMetadata::Event::Flush);
REQUIRE_EQ(macro_metadata.has_named_args(), true);
REQUIRE_NE(std::string_view{macro_metadata.source_location()}.find("MacroMetadataTest.cpp"),
std::string_view::npos);
REQUIRE_NE(std::string_view{macro_metadata.full_path()}.find("MacroMetadataTest.cpp"),
std::string_view::npos);
}
{
constexpr MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__FUNCTION__,
"Test another fmt {name} and {surname} and {{age}}",
nullptr,
quill::LogLevel::Info,
MacroMetadata::Event::Flush};
REQUIRE_STREQ(macro_metadata.message_format(),
"Test another fmt {name} and {surname} and {{age}}");
REQUIRE_EQ(macro_metadata.log_level(), quill::LogLevel::Info);
REQUIRE_STREQ(macro_metadata.line(), "51");
REQUIRE_EQ(macro_metadata.file_name(), std::string_view{"MacroMetadataTest.cpp"});
REQUIRE_STREQ(macro_metadata.short_source_location(), "MacroMetadataTest.cpp:51");
REQUIRE_STREQ(macro_metadata.caller_function(), "DOCTEST_ANON_FUNC_3");
REQUIRE_EQ(macro_metadata.event(), MacroMetadata::Event::Flush);
REQUIRE_EQ(macro_metadata.has_named_args(), true);
}
{
constexpr MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__FUNCTION__,
"Test another fmt {0} and {1} and {2}",
nullptr,
quill::LogLevel::Info,
MacroMetadata::Event::Flush};
REQUIRE_STREQ(macro_metadata.message_format(), "Test another fmt {0} and {1} and {2}");
REQUIRE_EQ(macro_metadata.log_level(), quill::LogLevel::Info);
REQUIRE_STREQ(macro_metadata.line(), "70");
REQUIRE_EQ(macro_metadata.file_name(), std::string_view{"MacroMetadataTest.cpp"});
REQUIRE_STREQ(macro_metadata.short_source_location(), "MacroMetadataTest.cpp:70");
REQUIRE_STREQ(macro_metadata.caller_function(), "DOCTEST_ANON_FUNC_3");
REQUIRE_EQ(macro_metadata.event(), MacroMetadata::Event::Flush);
REQUIRE_EQ(macro_metadata.has_named_args(), false);
}
}
TEST_SUITE_END();

View file

@ -0,0 +1,81 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/core/MathUtils.h"
TEST_SUITE_BEGIN("MathUtils");
using namespace quill::detail;
using namespace quill;
TEST_CASE("is_power_of_two")
{
// Test with power of two numbers
CHECK(is_power_of_two(1) == true);
CHECK(is_power_of_two(2) == true);
CHECK(is_power_of_two(4) == true);
CHECK(is_power_of_two(8) == true);
CHECK(is_power_of_two(16) == true);
CHECK(is_power_of_two(1024) == true);
// Test with non-power of two numbers
CHECK(is_power_of_two(0) == false);
CHECK(is_power_of_two(3) == false);
CHECK(is_power_of_two(5) == false);
CHECK(is_power_of_two(6) == false);
CHECK(is_power_of_two(7) == false);
CHECK(is_power_of_two(9) == false);
// Test edge cases
CHECK(is_power_of_two(std::numeric_limits<std::size_t>::max()) == false);
}
TEST_CASE("next_power_of_two_unsigned")
{
// Test with numbers that are already powers of two
CHECK(next_power_of_two(1u) == 1u);
CHECK(next_power_of_two(2u) == 2u);
CHECK(next_power_of_two(4u) == 4u);
CHECK(next_power_of_two(8u) == 8u);
CHECK(next_power_of_two(16u) == 16u);
CHECK(next_power_of_two(1024u) == 1024u);
// Test with numbers that are not powers of two
CHECK(next_power_of_two(0u) == 1u);
CHECK(next_power_of_two(3u) == 4u);
CHECK(next_power_of_two(5u) == 8u);
CHECK(next_power_of_two(6u) == 8u);
CHECK(next_power_of_two(7u) == 8u);
CHECK(next_power_of_two(9u) == 16u);
// Test edge cases
constexpr std::size_t max_power_of_2 = (std::numeric_limits<std::size_t>::max() >> 1) + 1;
CHECK(next_power_of_two(std::numeric_limits<std::size_t>::max() - 1) == max_power_of_2); // Handling near overflow case
CHECK(next_power_of_two(std::numeric_limits<std::size_t>::max() / 2 + 1) == max_power_of_2); // Largest possible input
}
TEST_CASE("next_power_of_two_signed")
{
// Test with positive numbers that are already powers of two
CHECK(next_power_of_two(1) == 1);
CHECK(next_power_of_two(2) == 2);
CHECK(next_power_of_two(4) == 4);
CHECK(next_power_of_two(8) == 8);
CHECK(next_power_of_two(16) == 16);
CHECK(next_power_of_two(1024) == 1024);
// Test with positive numbers that are not powers of two
CHECK(next_power_of_two(0) == 1);
CHECK(next_power_of_two(3) == 4);
CHECK(next_power_of_two(5) == 8);
CHECK(next_power_of_two(6) == 8);
CHECK(next_power_of_two(7) == 8);
CHECK(next_power_of_two(9) == 16);
// Test edge cases
constexpr int max_power_of_2_signed = (std::numeric_limits<int>::max() >> 1) + 1;
CHECK(next_power_of_two(std::numeric_limits<int>::max() - 1) == max_power_of_2_signed); // Handling near overflow case for int
CHECK(next_power_of_two(std::numeric_limits<int>::max() / 2 + 1) == max_power_of_2_signed); // Middle large input for int
}
TEST_SUITE_END();

View file

@ -0,0 +1,419 @@
#include "doctest/doctest.h"
#include "quill/backend/PatternFormatter.h"
#include "quill/core/Common.h"
#include "quill/core/MacroMetadata.h"
#include <chrono>
#include <string_view>
#include <vector>
TEST_SUITE_BEGIN("PatternFormatter");
using namespace quill::detail;
using namespace quill;
char const* thread_name = "test_thread";
std::string_view process_id = "123";
TEST_CASE("default_pattern_formatter")
{
PatternFormatter default_pattern_formatter;
uint64_t const ts{1579815761000023021};
char const* thread_id = "31341";
std::string const logger_name = "test_logger";
MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__func__,
"This the {} formatter {}",
nullptr,
LogLevel::Info,
MacroMetadata::Event::Log};
// Format to a buffer
fmtquill::memory_buffer log_msg;
fmtquill::format_to(std::back_inserter(log_msg),
fmtquill::runtime(macro_metadata.message_format()), "pattern", 1234);
std::vector<std::pair<std::string, std::string>> named_args;
auto const& formatted_buffer = default_pattern_formatter.format(
ts, thread_id, thread_name, process_id, logger_name, loglevel_to_string(macro_metadata.log_level()),
macro_metadata, &named_args, std::string_view{log_msg.data(), log_msg.size()});
// Convert the buffer to a string
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
// Default pattern formatter is using local time to convert the timestamp to timezone, in this test we ignore the timestamp
std::string const expected_string =
"[31341] PatternFormatterTest.cpp:25 LOG_INFO test_logger This the pattern formatter "
"1234\n";
auto const found_expected = formatted_string.find(expected_string);
REQUIRE_NE(found_expected, std::string::npos);
}
TEST_CASE("custom_pattern_message_only")
{
// Message only
PatternFormatter custom_pattern_formatter{"%(log_level_id) %(message)", "%H:%M:%S.%Qns", Timezone::GmtTime};
uint64_t const ts{1579815761000023000};
char const* thread_id = "31341";
std::string const logger_name = "test_logger";
MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__func__,
"This the {1} formatter {0}",
nullptr,
LogLevel::Debug,
MacroMetadata::Event::Log};
// Format to a buffer
fmtquill::memory_buffer log_msg;
fmtquill::format_to(std::back_inserter(log_msg),
fmtquill::runtime(macro_metadata.message_format()), "pattern", 12.34);
std::vector<std::pair<std::string, std::string>> named_args;
auto const& formatted_buffer = custom_pattern_formatter.format(
ts, thread_id, thread_name, process_id, logger_name, loglevel_to_string(macro_metadata.log_level()),
macro_metadata, &named_args, std::string_view{log_msg.data(), log_msg.size()});
// Convert the buffer to a string
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
std::string const expected_string = "D This the 12.34 formatter pattern\n";
REQUIRE_EQ(formatted_string, expected_string);
}
TEST_CASE("custom_pattern_timestamp_precision_nanoseconds")
{
// Custom pattern with part 1 and part 3
PatternFormatter custom_pattern_formatter{
"%(time) [%(thread_id)] %(file_name):%(line_number) LOG_%(log_level) %(logger) "
"%(message) [%(caller_function)]",
"%m-%d-%Y %H:%M:%S.%Qns", Timezone::GmtTime};
uint64_t const ts{1579815761000023000};
char const* thread_id = "31341";
std::string const logger_name = "test_logger";
MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__func__,
"This the {1} formatter {0}",
nullptr,
LogLevel::Debug,
MacroMetadata::Event::Log};
// Format to a buffer
fmtquill::memory_buffer log_msg;
fmtquill::format_to(std::back_inserter(log_msg),
fmtquill::runtime(macro_metadata.message_format()), "pattern", 1234);
std::vector<std::pair<std::string, std::string>> named_args;
auto const& formatted_buffer = custom_pattern_formatter.format(
ts, thread_id, thread_name, process_id, logger_name, loglevel_to_string(macro_metadata.log_level()),
macro_metadata, &named_args, std::string_view{log_msg.data(), log_msg.size()});
// Convert the buffer to a string
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
std::string const expected_string =
"01-23-2020 21:42:41.000023000 [31341] PatternFormatterTest.cpp:98 LOG_DEBUG test_logger "
"This the 1234 formatter pattern [DOCTEST_ANON_FUNC_7]\n";
REQUIRE_EQ(formatted_string, expected_string);
}
TEST_CASE("custom_pattern_timestamp_precision_microseconds")
{
PatternFormatter custom_pattern_formatter{
"%(time) [%(thread_id)] %(file_name):%(line_number) LOG_%(log_level) %(logger) "
"%(message) [%(caller_function)]",
"%m-%d-%Y %H:%M:%S.%Qus", Timezone::GmtTime};
uint64_t const ts{1579815761020123000};
char const* thread_id = "31341";
std::string const logger_name = "test_logger";
MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__func__,
"This the {1} formatter {0}",
nullptr,
LogLevel::Debug,
MacroMetadata::Event::Log};
// Format to a buffer
fmtquill::memory_buffer log_msg;
fmtquill::format_to(std::back_inserter(log_msg),
fmtquill::runtime(macro_metadata.message_format()), "pattern", 1234);
std::vector<std::pair<std::string, std::string>> named_args;
auto const& formatted_buffer = custom_pattern_formatter.format(
ts, thread_id, thread_name, process_id, logger_name, loglevel_to_string(macro_metadata.log_level()),
macro_metadata, &named_args, std::string_view{log_msg.data(), log_msg.size()});
// Convert the buffer to a string
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
std::string const expected_string =
"01-23-2020 21:42:41.020123 [31341] PatternFormatterTest.cpp:136 LOG_DEBUG test_logger "
"This the 1234 formatter pattern [DOCTEST_ANON_FUNC_9]\n";
REQUIRE_EQ(formatted_string, expected_string);
}
TEST_CASE("custom_pattern_timestamp_precision_milliseconds")
{
PatternFormatter custom_pattern_formatter{
"%(time) [%(thread_id)] %(file_name):%(line_number) LOG_%(log_level) %(logger) "
"%(message) [%(caller_function)]",
"%m-%d-%Y %H:%M:%S.%Qms", Timezone::GmtTime};
uint64_t const ts{1579815761099000000};
char const* thread_id = "31341";
std::string const logger_name = "test_logger";
MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__func__,
"This the {1} formatter {0}",
nullptr,
LogLevel::Debug,
MacroMetadata::Event::Log};
// Format to a buffer
fmtquill::memory_buffer log_msg;
fmtquill::format_to(std::back_inserter(log_msg),
fmtquill::runtime(macro_metadata.message_format()), "pattern", 1234);
std::vector<std::pair<std::string, std::string>> named_args;
auto const& formatted_buffer = custom_pattern_formatter.format(
ts, thread_id, thread_name, process_id, logger_name, loglevel_to_string(macro_metadata.log_level()),
macro_metadata, &named_args, std::string_view{log_msg.data(), log_msg.size()});
// Convert the buffer to a string
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
std::string const expected_string =
"01-23-2020 21:42:41.099 [31341] PatternFormatterTest.cpp:174 LOG_DEBUG test_logger This "
"the 1234 formatter pattern [DOCTEST_ANON_FUNC_11]\n";
REQUIRE_EQ(formatted_string, expected_string);
}
TEST_CASE("custom_pattern_timestamp_precision_none")
{
PatternFormatter custom_pattern_formatter{
"%(time) [%(thread_id)] %(file_name):%(line_number) LOG_%(log_level) %(logger) "
"%(message) [%(caller_function)]",
"%m-%d-%Y %H:%M:%S", Timezone::GmtTime};
uint64_t const ts{1579815761099220000};
char const* thread_id = "31341";
std::string const logger_name = "test_logger";
MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__func__,
"This the {1} formatter {0}",
nullptr,
LogLevel::Debug,
MacroMetadata::Event::Log};
// Format to a buffer
fmtquill::memory_buffer log_msg;
fmtquill::format_to(std::back_inserter(log_msg),
fmtquill::runtime(macro_metadata.message_format()), "pattern", 1234);
std::vector<std::pair<std::string, std::string>> named_args;
auto const& formatted_buffer = custom_pattern_formatter.format(
ts, thread_id, thread_name, process_id, logger_name, loglevel_to_string(macro_metadata.log_level()),
macro_metadata, &named_args, std::string_view{log_msg.data(), log_msg.size()});
// Convert the buffer to a string
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
std::string const expected_string =
"01-23-2020 21:42:41 [31341] PatternFormatterTest.cpp:212 LOG_DEBUG test_logger This the "
"1234 formatter pattern [DOCTEST_ANON_FUNC_13]\n";
REQUIRE_EQ(formatted_string, expected_string);
}
TEST_CASE("custom_pattern_timestamp_strftime_reallocation_on_format_string_2")
{
// set a timestamp_format that will cause timestamp _formatted_date to re-allocate.
PatternFormatter custom_pattern_formatter{
"%(time) [%(thread_id)] %(file_name):%(line_number) LOG_%(log_level) %(logger) "
"%(message) [%(caller_function)]",
"%FT%T.%Qus%FT%T", Timezone::GmtTime};
uint64_t const ts{1579815761099220000};
char const* thread_id = "31341";
std::string const logger_name = "test_logger";
MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__func__,
"This the {1} formatter {0}",
nullptr,
LogLevel::Debug,
MacroMetadata::Event::Log};
for (size_t i = 0; i < 5; ++i)
{
// Format to a buffer
fmtquill::memory_buffer log_msg;
fmtquill::format_to(std::back_inserter(log_msg),
fmtquill::runtime(macro_metadata.message_format()), "pattern", 1234);
std::vector<std::pair<std::string, std::string>> named_args;
auto const& formatted_buffer = custom_pattern_formatter.format(
ts, thread_id, thread_name, process_id, logger_name, loglevel_to_string(macro_metadata.log_level()),
macro_metadata, &named_args, std::string_view{log_msg.data(), log_msg.size()});
// Convert the buffer to a string
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
std::string const expected_string =
"2020-01-23T21:42:41.0992202020-01-23T21:42:41 [31341] PatternFormatterTest.cpp:251 "
"LOG_DEBUG test_logger This the 1234 formatter pattern [DOCTEST_ANON_FUNC_15]\n";
REQUIRE_EQ(formatted_string, expected_string);
}
}
TEST_CASE("custom_pattern_timestamp_strftime_reallocation_when_adding_fractional_seconds")
{
// set a timestamp_format that will cause timestamp _formatted_date to re-allocate.
PatternFormatter custom_pattern_formatter{
"%(time) [%(thread_id)] %(file_name):%(line_number) LOG_%(log_level) %(logger) "
"%(message) [%(caller_function)]",
"%FT%T.%T.%Qus%FT%T", Timezone::GmtTime};
uint64_t const ts{1579815761099220000};
char const* thread_id = "31341";
std::string const logger_name = "test_logger";
MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__func__,
"This the {1} formatter {0}",
nullptr,
LogLevel::Debug,
MacroMetadata::Event::Log};
for (size_t i = 0; i < 5; ++i)
{
// Format to a buffer
fmtquill::memory_buffer log_msg;
fmtquill::format_to(std::back_inserter(log_msg),
fmtquill::runtime(macro_metadata.message_format()), "pattern", 1234);
std::vector<std::pair<std::string, std::string>> named_args;
auto const& formatted_buffer = custom_pattern_formatter.format(
ts, thread_id, thread_name, process_id, logger_name, loglevel_to_string(macro_metadata.log_level()),
macro_metadata, &named_args, std::string_view{log_msg.data(), log_msg.size()});
// Convert the buffer to a string
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
std::string const expected_string =
"2020-01-23T21:42:41.21:42:41.0992202020-01-23T21:42:41 [31341] PatternFormatterTest.cpp:293 "
"LOG_DEBUG test_logger This the 1234 formatter pattern [DOCTEST_ANON_FUNC_17]\n";
REQUIRE_EQ(formatted_string, expected_string);
}
}
#ifndef QUILL_NO_EXCEPTIONS
TEST_CASE("invalid_pattern")
{
// missing %)
REQUIRE_THROWS_AS(
PatternFormatter("%(time [%(thread_id)] %(file_name):%(line_number) %(log_level) %(logger) "
"%(message) [%(caller_function)]",
"%H:%M:%S.%Qns", Timezone::GmtTime),
quill::QuillError);
// invalid attribute %(invalid)
REQUIRE_THROWS_AS(
PatternFormatter("%(invalid) [%(thread_id)] %(file_name):%(line_number) %(log_level) %(logger) "
"%(message) [%(caller_function)]",
"%H:%M:%S.%Qns", Timezone::GmtTime),
quill::QuillError);
}
#endif
TEST_CASE("custom_pattern")
{
// Custom pattern with part 1 and part 2
PatternFormatter custom_pattern_formatter{
"%(time) [%(thread_id)] %(file_name):%(line_number) LOG_%(log_level) %(logger) %(message)",
"%m-%d-%Y %H:%M:%S.%Qns", Timezone::GmtTime};
uint64_t const ts{1579815761000023000};
char const* thread_id = "31341";
std::string const logger_name = "test_logger";
MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__func__,
"This the {1} formatter {0}",
nullptr,
LogLevel::Debug,
MacroMetadata::Event::Log};
// Format to a buffer
fmtquill::memory_buffer log_msg;
fmtquill::format_to(std::back_inserter(log_msg),
fmtquill::runtime(macro_metadata.message_format()), "pattern", 1234);
std::vector<std::pair<std::string, std::string>> named_args;
auto const& formatted_buffer = custom_pattern_formatter.format(
ts, thread_id, thread_name, process_id, logger_name, loglevel_to_string(macro_metadata.log_level()),
macro_metadata, &named_args, std::string_view{log_msg.data(), log_msg.size()});
// Convert the buffer to a string
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
std::string const expected_string =
"01-23-2020 21:42:41.000023000 [31341] PatternFormatterTest.cpp:353 LOG_DEBUG test_logger "
"This the 1234 formatter pattern\n";
REQUIRE_EQ(formatted_string, expected_string);
}
TEST_CASE("custom_pattern_part_3_no_format_specifiers")
{
// Custom pattern with a part 3 that has no format specifiers:
// Part 1 - "|{}|{}|"
// Part 3 - "|EOM|"
PatternFormatter custom_pattern_formatter{"|LOG_%(log_level)|%(logger)|%(message)|EOM|",
"%H:%M:%S", Timezone::GmtTime};
uint64_t const ts{1579815761000023000};
char const* thread_id = "31341";
std::string const logger_name = "test_logger";
MacroMetadata macro_metadata{__FILE__ ":" QUILL_STRINGIFY(__LINE__),
__func__,
"This the {1} formatter {0}",
nullptr,
LogLevel::Debug,
MacroMetadata::Event::Log};
// Format to a buffer
fmtquill::memory_buffer log_msg;
fmtquill::format_to(std::back_inserter(log_msg),
fmtquill::runtime(macro_metadata.message_format()), "pattern", 1234);
std::vector<std::pair<std::string, std::string>> named_args;
auto const& formatted_buffer = custom_pattern_formatter.format(
ts, thread_id, thread_name, process_id, logger_name, loglevel_to_string(macro_metadata.log_level()),
macro_metadata, &named_args, std::string_view{log_msg.data(), log_msg.size()});
// Convert the buffer to a string
std::string const formatted_string = fmtquill::to_string(formatted_buffer);
std::string const expected_string =
"|LOG_DEBUG|test_logger|This the 1234 formatter pattern|EOM|\n";
REQUIRE_EQ(formatted_string, expected_string);
}
TEST_SUITE_END();

View file

@ -0,0 +1,49 @@
#include "doctest/doctest.h"
#include "quill/backend/RdtscClock.h"
#include "quill/core/Rdtsc.h"
#include <chrono>
#include <thread>
TEST_SUITE_BEGIN("RdtscClock");
void check_wall_time_now(quill::detail::RdtscClock const& tsc_clock, size_t& failures)
{
std::chrono::milliseconds constexpr offset{10};
auto const wall_time_chrono = std::chrono::system_clock::now().time_since_epoch();
auto const wall_time_tsc = std::chrono::nanoseconds{tsc_clock.time_since_epoch(quill::detail::rdtsc())};
auto const lower_bound = wall_time_chrono - offset;
auto const upper_bound = wall_time_chrono + offset;
if (!((wall_time_tsc > lower_bound) && (wall_time_tsc < upper_bound)))
{
++failures;
if (failures > 1)
{
// wall_time_tsc is not between wall_time_chrono - 1 and wall_time_chrono + 1
FAIL("wall_time_tsc: " << wall_time_tsc.count() << " lower_bound: " << lower_bound.count()
<< " upper_bound: " << upper_bound.count() << "\n");
}
}
}
TEST_CASE("wall_time")
{
quill::detail::RdtscClock const tsc_clock{std::chrono::milliseconds{1200}};
constexpr size_t num_reps{10};
size_t failures{0};
for (size_t i = 1; i <= num_reps; ++i)
{
check_wall_time_now(tsc_clock, failures);
std::this_thread::sleep_for(std::chrono::milliseconds{i * 100});
}
REQUIRE_LE(failures, 1);
}
TEST_SUITE_END();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,68 @@
#include "doctest/doctest.h"
#include "quill/core/SinkManager.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
TEST_SUITE_BEGIN("SinkManager");
using namespace quill;
using namespace quill::detail;
/***/
TEST_CASE("subscribe_get_active_different_sinks")
{
std::string file_1 = "file1.log";
std::string file_2 = "file2.log";
{
// Create a file sink
std::shared_ptr<Sink> file_sink_1_a = SinkManager::instance().create_or_get_sink<quill::FileSink>(
file_1,
[]()
{
quill::FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
// Request the same sink
std::shared_ptr<Sink> file_sink_1_b = SinkManager::instance().create_or_get_sink<quill::FileSink>(
file_1,
[]()
{
quill::FileSinkConfig cfg;
cfg.set_open_mode('a');
return cfg;
}(),
FileEventNotifier{});
std::shared_ptr<Sink> file_sink_1_c = SinkManager::instance().get_sink(file_1);
// Request a new sink of the same file
std::shared_ptr<Sink> file_sink_3 = SinkManager::instance().create_or_get_sink<quill::FileSink>(
file_2,
[]()
{
quill::FileSinkConfig cfg;
cfg.set_open_mode('a');
return cfg;
}(),
FileEventNotifier{});
// Compare the pointers
REQUIRE_EQ(file_sink_1_a.get(), file_sink_1_b.get());
REQUIRE_EQ(file_sink_1_a.get(), file_sink_1_c.get());
REQUIRE_NE(file_sink_1_a.get(), file_sink_3.get());
REQUIRE_EQ(SinkManager::instance().cleanup_unused_sinks(), 0);
}
// Pointers are out of score and we except them cleaned up
REQUIRE_EQ(SinkManager::instance().cleanup_unused_sinks(), 2);
std::remove(file_1.data());
std::remove(file_2.data());
}
TEST_SUITE_END();

View file

@ -0,0 +1,449 @@
#include "quill/backend/StringFromTime.h"
#include "DocTestExtensions.h"
#include "doctest/doctest.h"
#include <ctime>
TEST_SUITE_BEGIN("StringFromTime");
using namespace quill;
using namespace quill::detail;
/***/
TEST_CASE("string_from_time_localtime_format_time")
{
std::string fmt2 = "%H:%M:%S";
StringFromTime string_from_time;
string_from_time.init(fmt2, Timezone::LocalTime);
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
// Try for a few timestamps
for (uint32_t i = 0; i <= 500'000; ++i)
{
// Get the time from string from time
auto const& time_s1 = string_from_time.format_timestamp(raw_ts);
// Get the time from strftime
std::tm time_info{};
quill::detail::localtime_rs(&raw_ts, &time_info);
char buffer[256];
std::strftime(buffer, 256, fmt2.data(), &time_info);
auto const time_s2 = std::string{buffer};
REQUIRE_STREQ(time_s1.data(), time_s2.data());
// Increment the timestamp for the next loop
raw_ts += 1;
}
}
/***/
TEST_CASE("string_from_time_localtime_format_I")
{
std::string fmt2 = "%I:%M:%S%p";
StringFromTime string_from_time;
string_from_time.init(fmt2, Timezone::LocalTime);
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
// Try for a few timestamps
for (uint32_t i = 0; i <= 500'000; ++i)
{
// Get the time from string from time
auto const& time_s1 = string_from_time.format_timestamp(raw_ts);
// Get the time from strftime
std::tm time_info{};
quill::detail::localtime_rs(&raw_ts, &time_info);
char buffer[256];
std::strftime(buffer, 256, fmt2.data(), &time_info);
auto const time_s2 = std::string{buffer};
REQUIRE_STREQ(time_s1.data(), time_s2.data());
// Increment the timestamp for the next loop
raw_ts += 1;
}
}
/***/
TEST_CASE("string_from_time_localtime_fallback_to_strftime")
{
// In this edge case we pass a timestamp that is back in time from our cached timestamp value.
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
// This will create the StringFromTime class and pre-format a string for the timestamp now
std::string fmt2 = "%Y-%m-%dT%H:%M:%SZ";
StringFromTime string_from_time;
string_from_time.init(fmt2, Timezone::GmtTime);
// Ask StringFromTime to format timestamps in the past
// Try for a few timestamps
for (uint32_t i = 0; i <= 500'000; ++i)
{
// Get the time from string from time
auto const& time_s1 = string_from_time.format_timestamp(raw_ts);
// Get the time from strftime
std::tm time_info{};
quill::detail::gmtime_rs(&raw_ts, &time_info);
char buffer[256];
std::strftime(buffer, 256, fmt2.data(), &time_info);
auto const time_s2 = std::string{buffer};
REQUIRE_STREQ(time_s1.data(), time_s2.data());
// Decrement the timestamp for the next loop
raw_ts -= 1;
}
}
/***/
TEST_CASE("string_from_time_localtime_main_format")
{
std::string fmt2 = "%Y-%m-%dT%H:%M:%SZ";
StringFromTime string_from_time;
string_from_time.init(fmt2, Timezone::LocalTime);
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
// Try for a few timestamps
for (uint32_t i = 0; i <= 600'000; ++i)
{
// Get the time from string from time
auto const& time_s1 = string_from_time.format_timestamp(raw_ts);
// Get the time from strftime
std::tm time_info{};
quill::detail::localtime_rs(&raw_ts, &time_info);
char buffer[256];
std::strftime(buffer, 256, fmt2.data(), &time_info);
auto const time_s2 = std::string{buffer};
REQUIRE_STREQ(time_s1.data(), time_s2.data());
// Increment the timestamp for the next loop
raw_ts += 1;
}
}
/***/
TEST_CASE("string_from_time_gmtime_main_format")
{
std::string fmt2 = "%Y-%m-%dT%H:%M:%SZ";
StringFromTime string_from_time;
string_from_time.init(fmt2, Timezone::GmtTime);
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
// Try for a few timestamps
for (uint32_t i = 0; i <= 600'000; ++i)
{
// Get the time from string from time
auto const& time_s1 = string_from_time.format_timestamp(raw_ts);
// Get the time from strftime
std::tm time_info{};
quill::detail::gmtime_rs(&raw_ts, &time_info);
char buffer[256];
std::strftime(buffer, 256, fmt2.data(), &time_info);
auto const time_s2 = std::string{buffer};
REQUIRE_STREQ(time_s1.data(), time_s2.data());
// Increment the timestamp for the next loop
raw_ts += 1;
}
}
TEST_CASE("string_from_time_localtime_main_format_increment_ts")
{
std::string fmt2 = "%Y-%m-%dT%H:%M:%SZ";
StringFromTime string_from_time;
string_from_time.init(fmt2, Timezone::LocalTime);
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
// Try for a few timestamps
for (uint32_t i = 0; i <= 10'000; ++i)
{
// Get the time from string from time
auto const& time_s1 = string_from_time.format_timestamp(raw_ts);
// Get the time from strftime
std::tm time_info{};
quill::detail::localtime_rs(&raw_ts, &time_info);
char buffer[256];
std::strftime(buffer, 256, fmt2.data(), &time_info);
auto const time_s2 = std::string{buffer};
REQUIRE_STREQ(time_s1.data(), time_s2.data());
// Increment the timestamp for the next loop
// This test increments the ts by a huge amount trying to mimic e.g. system clock changes
raw_ts += 7200;
}
}
/***/
TEST_CASE("string_from_time_localtime_empty_cached_indexes")
{
// try with a format that doesn't have hours, minutes, seconds
std::string fmt2 = "%Y-%m-%d";
StringFromTime string_from_time;
string_from_time.init(fmt2, Timezone::LocalTime);
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
// Try for a few timestamps
for (uint32_t i = 0; i <= 500'000; ++i)
{
// Get the time from string from time
auto const& time_s1 = string_from_time.format_timestamp(raw_ts);
// Get the time from strftime
std::tm time_info{};
quill::detail::localtime_rs(&raw_ts, &time_info);
char buffer[256];
std::strftime(buffer, 256, fmt2.data(), &time_info);
auto const time_s2 = std::string{buffer};
REQUIRE_STREQ(time_s1.data(), time_s2.data());
// Increment the timestamp for the next loop
raw_ts += 1;
}
}
#if !defined(_WIN32)
// The following tests don't run on windows because the format identifiers are not supported.
/***/
TEST_CASE("string_from_time_localtime_format_l")
{
std::string fmt2 = "%l:%M:%S%p";
StringFromTime string_from_time;
string_from_time.init(fmt2, Timezone::LocalTime);
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
// Try for a few timestamps
for (uint32_t i = 0; i <= 500'000; ++i)
{
// Get the time from string from time
auto const& time_s1 = string_from_time.format_timestamp(raw_ts);
// Get the time from strftime
std::tm* time_info;
time_info = std::localtime(&raw_ts);
char buffer[256];
std::strftime(buffer, 256, fmt2.data(), time_info);
auto const time_s2 = std::string{buffer};
REQUIRE_STREQ(time_s1.data(), time_s2.data());
// Increment the timestamp for the next loop
raw_ts += 1;
}
}
/***/
TEST_CASE("string_from_time_localtime_format_k")
{
std::string fmt2 = "%k:%M:%S%p";
StringFromTime string_from_time;
string_from_time.init(fmt2, Timezone::LocalTime);
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
// Try for a few timestamps
for (uint32_t i = 0; i <= 500'000; ++i)
{
// Get the time from string from time
auto const& time_s1 = string_from_time.format_timestamp(raw_ts);
// Get the time from strftime
std::tm* time_info;
time_info = std::localtime(&raw_ts);
char buffer[256];
std::strftime(buffer, 256, fmt2.data(), time_info);
auto const time_s2 = std::string{buffer};
REQUIRE_STREQ(time_s1.data(), time_s2.data());
// Increment the timestamp for the next loop
raw_ts += 1;
}
}
/***/
TEST_CASE("string_from_time_localtime_format_s")
{
std::string fmt2 = "%Y-%m-%d %s";
StringFromTime string_from_time;
string_from_time.init(fmt2, Timezone::LocalTime);
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
// Try for a few timestamps
for (uint32_t i = 0; i <= 100'000; ++i)
{
// Get the time from string from time
auto const& time_s1 = string_from_time.format_timestamp(raw_ts);
// Get the time from strftime
std::tm* time_info;
time_info = std::localtime(&raw_ts);
char buffer[256];
std::strftime(buffer, 256, fmt2.data(), time_info);
auto const time_s2 = std::string{buffer};
REQUIRE_STREQ(time_s1.data(), time_s2.data());
// Increment the timestamp for the next loop
raw_ts += 1;
}
}
#endif
class StringFromTimeMock : public quill::detail::StringFromTime
{
public:
static time_t next_noon_or_midnight_timestamp(time_t timestamp, Timezone timezone) noexcept
{
return quill::detail::StringFromTime::_next_noon_or_midnight_timestamp(timestamp, timezone);
}
static time_t nearest_hour_timestamp(time_t timestamp) noexcept
{
return quill::detail::StringFromTime::_nearest_hour_timestamp(timestamp);
}
static time_t next_hour_timestamp(time_t timestamp) noexcept
{
return quill::detail::StringFromTime::_next_hour_timestamp(timestamp);
}
static std::vector<char> safe_strftime(char const* format_string, time_t timestamp, Timezone timezone)
{
return quill::detail::StringFromTime::_safe_strftime(format_string, timestamp, timezone);
}
};
/***/
TEST_CASE("next_noon_or_midnight_timestamp")
{
// We do not test local because if we hardcode an expected_timestamp in our local timezone
// it can be different in another system
{
// Noon utc
time_t constexpr timestamp{1599033200};
time_t constexpr expected_timestamp{1599048000};
time_t const res = StringFromTimeMock::next_noon_or_midnight_timestamp(timestamp, Timezone::GmtTime);
REQUIRE_EQ(res, expected_timestamp);
}
{
// Midnight utc
time_t constexpr timestamp{1599079200};
time_t constexpr expected_timestamp{1599091200};
time_t const res = StringFromTimeMock::next_noon_or_midnight_timestamp(timestamp, Timezone::GmtTime);
REQUIRE_EQ(res, expected_timestamp);
}
}
/***/
TEST_CASE("nearest_hour_timestamp")
{
time_t constexpr timestamp = 1599473669;
time_t constexpr expected_timestamp = 1599472800;
REQUIRE_EQ(StringFromTimeMock::nearest_hour_timestamp(timestamp), expected_timestamp);
}
/***/
TEST_CASE("next_hour_timestamp")
{
time_t constexpr timestamp = 1599473669;
time_t constexpr expected_timestamp = 1599476400;
REQUIRE_EQ(StringFromTimeMock::next_hour_timestamp(timestamp), expected_timestamp);
}
/***/
TEST_CASE("safe_strftime_resize")
{
// e.g. "Monday September 2020 (09/07/20) 15:37 EEST"
constexpr char const* format_string = "%A %B %Y (%x) %R %Z";
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
std::tm time_info{};
quill::detail::localtime_rs(&raw_ts, &time_info);
// we will format a string greater than 32
char expected_result[256];
std::strftime(expected_result, 256, format_string, &time_info);
// Also try our version
std::string const safe_strftime_result =
std::string{StringFromTimeMock::safe_strftime(format_string, raw_ts, Timezone::LocalTime).data()};
REQUIRE_STREQ(expected_result, safe_strftime_result.data());
}
TEST_CASE("safe_strftime_empty")
{
// Get the timestamp now
time_t raw_ts;
std::time(&raw_ts);
std::tm time_info{};
quill::detail::localtime_rs(&raw_ts, &time_info);
// we will format a string greater than 32
char expected_result[256];
std::strftime(expected_result, 256, "", &time_info);
// Also try our version
std::string const safe_strftime_result =
std::string{StringFromTimeMock::safe_strftime("", raw_ts, Timezone::LocalTime).data()};
REQUIRE_STREQ(expected_result, safe_strftime_result.data());
}
TEST_SUITE_END();

View file

@ -0,0 +1,115 @@
#include "doctest/doctest.h"
#include "quill/core/FrontendOptions.h"
#include "quill/core/ThreadContextManager.h"
#include <array>
#include <thread>
TEST_SUITE_BEGIN("ThreadContextManager");
using namespace quill;
using namespace quill::detail;
/***/
TEST_CASE("add_and_remove_thread_contexts")
{
// 1) Test that every time a new thread context is added to the thread context shared collection
// and to the thread context cache when a new thread spawns and we load the cache
// 2) Test that the thread context is invalidated when the thread that created it completes
// 3) Test that when the threads complete they are removed
constexpr uint32_t tries = 4;
for (uint32_t k = 0; k < tries; ++k)
{
constexpr size_t num_threads{25};
std::array<std::thread, num_threads> threads;
std::array<std::atomic<bool>, num_threads> terminate_flag{};
std::fill(terminate_flag.begin(), terminate_flag.end(), false);
std::atomic<uint32_t> threads_started{0};
// spawn x number of threads
for (size_t i = 0; i < threads.size(); ++i)
{
auto& thread_terminate_flag = terminate_flag[i];
threads[i] = std::thread(
[&thread_terminate_flag, &threads_started]()
{
// create a context for that thread
ThreadContext* tc = get_local_thread_context<FrontendOptions>();
REQUIRE(tc->has_unbounded_queue_type());
REQUIRE(tc->has_blocking_queue());
REQUIRE_FALSE(tc->has_bounded_queue_type());
REQUIRE_FALSE(tc->has_dropping_queue());
threads_started.fetch_add(1);
while (!thread_terminate_flag.load())
{
// loop waiting for main to signal
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
});
}
// main wait for all of them to start
while (threads_started.load() < num_threads)
{
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
// Check we have exactly as many thread contexts as the amount of threads in our backend cache
uint64_t thread_ctx_cnt{0};
// Check all thread contexts in the backend thread contexts cache
ThreadContextManager::instance().for_each_thread_context(
[&thread_ctx_cnt](ThreadContext const* tc)
{
REQUIRE(tc->is_valid_context());
REQUIRE(tc->get_spsc_queue<FrontendOptions::queue_type>().empty());
++thread_ctx_cnt;
});
REQUIRE_EQ(thread_ctx_cnt, num_threads);
REQUIRE_FALSE(ThreadContextManager::instance().has_invalid_thread_context());
// terminate all threads - This will invalidate all the thread contexts
for (size_t j = 0; j < threads.size(); ++j)
{
terminate_flag[j].store(true);
threads[j].join();
}
REQUIRE(ThreadContextManager::instance().has_invalid_thread_context());
// Now check all thread contexts still exist but they are invalided and then remove them
REQUIRE(ThreadContextManager::instance().has_invalid_thread_context());
// For this we use the old cache avoiding to update it - This never happens in the real logger
std::vector<ThreadContext const*> tc_cache;
ThreadContextManager::instance().for_each_thread_context(
[&tc_cache](ThreadContext const* tc)
{
REQUIRE_FALSE(tc->is_valid_context());
REQUIRE(tc->get_spsc_queue<FrontendOptions::queue_type>().empty());
tc_cache.push_back(tc);
});
// Remove them
for (ThreadContext const* tc : tc_cache)
{
ThreadContextManager::instance().remove_shared_invalidated_thread_context(tc);
}
// Check all are removed
bool tc_found = false;
ThreadContextManager::instance().for_each_thread_context([&tc_found](ThreadContext const* tc)
{ tc_found = true; });
REQUIRE_FALSE(tc_found);
}
}
TEST_SUITE_END();

View file

@ -0,0 +1,183 @@
#include "doctest/doctest.h"
#include "misc/DocTestExtensions.h"
#include "quill/backend/TimestampFormatter.h"
#include "quill/core/QuillError.h"
TEST_SUITE_BEGIN("TimestampFormatter");
using namespace quill::detail;
/***/
TEST_CASE("simple_format_string")
{
// invalid format strings
#if !defined(QUILL_NO_EXCEPTIONS)
REQUIRE_THROWS_AS(TimestampFormatter ts_formatter{"%I:%M%p%Qms%S%Qus z"}, quill::QuillError);
REQUIRE_THROWS_AS(TimestampFormatter ts_formatter{"%I:%M%p%Qms%S%Qus%Qns z"}, quill::QuillError);
REQUIRE_THROWS_AS(TimestampFormatter ts_formatter{"%I:%M%p%S%Qus%Qns z"}, quill::QuillError);
#endif
// valid simple string
REQUIRE_NOTHROW(TimestampFormatter ts_formatter{"%I:%M%p%S%Qns z"});
}
/***/
TEST_CASE("format_string_no_additional_specifier")
{
std::chrono::nanoseconds constexpr timestamp{1587161887987654321};
// simple formats without any ms/us/ns specifiers
{
TimestampFormatter ts_formatter{"%H:%M:%S", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07");
}
{
TimestampFormatter ts_formatter{"%F %H:%M:%S", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "2020-04-17 22:18:07");
}
// large simple string to cause reallocation
{
TimestampFormatter ts_formatter{"%A %B %d %T %Y %F", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "Friday April 17 22:18:07 2020 2020-04-17");
}
}
/***/
TEST_CASE("format_string_with_millisecond_precision")
{
// simple
{
std::chrono::nanoseconds constexpr timestamp{1587161887987654321};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qms", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.987");
}
// with double formatting
{
std::chrono::nanoseconds constexpr timestamp{1587161887803654321};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qms %D", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.803 04/17/20");
}
// with double formatting 2
{
std::chrono::nanoseconds constexpr timestamp{1587161887023654321};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qms-%G", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.023-2020");
}
// with zeros
{
std::chrono::nanoseconds constexpr timestamp{1587161887009654321};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qms", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.009");
}
}
/***/
TEST_CASE("format_string_with_microsecond_precision")
{
// simple
{
std::chrono::nanoseconds constexpr timestamp{1587161887987654321};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qus", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.987654");
}
// with double formatting
{
std::chrono::nanoseconds constexpr timestamp{1587161887803654321};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qus %D", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.803654 04/17/20");
}
// with double formatting 2
{
std::chrono::nanoseconds constexpr timestamp{1587161887010654321};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qus-%G", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.010654-2020");
}
// with zeros
{
std::chrono::nanoseconds constexpr timestamp{1587161887000004321};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qus", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.000004");
}
}
/***/
TEST_CASE("format_string_with_nanosecond_precision")
{
// simple
{
std::chrono::nanoseconds constexpr timestamp{1587161887987654321};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qns", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.987654321");
}
// with double formatting
{
std::chrono::nanoseconds constexpr timestamp{1587161887803654320};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qns %D", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.803654320 04/17/20");
}
// with double formatting 2
{
std::chrono::nanoseconds constexpr timestamp{1587161887000654321};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qns-%G", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.000654321-2020");
}
// with zeros
{
std::chrono::nanoseconds constexpr timestamp{1587161887000000009};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qns", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.000000009");
}
// with max
{
std::chrono::nanoseconds constexpr timestamp{1587161887999999999};
TimestampFormatter ts_formatter{"%H:%M:%S.%Qns", quill::Timezone::GmtTime};
auto const& result = ts_formatter.format_timestamp(timestamp);
REQUIRE_STREQ(result.data(), "22:18:07.999999999");
}
}
TEST_SUITE_END();

View file

@ -0,0 +1,254 @@
#include "DocTestExtensions.h"
#include "doctest/doctest.h"
#include "quill/backend/TransitEventBuffer.h"
TEST_SUITE_BEGIN("TransitEventBuffer");
using namespace quill;
using namespace quill::detail;
/***/
TEST_CASE("transit_event_bounded_buffer")
{
BoundedTransitEventBuffer bte{4};
REQUIRE_EQ(bte.capacity(), 4);
for (size_t i = 0; i < 12; ++i)
{
REQUIRE_FALSE(bte.front());
REQUIRE_EQ(bte.size(), 0);
{
TransitEvent* te1 = bte.back();
REQUIRE(te1);
te1->named_args = std::make_unique<std::vector<std::pair<std::string, std::string>>>();
te1->named_args->clear();
te1->named_args->emplace_back(std::string{"test1"} + std::to_string(i), std::string{});
bte.push_back();
}
REQUIRE_EQ(bte.size(), 1);
{
TransitEvent* te2 = bte.back();
REQUIRE(te2);
te2->named_args = std::make_unique<std::vector<std::pair<std::string, std::string>>>();
te2->named_args->clear();
te2->named_args->emplace_back(std::string{"test2"} + std::to_string(i), std::string{});
bte.push_back();
}
REQUIRE_EQ(bte.size(), 2);
{
TransitEvent* te3 = bte.back();
REQUIRE(te3);
te3->named_args = std::make_unique<std::vector<std::pair<std::string, std::string>>>();
te3->named_args->clear();
te3->named_args->emplace_back(std::string{"test3"} + std::to_string(i), std::string{});
bte.push_back();
}
REQUIRE_EQ(bte.size(), 3);
{
TransitEvent* te4 = bte.back();
REQUIRE(te4);
te4->named_args = std::make_unique<std::vector<std::pair<std::string, std::string>>>();
te4->named_args->clear();
te4->named_args->emplace_back(std::string{"test4"} + std::to_string(i), std::string{});
bte.push_back();
}
REQUIRE_EQ(bte.size(), 4);
// read
{
TransitEvent* te1 = bte.front();
REQUIRE(te1);
std::string const expected = std::string{"test1"} + std::to_string(i);
REQUIRE_STREQ((*te1->named_args)[0].first.data(), expected.data());
bte.pop_front();
}
REQUIRE_EQ(bte.size(), 3);
{
TransitEvent* te2 = bte.front();
REQUIRE(te2);
std::string const expected = std::string{"test2"} + std::to_string(i);
REQUIRE_STREQ((*te2->named_args)[0].first.data(), expected.data());
bte.pop_front();
}
REQUIRE_EQ(bte.size(), 2);
{
TransitEvent* te3 = bte.front();
REQUIRE(te3);
std::string const expected = std::string{"test3"} + std::to_string(i);
REQUIRE_STREQ((*te3->named_args)[0].first.data(), expected.data());
bte.pop_front();
}
REQUIRE_EQ(bte.size(), 1);
{
TransitEvent* te4 = bte.front();
REQUIRE(te4);
std::string const expected = std::string{"test4"} + std::to_string(i);
REQUIRE_STREQ((*te4->named_args)[0].first.data(), expected.data());
bte.pop_front();
}
REQUIRE_EQ(bte.size(), 0);
REQUIRE_FALSE(bte.front());
}
}
/***/
TEST_CASE("transit_event_bounded_buffer_integer_overflow")
{
BoundedTransitEventBufferImpl<uint8_t> bte{128};
size_t constexpr iterations = static_cast<size_t>(std::numeric_limits<uint8_t>::max()) * 8ull;
for (size_t i = 0; i < iterations; ++i)
{
REQUIRE_EQ(bte.size(), 0);
{
TransitEvent* te = bte.back();
REQUIRE(te);
te->named_args = std::make_unique<std::vector<std::pair<std::string, std::string>>>();
te->named_args->clear();
te->named_args->emplace_back(std::string{"test"} + std::to_string(i), std::string{});
bte.push_back();
}
REQUIRE_EQ(bte.size(), 1);
// read
{
TransitEvent* te = bte.front();
REQUIRE(te);
std::string const expected = std::string{"test"} + std::to_string(i);
REQUIRE_STREQ((*te->named_args)[0].first.data(), expected.data());
bte.pop_front();
}
}
}
/***/
TEST_CASE("transit_event_unbounded_buffer")
{
UnboundedTransitEventBuffer bte{4};
REQUIRE_FALSE(bte.front());
REQUIRE(bte.empty());
REQUIRE_EQ(bte.size(), 0);
for (size_t i = 0; i < 128; ++i)
{
{
TransitEvent* te1 = bte.back();
REQUIRE(te1);
te1->named_args = std::make_unique<std::vector<std::pair<std::string, std::string>>>();
te1->named_args->clear();
te1->named_args->emplace_back(std::string{"test1"} + std::to_string(i), std::string{});
bte.push_back();
}
REQUIRE_EQ(bte.size(), i * 4 + 1);
{
TransitEvent* te2 = bte.back();
REQUIRE(te2);
te2->named_args = std::make_unique<std::vector<std::pair<std::string, std::string>>>();
te2->named_args->clear();
te2->named_args->emplace_back(std::string{"test2"} + std::to_string(i), std::string{});
bte.push_back();
}
REQUIRE_EQ(bte.size(), i * 4 + 2);
{
TransitEvent* te3 = bte.back();
REQUIRE(te3);
te3->named_args = std::make_unique<std::vector<std::pair<std::string, std::string>>>();
te3->named_args->clear();
te3->named_args->emplace_back(std::string{"test3"} + std::to_string(i), std::string{});
bte.push_back();
}
REQUIRE_EQ(bte.size(), i * 4 + 3);
{
TransitEvent* te4 = bte.back();
REQUIRE(te4);
te4->named_args = std::make_unique<std::vector<std::pair<std::string, std::string>>>();
te4->named_args->clear();
te4->named_args->emplace_back(std::string{"test4"} + std::to_string(i), std::string{});
bte.push_back();
}
REQUIRE_EQ(bte.size(), i * 4 + 4);
}
for (size_t i = 0; i < 128; ++i)
{
// read
{
TransitEvent* te1 = bte.front();
REQUIRE(te1);
std::string const expected = std::string{"test1"} + std::to_string(i);
REQUIRE_STREQ((*te1->named_args)[0].first.data(), expected.data());
bte.pop_front();
}
REQUIRE_EQ(bte.size(), (128 - i) * 4 - 1);
{
TransitEvent* te2 = bte.front();
REQUIRE(te2);
std::string const expected = std::string{"test2"} + std::to_string(i);
REQUIRE_STREQ((*te2->named_args)[0].first.data(), expected.data());
bte.pop_front();
}
REQUIRE_EQ(bte.size(), (128 - i) * 4 - 2);
{
TransitEvent* te3 = bte.front();
REQUIRE(te3);
std::string const expected = std::string{"test3"} + std::to_string(i);
REQUIRE_STREQ((*te3->named_args)[0].first.data(), expected.data());
bte.pop_front();
}
REQUIRE_EQ(bte.size(), (128 - i) * 4 - 3);
{
TransitEvent* te4 = bte.front();
REQUIRE(te4);
std::string const expected = std::string{"test4"} + std::to_string(i);
REQUIRE_STREQ((*te4->named_args)[0].first.data(), expected.data());
bte.pop_front();
}
REQUIRE_EQ(bte.size(), (128 - i) * 4 - 4);
}
REQUIRE_FALSE(bte.front());
REQUIRE(bte.empty());
REQUIRE_EQ(bte.size(), 0);
}

View file

@ -0,0 +1,68 @@
#include "doctest/doctest.h"
#include "quill/core/UnboundedSPSCQueue.h"
#include <cstring>
#include <thread>
#include <vector>
TEST_SUITE_BEGIN("UnboundedQueue");
using namespace quill::detail;
TEST_CASE("unbounded_queue_read_write_multithreaded_plain_ints")
{
UnboundedSPSCQueue buffer{1024};
std::thread producer_thread(
[&buffer]()
{
for (uint32_t wrap_cnt = 0; wrap_cnt < 20; ++wrap_cnt)
{
for (uint32_t i = 0; i < 8192; ++i)
{
auto* write_buffer = buffer.prepare_write(sizeof(uint32_t), quill::QueueType::UnboundedBlocking);
while (!write_buffer)
{
std::this_thread::sleep_for(std::chrono::microseconds{2});
write_buffer = buffer.prepare_write(sizeof(uint32_t), quill::QueueType::UnboundedBlocking);
}
std::memcpy(write_buffer, &i, sizeof(uint32_t));
buffer.finish_write(sizeof(uint32_t));
buffer.commit_write();
}
}
});
// Delay creating the consumer thread
std::this_thread::sleep_for(std::chrono::milliseconds{300});
std::thread consumer_thread(
[&buffer]()
{
for (uint32_t wrap_cnt = 0; wrap_cnt < 20; ++wrap_cnt)
{
for (uint32_t i = 0; i < 8192; ++i)
{
auto read_result = buffer.prepare_read();
while (!read_result.read_pos)
{
std::this_thread::sleep_for(std::chrono::microseconds{2});
read_result = buffer.prepare_read();
}
auto const value = reinterpret_cast<uint32_t const*>(read_result.read_pos);
REQUIRE_EQ(*value, i);
buffer.finish_read(sizeof(uint32_t));
buffer.commit_read();
}
}
});
producer_thread.join();
consumer_thread.join();
REQUIRE(buffer.empty());
}
TEST_SUITE_END();

View file

@ -0,0 +1,55 @@
#include "doctest/doctest.h"
#include "quill/Utility.h"
#include <cstdint>
#include <cstring>
#include <cstdint>
TEST_SUITE_BEGIN("Utility");
/***/
TEST_CASE("ascii_string_to_hex_1")
{
std::string buffer = "Hello World";
std::string const result = quill::utility::to_hex(buffer.data(), buffer.length());
std::string const expected = "48 65 6C 6C 6F 20 57 6F 72 6C 64";
REQUIRE_EQ(result, expected);
}
/***/
TEST_CASE("ascii_string_to_hex_2")
{
std::string buffer = "A longer ASCII text";
std::string const result = quill::utility::to_hex(buffer.data(), buffer.length());
std::string const expected = "41 20 6C 6F 6E 67 65 72 20 41 53 43 49 49 20 74 65 78 74";
REQUIRE_EQ(result, expected);
}
/***/
TEST_CASE("ascii_string_to_hex_2_const")
{
std::string const buffer = "A longer ASCII text";
std::string const result = quill::utility::to_hex(buffer.data(), buffer.length());
std::string const expected = "41 20 6C 6F 6E 67 65 72 20 41 53 43 49 49 20 74 65 78 74";
REQUIRE_EQ(result, expected);
}
/***/
TEST_CASE("byte_buffer_to_hex_1")
{
uint32_t input = 431234;
unsigned char buffer[4];
std::memcpy(buffer, reinterpret_cast<char*>(&input), sizeof(input));
// non const overload
std::string const result = quill::utility::to_hex(buffer, 4);
std::string const expected = "82 94 06 00";
REQUIRE_EQ(result, expected);
// const overload
unsigned char* const buffer_const = &buffer[0];
std::string const result_2 = quill::utility::to_hex(buffer_const, 4);
REQUIRE_EQ(result_2, expected);
}
TEST_SUITE_END();