bleh
This commit is contained in:
parent
6e5045f1f4
commit
693fa17d10
266 changed files with 60543 additions and 1000 deletions
|
@ -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();
|
66
subprojects/quill-4.2.0/quill/test/unit_tests/CMakeLists.txt
Normal file
66
subprojects/quill-4.2.0/quill/test/unit_tests/CMakeLists.txt
Normal 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 ()
|
|
@ -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();
|
|
@ -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();
|
146
subprojects/quill-4.2.0/quill/test/unit_tests/LogLevelTest.cpp
Normal file
146
subprojects/quill-4.2.0/quill/test/unit_tests/LogLevelTest.cpp
Normal 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();
|
|
@ -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();
|
70
subprojects/quill-4.2.0/quill/test/unit_tests/LoggerTest.cpp
Normal file
70
subprojects/quill-4.2.0/quill/test/unit_tests/LoggerTest.cpp
Normal 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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
|
@ -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();
|
Loading…
Add table
Add a link
Reference in a new issue