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,2 @@
add_subdirectory(unit_tests)
add_subdirectory(integration_tests)

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-2019 Viktor Kirilov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,143 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("arithmetic_types_logging")
{
static constexpr char const* filename = "arithmetic_types_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
BackendOptions bo;
bo.error_notifier = [](std::string const&) {};
Backend::start(bo);
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
bool b = true;
LOG_INFO(logger, "v [{}]", b);
char c = 'a';
LOG_INFO(logger, "c [{}]", c);
short int si = -12;
LOG_INFO(logger, "si [{}]", si);
int i = -123;
LOG_INFO(logger, "i [{}]", i);
long int li = 9876;
LOG_INFO(logger, "li [{}]", li);
long long int lli = 321;
LOG_INFO(logger, "lli [{}]", lli);
unsigned short int usi = 15;
LOG_INFO(logger, "usi [{}]", usi);
unsigned int ui = 123;
LOG_INFO(logger, "ui [{}]", ui);
unsigned long int uli = 2876;
LOG_INFO(logger, "uli [{}]", uli);
unsigned long long int ulli = 1321;
LOG_INFO(logger, "ulli [{}]", ulli);
float f = 323.31f;
LOG_INFO(logger, "f [{}]", f);
double d = 3213213.123;
LOG_INFO(logger, "d [{}]", d);
int const& cri = i;
LOG_INFO(logger, "cri [{}]", cri);
int& ci = i;
LOG_INFO(logger, "ci [{}]", ci);
LOG_INFO(logger, "invalid format [{%f}]", 321.1);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
REQUIRE_FALSE(Backend::is_running());
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " v [true]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c [a]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " si [-12]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i [-123]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " li [9876]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " lli [321]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usi [15]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ui [123]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uli [2876]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ulli [1321]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " f [323.31]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d [3213213.123]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cri [-123]"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_INFO " + logger_name +
" [Could not format log statement. message: \"invalid format [{%f}]\""}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,116 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
#if !defined(QUILL_NO_EXCEPTIONS)
/***/
TEST_CASE("backend_exception_notifier")
{
static constexpr char const* filename = "backend_exception_notifier.log";
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger("logger", std::move(file_sink));
// counter to check our error handler was invoked
// atomic because we check this value on this thread, but the backend worker thread updates it
std::atomic<size_t> error_notifier_invoked{0};
// Set invalid thread name
BackendOptions backend_options;
// Setting to an invalid CPU. When we call quill::start() our error handler will be invoked and an error will be logged
backend_options.backend_cpu_affinity = static_cast<uint16_t>(std::numeric_limits<uint16_t>::max() - 1);
backend_options.thread_name =
"Lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_sed_do_eiusmod_tempor_incididunt_ut_"
"labore_et_dolore_magna_aliqua";
backend_options.error_notifier = [logger, &error_notifier_invoked](std::string const& error_message)
{
// Log inside the function from the backend thread, for testing
// Note that this log is asynchronous here which means when error_notifier_invoked is
// incremented does not mean we have logged this yet
// flush_log() is not permitted inside this callback
LOG_WARNING(logger, "error handler invoked {}", error_message);
error_notifier_invoked.fetch_add(1);
};
Backend::start(backend_options);
// Log a message and wait for it to get processed, that way we know the backend thread has started
LOG_INFO(logger, "frontend");
logger->flush_log();
// Check our handler was invoked since either set_backend_thread_name or set_backend_thread_cpu_affinity should have failed
REQUIRE_GE(error_notifier_invoked.load(), 1);
// Now we can try to get another exception by calling LOG_BACKTRACE without calling init first
error_notifier_invoked.store(0);
LOG_BACKTRACE(logger, "Backtrace message");
logger->flush_log();
// Check our handler was invoked
REQUIRE_EQ(error_notifier_invoked.load(), 1);
// Pass an invalid fmt format and see if it throws
error_notifier_invoked.store(0);
LOG_INFO(logger, "Format {:>321.}", 321.3);
logger->flush_log();
// Check our handler was invoked
REQUIRE_EQ(error_notifier_invoked.load(), 1);
// Wait until the backend thread stops for test stability
Backend::stop();
// After the backend has stopped, all messages include the async ones from the notifier will
// be in the log file. At this point we can safely check it
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_GE(file_contents.size(), 5);
// Look for the async errors
std::string const expected_string_1 = "error handler invoked Failed to set cpu affinity ";
std::string const expected_string_2 = "error handler invoked Failed to set thread name ";
bool const has_any_error = quill::testing::file_contains(file_contents, expected_string_1) ||
quill::testing::file_contains(file_contents, expected_string_2);
REQUIRE(has_any_error);
std::string const expected_string_3 = "error handler invoked logger->init_backtrace(...)";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_3));
std::string const expected_string_4 = "error handler invoked [Could not format log statement.";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_4));
testing::remove_file(filename);
}
#endif

View file

@ -0,0 +1,90 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <thread>
#include <vector>
using namespace quill;
/***/
TEST_CASE("backend_long_sleep_and_notify")
{
static constexpr size_t number_of_messages = 100u;
static constexpr size_t number_of_threads = 4;
static constexpr char const* filename = "log_backend_long_sleep_and_notify.log";
static std::string const logger_name_prefix = "logger_";
// Start the backend thread
BackendOptions backend_options;
backend_options.sleep_duration = std::chrono::hours{24};
Backend::start(backend_options);
std::vector<std::thread> threads;
for (size_t i = 0; i < number_of_threads; ++i)
{
threads.emplace_back(
[i]() mutable
{
// Also use preallocate
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger =
Frontend::create_or_get_logger(logger_name_prefix + std::to_string(i), std::move(file_sink));
for (size_t j = 0; j < number_of_messages; ++j)
{
LOG_INFO(logger, "Hello from thread {thread_index} this is message {message_num}", i, j);
}
});
}
for (auto& elem : threads)
{
elem.join();
}
// The backend worker is still sleeping here so no file should exist
REQUIRE_EQ(testing::file_contents(filename).size(), 0);
// Notify the backend to wake up and process
Backend::notify();
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages * number_of_threads);
for (size_t i = 0; i < number_of_threads; ++i)
{
// for each thread
for (size_t j = 0; j < number_of_messages; ++j)
{
std::string expected_string = logger_name_prefix + std::to_string(i) +
" Hello from thread " + std::to_string(i) + " this is message " + std::to_string(j);
REQUIRE(testing::file_contains(file_contents, expected_string));
}
}
testing::remove_file(filename);
}

View file

@ -0,0 +1,91 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <thread>
#include <vector>
using namespace quill;
/***/
TEST_CASE("backend_transit_buffer_hard_limit")
{
static constexpr size_t number_of_messages = 500u;
static constexpr size_t number_of_threads = 10;
static constexpr char const* filename = "backend_transit_buffer_hard_limit.log";
static std::string const logger_name_prefix = "logger_";
// Start the backend thread
BackendOptions backend_options;
backend_options.transit_events_hard_limit = 0;
backend_options.transit_event_buffer_initial_capacity = 0;
Backend::start(backend_options);
std::vector<std::thread> threads;
for (size_t i = 0; i < number_of_threads; ++i)
{
threads.emplace_back(
[i]() mutable
{
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger =
Frontend::create_or_get_logger(logger_name_prefix + std::to_string(i), std::move(file_sink));
for (size_t j = 0; j < number_of_messages; ++j)
{
LOG_INFO(logger, "Hello from thread {thread_index} this is message {message_num}", i, j);
}
});
}
for (auto& elem : threads)
{
elem.join();
}
// flush all log and remove all loggers
for (Logger* logger : Frontend::get_all_loggers())
{
logger->flush_log();
Frontend::remove_logger(logger);
}
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages * number_of_threads);
for (size_t i = 0; i < number_of_threads; ++i)
{
// for each thread
for (size_t j = 0; j < number_of_messages; ++j)
{
std::string expected_string = logger_name_prefix + std::to_string(i) +
" Hello from thread " + std::to_string(i) + " this is message " + std::to_string(j);
REQUIRE(testing::file_contains(file_contents, expected_string));
}
}
testing::remove_file(filename);
}

View file

@ -0,0 +1,75 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <thread>
#include <vector>
using namespace quill;
/***/
TEST_CASE("backend_transit_buffer_soft_limit")
{
static constexpr char const* filename = "backend_transit_buffer_soft_limit.log";
static std::string const logger_name = "logger";
size_t constexpr soft_limit = 100;
// First log some messages and then start the backend worker thread so that the soft limit is hit
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
for (size_t i = 0; i < soft_limit * 2; ++i)
{
// log all messages
LOG_INFO(logger, "Message num {}", i);
}
// Start the backend thread
BackendOptions backend_options;
backend_options.transit_events_soft_limit = soft_limit;
Backend::start(backend_options);
// log more messages after the backend started
for (size_t i = soft_limit * 2; i < soft_limit * 4; ++i)
{
// log all messages
LOG_INFO(logger, "Message num {}", i);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = testing::file_contents(filename);
size_t const total_messages = soft_limit * 4;
REQUIRE_EQ(file_contents.size(), total_messages);
for (size_t i = 0; i < total_messages; ++i)
{
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " Message num "} + std::to_string(i)));
}
testing::remove_file(filename);
}

View file

@ -0,0 +1,83 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("backtrace_dynamic_log_level")
{
static constexpr char const* filename = "backtrace_dynamic_log_level.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
// Enable backtrace for 2 messages for error
logger->init_backtrace(2, LogLevel::Error);
// log using dynamic log level and flush on warning instead
LOG_DYNAMIC(logger, LogLevel::Info, "Before dynamic backtrace log");
for (size_t i = 100; i < 120; ++i)
{
#if defined(__aarch64__) || ((__ARM_ARCH >= 6) || defined(_M_ARM64))
// On ARM we add a small delay because log messages can get the same timestamp from rdtsc
// when in this loop and make the test unstable
std::this_thread::sleep_for(std::chrono::microseconds{200});
#endif
LOG_DYNAMIC(logger, LogLevel::Backtrace, "Dynamic backtrace log {}", i);
}
LOG_DYNAMIC(logger, LogLevel::Error, "After dynamic error");
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 4);
std::string expected_string_1 =
"LOG_INFO " + logger_name + " Before dynamic backtrace log";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_1));
std::string expected_string_2 = "LOG_ERROR " + logger_name + " After dynamic error";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_2));
std::string expected_string_3 =
"LOG_BACKTRACE " + logger_name + " Dynamic backtrace log 118";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_3));
std::string expected_string_4 =
"LOG_BACKTRACE " + logger_name + " Dynamic backtrace log 119";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_4));
testing::remove_file(filename);
}

View file

@ -0,0 +1,79 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("backtrace_flush_on_error")
{
static constexpr char const* filename = "backtrace_flush_on_error.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
// Enable backtrace for 2 messages for error
logger->init_backtrace(2, LogLevel::Error);
// flush backtrace on error
LOG_INFO(logger, "Before backtrace log");
for (size_t i = 0; i < 12; ++i)
{
#if defined(__aarch64__) || ((__ARM_ARCH >= 6) || defined(_M_ARM64))
// On ARM we add a small delay because log messages can get the same timestamp from rdtsc
// when in this loop and make the test unstable
std::this_thread::sleep_for(std::chrono::microseconds{200});
#endif
LOG_BACKTRACE(logger, "Backtrace log {}", i);
}
LOG_ERROR(logger, "After error");
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 4);
std::string expected_string_1 = "LOG_INFO " + logger_name + " Before backtrace log";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_1));
std::string expected_string_2 = "LOG_ERROR " + logger_name + " After error";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_2));
std::string expected_string_3 = "LOG_BACKTRACE " + logger_name + " Backtrace log 10";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_3));
std::string expected_string_4 = "LOG_BACKTRACE " + logger_name + " Backtrace log 11";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_4));
testing::remove_file(filename);
}

View file

@ -0,0 +1,76 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("backtrace_manual_flush")
{
static constexpr char const* filename = "backtrace_manual_flush.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
// Enable backtrace for 2 messages for error
logger->init_backtrace(2, LogLevel::Error);
// flush backtrace on error
LOG_INFO(logger, "Before backtrace log");
for (size_t i = 0; i < 12; ++i)
{
#if defined(__aarch64__) || ((__ARM_ARCH >= 6) || defined(_M_ARM64))
// On ARM we add a small delay because log messages can get the same timestamp from rdtsc
// when in this loop and make the test unstable
std::this_thread::sleep_for(std::chrono::microseconds{200});
#endif
LOG_BACKTRACE(logger, "Backtrace log {}", i);
}
logger->flush_backtrace();
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 3);
std::string expected_string_1 = "LOG_INFO " + logger_name + " Before backtrace log";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_1));
std::string expected_string_3 = "LOG_BACKTRACE " + logger_name + " Backtrace log 10";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_3));
std::string expected_string_4 = "LOG_BACKTRACE " + logger_name + " Backtrace log 11";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_4));
testing::remove_file(filename);
}

View file

@ -0,0 +1,70 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("backtrace_no_flush")
{
static constexpr char const* filename = "backtrace_no_flush.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
// Enable backtrace for 2 messages for warning
logger->init_backtrace(2, LogLevel::Error);
// try with LOG_WARNING and expect no flush
LOG_INFO(logger, "Before backtrace log retry");
for (size_t i = 0; i < 12; ++i)
{
LOG_BACKTRACE(logger, "Backtrace log {}", i);
}
LOG_WARNING(logger, "After backtrace log retry");
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 2);
std::string expected_string_1 =
"LOG_INFO " + logger_name + " Before backtrace log retry";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_1));
std::string expected_string_2 =
"LOG_WARNING " + logger_name + " After backtrace log retry";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_2));
testing::remove_file(filename);
}

View file

@ -0,0 +1,105 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("backtrace_terminated_thread_flush")
{
// In this test we store in backtrace from one thread that terminates
// then we log that backtrace from a different thread
static constexpr char const* filename = "backtrace_terminated_thread_flush.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
std::thread thread_a(
[]()
{
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
// Get a logger and enable backtrace
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
// Enable backtrace for 2 messages
logger->init_backtrace(2, LogLevel::Error);
LOG_INFO(logger, "Before backtrace log");
for (uint32_t i = 0; i < 12; ++i)
{
LOG_BACKTRACE(logger, "Backtrace message {}", i);
}
});
thread_a.join();
// thread_a logged something in backtrace and finished.
// Now we spawn a different thread and LOG_ERROR
// we expect to see the backtrace from the previous thread
std::thread thread_b(
[]()
{
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
// Get a logger and enable backtrace
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
LOG_ERROR(logger, "After error");
logger->flush_log();
Frontend::remove_logger(logger);
});
thread_b.join();
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 4);
std::string expected_string_1 = "LOG_INFO " + logger_name + " Before backtrace log";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_1));
std::string expected_string_2 = "LOG_ERROR " + logger_name + " After error";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_2));
std::string expected_string_3 = "LOG_BACKTRACE " + logger_name + " Backtrace message 10";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_3));
std::string expected_string_4 = "LOG_BACKTRACE " + logger_name + " Backtrace message 11";
REQUIRE(quill::testing::file_contains(file_contents, expected_string_4));
testing::remove_file(filename);
}

View file

@ -0,0 +1,78 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <string>
#include <vector>
using namespace quill;
// Define custom Frontend Options
struct CustomFrontendOptions
{
static constexpr quill::QueueType queue_type = quill::QueueType::BoundedBlocking;
static constexpr uint32_t initial_queue_capacity = 131'072;
static constexpr uint32_t blocking_queue_retry_interval_ns = 800;
static constexpr bool huge_pages_enabled = false;
};
using CustomFrontend = FrontendImpl<CustomFrontendOptions>;
using CustomLogger = LoggerImpl<CustomFrontendOptions>;
TEST_CASE("bounded_blocking_queue")
{
static constexpr char const* filename = "bounded_blocking_queue.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
auto file_sink = CustomFrontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
CustomLogger* logger = CustomFrontend::create_or_get_logger(logger_name, std::move(file_sink));
for (int i = 0; i < 5000; ++i)
{
LOG_INFO(logger, "Log something to fulfill the bound queue {}", i);
LOG_WARNING(logger, "Log something to fulfill the bound queue {}", i);
LOG_ERROR(logger, "Log something to fulfill the bound queue {}", i);
}
logger->flush_log();
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check the first messages as we can't if some were dropped
std::vector<std::string> const file_contents = testing::file_contents(filename);
for (int i = 0; i < 5000; ++i)
{
std::string expected_string_1 = "LOG_INFO " + logger_name +
" Log something to fulfill the bound queue " + std::to_string(i);
std::string expected_string_2 = "LOG_WARNING " + logger_name +
" Log something to fulfill the bound queue " + std::to_string(i);
std::string expected_string_3 = "LOG_ERROR " + logger_name +
" Log something to fulfill the bound queue " + std::to_string(i);
REQUIRE(testing::file_contains(file_contents, expected_string_1));
REQUIRE(testing::file_contains(file_contents, expected_string_2));
REQUIRE(testing::file_contains(file_contents, expected_string_3));
}
testing::remove_file(filename);
}

View file

@ -0,0 +1,78 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <string>
#include <vector>
using namespace quill;
// Define custom Frontend Options
struct CustomFrontendOptions
{
static constexpr quill::QueueType queue_type = quill::QueueType::BoundedDropping;
static constexpr uint32_t initial_queue_capacity = 131'072;
static constexpr uint32_t blocking_queue_retry_interval_ns = 800;
static constexpr bool huge_pages_enabled = false;
};
using CustomFrontend = FrontendImpl<CustomFrontendOptions>;
using CustomLogger = LoggerImpl<CustomFrontendOptions>;
TEST_CASE("bounded_dropping_queue")
{
static constexpr char const* filename = "bounded_dropping_queue.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
auto file_sink = CustomFrontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
CustomLogger* logger = CustomFrontend::create_or_get_logger(logger_name, std::move(file_sink));
for (int i = 0; i < 5000; ++i)
{
LOG_INFO(logger, "Log something to fulfill the bound queue {}", i);
LOG_WARNING(logger, "Log something to fulfill the bound queue {}", i);
LOG_ERROR(logger, "Log something to fulfill the bound queue {}", i);
}
logger->flush_log();
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check the first messages as we can't if some were dropped
std::vector<std::string> const file_contents = testing::file_contents(filename);
for (int i = 0; i < 100; ++i)
{
std::string expected_string_1 = "LOG_INFO " + logger_name +
" Log something to fulfill the bound queue " + std::to_string(i);
std::string expected_string_2 = "LOG_WARNING " + logger_name +
" Log something to fulfill the bound queue " + std::to_string(i);
std::string expected_string_3 = "LOG_ERROR " + logger_name +
" Log something to fulfill the bound queue " + std::to_string(i);
REQUIRE(testing::file_contains(file_contents, expected_string_1));
REQUIRE(testing::file_contains(file_contents, expected_string_2));
REQUIRE(testing::file_contains(file_contents, expected_string_3));
}
testing::remove_file(filename);
}

View file

