From 976af140b050a13216d9ea1558bc5f24519b463e Mon Sep 17 00:00:00 2001 From: Felix Heitmann Date: Fri, 5 Jul 2024 15:00:06 +0200 Subject: [PATCH] Attributes include unit-tests --- tests/test_attributes.cpp | 187 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 181 insertions(+), 6 deletions(-) diff --git a/tests/test_attributes.cpp b/tests/test_attributes.cpp index fec7fa8e..de5482c4 100644 --- a/tests/test_attributes.cpp +++ b/tests/test_attributes.cpp @@ -1,13 +1,30 @@ +#include + #include "includes.h" #include "spdlog/mdc.h" #include "spdlog/sinks/stdout_sinks.h" #include "spdlog/spdlog.h" #include "test_sink.h" -TEST_CASE("Attribute test") { - auto test_sink = std::make_shared(); - spdlog::logger log_a("log_a", test_sink); - spdlog::logger log_b("log_b", test_sink); +using namespace spdlog; + +std::string format_attrs(std::string_view const log_name, log_attributes::attr_map_t const& attrs = {}) { + std::vector fmt_attrs; + for (auto&& attr : attrs) fmt_attrs.push_back(fmt::format("{}:{}", attr.first, attr.second)); + return fmt::format("[{}] [{}]", log_name, fmt::join(fmt_attrs, " ")); +} + +std::pair> make_logger() { + auto test_sink = std::make_shared(); + auto log = logger("logger", test_sink); + log.set_pattern("[%n] [%*]"); + return std::make_pair(log, test_sink); +} + +TEST_CASE("attribute test - multiple (single thread)") { + auto test_sink = std::make_shared(); + logger log_a("log_a", test_sink); + logger log_b("log_b", test_sink); log_a.set_pattern("[%n] [%*]"); log_b.set_pattern("[%n] [%*]"); @@ -16,11 +33,169 @@ TEST_CASE("Attribute test") { log_a.info("Hello"); log_b.info("Hello"); - auto expected_log_a = spdlog::fmt_lib::format("[log_a] [my_key:my_value]"); - auto expected_log_b = spdlog::fmt_lib::format("[log_b] []"); + auto expected_log_a = format_attrs("log_a", {{"my_key", "my_value"}}); + auto expected_log_b = format_attrs("log_b"); + + auto [found, value] = log_a.attrs().get("my_key"); + REQUIRE(found); + REQUIRE(value->first == "my_key"); + REQUIRE(value->second == "my_value"); + + auto [not_found, _] = log_a.attrs().get("my_non_existent_key"); + REQUIRE(!not_found); auto lines = test_sink->lines(); REQUIRE(lines.size() == 2); REQUIRE(lines[0] == expected_log_a); REQUIRE(lines[1] == expected_log_b); +} + +TEST_CASE("attribute test - remove") { + std::vector attrs{ + {"my_key", "my_value"}, + {"my_key2", "my_value2"}, + }; + + auto [log, test_sink] = make_logger(); + + log.attrs().put(attrs[0].first, attrs[0].second); + log.attrs().put(attrs[1].first, attrs[1].second); + + log.info(""); + auto expected_log = format_attrs("logger", {attrs.begin(), attrs.end()}); + + REQUIRE(!test_sink->lines().empty()); + REQUIRE(test_sink->lines().back() == expected_log); + + log.attrs().remove(attrs.front().first); + log.info(""); + expected_log = format_attrs("logger", {attrs.begin() + 1, attrs.end()}); + REQUIRE(test_sink->lines().back() == expected_log); + + log.attrs().remove(attrs.back().first); + log.info(""); + expected_log = format_attrs("logger"); + REQUIRE(test_sink->lines().back() == expected_log); +} + +TEST_CASE("attribute test - from range") { + std::vector attrs{ + {"my_key", "my_value"}, + {"my_key2", "my_value2"}, + }; + + auto [log, test_sink] = make_logger(); + + log.attrs().put({attrs.begin(), attrs.end()}); + log.info(""); + auto expected_log = format_attrs("logger", {attrs.begin(), attrs.end()}); + + REQUIRE(!test_sink->lines().empty()); + REQUIRE(test_sink->lines().back() == expected_log); +} + +TEST_CASE("attribute test - scoped") { + std::vector attrs{ + {"my_key", "my_value"}, + {"my_key2", "my_value2"}, + }; + + auto [log, test_sink] = make_logger(); + + auto key_value = std::make_pair("additional_key", "additional_value"); + { + // add the attributes in scope + auto ctx = log.attrs().scoped_ctx({attrs.begin(), attrs.end()}); + log.info(""); + auto expected_log = format_attrs("logger", {attrs.begin(), attrs.end()}); + + REQUIRE(!test_sink->lines().empty()); + REQUIRE(test_sink->lines().back() == expected_log); + + // remove one of the scoped attributes + log.attrs().remove(attrs.front().first); + log.info(""); + expected_log = format_attrs("logger", {attrs.begin() + 1, attrs.end()}); + REQUIRE(test_sink->lines().back() == expected_log); + + // add another non-scoped attribute + log.attrs().put(key_value.first, key_value.second); + log.info(""); + expected_log = format_attrs("logger", {attrs[1], key_value}); + REQUIRE(test_sink->lines().back() == expected_log); + } + + log.info(""); + auto expected_log = format_attrs("logger", {key_value}); +} + +TEST_CASE("attribute test - nested scoped") { + std::vector attrs{ + {"my_key", "my_value"}, + {"my_key2", "my_value2"}, + }; + std::vector nested_attrs{ + {"nested_key", "nested_value"}, + {"nested_key2", "nested_value2"}, + }; + + auto [log, test_sink] = make_logger(); + + { + // add the attributes in scope + auto ctx = log.attrs().scoped_ctx({attrs.begin(), attrs.end()}); + log.info(""); + auto expected_log = format_attrs("logger", {attrs.begin(), attrs.end()}); + + REQUIRE(!test_sink->lines().empty()); + REQUIRE(test_sink->lines().back() == expected_log); + + // add another scoped context + { + auto ctx_nested = log.attrs().scoped_ctx({nested_attrs.begin(), nested_attrs.end()}); + log.info(""); + log_attributes::attr_map_t all_attrs{attrs.begin(), attrs.end()}; + all_attrs.insert(nested_attrs.begin(), nested_attrs.end()); + expected_log = format_attrs("logger", {all_attrs.begin(), all_attrs.end()}); + REQUIRE(test_sink->lines().back() == expected_log); + } + + // delete nested scope + log.info(""); + expected_log = format_attrs("logger", {attrs.begin(), attrs.end()}); + REQUIRE(test_sink->lines().back() == expected_log); + } + + log.info(""); + auto expected_log = format_attrs("logger"); +} + +TEST_CASE("attribute test - multi threaded") { + const int n_tasks = std::thread::hardware_concurrency(); + constexpr auto n_values = 30; + auto mt_sink = std::make_shared(); + auto logger = spdlog::logger("logger", mt_sink); + logger.set_pattern("[%n] [%*]"); + + // put attributes with multiple threads simultaneously + std::vector> tasks; + for (auto i = 0; i < n_tasks; ++i) { + auto task = std::async([&logger, i, n_values] { + for (auto j = 0; j < n_values; ++j) + logger.attrs().put(fmt::format("log_{}_key_{}", i, j), fmt::format("log_{}_value_{}", i, j)); + }); + tasks.emplace_back(std::move(task)); + } + + for (auto&& task : tasks) task.wait(); + + logger.info(""); + REQUIRE(!mt_sink->lines().empty()); + auto log_line = mt_sink->lines().back(); + for (auto i = 0; i < n_tasks; ++i) { + for (auto j = 0; j < n_values; ++j) { + auto search_term = fmt::format("log_{0}_key_{1}:log_{0}_value_{1}", i, j); + REQUIRE(log_line.find(search_term) != std::string::npos); + } + } } \ No newline at end of file