@ -0,0 +1,109 @@
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_ArithmeticTypesLogging ArithmeticTypesLoggingTest.cpp)
quill_add_test(TEST_BackendExceptionNotifier BackendExceptionNotifierTest.cpp)
quill_add_test(TEST_BackendLongSleepAndNotify BackendLongSleepAndNotifyTest.cpp)
quill_add_test(TEST_BackendTransitBufferHardLimit BackendTransitBufferHardLimitTest.cpp)
quill_add_test(TEST_BackendTransitBufferSoftLimit BackendTransitBufferSoftLimitTest.cpp)
quill_add_test(TEST_BacktraceDynamicLogLevel BacktraceDynamicLogLevelTest.cpp)
quill_add_test(TEST_BacktraceFlushOnError BacktraceFlushOnErrorTest.cpp)
quill_add_test(TEST_BacktraceManualFlush BacktraceManualFlushTest.cpp)
quill_add_test(TEST_BacktraceNoFlush BacktraceNoFlushTest.cpp)
quill_add_test(TEST_BacktraceTerminatedThreadFlush BacktraceTerminatedThreadFlushTest.cpp)
quill_add_test(TEST_BoundedBlockingQueue BoundedBlockingQueueTest.cpp)
quill_add_test(TEST_BoundedDroppingQueue BoundedDroppingQueueTest.cpp)
quill_add_test(TEST_ConsoleSinkStderrMultipleFormats ConsoleSinkStderrMultipleFormatsTest.cpp)
quill_add_test(TEST_ConsoleSinkStdoutMultipleFormats ConsoleSinkStdoutMultipleFormatsTest.cpp)
quill_add_test(TEST_LoggerAddRemoveGet LoggerAddRemoveGetTest.cpp)
quill_add_test(TEST_EnumLogging EnumLoggingTest.cpp)
quill_add_test(TEST_FlushWithoutAnyLog FlushWithoutAnyLog.cpp)
quill_add_test(TEST_JsonConsoleLoggingTest JsonConsoleLoggingTest.cpp)
quill_add_test(TEST_JsonFileLogging JsonFileLoggingTest.cpp)
quill_add_test(TEST_LogArgumentsEvaluation LogArgumentsEvaluationTest.cpp)
quill_add_test(TEST_LogFlushWithSoftLimit LogFlushWithSoftLimitTest.cpp)
quill_add_test(TEST_MultiFrontendThreads MultiFrontendThreadsTest.cpp)
quill_add_test(TEST_MultipleSinksSameLogger MultipleSinksSameLoggerTest.cpp)
quill_add_test(TEST_RotatingSinkDailyRotation RotatingSinkDailyRotationTest.cpp)
quill_add_test(TEST_RotatingSinkKeepOldest RotatingSinkKeepOldestTest.cpp)
quill_add_test(TEST_RotatingSinkOverwriteOldest RotatingSinkOverwriteOldestTest.cpp)
quill_add_test(TEST_SignalHandler SignalHandlerTest.cpp)
quill_add_test(TEST_SingleFrontendThread SingleFrontendThreadTest.cpp)
quill_add_test(TEST_SinkFilter SinkFilterTest.cpp)
quill_add_test(TEST_StdArrayLogging StdArrayLoggingTest.cpp)
quill_add_test(TEST_StdDequeLogging StdDequeLoggingTest.cpp)
quill_add_test(TEST_StdFilesystemPathLogging StdFilesystemPathLoggingTest.cpp)
quill_add_test(TEST_StdForwardListLogging StdForwardListLoggingTest.cpp)
quill_add_test(TEST_StdListLogging StdListLoggingTest.cpp)
quill_add_test(TEST_StdMapLogging StdMapLoggingTest.cpp)
quill_add_test(TEST_StdMultiMapLogging StdMultiMapLoggingTest.cpp)
quill_add_test(TEST_StdMultiSetLogging StdMultiSetLoggingTest.cpp)
quill_add_test(TEST_StdOptionalLogging StdOptionalLoggingTest.cpp)
quill_add_test(TEST_StdPairLogging StdPairLoggingTest.cpp)
quill_add_test(TEST_StdSetLogging StdSetLoggingTest.cpp)
quill_add_test(TEST_StdTupleLogging StdTupleLoggingTest.cpp)
quill_add_test(TEST_StdUnorderedMapLogging StdUnorderedMapLoggingTest.cpp)
quill_add_test(TEST_StdUnorderedMultiMapLogging StdUnorderedMultiMapLoggingTest.cpp)
quill_add_test(TEST_StdUnorderedMultiSetLogging StdUnorderedMultiSetLoggingTest.cpp)
quill_add_test(TEST_StdUnorderedSetLogging StdUnorderedSetLoggingTest.cpp)
quill_add_test(TEST_StdVectorLogging StdVectorLoggingTest.cpp)
quill_add_test(TEST_SinkLogLevelFilter SinkLogLevelFilterTest.cpp)
quill_add_test(TEST_StringLargeLogging StringLargeLoggingTest.cpp)
quill_add_test(TEST_StringLoggingDynamicLogLevel StringLoggingDynamicLogLevelTest.cpp)
quill_add_test(TEST_StringLogging StringLoggingTest.cpp)
quill_add_test(TEST_StringRandomLargeLogging StringRandomLargeLoggingTest.cpp)
quill_add_test(TEST_StringRandomLogging StringRandomLoggingTest.cpp)
quill_add_test(TEST_StringRandomSmallLogging StringRandomSmallLoggingTest.cpp)
quill_add_test(TEST_TagsLogging TagsLoggingTest.cpp)
quill_add_test(TEST_UserClockSource UserClockSourceTest.cpp)
quill_add_test(TEST_UserDefinedTypeLogging UserDefinedTypeLoggingTest.cpp)
quill_add_test(TEST_CompileActiveLogLevel CompileActiveLogLevelTest.cpp)
target_compile_definitions(TEST_CompileActiveLogLevel PRIVATE -DQUILL_COMPILE_ACTIVE_LOG_LEVEL=QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING)
if (WIN32)
quill_add_test(TEST_WideStdTypesLogging WideStdTypesLoggingTest.cpp)
quill_add_test(TEST_WideStringLogging WideStringLoggingTest.cpp)
endif ()

View file

@ -0,0 +1,67 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("compile_active_log_level")
{
// we build this target with
// add_compile_definitions(-DQUILL_COMPILE_ACTIVE_LOG_LEVEL=QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING)
static constexpr char const* filename = "compile_active_log_level.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
logger->set_log_level(quill::LogLevel::TraceL3);
REQUIRE_EQ(logger->get_log_level(), quill::LogLevel::TraceL3);
LOG_TRACE_L3(logger, "This is a log trace l3 example {}", 1);
LOG_TRACE_L2(logger, "This is a log trace l2 example {} {}", 2, 2.3);
LOG_TRACE_L1(logger, "This is a log trace l1 {} example", "string");
LOG_DEBUG(logger, "This is a log debug example {}", 4);
LOG_INFO(logger, "This is a log info example {}", 21);
LOG_WARNING(logger, "This is a log warning example {}", 11);
LOG_ERROR(logger, "This is a log error example {}", 3212);
LOG_CRITICAL(logger, "This is a log critical example {}", 321);
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check, we only except statements above warning
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 3);
REQUIRE(quill::testing::file_contains(file_contents, std::string{"This is a log warning example 11"}));
REQUIRE(quill::testing::file_contains(file_contents, std::string{"This is a log error example 3212"}));
REQUIRE(quill::testing::file_contains(file_contents, std::string{"This is a log critical example 321"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,97 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/bundled/fmt/format.h"
#include "quill/bundled/fmt/ranges.h"
#include "quill/sinks/ConsoleSink.h"
#include <cstdio>
#include <sstream>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("console_sink_stderr_multiple_formats")
{
static std::string const logger_name_a = "logger_a";
static std::string const logger_name_b = "logger_b";
// Start the logging backend thread
Backend::start();
quill::testing::CaptureStderr();
// Set writing logging to a file
bool const using_colours = false;
std::string const stream = "stderr";
auto console_sink = Frontend::create_or_get_sink<ConsoleSink>("console_sink", using_colours, stream);
Logger* logger_a = Frontend::create_or_get_logger(logger_name_a, console_sink);
Logger* logger_b = Frontend::create_or_get_logger(logger_name_b, console_sink,
"%(logger) - %(message) (%(caller_function))");
console_sink.reset();
// log a few messages so we rotate files
for (size_t i = 0; i < 20; ++i)
{
if (i % 2 == 0)
{
LOG_INFO(logger_a, "Hello log num {}", i);
}
else
{
LOG_INFO(logger_b, "Hello log num {}", i);
}
}
// flush all log and remove all loggers
for (Logger* logger : Frontend::get_all_loggers())
{
logger->flush_log();
Frontend::remove_logger(logger);
}
// Wait until the backend thread stops for test stability
Backend::stop();
// convert result to vector
std::string results = quill::testing::GetCapturedStderr();
std::stringstream data(results);
std::string line;
std::vector<std::string> result_arr;
while (std::getline(data, line, '\n'))
{
result_arr.push_back(line);
}
for (uint32_t i = 0; i < 20; ++i)
{
if (i % 2 == 0)
{
std::string expected_string =
"LOG_INFO " + logger_name_a + " Hello log num " + std::to_string(i);
if (!quill::testing::file_contains(result_arr, expected_string))
{
FAIL(fmtquill::format("expected [{}] is not in results [{}]", expected_string, result_arr).data());
}
}
else
{
std::string expected_string =
logger_name_b + " - Hello log num " + std::to_string(i) + " (DOCTEST_ANON_FUNC_2)";
if (!quill::testing::file_contains(result_arr, expected_string))
{
FAIL(fmtquill::format("expected [{}] is not in results [{}]", expected_string, result_arr).data());
}
}
}
}

View file

@ -0,0 +1,97 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/bundled/fmt/format.h"
#include "quill/bundled/fmt/ranges.h"
#include "quill/sinks/ConsoleSink.h"
#include <cstdio>
#include <sstream>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("console_sink_stdout_multiple_formats")
{
static std::string const logger_name_a = "logger_a";
static std::string const logger_name_b = "logger_b";
// Start the logging backend thread
Backend::start();
quill::testing::CaptureStdout();
// Set writing logging to a file
bool const using_colours = false;
std::string const stream = "stdout";
auto console_sink = Frontend::create_or_get_sink<ConsoleSink>("console_sink", using_colours, stream);
Logger* logger_a = Frontend::create_or_get_logger(logger_name_a, console_sink);
Logger* logger_b = Frontend::create_or_get_logger(logger_name_b, console_sink,
"%(logger) - %(message) (%(caller_function))");
console_sink.reset();
// log a few messages so we rotate files
for (size_t i = 0; i < 20; ++i)
{
if (i % 2 == 0)
{
LOG_INFO(logger_a, "Hello log num {}", i);
}
else
{
LOG_INFO(logger_b, "Hello log num {}", i);
}
}
// flush all log and remove all loggers
for (Logger* logger : Frontend::get_all_loggers())
{
logger->flush_log();
Frontend::remove_logger(logger);
}
// Wait until the backend thread stops for test stability
Backend::stop();
// convert result to vector
std::string results = quill::testing::GetCapturedStdout();
std::stringstream data(results);
std::string line;
std::vector<std::string> result_arr;
while (std::getline(data, line, '\n'))
{
result_arr.push_back(line);
}
for (uint32_t i = 0; i < 20; ++i)
{
if (i % 2 == 0)
{
std::string expected_string =
"LOG_INFO " + logger_name_a + " Hello log num " + std::to_string(i);
if (!quill::testing::file_contains(result_arr, expected_string))
{
FAIL(fmtquill::format("expected [{}] is not in results [{}]", expected_string, result_arr).data());
}
}
else
{
std::string expected_string =
logger_name_b + " - Hello log num " + std::to_string(i) + " (DOCTEST_ANON_FUNC_2)";
if (!quill::testing::file_contains(result_arr, expected_string))
{
FAIL(fmtquill::format("expected [{}] is not in results [{}]", expected_string, result_arr).data());
}
}
}
}

View file

@ -0,0 +1,121 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/bundled/fmt/format.h"
#include "quill/bundled/fmt/ostream.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
enum TestEnum : int
{
Test1 = 1,
Test2 = 2,
Test3 = 3
};
std::ostream& operator<<(std::ostream& os, TestEnum const& test_enum)
{
switch (test_enum)
{
case TestEnum::Test1:
os << "Test1";
break;
case TestEnum::Test2:
os << "Test2";
break;
case TestEnum::Test3:
os << "Test3";
break;
default:
os << "Unknown";
break;
}
return os;
}
template <>
struct fmtquill::formatter<TestEnum> : fmtquill::ostream_formatter
{
};
enum class TestEnumClass : uint64_t
{
Test4 = 4,
Test5 = 5,
Test6 = 6
};
std::ostream& operator<<(std::ostream& os, const TestEnumClass& test_enum_class)
{
switch (test_enum_class)
{
case TestEnumClass::Test4:
os << "Test4";
break;
case TestEnumClass::Test5:
os << "Test5";
break;
case TestEnumClass::Test6:
os << "Test6";
break;
default:
os << "Unknown";
break;
}
return os;
}
template <>
struct fmtquill::formatter<TestEnumClass> : fmtquill::ostream_formatter
{
};
/***/
TEST_CASE("enum_logging")
{
static constexpr char const* filename = "enum_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
LOG_INFO(logger, "{} -> {}, {} -> {}", TestEnum::Test1, static_cast<int>(TestEnum::Test1),
TestEnumClass::Test4, static_cast<uint64_t>(TestEnumClass::Test4));
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
std::string expected_string = "LOG_INFO " + logger_name + " Test1 -> 1, Test4 -> 4";
REQUIRE(quill::testing::file_contains(file_contents, expected_string));
testing::remove_file(filename);
}

View file

@ -0,0 +1,56 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("flush_without_any_log")
{
static constexpr char const* filename = "flush_without_any_log.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
std::atomic<bool> is_flush_done = false;
std::thread watcher(
[&is_flush_done]()
{
uint32_t try_count = 2000;
while (!is_flush_done.load() && try_count--)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
REQUIRE_EQ(is_flush_done.load(), true);
});
logger->flush_log();
is_flush_done = true;
watcher.join();
Backend::stop();
testing::remove_file(filename);
}

View file

@ -0,0 +1,71 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/JsonConsoleSink.h"
#include "quill/bundled/fmt/format.h"
#include <cstdio>
#include <sstream>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("json_console_logging")
{
static constexpr size_t number_of_messages = 50;
static std::string const logger_name_a = "logger_a";
// Start the logging backend thread
Backend::start();
quill::testing::CaptureStdout();
// Set writing logging to a file
auto console_sink = Frontend::create_or_get_sink<JsonConsoleSink>("json_console");
Logger* logger_a = Frontend::create_or_get_logger(logger_name_a, console_sink);
// log a few messages
for (size_t i = 0; i < number_of_messages; ++i)
{
LOG_INFO(logger_a, "Hello log num [{num}, {multiply}, {add}]", i, i * i, i + i);
}
// flush all log and remove all loggers
for (Logger* logger : Frontend::get_all_loggers())
{
logger->flush_log();
Frontend::remove_logger(logger);
}
// Wait until the backend thread stops for test stability
Backend::stop();
// convert result to vector
std::string results = quill::testing::GetCapturedStdout();
std::stringstream data(results);
std::string line;
std::vector<std::string> file_contents;
while (std::getline(data, line, '\n'))
{
file_contents.push_back(line);
}
REQUIRE_EQ(file_contents.size(), number_of_messages);
for (size_t i = 0; i < number_of_messages; ++i)
{
std::string const expected_format =
R"("logger_a","log_level":"INFO","message":"Hello log num [{{num}}, {{multiply}}, {{add}}]","num":"{}","multiply":"{}","add":"{}")";
std::string const expected_string =
fmtquill::format(fmtquill::runtime(expected_format), i, i * i, i + i);
REQUIRE(testing::file_contains(file_contents, expected_string));
}
}

View file

@ -0,0 +1,180 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/bundled/fmt/format.h"
#include "quill/bundled/fmt/ostream.h"
#include "quill/sinks/JsonFileSink.h"
#include <cstdio>
#include <optional>
#include <string>
#include <thread>
#include <vector>
using namespace quill;
class UserDefinedType
{
public:
UserDefinedType() = default;
UserDefinedType(size_t i, std::string const& s) : _i(i), _s(s) {}
virtual ~UserDefinedType() = default;
friend std::ostream& operator<<(std::ostream& os, UserDefinedType const& obj)
{
if (obj._i && obj._s)
{
os << "i: " << *obj._i << ", s: " << *obj._s;
}
return os;
}
private:
std::optional<size_t> _i;
std::optional<std::string> _s;
};
template <>
struct fmtquill::formatter<UserDefinedType> : fmtquill::ostream_formatter
{
};
/***/
TEST_CASE("json_file_logging")
{
static constexpr size_t number_of_messages = 500u;
static constexpr size_t number_of_threads = 6;
static constexpr char const* json_filename = "json_file_logging.json";
static constexpr char const* filename = "json_file_logging_file.log";
static std::string const logger_name_prefix = "logger_";
// Start the logging backend thread
BackendOptions bo;
bo.error_notifier = [](std::string const&) {};
Backend::start(bo);
std::vector<std::thread> threads;
for (size_t i = 0; i < number_of_threads; ++i)
{
threads.emplace_back(
[i]() mutable
{
// log to json
auto json_file_sink = Frontend::create_or_get_sink<JsonFileSink>(
json_filename,
[]()
{
JsonFileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(
logger_name_prefix + std::to_string(i),
std::initializer_list<std::shared_ptr<Sink>>{std::move(json_file_sink), std::move(file_sink)},
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<12) "
"%(message) [%(named_args)]");
if (i == 0)
{
// log a message without any args, only from the first thread
LOG_INFO(logger, "Hello from thread");
}
for (size_t j = 0; j < number_of_messages; ++j)
{
LOG_INFO(logger,
"Hello from thread {thread_index} this is message {message_num} [{custom}]", i,
j, fmtquill::format("{}", UserDefinedType{j, std::to_string(j)}));
}
if (i == 0)
{
// log an invalid format for testing, only from the first thread
LOG_INFO(logger, "invalid format [{%f}]", 321.1);
}
});
}
for (auto& elem : threads)
{
elem.join();
}
// flush all log and remove all loggers
for (Logger* logger : Frontend::get_all_loggers())
{
logger->flush_log();
Frontend::remove_logger(logger);
}
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(json_filename);
std::vector<std::string> const file_contents_s = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages * number_of_threads + 2);
REQUIRE_EQ(file_contents_s.size(), number_of_messages * number_of_threads + 2);
for (size_t i = 0; i < number_of_threads; ++i)
{
// for each thread
for (size_t j = 0; j < number_of_messages; ++j)
{
// check json log
std::string expected_json_string = std::string{"\"logger\":\""} + logger_name_prefix +
std::to_string(i) +
std::string{
"\",\"log_level\":\"INFO\",\"message\":\"Hello from thread {thread_index} this is "
"message {message_num} [{custom}]\","} +
std::string{"\"thread_index\":\""} + std::to_string(i) +
std::string{"\",\"message_num\":\""} + std::to_string(j) +
std::string{"\",\"custom\":\"i: "} + std::to_string(j) + ", s: " + std::to_string(j) +
std::string{"\""};
REQUIRE(quill::testing::file_contains(file_contents, expected_json_string));
// check standard log
// for each thread [i: 0, s: 0]
std::string expected_string = logger_name_prefix + std::to_string(i) +
" Hello from thread " + std::to_string(i) + " this is message " + std::to_string(j) +
+" [i: " + std::to_string(j) + ", s: " + std::to_string(j) +
"] [thread_index: " + std::to_string(i) + ", message_num: " + std::to_string(j) + ", ";
REQUIRE(quill::testing::file_contains(file_contents_s, expected_string));
}
std::string expected_no_args_json = "\"log_level\":\"INFO\",\"message\":\"Hello from thread\"";
std::string expected_no_args_fmt = "Hello from thread";
REQUIRE(quill::testing::file_contains(file_contents, expected_no_args_json));
REQUIRE(quill::testing::file_contains(file_contents_s, expected_no_args_fmt));
std::string expected_invalid_fmt_json =
"\"log_level\":\"INFO\",\"message\":\"invalid format [{%f}]\"";
std::string expected_invalid_fmt = "invalid format [{%f}]\", location: \"";
REQUIRE(quill::testing::file_contains(file_contents, expected_invalid_fmt_json));
REQUIRE(quill::testing::file_contains(file_contents_s, expected_invalid_fmt));
}
testing::remove_file(json_filename);
testing::remove_file(filename);
}

View file

@ -0,0 +1,67 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("log_arguments_evaluation")
{
static constexpr char const* filename = "log_arguments_evaluation.log";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
std::string const logger_name = "logger";
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
size_t cnt{0};
auto arg_str = [&cnt]()
{
++cnt;
return "expensive_calculation";
};
// 1. checks that log arguments are not evaluated when we don't log
logger->set_log_level(quill::LogLevel::Info);
LOG_DEBUG(logger, "Test log arguments {}", arg_str());
LOG_TRACE_L1(logger, "Test log arguments {}", arg_str());
REQUIRE_EQ(cnt, 0);
// 2. checks that log arguments are evaluated only once per log statement
LOG_INFO(logger, "Test log arguments {}", arg_str());
REQUIRE_EQ(cnt, 1);
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 1);
testing::remove_file(filename);
}

View file

@ -0,0 +1,86 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <thread>
#include <vector>
using namespace quill;
/***/
TEST_CASE("log_flush_with_soft_limit_test")
{
static constexpr char const* filename = "log_flush_with_soft_limit_test.log";
static std::string const logger_name = "logger";
static size_t constexpr soft_limit = 100;
// make sure we at least x 100 the soft limit to make sure we are testing what we want
// to not reduce this number. Increasing is fine
static size_t constexpr number_of_messages = soft_limit * 100;
static constexpr size_t number_of_threads = 4;
// When hitting the transit_events_soft_limit several events are processed in the backend at the
// same time If all of them are processed there is a chance to miss messages that where in the
// queues but never buffered result in example issuing the flush_log() earlier than it should
// Start the backend thread
BackendOptions backend_options;
backend_options.transit_events_soft_limit = soft_limit;
Backend::start(backend_options);
std::vector<std::thread> threads;
for (size_t i = 0; i < number_of_threads; ++i)
{
threads.emplace_back(
[i]() mutable
{
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
cfg.set_do_fsync(true);
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
for (size_t j = 0; j < number_of_messages; ++j)
{
LOG_INFO(logger, "Hello from thread {} this is message {}", i, j);
}
});
}
for (auto& elem : threads)
{
elem.join();
}
Frontend::get_valid_logger()->flush_log();
// Now check we have all messages in the file after the flush statement
size_t const total_messages = number_of_messages * number_of_threads;
// Read file and check
REQUIRE_EQ(testing::file_contents(filename).size(), total_messages);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check again, nothing else was logged
REQUIRE_EQ(testing::file_contents(filename).size(), total_messages);
testing::remove_file(filename);
}

View file

@ -0,0 +1,107 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/**
* A test class example that is using the logger
*/
class LoggingTestClass
{
public:
LoggingTestClass(std::string const& filename, std::string const& logger_name)
{
// create a new logger in the ctor
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
cfg.set_do_fsync(true);
return cfg;
}(),
FileEventNotifier{});
quill::Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
_logger = logger;
}
~LoggingTestClass()
{
_logger->flush_log();
Frontend::remove_logger(_logger);
}
/**
* Use logger in const function
*/
void use_logger_const() const noexcept { LOG_INFO(_logger, "Test message for test class const"); }
/**
* Use logger in normal function
*/
void use_logger() const { LOG_INFO(_logger, "Test message for test class non const"); }
private:
quill::Logger* _logger{nullptr};
};
/***/
TEST_CASE("logger_add_remove_get")
{
// Verifies logging behavior adding, removing and getting loggers
// and logging from const member functions while dynamically creating and
// removing loggers and reusing the same file after removal.
static constexpr size_t iterations = 100;
static constexpr char const* filename = "logger_add_remove_get.log";
// Start the logging backend thread
Backend::start();
for (size_t i = 0; i < iterations; ++i)
{
std::string const logger_a_name = "logger_a" + std::to_string(i);
std::string const logger_b_name = "logger_b" + std::to_string(i);
{
// log for class a
LoggingTestClass logging_test_class_a{filename, logger_a_name};
logging_test_class_a.use_logger_const();
logging_test_class_a.use_logger();
// log again for class b
LoggingTestClass const logging_test_class_b{filename, logger_b_name};
logging_test_class_b.use_logger_const();
}
// Test that get_all_loggers() works and also it should be empty after removing the loggers
REQUIRE(Frontend::get_all_loggers().empty());
REQUIRE(!Frontend::get_logger(logger_a_name));
REQUIRE(!Frontend::get_logger(logger_b_name));
// The only safe way to know that the loggers are really removed is currently this
while (Frontend::get_number_of_loggers())
{
// wait for all the to be removed first by the backend thread first then repeat the test
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 3);
testing::remove_file(filename);
}
Backend::stop();
}

View file

@ -0,0 +1,95 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdint>
#include <string>
#include <thread>
#include <vector>
using namespace quill;
/***/
TEST_CASE("multi_frontend_threads")
{
static constexpr size_t number_of_messages = 1000;
static constexpr size_t number_of_threads = 10;
static constexpr char const* filename = "multi_frontend_threads.log";
static std::string const logger_name_prefix = "logger_";
// Start the logging backend thread
Backend::start();
std::vector<std::thread> threads;
for (size_t i = 0; i < number_of_threads; ++i)
{
threads.emplace_back(
[i]() mutable
{
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(
logger_name_prefix + std::to_string(i), std::move(file_sink),
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<12) "
"%(message)",
"%Y-%m-%d %H:%M:%S.%Qns", Timezone::GmtTime, ClockSourceType::System);
for (size_t j = 0; j < number_of_messages; ++j)
{
LOG_INFO(logger, "Hello from thread {} this is message {}", i, j);
}
});
}
for (auto& elem : threads)
{
elem.join();
}
// flush all log and remove all loggers
for (Logger* logger : Frontend::get_all_loggers())
{
logger->flush_log();
Frontend::remove_logger(logger);
}
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages * number_of_threads);
for (size_t i = 0; i < number_of_threads; ++i)
{
// for each thread
for (size_t j = 0; j < number_of_messages; ++j)
{
std::string expected_string = logger_name_prefix + std::to_string(i) +
" Hello from thread " + std::to_string(i) + " this is message " + std::to_string(j);
REQUIRE(testing::file_contains(file_contents, expected_string));
}
}
// Check log file is timestamp ordered
REQUIRE(quill::testing::is_timestamp_ordered(file_contents));
testing::remove_file(filename);
}

View file

@ -0,0 +1,93 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/ConsoleSink.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <sstream>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("multiple_sinks_same_logger")
{
static constexpr size_t number_of_messages = 10000;
static constexpr char const* filename = "multiple_sinks_same_logger.log";
// Start the logging backend thread
Backend::start();
quill::testing::CaptureStdout();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
bool const using_colours = false;
std::string const stream = "stdout";
auto console_sink = Frontend::create_or_get_sink<ConsoleSink>("console_sink", using_colours, stream);
std::string const logger_name = "logger";
Logger* logger =
Frontend::create_or_get_logger(logger_name, {std::move(file_sink), std::move(console_sink)});
for (size_t i = 0; i < number_of_messages; ++i)
{
LOG_INFO(logger, "This is message {}", i);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
{
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages);
for (size_t i = 0; i < number_of_messages; ++i)
{
std::string expected_string = logger_name + " This is message " + std::to_string(i);
REQUIRE(quill::testing::file_contains(file_contents, expected_string));
}
}
{
// convert stdout result to vector
std::string results = quill::testing::GetCapturedStdout();
std::stringstream data(results);
std::string line;
std::vector<std::string> file_contents;
while (std::getline(data, line, '\n'))
{
file_contents.push_back(line);
}
REQUIRE_EQ(file_contents.size(), number_of_messages);
for (size_t i = 0; i < number_of_messages; ++i)
{
std::string expected_string = "logger This is message " + std::to_string(i);
REQUIRE(quill::testing::file_contains(file_contents, expected_string));
}
}
testing::remove_file(filename);
}

View file

@ -0,0 +1,56 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/RotatingFileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("rotating_sink_daily_rotation")
{
// This test has 2 loggers that they logged to different rotating file handlers
static char const* base_filename = "rotating_sink_daily_rotation.log";
// Start the logging backend thread
Backend::start();
// Create a rotating file handler
std::shared_ptr<quill::Sink> rotating_file_sink_a =
Frontend::create_or_get_sink<RotatingFileSink>(base_filename,
[]()
{
RotatingFileSinkConfig rfh_cfg;
rfh_cfg.set_rotation_time_daily("00:00");
rfh_cfg.set_open_mode('w');
return rfh_cfg;
}());
// Get the same instance back - we search it again (for testing only)
quill::Logger* logger = Frontend::create_or_get_logger("logger", std::move(rotating_file_sink_a));
// log a few messages so we rotate files
for (uint32_t i = 0; i < 20; ++i)
{
LOG_INFO(logger, "Hello daily file log num {}", i);
}
// flush all log and remove all loggers
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(base_filename);
REQUIRE_EQ(file_contents.size(), 20);
testing::remove_file(base_filename);
}

View file

@ -0,0 +1,102 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/RotatingFileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("rotating_sink_keep_oldest")
{
// This test has 2 loggers that they logged to different rotating file handlers
static char const* base_filename_a = "rotating_sink_keep_oldest_a.log";
static constexpr char const* base_filename_a_1 = "rotating_sink_keep_oldest_a.1.log";
static constexpr char const* base_filename_a_2 = "rotating_sink_keep_oldest_a.2.log";
static char const* base_filename_b = "rotating_sink_keep_oldest_b.log";
static constexpr char const* base_filename_b_1 = "rotating_sink_keep_oldest_b.1.log";
static constexpr size_t max_file_size = 900;
// Start the logging backend thread
Backend::start();
// Create a rotating file handler
std::shared_ptr<quill::Sink> rotating_file_sink_a =
Frontend::create_or_get_sink<RotatingFileSink>(base_filename_a,
[]()
{
RotatingFileSinkConfig rfh_cfg;
rfh_cfg.set_rotation_max_file_size(max_file_size);
rfh_cfg.set_max_backup_files(2);
rfh_cfg.set_overwrite_rolled_files(false);
rfh_cfg.set_open_mode('w');
return rfh_cfg;
}());
// Get the same instance back - we search it again (for testing only)
quill::Logger* logger_a = Frontend::create_or_get_logger("logger_a", std::move(rotating_file_sink_a));
// Another rotating logger to another file with max backup count 1 this time. Here we rotate only once
std::shared_ptr<quill::Sink> rotating_file_sink_b =
Frontend::create_or_get_sink<RotatingFileSink>(base_filename_b,
[]()
{
RotatingFileSinkConfig rfh_cfg;
rfh_cfg.set_rotation_max_file_size(max_file_size);
rfh_cfg.set_max_backup_files(1);
rfh_cfg.set_overwrite_rolled_files(false);
rfh_cfg.set_open_mode('w');
return rfh_cfg;
}());
quill::Logger* logger_b = Frontend::create_or_get_logger("logger_b", std::move(rotating_file_sink_b));
// log a few messages so we rotate files
for (uint32_t i = 0; i < 20; ++i)
{
LOG_INFO(logger_a, "Hello rotating file log num {}", i);
LOG_INFO(logger_b, "Hello rotating file log num {}", i);
}
// flush all log and remove all loggers
for (Logger* logger : Frontend::get_all_loggers())
{
logger->flush_log();
Frontend::remove_logger(logger);
}
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(base_filename_a);
REQUIRE_GE(file_contents.size(), 3);
std::vector<std::string> const file_contents_1 = quill::testing::file_contents(base_filename_a_1);
REQUIRE_GE(file_contents_1.size(), 7);
std::vector<std::string> const file_contents_2 = quill::testing::file_contents(base_filename_a_2);
REQUIRE_GE(file_contents_2.size(), 7);
// File from 2nd logger
std::vector<std::string> const file_contents_3 = quill::testing::file_contents(base_filename_b);
REQUIRE_GE(file_contents_3.size(), 11);
std::vector<std::string> const file_contents_4 = quill::testing::file_contents(base_filename_b_1);
REQUIRE_GE(file_contents_4.size(), 7);
// Remove filenames
testing::remove_file(base_filename_a);
testing::remove_file(base_filename_a_1);
testing::remove_file(base_filename_a_2);
testing::remove_file(base_filename_b);
testing::remove_file(base_filename_b_1);
}

View file

@ -0,0 +1,102 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/RotatingFileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("rotating_sink_overwrite_oldest")
{
// This test has 2 loggers that they logged to different rotating file handlers
static char const* base_filename_a = "rotating_sink_overwrite_oldest_a.log";
static constexpr char const* base_filename_a_1 = "rotating_sink_overwrite_oldest_a.1.log";
static constexpr char const* base_filename_a_2 = "rotating_sink_overwrite_oldest_a.2.log";
static char const* base_filename_b = "rotating_sink_overwrite_oldest_b.log";
static constexpr char const* base_filename_b_1 = "rotating_sink_overwrite_oldest_b.1.log";
static constexpr size_t max_file_size = 900;
// Start the logging backend thread
Backend::start();
// Create a rotating file handler
std::shared_ptr<quill::Sink> rotating_file_sink_a =
Frontend::create_or_get_sink<RotatingFileSink>(base_filename_a,
[]()
{
RotatingFileSinkConfig rfh_cfg;
rfh_cfg.set_rotation_max_file_size(max_file_size);
rfh_cfg.set_max_backup_files(2);
rfh_cfg.set_overwrite_rolled_files(true);
rfh_cfg.set_open_mode('w');
return rfh_cfg;
}());
// Get the same instance back - we search it again (for testing only)
quill::Logger* logger_a = Frontend::create_or_get_logger("logger_a", std::move(rotating_file_sink_a));
// Another rotating logger to another file with max backup count 1 this time. Here we rotate only once
std::shared_ptr<quill::Sink> rotating_file_sink_b =
Frontend::create_or_get_sink<RotatingFileSink>(base_filename_b,
[]()
{
RotatingFileSinkConfig rfh_cfg;
rfh_cfg.set_rotation_max_file_size(max_file_size);
rfh_cfg.set_max_backup_files(1);
rfh_cfg.set_overwrite_rolled_files(true);
rfh_cfg.set_open_mode('w');
return rfh_cfg;
}());
quill::Logger* logger_b = Frontend::create_or_get_logger("logger_b", std::move(rotating_file_sink_b));
// log a few messages so we rotate files
for (uint32_t i = 0; i < 20; ++i)
{
LOG_INFO(logger_a, "Hello rotating file log num {}", i);
LOG_INFO(logger_b, "Hello rotating file log num {}", i);
}
// flush all log and remove all loggers
for (Logger* logger : Frontend::get_all_loggers())
{
logger->flush_log();
Frontend::remove_logger(logger);
}
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(base_filename_a);
REQUIRE_GE(file_contents.size(), 3);
std::vector<std::string> const file_contents_1 = quill::testing::file_contents(base_filename_a_1);
REQUIRE_GE(file_contents_1.size(), 7);
std::vector<std::string> const file_contents_2 = quill::testing::file_contents(base_filename_a_2);
REQUIRE_GE(file_contents_2.size(), 7);
// File from 2nd logger
std::vector<std::string> const file_contents_3 = quill::testing::file_contents(base_filename_b);
REQUIRE_GE(file_contents_3.size(), 4);
std::vector<std::string> const file_contents_4 = quill::testing::file_contents(base_filename_b_1);
REQUIRE_GE(file_contents_4.size(), 7);
// Remove filenames
testing::remove_file(base_filename_a);
testing::remove_file(base_filename_a_1);
testing::remove_file(base_filename_a_2);
testing::remove_file(base_filename_b);
testing::remove_file(base_filename_b_1);
}

View file

@ -0,0 +1,117 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("signal_handler")
{
static constexpr char const* filename = "signal_handler.log";
static std::string const logger_name = "logger";
static constexpr size_t number_of_messages = 10;
static constexpr size_t number_of_threads = 10;
static std::string const logger_name_prefix = "logger_";
// Start the logging backend thread, we expect the signal handler to catch the signal,
// flush the log and raise the signal back
Backend::start_with_signal_handler<FrontendOptions>(BackendOptions{},
std::initializer_list<int>{SIGABRT}, 40);
// For testing purposes we want to keep the application running, we do not reraise the signal
detail::SignalHandlerContext::instance().should_reraise_signal.store(false);
quill::Frontend::preallocate();
std::vector<std::thread> threads;
for (size_t i = 0; i < number_of_threads; ++i)
{
threads.emplace_back(
[i]() mutable
{
#if defined(_WIN32)
// NOTE: On windows the signal handler must be installed on each new thread
quill::init_signal_handler<quill::FrontendOptions>();
#endif
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
cfg.set_do_fsync(true);
return cfg;
}(),
FileEventNotifier{});
Logger* logger =
Frontend::create_or_get_logger(logger_name_prefix + std::to_string(i), std::move(file_sink));
for (size_t j = 0; j < number_of_messages; ++j)
{
LOG_INFO(logger, "{} {} {}", i, j,
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor "
"incididunt ut labore et dolore magna aliqua.");
}
});
}
for (auto& elem : threads)
{
elem.join();
}
// Now raise a signal
std::raise(SIGABRT);
{
// Except the log and the signal handler in the logs
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
for (size_t i = 0; i < number_of_threads; ++i)
{
// for each thread
for (size_t j = 0; j < number_of_messages; ++j)
{
std::string expected_string = logger_name_prefix + std::to_string(i) + " " +
std::to_string(i) + " " + std::to_string(j) +
" Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor "
"incididunt ut labore et dolore magna aliqua.";
REQUIRE(testing::file_contains(file_contents, expected_string));
}
}
#if defined(_WIN32)
REQUIRE(quill::testing::file_contains(file_contents, std::string{"Received signal: 22 (signum: 22)"}));
#elif defined(__apple_build_version__)
REQUIRE(quill::testing::file_contains(file_contents, std::string{"Received signal: Abort trap: 6 (signum: 6)"}));
#else
REQUIRE(quill::testing::file_contains(file_contents, std::string{"Received signal: Aborted (signum: 6)"}));
#endif
}
// Wait until the backend thread stops for test stability
for (Logger* logger : Frontend::get_all_loggers())
{
logger->flush_log();
Frontend::remove_logger(logger);
}
Backend::stop();
REQUIRE_FALSE(Backend::is_running());
testing::remove_file(filename);
}

View file

@ -0,0 +1,62 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("single_frontend_thread")
{
static constexpr size_t number_of_messages = 10000;
static constexpr char const* filename = "single_frontend_thread.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
for (size_t i = 0; i < number_of_messages; ++i)
{
LOG_INFO(logger, "This is message {}", i);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages);
for (size_t i = 0; i < number_of_messages; ++i)
{
std::string expected_string = logger_name + " This is message " + std::to_string(i);
REQUIRE(quill::testing::file_contains(file_contents, expected_string));
}
testing::remove_file(filename);
}

View file

@ -0,0 +1,132 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/**
* Filter class for our file handler
*/
class FileFilterA : public Filter
{
public:
FileFilterA() : Filter("FileFilterA") {}
QUILL_NODISCARD bool filter(MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id,
std::string_view thread_name, std::string_view logger_name,
LogLevel log_level, std::string_view log_message) noexcept override
{
if (log_metadata->log_level() < LogLevel::Warning)
{
return true;
}
return false;
}
};
/**
* Filter for the stdout handler
*/
class FileFilterB : public Filter
{
public:
FileFilterB() : Filter("FileFilterB") {}
QUILL_NODISCARD bool filter(MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id,
std::string_view thread_name, std::string_view logger_name,
LogLevel log_level, std::string_view log_message) noexcept override
{
if (log_metadata->log_level() >= LogLevel::Warning)
{
return true;
}
return false;
}
};
/***/
TEST_CASE("sink_filter")
{
static constexpr char const* filename_a = "sink_filter_a.log";
static constexpr char const* filename_b = "sink_filter_b.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
// Set writing logging to a file
auto file_sink_a = Frontend::create_or_get_sink<FileSink>(
filename_a,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
// log to filename_a anything below warning
std::unique_ptr<Filter> filter_a = std::make_unique<FileFilterA>();
// Also test get_filter_name()
REQUIRE_EQ(filter_a->get_filter_name(), std::string_view{"FileFilterA"});
// Add the filter
file_sink_a->add_filter(std::move(filter_a));
// Try to add the same again (same name)
std::unique_ptr<Filter> filter_a_2 = std::make_unique<FileFilterA>();
REQUIRE_EQ(filter_a_2->get_filter_name(), std::string_view{"FileFilterA"});
REQUIRE_THROWS_AS(file_sink_a->add_filter(std::move(filter_a_2)), QuillError);
auto file_sink_b = Frontend::create_or_get_sink<FileSink>(
filename_b,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
// log to filename_b warning, error, critical
file_sink_b->add_filter(std::make_unique<FileFilterB>());
Logger* logger =
Frontend::create_or_get_logger(logger_name, {std::move(file_sink_a), std::move(file_sink_b)});
LOG_INFO(logger, "Lorem ipsum dolor sit amet, consectetur adipiscing elit");
LOG_ERROR(logger, "Nulla tempus, libero at dignissim viverra, lectus libero finibus ante");
// Let all log get flushed to the file
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents_a = quill::testing::file_contents(filename_a);
REQUIRE_EQ(file_contents_a.size(), 1);
REQUIRE(quill::testing::file_contains(
file_contents_a,
std::string{"LOG_INFO " + logger_name + " Lorem ipsum dolor sit amet, consectetur adipiscing elit"}));
std::vector<std::string> const file_contents_b = quill::testing::file_contents(filename_b);
REQUIRE_EQ(file_contents_b.size(), 1);
REQUIRE(quill::testing::file_contains(
file_contents_b,
std::string{"LOG_ERROR " + logger_name +
" Nulla tempus, libero at dignissim viverra, lectus libero finibus ante"}));
testing::remove_file(filename_a);
testing::remove_file(filename_b);
}

View file

@ -0,0 +1,89 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/***/
TEST_CASE("sink_log_level_filter")
{
static constexpr char const* filename_a = "sink_log_level_filter_a.log";
static constexpr char const* filename_b = "sink_log_level_filter_b.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
// Set writing logging to a file
auto file_sink_a = Frontend::create_or_get_sink<FileSink>(
filename_a,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
// Set filters to our handlers
REQUIRE_EQ(file_sink_a->get_log_level_filter(), LogLevel::TraceL3);
file_sink_a->set_log_level_filter(LogLevel::Info);
REQUIRE_EQ(file_sink_a->get_log_level_filter(), LogLevel::Info);
auto file_sink_b = Frontend::create_or_get_sink<FileSink>(
filename_b,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
// log to filename_b warning, error, critical
REQUIRE_EQ(file_sink_b->get_log_level_filter(), LogLevel::TraceL3);
file_sink_b->set_log_level_filter(LogLevel::Error);
REQUIRE_EQ(file_sink_b->get_log_level_filter(), LogLevel::Error);
Logger* logger =
Frontend::create_or_get_logger(logger_name, {std::move(file_sink_a), std::move(file_sink_b)});
LOG_INFO(logger, "Lorem ipsum dolor sit amet, consectetur adipiscing elit");
LOG_ERROR(logger, "Nulla tempus, libero at dignissim viverra, lectus libero finibus ante");
// Let all log get flushed to the file
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents_a = quill::testing::file_contents(filename_a);
REQUIRE_EQ(file_contents_a.size(), 2);
REQUIRE(quill::testing::file_contains(
file_contents_a,
std::string{"LOG_INFO " + logger_name + " Lorem ipsum dolor sit amet, consectetur adipiscing elit"}));
REQUIRE(quill::testing::file_contains(
file_contents_a,
std::string{"LOG_ERROR " + logger_name +
" Nulla tempus, libero at dignissim viverra, lectus libero finibus ante"}));
std::vector<std::string> const file_contents_b = quill::testing::file_contents(filename_b);
REQUIRE_EQ(file_contents_b.size(), 1);
REQUIRE(quill::testing::file_contains(
file_contents_b,
std::string{"LOG_ERROR " + logger_name +
" Nulla tempus, libero at dignissim viverra, lectus libero finibus ante"}));
testing::remove_file(filename_a);
testing::remove_file(filename_b);
}

View file

@ -0,0 +1,209 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Array.h"
#include <array>
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_array_logging")
{
static constexpr char const* filename = "std_array_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::array<bool, 2> b = {true, false};
LOG_INFO(logger, "v {}", b);
std::array<char, 2> c = {'a', 'c'};
LOG_INFO(logger, "c {}", c);
std::array<short, 2> si = {-12, 10};
LOG_INFO(logger, "si {}", si);
std::array<int, 2> i = {-123, 1};
LOG_INFO(logger, "i {}", i);
std::array<long, 2> li = {9876, 1232};
LOG_INFO(logger, "li {}", li);
std::array<long long, 2> lli = {321, 231};
LOG_INFO(logger, "lli {}", lli);
std::array<unsigned short, 2> usi = {15, 2};
LOG_INFO(logger, "usi {}", usi);
std::array<unsigned int, 2> ui = {123, 2};
LOG_INFO(logger, "ui {}", ui);
std::array<unsigned long, 2> uli = {3213, 2876};
LOG_INFO(logger, "uli {}", uli);
std::array<unsigned long long, 2> ulli = {321, 1321};
LOG_INFO(logger, "ulli {}", ulli);
std::array<float, 2> f = {111.1f, 323.31f};
LOG_INFO(logger, "f {}", f);
std::array<double, 2> d = {12.1, 3213213.123};
LOG_INFO(logger, "d {}", d);
std::array<int, 2> const& cri = i;
LOG_INFO(logger, "cri {}", cri);
std::array<int, 2>& ci = i;
LOG_INFO(logger, "ci {}", ci);
std::array<std::string, 2> sa = {"test", "string"};
LOG_INFO(logger, "sa {}", sa);
std::array<std::string_view, 2> sva = {"test", "string_view"};
LOG_INFO(logger, "sva {}", sva);
std::array<char const*, 4> scva = {"c style", "string test", "test", "log"};
LOG_INFO(logger, "scva {}", scva);
std::array<std::array<int, 2>, 3> aai = {{{321, 123}, {444, 333}, {111, 222}}};
LOG_INFO(logger, "aai {}", aai);
std::array<std::array<char const*, 2>, 3> aacs = {
{{"one", "two"}, {"three", "four"}, {"five", "six"}}};
LOG_INFO(logger, "aacs {}", aacs);
std::array<std::array<std::array<char const*, 2>, 2>, 3> aaacs = {
{{{{"one", "two"}, {"three", "four"}}},
{{{"five", "six"}, {"seven", "eight"}}},
{{{"nine", "ten"}, {"eleven", "twelve"}}}}};
LOG_INFO(logger, "aaacs {}", aaacs);
std::array<std::array<std::array<std::string, 2>, 2>, 3> aaabcs = {
{{{{"std_one", "two"}, {"three", "four"}}},
{{{"five", "six"}, {"seven", "eight"}}},
{{{"std_nine", "ten"}, {"eleven", "twelve"}}}}};
LOG_INFO(logger, "aaabcs {}", aaabcs);
std::array<std::array<std::array<std::string_view, 2>, 2>, 3> aaaccs = {
{{{{"std_one", "two"}, {"three_std", "four"}}},
{{{"five", "six"}, {"seven_std", "eight"}}},
{{{"std_nine", "ten"}, {"eleven", "twelve"}}}}};
LOG_INFO(logger, "aaaccs {}", aaaccs);
LOG_INFO(logger, "scva {} sa {} ulli {} scva {} sa {} aaacs {} aai {}", scva, sa, ulli, scva, sa, aaacs, aai);
std::array<int, 4> empt{};
LOG_INFO(logger, "empt {}", empt);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " v [true, false]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c ['a', 'c']"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " si [-12, 10]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " li [9876, 1232]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " lli [321, 231]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usi [15, 2]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ui [123, 2]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uli [3213, 2876]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ulli [321, 1321]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " f [111.1, 323.31]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d [12.1, 3213213.123]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cri [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ci [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva [\"test\", \"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva [\"c style\", \"string test\", \"test\", \"log\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aai [[321, 123], [444, 333], [111, 222]]"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_INFO " + logger_name +
" aacs [[\"one\", \"two\"], [\"three\", \"four\"], [\"five\", \"six\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaabcs [[[\"std_one\", \"two\"], [\"three\", \"four\"]], [[\"five\", \"six\"], [\"seven\", \"eight\"]], [[\"std_nine\", \"ten\"], [\"eleven\", \"twelve\"]]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaaccs [[[\"std_one\", \"two\"], [\"three_std\", \"four\"]], [[\"five\", \"six\"], [\"seven_std\", \"eight\"]], [[\"std_nine\", \"ten\"], [\"eleven\", \"twelve\"]]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaacs [[[\"one\", \"two\"], [\"three\", \"four\"]], [[\"five\", \"six\"], [\"seven\", \"eight\"]], [[\"nine\", \"ten\"], [\"eleven\", \"twelve\"]]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva [\"c style\", \"string test\", \"test\", \"log\"] sa [\"test\", \"string\"] ulli [321, 1321] scva [\"c style\", \"string test\", \"test\", \"log\"] sa [\"test\", \"string\"] aaacs [[[\"one\", \"two\"], [\"three\", \"four\"]], [[\"five\", \"six\"], [\"seven\", \"eight\"]], [[\"nine\", \"ten\"], [\"eleven\", \"twelve\"]]] aai [[321, 123], [444, 333], [111, 222]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empt [0, 0, 0, 0]"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,250 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Deque.h"
#include <cstdio>
#include <deque>
#include <string>
#include <string_view>
using namespace quill;
/***/
TEST_CASE("std_deque_logging")
{
static constexpr char const* filename = "std_deque_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::deque<char> c = {'a', 'c'};
LOG_INFO(logger, "c {}", c);
std::deque<short> si = {-12, 10};
LOG_INFO(logger, "si {}", si);
std::deque<int> i = {-123, 1};
LOG_INFO(logger, "i {}", i);
std::deque<long> li = {9876, 1232};
LOG_INFO(logger, "li {}", li);
std::deque<long long> lli = {321, 231};
LOG_INFO(logger, "lli {}", lli);
std::deque<unsigned short> usi = {15, 2};
LOG_INFO(logger, "usi {}", usi);
std::deque<unsigned int> ui = {123, 2};
LOG_INFO(logger, "ui {}", ui);
std::deque<unsigned long> uli = {3213, 2876};
LOG_INFO(logger, "uli {}", uli);
std::deque<unsigned long long> ulli = {321, 1321};
LOG_INFO(logger, "ulli {}", ulli);
std::deque<float> f = {111.1f, 323.31f};
LOG_INFO(logger, "f {}", f);
std::deque<double> d = {12.1, 3213213.123};
LOG_INFO(logger, "d {}", d);
std::deque<int> const& cri = i;
LOG_INFO(logger, "cri {}", cri);
std::deque<int>& ci = i;
LOG_INFO(logger, "ci {}", ci);
std::deque<std::string> sa = {"test", "string"};
LOG_INFO(logger, "sa {}", sa);
std::deque<std::string_view> sva = {"test", "string_view"};
LOG_INFO(logger, "sva {}", sva);
std::deque<char const*> scva = {"c style", "string test", "test", "log"};
LOG_INFO(logger, "scva {}", scva);
std::deque<std::deque<int>> aai = {{{321, 123}, {444, 333}, {111, 222}}};
LOG_INFO(logger, "aai {}", aai);
std::deque<std::deque<char const*>> aacs = {
{{"one", "two"}, {"three", "four"}, {"five", "six"}}};
LOG_INFO(logger, "aacs {}", aacs);
std::deque<std::deque<std::deque<char const*>>> aaacs = {
{{{{"one", "two"}, {"three", "four"}}},
{{{"five", "six"}, {"seven", "eight"}}},
{{{"nine", "ten"}, {"eleven", "twelve"}}}}};
LOG_INFO(logger, "aaacs {}", aaacs);
{
std::deque<std::deque<std::deque<std::string>>> aaabcs;
// First outer deque
std::deque<std::deque<std::string>> first_outer = {{"std_one", "two"}, {"three", "four"}};
// Second outer deque
std::deque<std::deque<std::string>> second_outer = {{"five", "six"}, {"seven", "eight"}};
// Third outer deque
std::deque<std::deque<std::string>> third_outer = {{"std_nine", "ten"}, {"eleven", "twelve"}};
aaabcs.push_back(first_outer);
aaabcs.push_back(second_outer);
aaabcs.push_back(third_outer);
LOG_INFO(logger, "aaabcs {}", aaabcs);
}
{
std::deque<std::deque<std::deque<std::string>>> aaaccs;
// First outer deque
std::deque<std::deque<std::string>> first_outer = {{"std_one", "two"}, {"three_std", "four"}};
// Second outer deque
std::deque<std::deque<std::string>> second_outer = {{"five", "six"}, {"seven_std", "eight"}};
// Third outer deque
std::deque<std::deque<std::string>> third_outer = {{"std_nine", "ten"}, {"eleven", "twelve"}};
aaaccs.push_back(first_outer);
aaaccs.push_back(second_outer);
aaaccs.push_back(third_outer);
LOG_INFO(logger, "aaaccs {}", aaaccs);
}
LOG_INFO(logger, "scva {} sa {} ulli {} scva {} sa {} aaacs {} aai {}", scva, sa, ulli, scva, sa, aaacs, aai);
std::deque<int> loopv;
for (int iter = 0; iter < 100; ++iter)
{
loopv.push_back(iter);
}
LOG_INFO(logger, "loopv {}", loopv);
std::deque<std::string> loopsv;
for (int iter = 0; iter < 100; ++iter)
{
loopsv.push_back(std::to_string(iter));
}
LOG_INFO(logger, "loopsv {}", loopsv);
std::deque<int> empt;
LOG_INFO(logger, "empt {}", empt);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c ['a', 'c']"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " si [-12, 10]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " li [9876, 1232]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " lli [321, 231]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usi [15, 2]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ui [123, 2]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uli [3213, 2876]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ulli [321, 1321]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " f [111.1, 323.31]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d [12.1, 3213213.123]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cri [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ci [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva [\"test\", \"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva [\"c style\", \"string test\", \"test\", \"log\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aai [[321, 123], [444, 333], [111, 222]]"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_INFO " + logger_name +
" aacs [[\"one\", \"two\"], [\"three\", \"four\"], [\"five\", \"six\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaabcs [[[\"std_one\", \"two\"], [\"three\", \"four\"]], [[\"five\", \"six\"], [\"seven\", \"eight\"]], [[\"std_nine\", \"ten\"], [\"eleven\", \"twelve\"]]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaaccs [[[\"std_one\", \"two\"], [\"three_std\", \"four\"]], [[\"five\", \"six\"], [\"seven_std\", \"eight\"]], [[\"std_nine\", \"ten\"], [\"eleven\", \"twelve\"]]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaacs [[[\"one\", \"two\"], [\"three\", \"four\"]], [[\"five\", \"six\"], [\"seven\", \"eight\"]], [[\"nine\", \"ten\"], [\"eleven\", \"twelve\"]]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva [\"c style\", \"string test\", \"test\", \"log\"] sa [\"test\", \"string\"] ulli [321, 1321] scva [\"c style\", \"string test\", \"test\", \"log\"] sa [\"test\", \"string\"] aaacs [[[\"one\", \"two\"], [\"three\", \"four\"]], [[\"five\", \"six\"], [\"seven\", \"eight\"]], [[\"nine\", \"ten\"], [\"eleven\", \"twelve\"]]] aai [[321, 123], [444, 333], [111, 222]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopv [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopsv [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\", \"17\", \"18\", \"19\", \"20\", \"21\", \"22\", \"23\", \"24\", \"25\", \"26\", \"27\", \"28\", \"29\", \"30\", \"31\", \"32\", \"33\", \"34\", \"35\", \"36\", \"37\", \"38\", \"39\", \"40\", \"41\", \"42\", \"43\", \"44\", \"45\", \"46\", \"47\", \"48\", \"49\", \"50\", \"51\", \"52\", \"53\", \"54\", \"55\", \"56\", \"57\", \"58\", \"59\", \"60\", \"61\", \"62\", \"63\", \"64\", \"65\", \"66\", \"67\", \"68\", \"69\", \"70\", \"71\", \"72\", \"73\", \"74\", \"75\", \"76\", \"77\", \"78\", \"79\", \"80\", \"81\", \"82\", \"83\", \"84\", \"85\", \"86\", \"87\", \"88\", \"89\", \"90\", \"91\", \"92\", \"93\", \"94\", \"95\", \"96\", \"97\", \"98\", \"99\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empt []"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,67 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/core/Filesystem.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/FilesystemPath.h"
#include <array>
#include <cstdio>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_filesystem_path_logging")
{
static constexpr char const* filename = "std_filesystem_path_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
fs::path sp{"/usr/local/bin"};
LOG_INFO(logger, "sp {} {} {}", sp, sp, sp);
LOG_INFO(logger, "sp_2 {} {} {}", sp, sp, sp);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sp /usr/local/bin /usr/local/bin /usr/local/bin"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sp_2 /usr/local/bin /usr/local/bin /usr/local/bin"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,161 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/ForwardList.h"
#include <cstdio>
#include <forward_list>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_forward_list_logging")
{
static constexpr char const* filename = "std_forward_list_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::forward_list<char> c = {'a', 'c'};
LOG_INFO(logger, "c {}", c);
std::forward_list<short> si = {-12, 10};
LOG_INFO(logger, "si {}", si);
std::forward_list<int> i = {-123, 1};
LOG_INFO(logger, "i {}", i);
std::forward_list<long> li = {9876, 1232};
LOG_INFO(logger, "li {}", li);
std::forward_list<long long> lli = {321, 231};
LOG_INFO(logger, "lli {}", lli);
std::forward_list<unsigned short> usi = {15, 2};
LOG_INFO(logger, "usi {}", usi);
std::forward_list<unsigned int> ui = {123, 2};
LOG_INFO(logger, "ui {}", ui);
std::forward_list<unsigned long> uli = {3213, 2876};
LOG_INFO(logger, "uli {}", uli);
std::forward_list<unsigned long long> ulli = {321, 1321};
LOG_INFO(logger, "ulli {}", ulli);
std::forward_list<float> f = {111.1f, 323.31f};
LOG_INFO(logger, "f {}", f);
std::forward_list<double> d = {12.1, 3213213.123};
LOG_INFO(logger, "d {}", d);
std::forward_list<int> const& cri = i;
LOG_INFO(logger, "cri {}", cri);
std::forward_list<int>& ci = i;
LOG_INFO(logger, "ci {}", ci);
std::forward_list<std::string> sa = {"test", "string"};
LOG_INFO(logger, "sa {}", sa);
std::forward_list<std::string_view> sva = {"test", "string_view"};
LOG_INFO(logger, "sva {}", sva);
std::forward_list<char const*> scva = {"c style", "string test", "test", "log"};
LOG_INFO(logger, "scva {}", scva);
LOG_INFO(logger, "scva {} sa {} ulli {} scva {} sa {}", scva, sa, ulli, scva, sa);
std::forward_list<int> empt;
LOG_INFO(logger, "empt {}", empt);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c ['a', 'c']"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " si [-12, 10]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " li [9876, 1232]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " lli [321, 231]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usi [15, 2]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ui [123, 2]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uli [3213, 2876]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ulli [321, 1321]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " f [111.1, 323.31]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d [12.1, 3213213.123]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cri [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ci [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva [\"test\", \"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva [\"c style\", \"string test\", \"test\", \"log\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva [\"c style\", \"string test\", \"test\", \"log\"] sa [\"test\", \"string\"] ulli [321, 1321] scva [\"c style\", \"string test\", \"test\", \"log\"] sa [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empt []"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,161 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/List.h"
#include <cstdio>
#include <list>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_list_logging")
{
static constexpr char const* filename = "std_list_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::list<char> c = {'a', 'c'};
LOG_INFO(logger, "c {}", c);
std::list<short> si = {-12, 10};
LOG_INFO(logger, "si {}", si);
std::list<int> i = {-123, 1};
LOG_INFO(logger, "i {}", i);
std::list<long> li = {9876, 1232};
LOG_INFO(logger, "li {}", li);
std::list<long long> lli = {321, 231};
LOG_INFO(logger, "lli {}", lli);
std::list<unsigned short> usi = {15, 2};
LOG_INFO(logger, "usi {}", usi);
std::list<unsigned int> ui = {123, 2};
LOG_INFO(logger, "ui {}", ui);
std::list<unsigned long> uli = {3213, 2876};
LOG_INFO(logger, "uli {}", uli);
std::list<unsigned long long> ulli = {321, 1321};
LOG_INFO(logger, "ulli {}", ulli);
std::list<float> f = {111.1f, 323.31f};
LOG_INFO(logger, "f {}", f);
std::list<double> d = {12.1, 3213213.123};
LOG_INFO(logger, "d {}", d);
std::list<int> const& cri = i;
LOG_INFO(logger, "cri {}", cri);
std::list<int>& ci = i;
LOG_INFO(logger, "ci {}", ci);
std::list<std::string> sa = {"test", "string"};
LOG_INFO(logger, "sa {}", sa);
std::list<std::string_view> sva = {"test", "string_view"};
LOG_INFO(logger, "sva {}", sva);
std::list<char const*> scva = {"c style", "string test", "test", "log"};
LOG_INFO(logger, "scva {}", scva);
LOG_INFO(logger, "scva {} sa {} ulli {} scva {} sa {}", scva, sa, ulli, scva, sa);
std::list<int> empt;
LOG_INFO(logger, "empt {}", empt);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c ['a', 'c']"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " si [-12, 10]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " li [9876, 1232]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " lli [321, 231]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usi [15, 2]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ui [123, 2]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uli [3213, 2876]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ulli [321, 1321]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " f [111.1, 323.31]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d [12.1, 3213213.123]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cri [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ci [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva [\"test\", \"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva [\"c style\", \"string test\", \"test\", \"log\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva [\"c style\", \"string test\", \"test\", \"log\"] sa [\"test\", \"string\"] ulli [321, 1321] scva [\"c style\", \"string test\", \"test\", \"log\"] sa [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empt []"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,101 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Map.h"
#include <cstdio>
#include <cstring>
#include <map>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
struct CStringComparator
{
bool operator()(char const* a, char const* b) const { return std::strcmp(a, b) < 0; }
};
/***/
TEST_CASE("std_map_logging")
{
static constexpr char const* filename = "std_map_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::map<int, double> idm = {{111, 3213.21}, {222, 321.19}, {333, 5555.99}};
LOG_INFO(logger, "idm {}", idm);
std::map<int, std::string> loopv;
for (int iter = 0; iter < 25; ++iter)
{
loopv.emplace(iter, std::to_string(iter));
}
LOG_INFO(logger, "loopv {}", loopv);
std::map<std::string, std::string> loopsv;
for (int iter = 0; iter < 25; ++iter)
{
loopsv.emplace(std::to_string(iter), std::to_string(iter * 2));
}
LOG_INFO(logger, "loopsv {}", loopv);
std::map<char const*, char const*, CStringComparator> ccm = {
{"4", "400"}, {"3", "300"}, {"1", "100"}, {"2", "200"}};
LOG_INFO(logger, "ccm {}", ccm);
std::map<char const*, int, CStringComparator> cim = {{"4", 4}, {"3", 3}, {"1", 1}, {"2", 2}};
LOG_INFO(logger, "cim {}", cim);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " idm {111: 3213.21, 222: 321.19, 333: 5555.99}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopv {0: \"0\", 1: \"1\", 2: \"2\", 3: \"3\", 4: \"4\", 5: \"5\", 6: \"6\", 7: \"7\", 8: \"8\", 9: \"9\", 10: \"10\", 11: \"11\", 12: \"12\", 13: \"13\", 14: \"14\", 15: \"15\", 16: \"16\", 17: \"17\", 18: \"18\", 19: \"19\", 20: \"20\", 21: \"21\", 22: \"22\", 23: \"23\", 24: \"24\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopsv {0: \"0\", 1: \"1\", 2: \"2\", 3: \"3\", 4: \"4\", 5: \"5\", 6: \"6\", 7: \"7\", 8: \"8\", 9: \"9\", 10: \"10\", 11: \"11\", 12: \"12\", 13: \"13\", 14: \"14\", 15: \"15\", 16: \"16\", 17: \"17\", 18: \"18\", 19: \"19\", 20: \"20\", 21: \"21\", 22: \"22\", 23: \"23\", 24: \"24\""}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_INFO " + logger_name +
" ccm {\"1\": \"100\", \"2\": \"200\", \"3\": \"300\", \"4\": \"400\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cim {\"1\": 1, \"2\": 2, \"3\": 3, \"4\": 4}"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,101 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Map.h"
#include <cstdio>
#include <cstring>
#include <map>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
struct CStringComparator
{
bool operator()(char const* a, char const* b) const { return std::strcmp(a, b) < 0; }
};
/***/
TEST_CASE("std_multimap_logging")
{
static constexpr char const* filename = "std_multimap_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::multimap<int, double> idm = {{111, 3213.21}, {222, 321.19}, {333, 5555.99}};
LOG_INFO(logger, "idm {}", idm);
std::multimap<int, std::string> loopv;
for (int iter = 0; iter < 25; ++iter)
{
loopv.emplace(iter, std::to_string(iter));
}
LOG_INFO(logger, "loopv {}", loopv);
std::multimap<std::string, std::string> loopsv;
for (int iter = 0; iter < 25; ++iter)
{
loopsv.emplace(std::to_string(iter), std::to_string(iter * 2));
}
LOG_INFO(logger, "loopsv {}", loopv);
std::multimap<char const*, char const*, CStringComparator> ccm = {
{"4", "400"}, {"3", "300"}, {"1", "100"}, {"2", "200"}};
LOG_INFO(logger, "ccm {}", ccm);
std::multimap<char const*, int, CStringComparator> cim = {{"4", 4}, {"3", 3}, {"1", 1}, {"2", 2}};
LOG_INFO(logger, "cim {}", cim);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " idm {111: 3213.21, 222: 321.19, 333: 5555.99}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopv {0: \"0\", 1: \"1\", 2: \"2\", 3: \"3\", 4: \"4\", 5: \"5\", 6: \"6\", 7: \"7\", 8: \"8\", 9: \"9\", 10: \"10\", 11: \"11\", 12: \"12\", 13: \"13\", 14: \"14\", 15: \"15\", 16: \"16\", 17: \"17\", 18: \"18\", 19: \"19\", 20: \"20\", 21: \"21\", 22: \"22\", 23: \"23\", 24: \"24\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopsv {0: \"0\", 1: \"1\", 2: \"2\", 3: \"3\", 4: \"4\", 5: \"5\", 6: \"6\", 7: \"7\", 8: \"8\", 9: \"9\", 10: \"10\", 11: \"11\", 12: \"12\", 13: \"13\", 14: \"14\", 15: \"15\", 16: \"16\", 17: \"17\", 18: \"18\", 19: \"19\", 20: \"20\", 21: \"21\", 22: \"22\", 23: \"23\", 24: \"24\""}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_INFO " + logger_name +
" ccm {\"1\": \"100\", \"2\": \"200\", \"3\": \"300\", \"4\": \"400\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cim {\"1\": 1, \"2\": 2, \"3\": 3, \"4\": 4}"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,218 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Set.h"
#include <cstdio>
#include <cstring>
#include <set>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
struct CStringComparator
{
bool operator()(char const* a, char const* b) const { return std::strcmp(a, b) < 0; }
};
/***/
TEST_CASE("std_multiset_logging")
{
static constexpr char const* filename = "std_multiset_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::multiset<char> c = {'a', 'c'};
LOG_INFO(logger, "c {}", c);
std::multiset<short> si = {-12, 10};
LOG_INFO(logger, "si {}", si);
std::multiset<int> i = {-123, 1};
LOG_INFO(logger, "i {}", i);
std::multiset<long> li = {9876, 1232};
LOG_INFO(logger, "li {}", li);
std::multiset<long long> lli = {321, 231};
LOG_INFO(logger, "lli {}", lli);
std::multiset<unsigned short> usi = {15, 2};
LOG_INFO(logger, "usi {}", usi);
std::multiset<unsigned int> ui = {123, 2};
LOG_INFO(logger, "ui {}", ui);
std::multiset<unsigned long> uli = {3213, 2876};
LOG_INFO(logger, "uli {}", uli);
std::multiset<unsigned long long> ulli = {321, 1321};
LOG_INFO(logger, "ulli {}", ulli);
std::multiset<float> f = {111.1f, 323.31f};
LOG_INFO(logger, "f {}", f);
std::multiset<double> d = {12.1, 3213213.123};
LOG_INFO(logger, "d {}", d);
std::multiset<int> const& cri = i;
LOG_INFO(logger, "cri {}", cri);
std::multiset<int>& ci = i;
LOG_INFO(logger, "ci {}", ci);
std::multiset<std::string> sa = {"test", "string"};
LOG_INFO(logger, "sa {}", sa);
std::multiset<std::string_view> sva = {"test", "string_view"};
LOG_INFO(logger, "sva {}", sva);
std::multiset<char const*, CStringComparator> scva = {"c_style", "aa", "string_test", "test",
"log"};
LOG_INFO(logger, "scva {}", scva);
std::multiset<std::multiset<int>> aai = {{{321, 123}, {444, 333}, {111, 222}}};
LOG_INFO(logger, "aai {}", aai);
{
std::multiset<std::multiset<char const*, CStringComparator>> aacs = {
{{"one", "two"}, {"three", "four"}, {"five", "six"}}};
LOG_INFO(logger, "aacs {}", aacs);
}
{
std::multiset<std::multiset<std::multiset<char const*, CStringComparator>>> aaacs = {
{{{{"one", "two"}, {"three", "four"}}},
{{{"five", "six"}, {"seven", "eight"}}},
{{{"nine", "ten"}, {"eleven", "twelve"}}}}};
LOG_INFO(logger, "aaacs {}", aaacs);
}
LOG_INFO(logger, "scva {} sa {} ulli {} scva {} sa {} aai {}", scva, sa, ulli, scva, sa, aai);
std::multiset<int> loopv;
for (int iter = 0; iter < 100; ++iter)
{
loopv.insert(iter);
}
LOG_INFO(logger, "loopv {}", loopv);
std::multiset<std::string> loopsv;
for (int iter = 0; iter < 100; ++iter)
{
loopsv.insert(std::to_string(iter));
}
LOG_INFO(logger, "loopsv {}", loopsv);
std::multiset<int> empt;
LOG_INFO(logger, "empt {}", empt);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c {'a', 'c'}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " si {-12, 10}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i {-123, 1}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " li {1232, 9876}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " lli {231, 321}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usi {2, 15}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ui {2, 123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uli {2876, 3213}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ulli {321, 1321}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " f {111.1, 323.31}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d {12.1, 3213213.123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cri {-123, 1}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ci {-123, 1}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa {\"string\", \"test\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva {\"string_view\", \"test\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_INFO " + logger_name +
" scva {\"aa\", \"c_style\", \"log\", \"string_test\", \"test\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aai {{111, 222}, {123, 321}, {333, 444}}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aacs {{"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaacs {{{"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva {\"aa\", \"c_style\", \"log\", \"string_test\", \"test\"} sa {\"string\", \"test\"} ulli {321, 1321} scva {\"aa\", \"c_style\", \"log\", \"string_test\", \"test\"} sa {\"string\", \"test\"} aai {{111, 222}, {123, 321}, {333, 444}}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopv {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopsv {\"0\", \"1\", \"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\", \"17\", \"18\", \"19\", \"2\", \"20\", \"21\", \"22\", \"23\", \"24\", \"25\", \"26\", \"27\", \"28\", \"29\", \"3\", \"30\", \"31\", \"32\", \"33\", \"34\", \"35\", \"36\", \"37\", \"38\", \"39\", \"4\", \"40\", \"41\", \"42\", \"43\", \"44\", \"45\", \"46\", \"47\", \"48\", \"49\", \"5\", \"50\", \"51\", \"52\", \"53\", \"54\", \"55\", \"56\", \"57\", \"58\", \"59\", \"6\", \"60\", \"61\", \"62\", \"63\", \"64\", \"65\", \"66\", \"67\", \"68\", \"69\", \"7\", \"70\", \"71\", \"72\", \"73\", \"74\", \"75\", \"76\", \"77\", \"78\", \"79\", \"8\", \"80\", \"81\", \"82\", \"83\", \"84\", \"85\", \"86\", \"87\", \"88\", \"89\", \"9\", \"90\", \"91\", \"92\", \"93\", \"94\", \"95\", \"96\", \"97\", \"98\", \"99\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empt {}"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,109 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Optional.h"
#include <array>
#include <cstdio>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_optional_logging")
{
static constexpr char const* filename = "std_optional_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::optional<int> ei{std::nullopt};
LOG_INFO(logger, "ei [{}]", ei);
std::optional<double> ed{std::nullopt};
LOG_INFO(logger, "ed [{}]", ed);
std::optional<char const*> ccp{"testing"};
LOG_INFO(logger, "ccp [{}]", ccp);
std::optional<std::string> sp{"sp_testing"};
LOG_INFO(logger, "sp [{}]", sp);
std::optional<std::string> esp{std::nullopt};
LOG_INFO(logger, "esp [{}]", esp);
std::optional<std::string_view> svp{"svp_testing"};
LOG_INFO(logger, "svp [{}]", svp);
std::optional<int> i{123321};
LOG_INFO(logger, "i [{}]", i);
std::optional<double> d{333.221};
LOG_INFO(logger, "d [{}]", d);
LOG_INFO(logger, "zzzz [{}] [{}] [{}] [{}] [{}]", d, "test", *svp, *sp, ccp);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ei [none]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ed [none]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ccp [optional(\"testing\")]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sp [optional(\"sp_testing\")]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " esp [none]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " svp [optional(\"svp_testing\")]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i [optional(123321)]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d [optional(333.221)]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " zzzz [optional(333.221)] [test] [svp_testing] [sp_testing] [optional(\"testing\")]"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,92 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Pair.h"
#include <array>
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_pair_logging")
{
static constexpr char const* filename = "std_pair_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::pair<bool, int> b = {true, 312};
LOG_INFO(logger, "v {}", b);
std::pair<std::string, std::string> sa = {"test", "string"};
LOG_INFO(logger, "sa {}", sa);
std::pair<std::string_view, std::string> sva = {"test", "string_view"};
LOG_INFO(logger, "sva {}", sva);
std::pair<char const*, std::string> scva = {"c style", "string test"};
LOG_INFO(logger, "scva {}", scva);
std::pair<std::pair<std::string, std::string_view>, std::pair<const char*, uint32_t>> cp = {
{"pair", "testing"}, {"first", 2}};
LOG_INFO(logger, "cp {}", cp);
std::pair<int, double> empt;
LOG_INFO(logger, "empt {}", empt);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " v (true, 312)"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa (\"test\", \"string\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva (\"test\", \"string_view\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva (\"c style\", \"string test\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cp ((\"pair\", \"testing\"), (\"first\", 2))"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empt (0, 0)"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,217 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Set.h"
#include <cstdio>
#include <cstring>
#include <set>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
struct CStringComparator
{
bool operator()(char const* a, char const* b) const { return std::strcmp(a, b) < 0; }
};
/***/
TEST_CASE("std_set_logging")
{
static constexpr char const* filename = "std_set_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::set<char> c = {'a', 'c'};
LOG_INFO(logger, "c {}", c);
std::set<short> si = {-12, 10};
LOG_INFO(logger, "si {}", si);
std::set<int> i = {-123, 1};
LOG_INFO(logger, "i {}", i);
std::set<long> li = {9876, 1232};
LOG_INFO(logger, "li {}", li);
std::set<long long> lli = {321, 231};
LOG_INFO(logger, "lli {}", lli);
std::set<unsigned short> usi = {15, 2};
LOG_INFO(logger, "usi {}", usi);
std::set<unsigned int> ui = {123, 2};
LOG_INFO(logger, "ui {}", ui);
std::set<unsigned long> uli = {3213, 2876};
LOG_INFO(logger, "uli {}", uli);
std::set<unsigned long long> ulli = {321, 1321};
LOG_INFO(logger, "ulli {}", ulli);
std::set<float> f = {111.1f, 323.31f};
LOG_INFO(logger, "f {}", f);
std::set<double> d = {12.1, 3213213.123};
LOG_INFO(logger, "d {}", d);
std::set<int> const& cri = i;
LOG_INFO(logger, "cri {}", cri);
std::set<int>& ci = i;
LOG_INFO(logger, "ci {}", ci);
std::set<std::string> sa = {"test", "string"};
LOG_INFO(logger, "sa {}", sa);
std::set<std::string_view> sva = {"test", "string_view"};
LOG_INFO(logger, "sva {}", sva);
std::set<char const*, CStringComparator> scva = {"c_style", "aa", "string_test", "test", "log"};
LOG_INFO(logger, "scva {}", scva);
std::set<std::set<int>> aai = {{{321, 123}, {444, 333}, {111, 222}}};
LOG_INFO(logger, "aai {}", aai);
{
std::set<std::set<char const*, CStringComparator>> aacs = {
{{"one", "two"}, {"three", "four"}, {"five", "six"}}};
LOG_INFO(logger, "aacs {}", aacs);
}
{
std::set<std::set<std::set<char const*, CStringComparator>>> aaacs = {
{{{{"one", "two"}, {"three", "four"}}},
{{{"five", "six"}, {"seven", "eight"}}},
{{{"nine", "ten"}, {"eleven", "twelve"}}}}};
LOG_INFO(logger, "aaacs {}", aaacs);
}
LOG_INFO(logger, "scva {} sa {} ulli {} scva {} sa {} aai {}", scva, sa, ulli, scva, sa, aai);
std::set<int> loopv;
for (int iter = 0; iter < 100; ++iter)
{
loopv.insert(iter);
}
LOG_INFO(logger, "loopv {}", loopv);
std::set<std::string> loopsv;
for (int iter = 0; iter < 100; ++iter)
{
loopsv.insert(std::to_string(iter));
}
LOG_INFO(logger, "loopsv {}", loopsv);
std::set<int> empt;
LOG_INFO(logger, "empt {}", empt);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c {'a', 'c'}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " si {-12, 10}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i {-123, 1}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " li {1232, 9876}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " lli {231, 321}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usi {2, 15}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ui {2, 123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uli {2876, 3213}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ulli {321, 1321}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " f {111.1, 323.31}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d {12.1, 3213213.123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cri {-123, 1}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ci {-123, 1}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa {\"string\", \"test\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva {\"string_view\", \"test\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_INFO " + logger_name +
" scva {\"aa\", \"c_style\", \"log\", \"string_test\", \"test\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aai {{111, 222}, {123, 321}, {333, 444}}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aacs {{"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaacs {{{"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva {\"aa\", \"c_style\", \"log\", \"string_test\", \"test\"} sa {\"string\", \"test\"} ulli {321, 1321} scva {\"aa\", \"c_style\", \"log\", \"string_test\", \"test\"} sa {\"string\", \"test\"} aai {{111, 222}, {123, 321}, {333, 444}}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopv {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopsv {\"0\", \"1\", \"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\", \"17\", \"18\", \"19\", \"2\", \"20\", \"21\", \"22\", \"23\", \"24\", \"25\", \"26\", \"27\", \"28\", \"29\", \"3\", \"30\", \"31\", \"32\", \"33\", \"34\", \"35\", \"36\", \"37\", \"38\", \"39\", \"4\", \"40\", \"41\", \"42\", \"43\", \"44\", \"45\", \"46\", \"47\", \"48\", \"49\", \"5\", \"50\", \"51\", \"52\", \"53\", \"54\", \"55\", \"56\", \"57\", \"58\", \"59\", \"6\", \"60\", \"61\", \"62\", \"63\", \"64\", \"65\", \"66\", \"67\", \"68\", \"69\", \"7\", \"70\", \"71\", \"72\", \"73\", \"74\", \"75\", \"76\", \"77\", \"78\", \"79\", \"8\", \"80\", \"81\", \"82\", \"83\", \"84\", \"85\", \"86\", \"87\", \"88\", \"89\", \"9\", \"90\", \"91\", \"92\", \"93\", \"94\", \"95\", \"96\", \"97\", \"98\", \"99\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empt {}"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,80 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Tuple.h"
#include <array>
#include <cstdio>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_tuple_logging")
{
static constexpr char const* filename = "std_tuple_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::tuple<std::string> st{"123456789"};
LOG_INFO(logger, "st {}", st);
std::tuple<std::string, int> et;
LOG_INFO(logger, "et {}", et);
std::tuple<std::string, std::string_view, int, double, char const*, std::string, int> ct{
"string", "string_view", 213, 33.12, "c_style", "another_string", 123};
LOG_INFO(logger, "ct {}", ct);
LOG_INFO(logger, "ct {} et {} st {}", ct, et, st);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " st (\"123456789\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " et (\"\", 0)"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ct (\"string\", \"string_view\", 213, 33.12, \"c_style\", \"another_string\", 123)"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ct (\"string\", \"string_view\", 213, 33.12, \"c_style\", \"another_string\", 123) et (\"\", 0) st (\"123456789\")"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,93 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/UnorderedMap.h"
#include <cstdio>
#include <cstring>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_unordered_map_logging")
{
static constexpr char const* filename = "std_unordered_map_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::unordered_map<int, double> idm = {{111, 3213.21}};
LOG_INFO(logger, "idm {}", idm);
std::unordered_map<int, std::string> loopv;
for (int iter = 0; iter < 25; ++iter)
{
loopv.emplace(iter, std::to_string(iter));
}
LOG_INFO(logger, "loopv {}", loopv);
std::unordered_map<std::string, std::string> loopsv;
for (int iter = 0; iter < 25; ++iter)
{
loopsv.emplace(std::to_string(iter), std::to_string(iter * 2));
}
LOG_INFO(logger, "loopsv {}", loopv);
std::unordered_map<char const*, char const*> ccm = {{"4", "400"}};
LOG_INFO(logger, "ccm {}", ccm);
std::unordered_map<char const*, int> cim = {{"4", 4}};
LOG_INFO(logger, "cim {}", cim);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " idm {111: 3213.21}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopv {"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopsv {"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ccm {\"4\": \"400\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cim {\"4\": 4}"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,93 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/UnorderedMap.h"
#include <cstdio>
#include <cstring>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_unordered_multimap_logging")
{
static constexpr char const* filename = "std_unordered_multimap_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::unordered_multimap<int, double> idm = {{111, 3213.21}};
LOG_INFO(logger, "idm {}", idm);
std::unordered_multimap<int, std::string> loopv;
for (int iter = 0; iter < 25; ++iter)
{
loopv.emplace(iter, std::to_string(iter));
}
LOG_INFO(logger, "loopv {}", loopv);
std::unordered_multimap<std::string, std::string> loopsv;
for (int iter = 0; iter < 25; ++iter)
{
loopsv.emplace(std::to_string(iter), std::to_string(iter * 2));
}
LOG_INFO(logger, "loopsv {}", loopv);
std::unordered_multimap<char const*, char const*> ccm = {{"4", "400"}};
LOG_INFO(logger, "ccm {}", ccm);
std::unordered_multimap<char const*, int> cim = {{"4", 4}};
LOG_INFO(logger, "cim {}", cim);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " idm {111: 3213.21}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopv {"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopsv {"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ccm {\"4\": \"400\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cim {\"4\": 4}"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,184 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/UnorderedSet.h"
#include <cstdio>
#include <cstring>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_unordered_multi_set_logging")
{
static constexpr char const* filename = "std_unordered_multi_set_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::unordered_multiset<char> c = {'a'};
LOG_INFO(logger, "c {}", c);
std::unordered_multiset<short> si = {-12};
LOG_INFO(logger, "si {}", si);
std::unordered_multiset<int> i = {-123};
LOG_INFO(logger, "i {}", i);
std::unordered_multiset<long> li = {9876};
LOG_INFO(logger, "li {}", li);
std::unordered_multiset<long long> lli = {321};
LOG_INFO(logger, "lli {}", lli);
std::unordered_multiset<unsigned short> usi = {15};
LOG_INFO(logger, "usi {}", usi);
std::unordered_multiset<unsigned int> ui = {123};
LOG_INFO(logger, "ui {}", ui);
std::unordered_multiset<unsigned long> uli = {3213};
LOG_INFO(logger, "uli {}", uli);
std::unordered_multiset<unsigned long long> ulli = {321};
LOG_INFO(logger, "ulli {}", ulli);
std::unordered_multiset<float> f = {111.1f};
LOG_INFO(logger, "f {}", f);
std::unordered_multiset<double> d = {12.1};
LOG_INFO(logger, "d {}", d);
std::unordered_multiset<int> const& cri = i;
LOG_INFO(logger, "cri {}", cri);
std::unordered_multiset<int>& ci = i;
LOG_INFO(logger, "ci {}", ci);
std::unordered_multiset<std::string> sa = {"test"};
LOG_INFO(logger, "sa {}", sa);
std::unordered_multiset<std::string_view> sva = {"string_view"};
LOG_INFO(logger, "sva {}", sva);
std::unordered_multiset<char const*> scva = {"c_style"};
LOG_INFO(logger, "scva {}", scva);
LOG_INFO(logger, "scva {} sa {} ulli {} scva {} sa {}", scva, sa, ulli, scva, sa);
std::unordered_multiset<int> loopv;
for (int iter = 0; iter < 100; ++iter)
{
loopv.insert(iter);
}
LOG_INFO(logger, "loopv {}", loopv);
std::unordered_multiset<std::string> loopsv;
for (int iter = 0; iter < 100; ++iter)
{
loopsv.insert(std::to_string(iter));
}
LOG_INFO(logger, "loopsv {}", loopsv);
std::unordered_multiset<int> empt;
LOG_INFO(logger, "empt {}", empt);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c {'a'}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " si {-12}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i {-123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " li {9876}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " lli {321}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usi {15}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ui {123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uli {3213}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ulli {321}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " f {111.1}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d {12.1}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cri {-123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ci {-123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa {\"test\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva {\"string_view\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva {\"c_style\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva {\"c_style\"} sa {\"test\"} ulli {321} scva {\"c_style\"} sa {\"test\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopv {"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopsv {"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empt {}"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,184 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/UnorderedSet.h"
#include <cstdio>
#include <cstring>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_unordered_set_logging")
{
static constexpr char const* filename = "std_unordered_set_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::unordered_set<char> c = {'a'};
LOG_INFO(logger, "c {}", c);
std::unordered_set<short> si = {-12};
LOG_INFO(logger, "si {}", si);
std::unordered_set<int> i = {-123};
LOG_INFO(logger, "i {}", i);
std::unordered_set<long> li = {9876};
LOG_INFO(logger, "li {}", li);
std::unordered_set<long long> lli = {321};
LOG_INFO(logger, "lli {}", lli);
std::unordered_set<unsigned short> usi = {15};
LOG_INFO(logger, "usi {}", usi);
std::unordered_set<unsigned int> ui = {123};
LOG_INFO(logger, "ui {}", ui);
std::unordered_set<unsigned long> uli = {3213};
LOG_INFO(logger, "uli {}", uli);
std::unordered_set<unsigned long long> ulli = {321};
LOG_INFO(logger, "ulli {}", ulli);
std::unordered_set<float> f = {111.1f};
LOG_INFO(logger, "f {}", f);
std::unordered_set<double> d = {12.1};
LOG_INFO(logger, "d {}", d);
std::unordered_set<int> const& cri = i;
LOG_INFO(logger, "cri {}", cri);
std::unordered_set<int>& ci = i;
LOG_INFO(logger, "ci {}", ci);
std::unordered_set<std::string> sa = {"test"};
LOG_INFO(logger, "sa {}", sa);
std::unordered_set<std::string_view> sva = {"string_view"};
LOG_INFO(logger, "sva {}", sva);
std::unordered_set<char const*> scva = {"c_style"};
LOG_INFO(logger, "scva {}", scva);
LOG_INFO(logger, "scva {} sa {} ulli {} scva {} sa {}", scva, sa, ulli, scva, sa);
std::unordered_set<int> loopv;
for (int iter = 0; iter < 100; ++iter)
{
loopv.insert(iter);
}
LOG_INFO(logger, "loopv {}", loopv);
std::unordered_set<std::string> loopsv;
for (int iter = 0; iter < 100; ++iter)
{
loopsv.insert(std::to_string(iter));
}
LOG_INFO(logger, "loopsv {}", loopsv);
std::unordered_set<int> empt;
LOG_INFO(logger, "empt {}", empt);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c {'a'}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " si {-12}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i {-123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " li {9876}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " lli {321}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usi {15}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ui {123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uli {3213}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ulli {321}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " f {111.1}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d {12.1}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cri {-123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ci {-123}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa {\"test\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva {\"string_view\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva {\"c_style\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva {\"c_style\"} sa {\"test\"} ulli {321} scva {\"c_style\"} sa {\"test\"}"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopv {"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopsv {"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empt {}"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,255 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Vector.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("std_vector_logging")
{
static constexpr char const* filename = "std_vector_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::vector<char> c = {'a', 'c'};
LOG_INFO(logger, "c {}", c);
std::vector<short> si = {-12, 10};
LOG_INFO(logger, "si {}", si);
std::vector<int> i = {-123, 1};
LOG_INFO(logger, "i {}", i);
std::vector<long> li = {9876, 1232};
LOG_INFO(logger, "li {}", li);
std::vector<long long> lli = {321, 231};
LOG_INFO(logger, "lli {}", lli);
std::vector<unsigned short> usi = {15, 2};
LOG_INFO(logger, "usi {}", usi);
std::vector<unsigned int> ui = {123, 2};
LOG_INFO(logger, "ui {}", ui);
std::vector<unsigned long> uli = {3213, 2876};
LOG_INFO(logger, "uli {}", uli);
std::vector<unsigned long long> ulli = {321, 1321};
LOG_INFO(logger, "ulli {}", ulli);
std::vector<float> f = {111.1f, 323.31f};
LOG_INFO(logger, "f {}", f);
std::vector<double> d = {12.1, 3213213.123};
LOG_INFO(logger, "d {}", d);
std::vector<int> const& cri = i;
LOG_INFO(logger, "cri {}", cri);
std::vector<int>& ci = i;
LOG_INFO(logger, "ci {}", ci);
std::vector<std::string> sa = {"test", "string"};
LOG_INFO(logger, "sa {}", sa);
std::vector<std::string_view> sva = {"test", "string_view"};
LOG_INFO(logger, "sva {}", sva);
std::vector<char const*> scva = {"c style", "string test", "test", "log"};
LOG_INFO(logger, "scva {}", scva);
std::vector<std::vector<int>> aai = {{{321, 123}, {444, 333}, {111, 222}}};
LOG_INFO(logger, "aai {}", aai);
std::vector<std::vector<char const*>> aacs = {
{{"one", "two"}, {"three", "four"}, {"five", "six"}}};
LOG_INFO(logger, "aacs {}", aacs);
std::vector<std::vector<std::vector<char const*>>> aaacs = {
{{{{"one", "two"}, {"three", "four"}}},
{{{"five", "six"}, {"seven", "eight"}}},
{{{"nine", "ten"}, {"eleven", "twelve"}}}}};
LOG_INFO(logger, "aaacs {}", aaacs);
{
std::vector<std::vector<std::vector<std::string>>> aaabcs;
// First outer vector
std::vector<std::vector<std::string>> first_outer = {{"std_one", "two"}, {"three", "four"}};
// Second outer vector
std::vector<std::vector<std::string>> second_outer = {{"five", "six"}, {"seven", "eight"}};
// Third outer vector
std::vector<std::vector<std::string>> third_outer = {{"std_nine", "ten"},
{"eleven", "twelve"}};
aaabcs.push_back(first_outer);
aaabcs.push_back(second_outer);
aaabcs.push_back(third_outer);
LOG_INFO(logger, "aaabcs {}", aaabcs);
}
{
std::vector<std::vector<std::vector<std::string>>> aaaccs;
// First outer vector
std::vector<std::vector<std::string>> first_outer = {{"std_one", "two"},
{"three_std", "four"}};
// Second outer vector
std::vector<std::vector<std::string>> second_outer = {{"five", "six"}, {"seven_std", "eight"}};
// Third outer vector
std::vector<std::vector<std::string>> third_outer = {{"std_nine", "ten"},
{"eleven", "twelve"}};
aaaccs.push_back(first_outer);
aaaccs.push_back(second_outer);
aaaccs.push_back(third_outer);
LOG_INFO(logger, "aaaccs {}", aaaccs);
}
LOG_INFO(logger, "scva {} sa {} ulli {} scva {} sa {} aaacs {} aai {}", scva, sa, ulli, scva, sa, aaacs, aai);
std::vector<int> loopv;
loopv.reserve(100);
for (int iter = 0; iter < 100; ++iter)
{
loopv.push_back(iter);
}
LOG_INFO(logger, "loopv {}", loopv);
std::vector<std::string> loopsv;
loopsv.reserve(100);
for (int iter = 0; iter < 100; ++iter)
{
loopsv.push_back(std::to_string(iter));
}
LOG_INFO(logger, "loopsv {}", loopsv);
std::vector<int> empt;
LOG_INFO(logger, "empt {}", empt);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c ['a', 'c']"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " si [-12, 10]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " i [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " li [9876, 1232]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " lli [321, 231]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usi [15, 2]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ui [123, 2]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uli [3213, 2876]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ulli [321, 1321]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " f [111.1, 323.31]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " d [12.1, 3213213.123]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cri [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ci [-123, 1]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva [\"test\", \"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva [\"c style\", \"string test\", \"test\", \"log\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aai [[321, 123], [444, 333], [111, 222]]"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_INFO " + logger_name +
" aacs [[\"one\", \"two\"], [\"three\", \"four\"], [\"five\", \"six\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaabcs [[[\"std_one\", \"two\"], [\"three\", \"four\"]], [[\"five\", \"six\"], [\"seven\", \"eight\"]], [[\"std_nine\", \"ten\"], [\"eleven\", \"twelve\"]]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaaccs [[[\"std_one\", \"two\"], [\"three_std\", \"four\"]], [[\"five\", \"six\"], [\"seven_std\", \"eight\"]], [[\"std_nine\", \"ten\"], [\"eleven\", \"twelve\"]]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " aaacs [[[\"one\", \"two\"], [\"three\", \"four\"]], [[\"five\", \"six\"], [\"seven\", \"eight\"]], [[\"nine\", \"ten\"], [\"eleven\", \"twelve\"]]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scva [\"c style\", \"string test\", \"test\", \"log\"] sa [\"test\", \"string\"] ulli [321, 1321] scva [\"c style\", \"string test\", \"test\", \"log\"] sa [\"test\", \"string\"] aaacs [[[\"one\", \"two\"], [\"three\", \"four\"]], [[\"five\", \"six\"], [\"seven\", \"eight\"]], [[\"nine\", \"ten\"], [\"eleven\", \"twelve\"]]] aai [[321, 123], [444, 333], [111, 222]]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopv [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " loopsv [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\", \"17\", \"18\", \"19\", \"20\", \"21\", \"22\", \"23\", \"24\", \"25\", \"26\", \"27\", \"28\", \"29\", \"30\", \"31\", \"32\", \"33\", \"34\", \"35\", \"36\", \"37\", \"38\", \"39\", \"40\", \"41\", \"42\", \"43\", \"44\", \"45\", \"46\", \"47\", \"48\", \"49\", \"50\", \"51\", \"52\", \"53\", \"54\", \"55\", \"56\", \"57\", \"58\", \"59\", \"60\", \"61\", \"62\", \"63\", \"64\", \"65\", \"66\", \"67\", \"68\", \"69\", \"70\", \"71\", \"72\", \"73\", \"74\", \"75\", \"76\", \"77\", \"78\", \"79\", \"80\", \"81\", \"82\", \"83\", \"84\", \"85\", \"86\", \"87\", \"88\", \"89\", \"90\", \"91\", \"92\", \"93\", \"94\", \"95\", \"96\", \"97\", \"98\", \"99\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empt []"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,73 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("string_large_logging")
{
static constexpr char const* filename = "string_large_logging.log";
static std::string const logger_name = "logger";
static constexpr size_t number_of_messages = 100;
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
for (size_t i = 0; i < number_of_messages; ++i)
{
std::string v{
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor "
"incididunt ut labore et dolore magna aliqua. Dui accumsan sit amet nulla facilisi "
"morbi tempus. Diam ut venenatis tellus in metus vulputate eu scelerisque felis. Lorem "
"mollis aliquam ut porttitor leo a. Posuere urna nec tincidunt praesent semper feugiat "
"nibh sed. Auctor urna nunc id cursus metus aliquam eleifend mi. Et ultrices neque ornare "
"aenean euismod elementum nisi quis. Phasellus vestibulum lorem sed risus ultricies "
"tristique nulla. Porta nibh venenatis cras sed felis eget velit aliquet sagittis. "
"Eget arcu dictum varius duis at consectetur lorem. Diam quam nulla porttitor massa id "
"neque aliquam vestibulum morbi. Sed euismod nisi porta lorem mollis aliquam. Arcu "
"felis bibendum ut tristique. Lorem ipsum dolor sit amet consectetur adipiscing elit "
"pellentesque habitant. Mauris augue neque gravida in. Dictum fusce ut placerat orci "
"nulla pellentesque dignissim "};
v += std::to_string(i);
LOG_INFO(logger, "Logging int: {}, int: {}, string: {}, string: {}", i, i * 10, v, v);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages);
testing::remove_file(filename);
}

View file

@ -0,0 +1,154 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("string_logging_dynamic_log_level")
{
static constexpr char const* filename = "string_logging_dynamic_log_level.log";
static std::string const logger_name = "logger";
static constexpr size_t number_of_messages = 10000;
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
logger->set_log_level(LogLevel::TraceL3);
{
std::string s = "adipiscing";
std::string const empty_s{};
std::string_view begin_s{"begin_s"};
std::string_view const end_s{"end_s"};
std::string_view empty_sv{};
char const* c_style_string_empty = "";
const char* c_style_string = "Lorem ipsum";
char c_style_char_array_empty[] = "";
char const c_style_char_array[] = "dolor";
char c_style_string_array_non_terminated[3];
c_style_string_array_non_terminated[0] = 'A';
c_style_string_array_non_terminated[1] = 'B';
c_style_string_array_non_terminated[2] = 'C';
LOG_DYNAMIC(logger, LogLevel::TraceL3, "s [{}]", s);
LOG_DYNAMIC(logger, LogLevel::TraceL2, "empty_s [{}]", empty_s);
LOG_DYNAMIC(logger, LogLevel::TraceL1, "begin_s [{}]", begin_s);
LOG_DYNAMIC(logger, LogLevel::Debug, "end_s [{}]", end_s);
LOG_DYNAMIC(logger, LogLevel::Info, "empty_sv [{}]", empty_sv);
LOG_DYNAMIC(logger, LogLevel::Warning, "c_style_string_empty [{}]", c_style_string_empty);
LOG_DYNAMIC(logger, LogLevel::Error, "c_style_string [{}]", c_style_string);
LOG_DYNAMIC(logger, LogLevel::Critical, "c_style_char_array_empty [{}]", c_style_char_array_empty);
LOG_DYNAMIC(logger, LogLevel::Info, "c_style_char_array [{}]", c_style_char_array);
LOG_DYNAMIC(logger, LogLevel::Info, "c_style_string_array_non_terminated [{}]",
c_style_string_array_non_terminated);
LOG_DYNAMIC(
logger, LogLevel::Info,
"Lorem ipsum dolor sit amet, consectetur [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] "
"[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}]",
s, "elit", 1, 3.14, empty_s, begin_s, end_s, empty_sv, c_style_string_empty, c_style_string,
c_style_char_array_empty, c_style_char_array, c_style_string_array_non_terminated, s, "elit",
1, 3.14, empty_s, begin_s, end_s, empty_sv, c_style_string_empty, c_style_string,
c_style_char_array_empty, c_style_char_array, c_style_string_array_non_terminated);
LOG_DYNAMIC(logger, LogLevel::Error,
"Nulla tempus, libero at dignissim viverra, lectus libero finibus ante [{}] [{}] "
"[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] "
"[{}] [{}] [{}] [{}]",
2, true, begin_s, empty_sv, empty_s, c_style_string_array_non_terminated, c_style_string_empty,
c_style_string, end_s, c_style_char_array_empty, c_style_char_array, 2, true,
begin_s, empty_sv, empty_s, c_style_string_array_non_terminated, c_style_string_empty,
c_style_string, end_s, c_style_char_array_empty, c_style_char_array);
}
// Log a big string
for (size_t i = 0; i < number_of_messages; ++i)
{
std::string v{"Lorem ipsum dolor sit amet, consectetur "};
v += std::to_string(i);
LOG_DYNAMIC(logger, LogLevel::Info, "Logging int: {}, int: {}, string: {}, char: {}", i, i * 10,
v, v.c_str());
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages + 12);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_TRACE_L3 " + logger_name + " s [adipiscing]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_TRACE_L2 " + logger_name + " empty_s []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_TRACE_L1 " + logger_name + " begin_s [begin_s]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_DEBUG " + logger_name + " end_s [end_s]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empty_sv []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_WARNING " + logger_name + " c_style_string_empty []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_ERROR " + logger_name + " c_style_string [Lorem ipsum]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_CRITICAL " + logger_name + " c_style_char_array_empty []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c_style_char_array [dolor]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c_style_string_array_non_terminated [ABC]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " Lorem ipsum dolor sit amet, consectetur [adipiscing] [elit] [1] [3.14] [] [begin_s] [end_s] [] [] [Lorem ipsum] [] [dolor] [ABC] [adipiscing] [elit] [1] [3.14] [] [begin_s] [end_s] [] [] [Lorem ipsum] [] [dolor] [ABC]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_ERROR " + logger_name + " Nulla tempus, libero at dignissim viverra, lectus libero finibus ante [2] [true] [begin_s] [] [] [ABC] [] [Lorem ipsum] [end_s] [] [dolor] [2] [true] [begin_s] [] [] [ABC] [] [Lorem ipsum] [end_s] [] [dolor]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " Logging int: 0, int: 0, string: Lorem ipsum dolor sit amet, consectetur 0, char: Lorem ipsum dolor sit amet, consectetur 0"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " Logging int: 1999, int: 19990, string: Lorem ipsum dolor sit amet, consectetur 1999, char: Lorem ipsum dolor sit amet, consectetur 1999"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,161 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("string_logging")
{
static constexpr char const* filename = "string_logging.log";
static std::string const logger_name = "logger";
static constexpr size_t number_of_messages = 10000;
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::string s = "adipiscing";
std::string const& scr = s;
std::string& sr = s;
std::string const empty_s{};
std::string_view begin_s{"begin_s"};
std::string_view const end_s{"end_s"};
std::string_view empty_sv{};
char const* c_style_string_empty = "";
const char* c_style_string = "Lorem ipsum";
char c_style_char_array_empty[] = "";
char const c_style_char_array[] = "dolor";
char c_style_string_array_non_terminated[3];
c_style_string_array_non_terminated[0] = 'A';
c_style_string_array_non_terminated[1] = 'B';
c_style_string_array_non_terminated[2] = 'C';
LOG_INFO(logger, "s [{}]", s);
LOG_INFO(logger, "scr [{}]", scr);
LOG_INFO(logger, "sr [{}]", sr);
LOG_INFO(logger, "empty_s [{}]", empty_s);
LOG_INFO(logger, "begin_s [{}]", begin_s);
LOG_INFO(logger, "end_s [{}]", end_s);
LOG_INFO(logger, "empty_sv [{}]", empty_sv);
LOG_INFO(logger, "c_style_string_empty [{}]", c_style_string_empty);
LOG_INFO(logger, "c_style_string [{}]", c_style_string);
LOG_INFO(logger, "c_style_char_array_empty [{}]", c_style_char_array_empty);
LOG_INFO(logger, "c_style_char_array [{}]", c_style_char_array);
LOG_INFO(logger, "c_style_string_array_non_terminated [{}]", c_style_string_array_non_terminated);
LOG_INFO(logger,
"Lorem ipsum dolor sit amet, consectetur [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] "
"[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}]",
s, "elit", 1, 3.14, empty_s, begin_s, end_s, empty_sv, c_style_string_empty, c_style_string,
c_style_char_array_empty, c_style_char_array, c_style_string_array_non_terminated, s,
"elit", 1, 3.14, empty_s, begin_s, end_s, empty_sv, c_style_string_empty, c_style_string,
c_style_char_array_empty, c_style_char_array, c_style_string_array_non_terminated);
LOG_ERROR(logger,
"Nulla tempus, libero at dignissim viverra, lectus libero finibus ante [{}] [{}] "
"[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] "
"[{}] [{}] [{}] [{}]",
2, true, begin_s, empty_sv, empty_s, c_style_string_array_non_terminated, c_style_string_empty,
c_style_string, end_s, c_style_char_array_empty, c_style_char_array, 2, true, begin_s,
empty_sv, empty_s, c_style_string_array_non_terminated, c_style_string_empty,
c_style_string, end_s, c_style_char_array_empty, c_style_char_array);
}
// Log a big string
for (size_t i = 0; i < number_of_messages; ++i)
{
std::string v{"Lorem ipsum dolor sit amet, consectetur "};
v += std::to_string(i);
LOG_INFO(logger, "Logging int: {}, int: {}, string: {}, char: {}", i, i * 10, v, v.c_str());
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages + 14);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " s [adipiscing]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scr [adipiscing]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sr [adipiscing]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empty_s []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " begin_s [begin_s]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " end_s [end_s]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empty_sv []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c_style_string_empty []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c_style_string [Lorem ipsum]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c_style_char_array_empty []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c_style_char_array [dolor]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c_style_string_array_non_terminated [ABC]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " Lorem ipsum dolor sit amet, consectetur [adipiscing] [elit] [1] [3.14] [] [begin_s] [end_s] [] [] [Lorem ipsum] [] [dolor] [ABC] [adipiscing] [elit] [1] [3.14] [] [begin_s] [end_s] [] [] [Lorem ipsum] [] [dolor] [ABC]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_ERROR " + logger_name + " Nulla tempus, libero at dignissim viverra, lectus libero finibus ante [2] [true] [begin_s] [] [] [ABC] [] [Lorem ipsum] [end_s] [] [dolor] [2] [true] [begin_s] [] [] [ABC] [] [Lorem ipsum] [end_s] [] [dolor]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " Logging int: 0, int: 0, string: Lorem ipsum dolor sit amet, consectetur 0, char: Lorem ipsum dolor sit amet, consectetur 0"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " Logging int: 1999, int: 19990, string: Lorem ipsum dolor sit amet, consectetur 1999, char: Lorem ipsum dolor sit amet, consectetur 1999"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,108 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("string_random_large_logging")
{
static constexpr char const* filename = "string_random_large_logging.log";
static std::string const logger_name = "logger";
static constexpr size_t number_of_strings = 500;
static constexpr int min_string_len = 10000;
static constexpr int max_string_len = 20000;
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
std::vector<std::string> random_strings_vec =
quill::testing::gen_random_strings(number_of_strings, min_string_len, max_string_len);
// First push in sequence
int32_t i = 0;
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem);
LOG_INFO(logger, "{}", elem.c_str());
LOG_INFO(logger, "{}", std::string_view{elem});
LOG_INFO(logger, "{} {}", i, static_cast<double>(i));
++i;
logger->flush_log();
}
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem);
LOG_INFO(logger, "{} {}", i, static_cast<double>(i));
++i;
logger->flush_log();
}
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem.c_str());
LOG_INFO(logger, "{} {}", i, static_cast<double>(i));
++i;
logger->flush_log();
}
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", std::string_view{elem});
LOG_INFO(logger, "{} {}", i, static_cast<double>(i));
++i;
logger->flush_log();
}
// Then also try to push all
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem);
LOG_INFO(logger, "{}", elem.c_str());
LOG_INFO(logger, "{}", std::string_view{elem});
LOG_INFO(logger, "{} {}", i, static_cast<double>(i));
++i;
}
// clear the vector for the strings to go out of scope for additional testing
size_t const total_log_messages = random_strings_vec.size() * 14;
random_strings_vec.clear();
// Let all log get flushed to the file
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check we logged everything
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), total_log_messages);
testing::remove_file(filename);
}

View file

@ -0,0 +1,97 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("string_random_logging")
{
static constexpr char const* filename = "string_random_logging.log";
static std::string const logger_name = "logger";
static constexpr size_t number_of_strings = 2000;
static constexpr int min_string_len = 1;
static constexpr int max_string_len = 1500;
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
std::vector<std::string> random_strings_vec =
quill::testing::gen_random_strings(number_of_strings, min_string_len, max_string_len);
// First push in sequence
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem);
LOG_INFO(logger, "{}", elem.c_str());
LOG_INFO(logger, "{}", std::string_view{elem});
logger->flush_log();
}
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem);
logger->flush_log();
}
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem.c_str());
logger->flush_log();
}
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", std::string_view{elem});
logger->flush_log();
}
// Then also try to push all
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem);
LOG_INFO(logger, "{}", elem.c_str());
LOG_INFO(logger, "{}", std::string_view{elem});
}
// clear the vector for the strings to go out of scope for additional testing
size_t const total_log_messages = random_strings_vec.size() * 9;
random_strings_vec.clear();
// Let all log get flushed to the file
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check we logged everything
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), total_log_messages);
testing::remove_file(filename);
}

View file

@ -0,0 +1,97 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("string_random_small_logging")
{
static constexpr char const* filename = "string_random_small_logging.log";
static std::string const logger_name = "logger";
static constexpr size_t number_of_strings = 500;
static constexpr int min_string_len = 1;
static constexpr int max_string_len = 50;
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
std::vector<std::string> random_strings_vec =
quill::testing::gen_random_strings(number_of_strings, min_string_len, max_string_len);
// First push in sequence
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem);
LOG_INFO(logger, "{}", elem.c_str());
LOG_INFO(logger, "{}", std::string_view{elem});
logger->flush_log();
}
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem);
logger->flush_log();
}
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem.c_str());
logger->flush_log();
}
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", std::string_view{elem});
logger->flush_log();
}
// Then also try to push all
for (auto const& elem : random_strings_vec)
{
LOG_INFO(logger, "{}", elem);
LOG_INFO(logger, "{}", elem.c_str());
LOG_INFO(logger, "{}", std::string_view{elem});
}
// clear the vector for the strings to go out of scope for additional testing
size_t const total_log_messages = random_strings_vec.size() * 9;
random_strings_vec.clear();
// Let all log get flushed to the file
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check we logged everything
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), total_log_messages);
testing::remove_file(filename);
}

View file

@ -0,0 +1,120 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Utility.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
class TestTags : public quill::Tags
{
public:
explicit constexpr TestTags(char const* tag_a) : _tag_a(tag_a) {}
void format(std::string& out) const override { out.append(fmtquill::format("{}", _tag_a)); }
private:
char const* _tag_a;
};
static constexpr TestTags tags_a{"TAG_A"};
static constexpr TestTags tags_b{"TAG_B"};
static constexpr quill::utility::CombinedTags<TestTags, TestTags> tags_ab{tags_a, tags_b, " -- "};
/***/
TEST_CASE("tags_logging")
{
static constexpr char const* filename = "tags_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(
logger_name, std::move(file_sink),
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) "
"%(logger:<12) [%(tags)] %(message)\"");
logger->set_log_level(quill::LogLevel::TraceL3);
LOG_TRACE_L3_WITH_TAGS(logger, tags_ab, "Lorem ipsum dolor sit amet, consectetur {} {} {}", "elit", 1, 3.14);
LOG_TRACE_L2_WITH_TAGS(logger, tags_ab, "Lorem ipsum dolor sit amet, consectetur {} {} {}", "elit", 1, 3.14);
LOG_TRACE_L1_WITH_TAGS(logger, tags_ab, "Lorem ipsum dolor sit amet, consectetur {} {} {}", "elit", 1, 3.14);
LOG_DEBUG_WITH_TAGS(logger, tags_ab, "Lorem ipsum dolor sit amet, consectetur {} {} {}", "elit", 1, 3.14);
LOG_INFO_WITH_TAGS(logger, tags_ab, "Lorem ipsum dolor sit amet, consectetur {} {} {}", "elit", 1, 3.14);
LOG_WARNING_WITH_TAGS(logger, tags_ab, "Lorem ipsum dolor sit amet, consectetur {} {} {}", "elit", 1, 3.14);
LOG_ERROR_WITH_TAGS(logger, tags_ab, "Lorem ipsum dolor sit amet, consectetur {} {} {}", "elit", 1, 3.14);
LOG_CRITICAL_WITH_TAGS(logger, tags_ab, "Lorem ipsum dolor sit amet, consectetur {} {} {}", "elit", 1, 3.14);
LOG_ERROR_WITH_TAGS(
logger, tags_ab, "Nulla tempus, libero at dignissim viverra, lectus libero finibus ante {} {}", 2, true);
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 9);
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_TRACE_L3 " + logger_name +
" [TAG_A -- TAG_B] Lorem ipsum dolor sit amet, consectetur elit 1 3.14"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_TRACE_L2 " + logger_name +
" [TAG_A -- TAG_B] Lorem ipsum dolor sit amet, consectetur elit 1 3.14"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_TRACE_L1 " + logger_name +
" [TAG_A -- TAG_B] Lorem ipsum dolor sit amet, consectetur elit 1 3.14"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_DEBUG " + logger_name +
" [TAG_A -- TAG_B] Lorem ipsum dolor sit amet, consectetur elit 1 3.14"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_INFO " + logger_name +
" [TAG_A -- TAG_B] Lorem ipsum dolor sit amet, consectetur elit 1 3.14"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_WARNING " + logger_name +
" [TAG_A -- TAG_B] Lorem ipsum dolor sit amet, consectetur elit 1 3.14"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_ERROR " + logger_name +
" [TAG_A -- TAG_B] Lorem ipsum dolor sit amet, consectetur elit 1 3.14"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_CRITICAL " + logger_name +
" [TAG_A -- TAG_B] Lorem ipsum dolor sit amet, consectetur elit 1 3.14"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_ERROR " + logger_name + " [TAG_A -- TAG_B] Nulla tempus, libero at dignissim viverra, lectus libero finibus ante 2 true"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,117 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace quill;
/**
* Custom timestamp class
*/
class UserClockSourceTest : public UserClockSource
{
public:
UserClockSourceTest() = default;
/**
* Required by TimestampClock
* @return current time now, in nanoseconds since epoch
*/
uint64_t now() const override { return _ts.load(); }
/**
* set custom timestamp
* @param time_since_epoch timestamp
*/
void set_timestamp(std::chrono::seconds time_since_epoch)
{
// always convert to nanos
_ts.store(static_cast<uint64_t>(std::chrono::nanoseconds{time_since_epoch}.count()));
}
private:
/**
* time since epoch - must always be in nanoseconds
* This class needs to be thread-safe, unless only a single thread in the application calling LOG macros
* **/
std::atomic<uint64_t> _ts;
};
/***/
TEST_CASE("user_clock_source")
{
static constexpr size_t number_of_messages = 10;
static constexpr char const* filename = "user_clock_source.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
UserClockSourceTest uct;
uct.set_timestamp(std::chrono::seconds{1655007309});
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink),
"%(time) %(log_level) %(logger:<16) %(message)", // format
"%Y-%m-%d %H:%M:%S.%Qms", // timestamp format
quill::Timezone::GmtTime, ClockSourceType::User, &uct);
for (size_t i = 0; i < number_of_messages; ++i)
{
// log an array so the log message is pushed to the queue
LOG_INFO(logger, "Lorem ipsum dolor sit amet, consectetur adipiscing elit {}", i);
}
uct.set_timestamp(std::chrono::seconds{1656007309});
for (size_t i = 0; i < number_of_messages; ++i)
{
LOG_ERROR(logger, "Nulla tempus, libero at dignissim viverra, lectus libero finibus ante {}", i);
}
// Let all log get flushed to the file
uct.set_timestamp(std::chrono::seconds{1658007309});
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages * 2);
for (size_t i = 0; i < number_of_messages; ++i)
{
std::string expected_string = "2022-06-12 04:15:09.000 INFO " + logger_name +
" Lorem ipsum dolor sit amet, consectetur adipiscing elit " + std::to_string(i);
REQUIRE(quill::testing::file_contains(file_contents, expected_string));
}
for (size_t i = 0; i < number_of_messages; ++i)
{
std::string expected_string = "2022-06-23 18:01:49.000 ERROR " + logger_name +
" Nulla tempus, libero at dignissim viverra, lectus libero finibus ante " +
std::to_string(i);
REQUIRE(quill::testing::file_contains(file_contents, expected_string));
}
testing::remove_file(filename);
}

View file

@ -0,0 +1,152 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Utility.h"
#include "quill/sinks/FileSink.h"
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/std/Array.h"
#include "quill/std/Vector.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/**
* CustomType defined type
*/
struct CustomType
{
std::string name;
std::string surname;
uint32_t age;
std::array<std::string, 3> favorite_colors;
};
/***/
template <>
struct fmtquill::formatter<CustomType>
{
template <typename FormatContext>
constexpr auto parse(FormatContext& ctx)
{
return ctx.begin();
}
template <typename FormatContext>
auto format(::CustomType const& custom_type, FormatContext& ctx) const
{
return fmtquill::format_to(ctx.out(), "Name: {}, Surname: {}, Age: {}, Favorite Colors: {}",
custom_type.name, custom_type.surname, custom_type.age,
custom_type.favorite_colors);
}
};
/***/
template <>
struct quill::detail::ArgSizeCalculator<CustomType>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache, ::CustomType const& custom_type) noexcept
{
// pass as arguments the class members you want to serialize
return calculate_total_size(conditional_arg_size_cache, custom_type.name, custom_type.surname,
custom_type.age, custom_type.favorite_colors);
}
};
/***/
template <>
struct quill::detail::Encoder<CustomType>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, ::CustomType const& custom_type) noexcept
{
// You must encode the same members and in the same order as in the ArgSizeCalculator::calculate
encode_members(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index,
custom_type.name, custom_type.surname, custom_type.age, custom_type.favorite_colors);
}
};
/***/
template <>
struct quill::detail::Decoder<CustomType>
{
static ::CustomType decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
// You must decode the same members and in the same order as in the Encoder::encode
::CustomType custom_type;
decode_and_assign_members(buffer, args_store, custom_type, custom_type.name,
custom_type.surname, custom_type.age, custom_type.favorite_colors);
return custom_type;
}
};
/***/
TEST_CASE("custom_type_defined_type_logging")
{
static constexpr char const* filename = "custom_type_defined_type_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(
logger_name, std::move(file_sink),
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) "
"%(logger:<12) %(message)");
logger->set_log_level(quill::LogLevel::TraceL3);
CustomType custom_type;
custom_type.name = "Quill";
custom_type.surname = "Library";
custom_type.age = 4;
custom_type.favorite_colors[0] = "red";
custom_type.favorite_colors[1] = "green";
custom_type.favorite_colors[2] = "blue";
LOG_INFO(logger, "The answer is {}", custom_type);
std::vector<CustomType> const custom_types = {{"Alice", "Doe", 25, {"red", "green"}},
{"Bob", "Smith", 30, {"blue", "yellow"}},
{"Charlie", "Johnson", 35, {"green", "orange"}},
{"David", "Brown", 40, {"red", "blue", "yellow"}}};
LOG_INFO(logger, "The answers are {}", custom_types);
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 2);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " The answer is Name: Quill, Surname: Library, Age: 4, Favorite Colors: [\"red\", \"green\", \"blue\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " The answers are [Name: Alice, Surname: Doe, Age: 25, Favorite Colors: [\"red\", \"green\", \"\"], Name: Bob, Surname: Smith, Age: 30, Favorite Colors: [\"blue\", \"yellow\", \"\"], Name: Charlie, Surname: Johnson, Age: 35, Favorite Colors: [\"green\", \"orange\", \"\"], Name: David, Surname: Brown, Age: 40, Favorite Colors: [\"red\", \"blue\", \"yellow\"]]"}));
testing::remove_file(filename);
}

View file

@ -0,0 +1,330 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/core/Filesystem.h"
#include "quill/sinks/FileSink.h"
#include "quill/std/Array.h"
#include "quill/std/Deque.h"
#include "quill/std/FilesystemPath.h"
#include "quill/std/ForwardList.h"
#include "quill/std/List.h"
#include "quill/std/Map.h"
#include "quill/std/Optional.h"
#include "quill/std/Pair.h"
#include "quill/std/Set.h"
#include "quill/std/UnorderedMap.h"
#include "quill/std/UnorderedSet.h"
#include "quill/std/Vector.h"
#include <array>
#include <cstdio>
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace quill;
struct CWideStringComparator
{
bool operator()(wchar_t const* a, wchar_t const* b) const { return std::wcscmp(a, b) < 0; }
};
/***/
TEST_CASE("wide_std_types_logging")
{
#if defined(_WIN32)
static constexpr char const* filename = "wide_std_types_logging.log";
static std::string const logger_name = "logger";
// Start the logging backend thread
Backend::start();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
fs::path wp{L"C:\\some\\path"};
LOG_INFO(logger, "wp {}", wp);
fs::path p{"C:\\another\\path"};
LOG_INFO(logger, "p {}", p);
LOG_INFO(logger, "wp {} p {}", wp, p);
std::array<std::wstring, 2> wsa = {L"test", L"string"};
LOG_INFO(logger, "wsa {}", wsa);
std::array<std::wstring_view, 2> wsva = {L"test", L"string_view"};
LOG_INFO(logger, "wsva {}", wsva);
std::array<wchar_t const*, 4> wscva = {L"c style", L"string test", L"test", L"log"};
LOG_INFO(logger, "wscva {}", wscva);
std::deque<std::wstring> wds = {L"test", L"string"};
LOG_INFO(logger, "wds {}", wds);
std::deque<std::wstring_view> wdsv = {L"test", L"string_view"};
LOG_INFO(logger, "wdsv {}", wdsv);
std::deque<wchar_t const*> wdcs = {L"c style", L"string test", L"test", L"log"};
LOG_INFO(logger, "wdcs {}", wdcs);
std::forward_list<std::wstring> wfs = {L"test", L"string"};
LOG_INFO(logger, "wfs {}", wfs);
std::forward_list<std::wstring_view> wfsv = {L"test", L"string_view"};
LOG_INFO(logger, "wfsv {}", wfsv);
std::forward_list<wchar_t const*> wfcs = {L"c style", L"string test", L"test", L"log"};
LOG_INFO(logger, "wfcs {}", wfcs);
std::list<std::wstring> sl = {L"test", L"string"};
LOG_INFO(logger, "sl {}", sl);
std::list<std::wstring_view> svl = {L"test", L"string_view"};
LOG_INFO(logger, "svl {}", svl);
std::list<wchar_t const*> scl = {L"c style", L"string test", L"test", L"log"};
LOG_INFO(logger, "scl {}", scl);
std::vector<std::wstring> wsv = {L"test", L"string"};
LOG_INFO(logger, "wsv {}", wsv);
std::vector<std::wstring_view> wsvv = {L"test", L"string_view"};
LOG_INFO(logger, "wsvv {}", wsvv);
std::vector<wchar_t const*> wscv = {L"c style", L"string test", L"test", L"log"};
LOG_INFO(logger, "wscv {}", wscv);
std::pair<std::wstring, std::wstring> wpss = {L"test", L"string"};
LOG_INFO(logger, "wpss {}", wpss);
std::pair<std::wstring_view, std::wstring> wpsv = {L"test", L"string_view"};
LOG_INFO(logger, "wpsv {}", wpsv);
std::pair<wchar_t const*, std::wstring> wscs = {L"c style", L"string test"};
LOG_INFO(logger, "wscs {}", wscs);
std::pair<int, std::wstring> wscsi = {1231, L"string test"};
LOG_INFO(logger, "wscsi {}", wscsi);
std::pair<std::wstring, double> wscsd = {L"string test", 443.2};
LOG_INFO(logger, "wscsd {}", wscsd);
std::optional<std::wstring> weo{std::nullopt};
LOG_INFO(logger, "eo {}", weo);
std::optional<std::wstring> wos{L"test"};
LOG_INFO(logger, "wos {}", wos);
std::optional<std::wstring_view> wosv{L"test"};
LOG_INFO(logger, "wosv {}", wosv);
std::optional<wchar_t const*> woc{L"test"};
LOG_INFO(logger, "woc {}", woc);
std::set<std::wstring> sa = {L"test", L"string"};
LOG_INFO(logger, "sa {}", sa);
std::set<std::wstring> sva = {L"test", L"string_view"};
LOG_INFO(logger, "sva {}", sva);
std::set<wchar_t const*, CWideStringComparator> scva = {L"c_style", L"aa", L"string_test",
L"test", L"log"};
LOG_INFO(logger, "scva {}", scva);
std::map<std::wstring, std::wstring> ccm = {{L"4", L"400"}, {L"3", L"300"}, {L"1", L"100"}};
LOG_INFO(logger, "ccm {}", ccm);
std::map<std::wstring, int> ccmx = {{L"4", 400}, {L"3", 300}, {L"1", 100}};
LOG_INFO(logger, "ccmx {}", ccmx);
std::map<wchar_t const*, std::wstring, CWideStringComparator> ccmc = {
{L"4", L"400"}, {L"3", L"300"}, {L"1", L"100"}};
LOG_INFO(logger, "ccmc {}", ccmc);
std::map<int, std::wstring> ccmi = {{4, L"400"}, {3, L"300"}, {1, L"100"}};
LOG_INFO(logger, "ccmi {}", ccmi);
std::unordered_set<std::wstring> uss = {L"test"};
LOG_INFO(logger, "uss {}", uss);
std::unordered_set<std::wstring_view> usvs = {L"string_view"};
LOG_INFO(logger, "usvs {}", usvs);
std::unordered_set<wchar_t const*> uscs = {L"c_style"};
LOG_INFO(logger, "uscs {}", uscs);
std::unordered_map<std::wstring, std::wstring> uccm = {{L"4", L"400"}};
LOG_INFO(logger, "uccm {}", uccm);
std::unordered_map<std::wstring, int> uccmx = {{L"5", 500}};
LOG_INFO(logger, "uccmx {}", uccmx);
std::unordered_map<wchar_t const*, std::wstring> uccmc = {{L"6", L"600"}};
LOG_INFO(logger, "uccmc {}", uccmc);
std::unordered_map<int, std::wstring> uccmi = {{7, L"700"}};
LOG_INFO(logger, "uccmi {}", uccmi);
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wp C:\\some\\path"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " p C:\\another\\path"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wp C:\\some\\path p C:\\another\\path"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wsa [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wsva [\"test\", \"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wscva [\"c style\", \"string test\", \"test\", \"log\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wds [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wdsv [\"test\", \"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wdcs [\"c style\", \"string test\", \"test\", \"log\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wfs [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wfsv [\"test\", \"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wfcs [\"c style\", \"string test\", \"test\", \"log\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sl [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " svl [\"test\", \"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " scl [\"c style\", \"string test\", \"test\", \"log\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wsv [\"test\", \"string\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wsvv [\"test\", \"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wscv [\"c style\", \"string test\", \"test\", \"log\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wpss (\"test\", \"string\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wpsv (\"test\", \"string_view\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wscs (\"c style\", \"string test\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wscsi (1231, \"string test\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wscsd (\"string test\", 443.2)"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " eo none"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wos optional(\"test\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " wosv optional(\"test\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " woc optional(\"test\")"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sa [\"string\", \"test\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " sva [\"string_view\", \"test\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents,
std::string{"LOG_INFO " + logger_name +
" scva [\"aa\", \"c_style\", \"log\", \"string_test\", \"test\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ccm [(\"1\", \"100\"), (\"3\", \"300\"), (\"4\", \"400\")]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ccmx [(\"1\", 100), (\"3\", 300), (\"4\", 400)]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ccmc [(\"1\", \"100\"), (\"3\", \"300\"), (\"4\", \"400\")]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ccmi [(1, \"100\"), (3, \"300\"), (4, \"400\")]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uss [\"test\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " usvs [\"string_view\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uscs [\"c_style\"]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uccm [(\"4\", \"400\")]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uccmx [(\"5\", 500)]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uccmc [(\"6\", \"600\")]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " uccmi [(7, \"700\")]"}));
testing::remove_file(filename);
#endif
}

View file

@ -0,0 +1,136 @@
#include "doctest/doctest.h"
#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/FileSink.h"
#include <cstdio>
#include <string>
#include <string_view>
#include <vector>
using namespace quill;
/***/
TEST_CASE("wide_string_logging")
{
#if defined(_WIN32)
static constexpr char const* filename = "wide_string_logging.log";
static std::string const logger_name = "logger";
static constexpr size_t number_of_messages = 10000;
// Start the logging backend thread
Backend::start();
Frontend::preallocate();
// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));
{
std::string s = "adipiscing";
std::wstring ws = L"adipiscing";
std::wstring const empty_s{};
std::wstring_view begin_s{L"begin_s"};
std::wstring_view const end_s{L"end_s"};
std::wstring_view empty_sv{};
wchar_t const* c_style_string_empty = L"";
wchar_t const* c_style_string = L"Lorem ipsum";
LOG_INFO(logger, "s [{}]", s);
LOG_INFO(logger, "ws [{}]", ws);
LOG_INFO(logger, "empty_s [{}]", empty_s);
LOG_INFO(logger, "begin_s [{}]", begin_s);
LOG_INFO(logger, "end_s [{}]", end_s);
LOG_INFO(logger, "empty_sv [{}]", empty_sv);
LOG_INFO(logger, "c_style_string_empty [{}]", c_style_string_empty);
LOG_INFO(logger, "c_style_string [{}]", c_style_string);
LOG_INFO(logger,
"Lorem ipsum dolor sit amet, consectetur [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] "
"[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}]",
ws, "elit", 1, 3.14, empty_s, begin_s, end_s, empty_sv, c_style_string_empty,
c_style_string, ws, "elit", 1, 3.14, empty_s, begin_s, end_s, empty_sv,
c_style_string_empty, c_style_string, s);
LOG_ERROR(logger,
"Nulla tempus, libero at dignissim viverra, lectus libero finibus ante [{}] [{}] "
"[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}]",
2, true, begin_s, empty_sv, empty_s, c_style_string_empty, c_style_string, end_s, 2,
true, begin_s, empty_sv, empty_s, c_style_string_empty, c_style_string, end_s, s);
}
// Log a big string
for (size_t i = 0; i < number_of_messages; ++i)
{
std::wstring v{
L"Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur Lorem "
L"ipsum dolor sit amet, consectetur "};
v += std::to_wstring(i);
LOG_INFO(logger, "Logging int: {}, int: {}, string: {}, char: {}", i, i * 10, v, v.c_str());
}
logger->flush_log();
Frontend::remove_logger(logger);
// Wait until the backend thread stops for test stability
Backend::stop();
// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages + 10);
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " s [adipiscing]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " ws [adipiscing]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empty_s []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " begin_s [begin_s]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " end_s [end_s]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " empty_sv []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c_style_string_empty []"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " c_style_string [Lorem ipsum]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " Lorem ipsum dolor sit amet, consectetur [adipiscing] [elit] [1] [3.14] [] [begin_s] [end_s] [] [] [Lorem ipsum] [adipiscing] [elit] [1] [3.14] [] [begin_s] [end_s] [] [] [Lorem ipsum] [adipiscing]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_ERROR " + logger_name + " Nulla tempus, libero at dignissim viverra, lectus libero finibus ante [2] [true] [begin_s] [] [] [] [Lorem ipsum] [end_s] [2] [true] [begin_s] [] [] [] [Lorem ipsum] [end_s] [adipiscing]"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " Logging int: 0, int: 0, string: Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur 0, char: Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur 0"}));
REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " Logging int: 1999, int: 19990, string: Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur 1999, char: Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur 1999"}));
testing::remove_file(filename);
#endif
}

View file

@ -0,0 +1,190 @@
#include "DocTestExtensions.h"
#include <memory>
#ifndef _WIN32
#include <cstdlib>
#include <unistd.h>
#else
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
// Mingw already defines this, so no need to redefine
#define NOMINMAX
#endif
#include <windows.h>
#include <codecvt>
#include <io.h>
#include <locale>
#include <sys/stat.h>
#endif
namespace quill
{
namespace testing
{
// The ctor redirects the stream to a temporary file.
CapturedStream::CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd))
{
#if defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(_WIN32)
char temp_dir_path[MAX_PATH + 1] = {'\0'}; // NOLINT
char temp_file_path[MAX_PATH + 1] = {'\0'}; // NOLINT
::GetTempPathA(sizeof(temp_dir_path), temp_dir_path);
const UINT success = ::GetTempFileNameA(temp_dir_path, "dtest_redir",
0, // Generate unique file name.
temp_file_path);
if (success == 0)
{
FAIL("Unable to create a temporary file in " << temp_dir_path;);
}
const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE);
if (captured_fd == -1)
{
FAIL("Unable to open_file temporary file " << temp_file_path);
}
filename_ = temp_file_path;
#else
// There's no guarantee that a test has write access to the current
// directory, so we create the temporary file in the /tmp directory
// instead.
// We use /tmp on most systems, and /sdcard on Android.
// That's because Android doesn't have /tmp.
#if defined(__ANDROID__)
char name_template[] = "/data/local/tmp/doctest_captured_stream.XXXXXX";
#else
char name_template[] = "/tmp/captured_stream.XXXXXX";
#endif // __ANDROID__
const int captured_fd = mkstemp(name_template);
if (captured_fd == -1)
{
FAIL("Failed to create tmp file "
<< name_template << " for test; does the test have access to the /tmp directory?");
}
filename_ = name_template;
#endif // _WIN32
fflush(nullptr);
dup2(captured_fd, fd_);
close(captured_fd);
}
CapturedStream::~CapturedStream() { remove(filename_.c_str()); }
std::string CapturedStream::GetCapturedString()
{
if (uncaptured_fd_ != -1)
{
// Restores the original stream.
fflush(nullptr);
dup2(uncaptured_fd_, fd_);
close(uncaptured_fd_);
uncaptured_fd_ = -1;
}
FILE* const file = _fopen(filename_.c_str(), "r");
if (file == nullptr)
{
// FAIL("Failed to open_file tmp file " << filename_ << " for capturing stream.");
}
std::string content = _read_entire_file(file);
_fclose(file);
return content;
}
std::string CapturedStream::_read_entire_file(FILE* file)
{
size_t const file_size = _get_file_size(file);
std::unique_ptr<char[]> buffer{new char[file_size]};
size_t bytes_last_read = 0; // # of bytes read in the last fread()
size_t bytes_read = 0; // # of bytes read so far
fseek(file, 0, SEEK_SET);
do
{
// Keeps reading the file until we cannot read further or the
// pre-determined file size is reached.
bytes_last_read = fread(buffer.get() + bytes_read, 1, file_size - bytes_read, file);
bytes_read += bytes_last_read;
} while (bytes_last_read > 0 && bytes_read < file_size);
std::string content{buffer.get(), bytes_read};
return content;
}
size_t CapturedStream::_get_file_size(FILE* file)
{
fseek(file, 0, SEEK_END);
return static_cast<size_t>(ftell(file));
}
FILE* CapturedStream::_fopen(char const* path, char const* mode)
{
#if defined(_WIN32)
struct wchar_codecvt : public std::codecvt<wchar_t, char, std::mbstate_t>
{
};
std::wstring_convert<wchar_codecvt> converter;
std::wstring wide_path = converter.from_bytes(path);
std::wstring wide_mode = converter.from_bytes(mode);
return _wfopen(wide_path.c_str(), wide_mode.c_str());
#else
return fopen(path, mode);
#endif // _WIN32
}
int CapturedStream::_fclose(FILE* fp) { return fclose(fp); }
// Starts capturing an output stream (stdout/stderr).
void CaptureStream(int fd, const char* stream_name, CapturedStream** stream)
{
if (*stream != nullptr)
{
FAIL("Only one " << stream_name << " capturer can exist at a time.");
}
*stream = new CapturedStream(fd);
}
// Stops capturing the output stream and returns the captured string.
std::string GetCapturedStream(CapturedStream** captured_stream)
{
std::string content = (*captured_stream)->GetCapturedString();
delete *captured_stream;
*captured_stream = nullptr;
return content;
}
#if defined(_MSC_VER) || defined(__BORLANDC__)
// MSVC and C++Builder do not provide a definition of STDERR_FILENO.
const int kStdOutFileno = 1;
const int kStdErrFileno = 2;
#else
const int kStdOutFileno = STDOUT_FILENO;
const int kStdErrFileno = STDERR_FILENO;
#endif // _MSC_VER
void CaptureStdout() { CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout); }
void CaptureStderr() { CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr); }
std::string GetCapturedStdout() { return GetCapturedStream(&g_captured_stdout); }
std::string GetCapturedStderr() { return GetCapturedStream(&g_captured_stderr); }
} // namespace testing
} // namespace quill

View file

@ -0,0 +1,97 @@
#pragma once
#include "doctest/doctest.h"
#include "quill/core/Attributes.h"
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <string_view>
#define REQUIRE_STREQ(str1, str2) \
do \
{ \
if (strcmp(str1, str2) != 0) \
{ \
MESSAGE("Expected equality of these values: \n\t str1: " \
<< std::string_view{str1} << "\n\t str2: " << std::string_view{str2}); \
REQUIRE_UNARY(false); \
}; \
} while (0)
#define REQUIRE_WSTREQ(str1, str2) \
do \
{ \
if (wcscmp(str1, str2) != 0) \
{ \
MESSAGE("Expected equality of these values: \n\t str1: " << str1 << "\n\t str2: " << str2); \
REQUIRE_UNARY(false); \
}; \
} while (0)
#define REQUIRE_STRNEQ(str1, str2) \
do \
{ \
if (strcmp(str1, str2) == 0) \
{ \
MESSAGE("Expected non-equality of these values: \n\t str1: " << str1 << "\n\t str2: " << str2); \
REQUIRE_UNARY(false); \
}; \
} while (0)
namespace quill
{
namespace testing
{
/**
* Object that captures an output stream (stdout/stderr).
*/
class CapturedStream
{
public:
// The ctor redirects the stream to a temporary file.
explicit CapturedStream(int fd);
~CapturedStream();
std::string GetCapturedString();
private:
static std::string _read_entire_file(FILE* file);
static size_t _get_file_size(FILE* file);
static FILE* _fopen(char const* path, char const* mode);
static int _fclose(FILE* fp);
private:
int fd_; // A stream to capture.
int uncaptured_fd_;
std::string filename_; // Name of the temporary file holding the stderr output.
};
QUILL_MAYBE_UNUSED static CapturedStream* g_captured_stderr = nullptr;
QUILL_MAYBE_UNUSED static CapturedStream* g_captured_stdout = nullptr;
// Starts capturing an output stream (stdout/stderr).
void CaptureStream(int fd, const char* stream_name, CapturedStream** stream);
// Stops capturing the output stream and returns the captured string.
std::string GetCapturedStream(CapturedStream** captured_stream);
// Starts capturing stdout.
void CaptureStdout();
// Starts capturing stderr.
void CaptureStderr();
// Stops capturing stdout and returns the captured string.
std::string GetCapturedStdout();
// Stops capturing stderr and returns the captured string.
std::string GetCapturedStderr();
} // namespace testing
} // namespace quill

View file

@ -0,0 +1,2 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest/doctest.h"

View file

@ -0,0 +1,155 @@
#include "TestUtilities.h"
#include <algorithm>
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <random>
#include <sstream>
#include <string>
#include <system_error>
#include <utility>
#include <vector>
namespace quill
{
namespace testing
{
// Convert the given file to a vector
std::vector<std::string> file_contents(fs::path const& filename)
{
std::ifstream out_file(filename.string());
std::vector<std::string> lines;
for (std::string current_line; getline(out_file, current_line);)
{
lines.push_back(current_line);
}
return lines;
}
// Convert the given file to a vector
std::vector<std::wstring> wfile_contents(fs::path const& filename)
{
std::wifstream out_file(filename.string());
std::vector<std::wstring> lines;
for (std::wstring current_line; getline(out_file, current_line);)
{
lines.push_back(current_line);
}
return lines;
}
// Search a vector for the given string
bool file_contains(std::vector<std::string> const& file_vector, std::string const& search_string)
{
auto const search =
std::find_if(file_vector.cbegin(), file_vector.cend(), [&search_string](std::string const& elem)
{ return elem.find(search_string) != std::string::npos; });
bool const success = search != file_vector.cend();
if (!success)
{
// We failed to find and we will log for diagnostic reasons
std::cout << "Failed to find '" << search_string << "' in:\n";
for (auto const& line : file_vector)
{
std::cout << "'" << line << "'\n";
}
}
return success;
}
void create_file(fs::path const& filename, std::string const& text)
{
if (std::ofstream file(filename); file.is_open())
{
if (!text.empty())
{
file << text;
}
file.close();
}
}
void remove_file(fs::path const& filename)
{
std::error_code ec;
fs::remove(filename, ec);
}
std::vector<std::string> gen_random_strings(size_t n, int min_len, int max_len)
{
// Generate random strings
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> dist_chars(32, 126);
// length of strings
std::uniform_int_distribution<int> dist_len(min_len, max_len);
// Generate a vector of random strings of dist_len
std::vector<std::string> random_strings_vec;
random_strings_vec.reserve(n);
std::string result;
for (size_t i = 0; i < n; ++i)
{
std::generate_n(std::back_inserter(result), dist_len(mt),
[&] { return static_cast<char>(dist_chars(mt)); });
random_strings_vec.emplace_back(std::move(result));
}
return random_strings_vec;
}
uint64_t parse_timestamp(std::string const& timestamp_str)
{
std::tm tm = {};
std::istringstream ss(timestamp_str);
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); // Extract date and time
// Extract fractional seconds
size_t pos = timestamp_str.find('.');
std::string fractional_seconds_str = timestamp_str.substr(pos + 1, 9);
uint64_t fractional_seconds = std::stoull(fractional_seconds_str);
auto time_point = std::chrono::system_clock::from_time_t(std::mktime(&tm));
auto nanoseconds_since_epoch =
std::chrono::duration_cast<std::chrono::nanoseconds>(time_point.time_since_epoch()).count();
return static_cast<uint64_t>(nanoseconds_since_epoch) + fractional_seconds;
}
bool is_timestamp_ordered(std::vector<std::string> const& file_contents)
{
uint64_t previous_timestamp = 0;
bool first = true;
for (auto const& line : file_contents)
{
uint64_t current_timestamp = parse_timestamp(line);
if (!first)
{
if (current_timestamp < previous_timestamp)
{
return false;
}
}
previous_timestamp = current_timestamp;
first = false;
}
return true;
}
} // namespace testing
} // namespace quill

View file

@ -0,0 +1,30 @@
#pragma once
#include "quill/core/Common.h"
#include "quill/core/Filesystem.h"
#include "DocTestExtensions.h"
#include <cstring>
#include <string>
#include <vector>
namespace quill
{
namespace testing
{
// Convert the given file to a vector
std::vector<std::string> file_contents(fs::path const& filename);
std::vector<std::wstring> wfile_contents(fs::path const& filename);
// Search a vector for the given string
bool file_contains(std::vector<std::string> const& file_vector, std::string const& search_string);
void create_file(fs::path const& filename, std::string const& text = std::string{});
void remove_file(fs::path const& filename);
std::vector<std::string> gen_random_strings(size_t n, int min_len, int max_len);
uint64_t parse_timestamp(std::string const& timestamp_str);
bool is_timestamp_ordered(std::vector<std::string> const& file_contents);
} // namespace testing
} // namespace quill

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();