From f36b544b11219803fce02fe36f0a7eb20422a08b Mon Sep 17 00:00:00 2001 From: Ivan-lis Date: Thu, 19 Dec 2024 17:16:29 +0000 Subject: [PATCH 1/5] Fixed spdlog loglevel initialization --- CMakeLists.txt | 3 +++ tests/main.cpp | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b1728c7..3ed51f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ set(BOOST_INCLUDE_LIBRARIES "pfr;asio;serialization") option(CPM_USE_LOCAL_PACKAGES "Use local packages" ON) option(UTEMPL_USE_LOCAL_PACKAGE "Use utempl local package" OFF) option(BUILD_EXECUTABLE ON) +option(LOG_LEVEL 0 "Available log levels: 0=TRACE, 1=DEBUG,2= INFO,3= WARN, 4=ERROR, 5=CRITICAL, 6=OFF") # Compile program with highest available log levle to trace everything set(UTEMPL_URL "https://sha512sum.xyz/git/sha512sum/utempl" CACHE STRING "utempl repository URL") @@ -259,6 +260,7 @@ if(ENABLE_TESTS) target_sources(larra_xmpp_tests PUBLIC ${SOURCES}) target_link_libraries(larra_xmpp_tests GTest::gtest_main larra_xmpp) + target_compile_definitions(larra_xmpp_tests PRIVATE SPDLOG_ACTIVE_LEVEL=0) # SPDLOG_LEVEL_TRACE=0. Check LOG_LEVEL variable and spdlog documentation for more details set_property(TARGET larra_xmpp_tests PROPERTY CXX_STANDARD 23) include(GoogleTest) gtest_discover_tests(larra_xmpp_tests) @@ -270,6 +272,7 @@ if(ENABLE_EXAMPLES) get_filename_component(EXAMPLE_NAME ${EXAMPLE_SRC} NAME_WE) add_executable(${EXAMPLE_NAME} ${EXAMPLE_SRC}) target_link_libraries(${EXAMPLE_NAME} larra_xmpp) + target_compile_definitions(${EXAMPLE_NAME} PRIVATE SPDLOG_ACTIVE_LEVEL=${LOG_LEVEL}) set_property(TARGET ${EXAMPLE_NAME} PROPERTY CXX_STANDARD 23) set_target_properties(${EXAMPLE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/examples/output") diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..538b54b --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include +#include + +class PreconfigureEnvironment : public ::testing::Environment { + public: + void SetUp() override { + spdlog::set_level(spdlog::level::trace); + std::println("\nPreconfigureEnvironment setup:\n\tCompiled max availabel log level: {}\n\tCurrently set log level: {}", + SPDLOG_ACTIVE_LEVEL, + std::to_underlying(spdlog::get_level())); + } +}; + +auto main(int argc, char** argv) -> int { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(new PreconfigureEnvironment); // NOLINT GTest takes ownership + return RUN_ALL_TESTS(); +} \ No newline at end of file -- 2.47.1 From 54e95564784d37fb7ce8142812ace7ef3e06d1c8 Mon Sep 17 00:00:00 2001 From: Ivan-lis Date: Thu, 19 Dec 2024 17:17:28 +0000 Subject: [PATCH 2/5] Moved ToKebapCasName to utils --- library/include/larra/stream_error.hpp | 35 +------------------------- library/include/larra/utils.hpp | 29 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/library/include/larra/stream_error.hpp b/library/include/larra/stream_error.hpp index ca908aa..648b2a4 100644 --- a/library/include/larra/stream_error.hpp +++ b/library/include/larra/stream_error.hpp @@ -2,43 +2,10 @@ #include #include -#include -#include #include namespace larra::xmpp { -namespace impl { -// std::isupper not declared as constexpr -constexpr auto IsUpper(char ch) -> bool { - return ch >= 'A' && ch <= 'Z'; -} - -constexpr auto ToLower(char ch) -> char { - return (ch >= 'A' && ch <= 'Z') ? static_cast(ch + ('a' - 'A')) : ch; -} - -template -constexpr auto ToKebabCaseName() -> std::string_view { - static constexpr auto rawStr = nameof::nameof_short_type(); - - constexpr auto str = [] { - return rawStr // - | std::views::transform([](auto ch) { - return impl::IsUpper(ch) ? std::array{'-', impl::ToLower(ch)} : std::array{ch, '\0'}; - }) // - | std::views::join // - | std::views::filter([](char ch) { - return ch != '\0'; - }) // - | std::views::drop(1); - }; - static constexpr auto arr = str() | std::ranges::to>>(); - return {arr.data(), arr.size()}; -} - -} // namespace impl - namespace error::stream { struct BaseError : std::exception {}; @@ -48,7 +15,7 @@ struct BaseError : std::exception {}; template struct ErrorImpl : BaseError { static constexpr auto kDefaultName = "stream:error"; - static inline const auto kKebabCaseName = static_cast(impl::ToKebabCaseName()); + static inline const auto kKebabCaseName = static_cast(utils::ToKebabCaseName()); static constexpr auto kErrorMessage = [] -> std::string_view { static constexpr auto name = nameof::nameof_short_type(); diff --git a/library/include/larra/utils.hpp b/library/include/larra/utils.hpp index 7534947..4628754 100644 --- a/library/include/larra/utils.hpp +++ b/library/include/larra/utils.hpp @@ -1,5 +1,7 @@ #pragma once #include +#include +#include #include namespace larra::xmpp::utils { @@ -309,4 +311,31 @@ auto AccumulateFieldLength(const T& obj) -> std::size_t { return totalLength; } +template +constexpr auto ToKebabCaseName() -> std::string_view { + static constexpr auto rawStr = nameof::nameof_short_type(); + + // std::isupper and std::tolower are not declared as constexpr + static constexpr auto isUpper = [](char ch) { + return ch >= 'A' && ch <= 'Z'; + }; + static constexpr auto toLower = [](char ch) { + return (ch >= 'A' && ch <= 'Z') ? static_cast(ch + ('a' - 'A')) : ch; + }; + + constexpr auto str = [] { + return rawStr // + | std::views::transform([](auto ch) { + return isUpper(ch) ? std::array{'-', toLower(ch)} : std::array{ch, '\0'}; + }) // + | std::views::join // + | std::views::filter([](char ch) { + return ch != '\0'; + }) // + | std::views::drop(1); + }; + static constexpr auto arr = str() | std::ranges::to>>(); + return {arr.data(), arr.size()}; +} + } // namespace larra::xmpp::utils -- 2.47.1 From b565a09c1a550cfc94770dc4dd746f18c479c4d8 Mon Sep 17 00:00:00 2001 From: Ivan-lis Date: Thu, 19 Dec 2024 17:18:52 +0000 Subject: [PATCH 3/5] Added std::variant and Visitor for all IQ Stanza errors --- library/include/larra/client/client.hpp | 16 +-- library/include/larra/iq.hpp | 41 ++++++- library/include/larra/stanza_error.hpp | 148 ++++++++++++++++++++++++ library/include/larra/stream_error.hpp | 7 +- library/src/bind.cpp | 2 +- library/src/roster.cpp | 2 +- 6 files changed, 199 insertions(+), 17 deletions(-) create mode 100644 library/include/larra/stanza_error.hpp diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index 8629c05..9fbdd3f 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -84,14 +84,12 @@ struct Client { auto set_bind_response = co_await connection.template Read>(); std::visit(utempl::Overloaded( - [](auto error) { - throw "Error response on IQ: Set::Bind: ''"; // TODO(unknown): Add exact error parsing - }, [&](::iq::ResultBind r) { jid.resource = std::move(r.payload.jid->resource); SPDLOG_INFO("Allocated resource: {}", jid.resource); - }), - set_bind_response); + }, + IqErrThrowVisitor{"Error response on IQ: Set::Bind"}), + std::move(set_bind_response)); co_return; } @@ -101,14 +99,12 @@ struct Client { const auto get_roster_response = co_await connection.template Read>(); std::visit(utempl::Overloaded( - [](auto error) { - throw "Error response on IQ: Get::Roster: ''"; // TODO(unknown): Add exact error parsing - }, [&](::iq::ResultRoster r) { roster = std::move(r.payload); SPDLOG_INFO("New roster: {}", ToString(roster)); - }), - get_roster_response); + }, + IqErrThrowVisitor{"Error response on IQ: Get::Roster"}), + std::move(get_roster_response)); co_return; } diff --git a/library/include/larra/iq.hpp b/library/include/larra/iq.hpp index 0ed886b..13cd081 100644 --- a/library/include/larra/iq.hpp +++ b/library/include/larra/iq.hpp @@ -1,9 +1,12 @@ #pragma once +#include + #include #include -#include +#include #include #include +#include #include namespace larra::xmpp { @@ -115,7 +118,41 @@ using Error = BaseImplWithPayload; } // namespace iq +using IqError = iq::Error; + template -using Iq = std::variant, iq::Set, iq::Result, iq::Error>; +using Iq = std::variant, iq::Set, iq::Result, IqError>; + +struct IqErrThrowVisitor { + constexpr IqErrThrowVisitor(std::string_view baseErrorMsg) : baseErrorMsg(baseErrorMsg) { + } + + template + void operator()(const iq::Get&) { + static constexpr std::string_view getErrorMsg = ": 'Get' is an invalid type for IQ result. Expected 'Result' or 'Error'"; + + auto finalErrorMsg = std::string(baseErrorMsg).append(getErrorMsg); + SPDLOG_ERROR(finalErrorMsg); + throw std::runtime_error{finalErrorMsg}; + } + template + void operator()(const iq::Set&) { + static constexpr std::string_view getErrorMsg = ": 'Set' is an invalid type for IQ result. Expected 'Result' or 'Error'"; + + auto finalErrorMsg = std::string(baseErrorMsg).append(getErrorMsg); + SPDLOG_ERROR(finalErrorMsg); + throw std::runtime_error{finalErrorMsg}; + } + void operator()(IqError err) { + SPDLOG_ERROR(baseErrorMsg); + std::visit( + [](auto exception) { + throw exception; + }, + std::move(err.payload)); + } + + std::string_view baseErrorMsg; +}; } // namespace larra::xmpp diff --git a/library/include/larra/stanza_error.hpp b/library/include/larra/stanza_error.hpp new file mode 100644 index 0000000..9931ac1 --- /dev/null +++ b/library/include/larra/stanza_error.hpp @@ -0,0 +1,148 @@ +#pragma once +#include + +#include +#include +#include + +namespace larra::xmpp::stanza::error { + +struct StanzaBaseError : std::exception {}; + +// DO NOT MOVE TO ANOTHER NAMESPACE(where no heirs). VIA friend A FUNCTION IS ADDED THAT VIA ADL WILL BE SEARCHED FOR HEIRS +// C++20 modules very unstable in clangd :( +template +struct StanzaErrorImpl : StanzaBaseError { + static constexpr auto kDefaultName = "error"; + static constexpr auto kDefaultNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas"; + static inline const auto kKebabCaseName = static_cast(utils::ToKebabCaseName()); + + static constexpr auto kErrorMessage = [] -> std::string_view { + static constexpr auto name = nameof::nameof_short_type(); + static constexpr auto str = [] { + return std::array{std::string_view{"Stanza IQ Error: "}, std::string_view{name}, std::string_view{"\0", 1}} | std::views::join; + }; + static constexpr auto array = str() | std::ranges::to>>(); + return {array.data(), array.size() - 1}; + }(); + + std::optional by{}; + std::string type; + + // TODO(unknown): Add "optional text children" support for stanza error. Check "XML Stanzas" -> "Syntax" for more details + static constexpr auto TryParse(xmlpp::Element* element) -> std::optional { + if(not element) { + return std::nullopt; + } + + auto by = element->get_attribute("by"); + auto type = element->get_attribute("type"); + if(not type) { + return std::nullopt; + } + + auto node = element->get_first_child(kKebabCaseName); + if(not node) { + return std::nullopt; + } + + T obj; + obj.type = type->get_value(); + if(by) { + obj.by = std::optional{by->get_value()}; + } + return obj; + } + static constexpr auto Parse(xmlpp::Element* element) -> T { + return TryParse(element).value(); + } + friend constexpr auto operator<<(xmlpp::Element* element, const T& obj) -> void { + element->set_attribute("type", obj.type); + if(obj.by) { + element->set_attribute("by", *obj.by); + } + + auto node = element->add_child_element(kKebabCaseName); + node->set_namespace_declaration(kDefaultNamespace); + } + constexpr auto operator==(const StanzaErrorImpl&) const -> bool { + return true; + }; + [[nodiscard]] constexpr auto what() const noexcept -> const char* override { + return kErrorMessage.data(); + } +}; + +// Helper class to prevent parsing response stream into an expected return type if its name is an 'error' +struct UnknownStanzaError : StanzaBaseError { + static constexpr auto kDefaultName = "stream:error"; + static constexpr std::string_view kErrorMessage = "Unknown XMPP stream error"; + + static constexpr auto TryParse(xmlpp::Element* element) { + return std::optional{UnknownStanzaError{}}; + } + static constexpr auto Parse(xmlpp::Element* element) { + return TryParse(element).value(); + } + friend constexpr auto operator<<(xmlpp::Element* element, const UnknownStanzaError& obj) -> void { + throw std::runtime_error{std::format("'{}' must never be written into the real stream!", kErrorMessage)}; + } + constexpr auto operator==(const UnknownStanzaError&) const -> bool { + return true; + }; + [[nodiscard]] constexpr auto what() const noexcept -> const char* override { + return kErrorMessage.data(); + } +}; + +struct BadRequest : StanzaErrorImpl {}; +struct Conflict : StanzaErrorImpl {}; +struct FeatureNotImplemented : StanzaErrorImpl {}; +struct Forbidden : StanzaErrorImpl {}; +struct Gone : StanzaErrorImpl {}; +struct InternalServerError : StanzaErrorImpl {}; +struct ItemNotFound : StanzaErrorImpl {}; +struct JidMalformed : StanzaErrorImpl {}; +struct NotAcceptable : StanzaErrorImpl {}; +struct NotAllowed : StanzaErrorImpl {}; +struct NotAuthorized : StanzaErrorImpl {}; +struct PolicyViolation : StanzaErrorImpl {}; +struct RecipientUnavailable : StanzaErrorImpl {}; +struct Redirect : StanzaErrorImpl {}; +struct RegistrationRequired : StanzaErrorImpl {}; +struct RemoteServerNotFound : StanzaErrorImpl {}; +struct RemoteServerTimeout : StanzaErrorImpl {}; +struct ResourceConstraint : StanzaErrorImpl {}; +struct ServiceUnavailable : StanzaErrorImpl {}; +struct SubscriptionRequired : StanzaErrorImpl {}; +struct UndefinedCondition : StanzaErrorImpl {}; +struct UnexpectedRequest : StanzaErrorImpl {}; + +using StanzaError = std::variant; + +static_assert(std::is_same_v - 1, StanzaError>, UnknownStanzaError>, + "'UnknownStanzaError' must be at the end of 'StanzaError' variant"); + +} // namespace larra::xmpp::stanza::error diff --git a/library/include/larra/stream_error.hpp b/library/include/larra/stream_error.hpp index 648b2a4..21d2058 100644 --- a/library/include/larra/stream_error.hpp +++ b/library/include/larra/stream_error.hpp @@ -52,7 +52,7 @@ struct UnknownXmppError : BaseError { return TryParse(element).value(); } friend constexpr auto operator<<(xmlpp::Element* element, const UnknownXmppError& obj) -> void { - throw std::format("'{}' must never be written into the real stream!", kErrorMessage); + throw std::runtime_error{std::format("'{}' must never be written into the real stream!", kErrorMessage)}; } [[nodiscard]] constexpr auto what() const noexcept -> const char* override { return kErrorMessage.data(); @@ -112,7 +112,8 @@ using StreamError = std::variant; -static_assert(!std::is_same_v - 1, StreamError>, StreamError>, - "'UnknownXmppError' must be at the end of 'StreamError' variant"); +static_assert( + std::is_same_v - 1, StreamError>, error::stream::UnknownXmppError>, + "'UnknownXmppError' must be at the end of 'StreamError' variant"); } // namespace larra::xmpp diff --git a/library/src/bind.cpp b/library/src/bind.cpp index b82f72b..ede05d0 100644 --- a/library/src/bind.cpp +++ b/library/src/bind.cpp @@ -2,7 +2,7 @@ namespace larra::xmpp::iq { auto operator<<(xmlpp::Element* element, const Bind& bind) -> void { - element->set_attribute("xmlns", Bind::kDefaultNamespace); + element->set_namespace_declaration(Bind::kDefaultNamespace); if(bind.jid) { auto* jid_el = element->add_child_element("jid"); diff --git a/library/src/roster.cpp b/library/src/roster.cpp index 7b01dcc..b06164b 100644 --- a/library/src/roster.cpp +++ b/library/src/roster.cpp @@ -21,7 +21,7 @@ auto RosterItem::Parse(xmlpp::Element* element) -> RosterItem { } auto operator<<(xmlpp::Element* element, const Roster& self) -> void { - element->set_attribute("xmlns", Roster::kDefaultNamespace); + element->set_namespace_declaration(Roster::kDefaultNamespace); S::Serialize(element, self); } auto Roster::Parse(xmlpp::Element* element) -> Roster { -- 2.47.1 From a6c89dee5a94c696ffe8269b58618305379cf25b Mon Sep 17 00:00:00 2001 From: Ivan-lis Date: Mon, 23 Dec 2024 21:05:44 +0000 Subject: [PATCH 4/5] Added tests for Iq Stanza errors --- .forgejo/workflows/pr_check.yaml | 4 ++ .vscode/launch.json | 6 +- .vscode/tasks.json | 7 +- CMakeLists.txt | 7 +- tests/iq.cpp | 112 ++++++++++++++++++++++++++++++- 5 files changed, 129 insertions(+), 7 deletions(-) diff --git a/.forgejo/workflows/pr_check.yaml b/.forgejo/workflows/pr_check.yaml index b8f8f80..cd9c23a 100644 --- a/.forgejo/workflows/pr_check.yaml +++ b/.forgejo/workflows/pr_check.yaml @@ -134,6 +134,8 @@ jobs: -GNinja -DCMAKE_BUILD_TYPE=Release \ -DENABLE_EXAMPLES=ON \ -DENABLE_TESTS=ON \ + -DMAX_LOG_LEVEL=0 \ + -DTEST_MAX_LOG_LEVEL=0 \ -DCMAKE_CXX_FLAGS="-ftemplate-backtrace-limit=0" cmake --build ${{ github.workspace }}/build_gcc --parallel `nproc` @@ -160,6 +162,8 @@ jobs: -GNinja -DCMAKE_BUILD_TYPE=Release \ -DENABLE_EXAMPLES=ON \ -DENABLE_TESTS=ON \ + -DMAX_LOG_LEVEL=0 \ + -DTEST_MAX_LOG_LEVEL=0 \ -DCMAKE_CXX_FLAGS="-stdlib=libc++ -I/home/LLVM-${LLVM_VER}/include/c++/v1 -fno-modules" echo "::group::compile_commands.json content" diff --git a/.vscode/launch.json b/.vscode/launch.json index fa2cb78..6f11203 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,10 @@ "request": "launch", "name": "Debug: connect", "program": "${workspaceFolder}/build/examples/output/connect", - "args": [], + "args": [ + "--log_level=0" + // --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS] + ], "cwd": "${workspaceFolder}", "preLaunchTask": "GCC: Build" }, @@ -19,6 +22,7 @@ "name": "Debug: tests", "program": "${workspaceFolder}/build/larra_xmpp_tests", "args": [ + "--log_level=0" // --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS] // "--gtest_filter=Roster*" ], diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4720fa7..99fb723 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -79,8 +79,8 @@ "command": [ "cd ${workspaceFolder} &&", "mkdir -p build && cd build &&", - "cmake cmake -Wno-dev ", - " -DCMAKE_BUILD_TYPE=Debug -DENABLE_EXAMPLES=ON -DENABLE_TESTS=ON .." + "cmake -Wno-dev ", + " -DCMAKE_BUILD_TYPE=Debug -DENABLE_EXAMPLES=ON -DENABLE_TESTS=ON -DMAX_LOG_LEVEL=0 -DTEST_MAX_LOG_LEVEL=0 .." ], "options": { "env": { @@ -138,6 +138,7 @@ "mkdir -p build_clang && cd build_clang &&", "cmake -Wno-dev ", " -DCMAKE_BUILD_TYPE=Debug -DENABLE_EXAMPLES=ON -DENABLE_TESTS=ON ", + " -DMAX_LOG_LEVEL=0 -DTEST_MAX_LOG_LEVEL=0", " -DCMAKE_CXX_FLAGS=\"-stdlib=libstdc++\"", " -DCMAKE_EXE_LINKER_FLAGS=\"-L/usr/lib/x86_64-linux-gnu -lstdc++\" .." ], @@ -160,6 +161,7 @@ "mkdir -p build_clang && cd build_clang &&", "cmake -Wno-dev ", " -DCMAKE_BUILD_TYPE=Debug -DENABLE_EXAMPLES=ON -DENABLE_TESTS=ON", + " -DMAX_LOG_LEVEL=0 -DTEST_MAX_LOG_LEVEL=0", // // Uncomment for GCC standart library: libstdc++ //" -DCMAKE_CXX_FLAGS=\"-stdlib=libstdc++ -fno-omit-frame-pointer -g -fsanitize=address,undefined,leak,function,nullability,vptr\"", @@ -211,6 +213,7 @@ "mkdir -p build_clang && cd build_clang &&", "cmake -Wno-dev ", " -DCMAKE_BUILD_TYPE=Debug -DENABLE_EXAMPLES=ON -DENABLE_TESTS=ON ", + " -DMAX_LOG_LEVEL=0 -DTEST_MAX_LOG_LEVEL=0", " -DCMAKE_CXX_FLAGS=\"-stdlib=libstdc++ -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -fsanitize=memory\"", " -DCMAKE_EXE_LINKER_FLAGS=\"-L/usr/lib/x86_64-linux-gnu -lstdc++ -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -fsanitize=memory\" ..", ], diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ed51f5..389da96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,8 @@ set(BOOST_INCLUDE_LIBRARIES "pfr;asio;serialization") option(CPM_USE_LOCAL_PACKAGES "Use local packages" ON) option(UTEMPL_USE_LOCAL_PACKAGE "Use utempl local package" OFF) option(BUILD_EXECUTABLE ON) -option(LOG_LEVEL 0 "Available log levels: 0=TRACE, 1=DEBUG,2= INFO,3= WARN, 4=ERROR, 5=CRITICAL, 6=OFF") # Compile program with highest available log levle to trace everything +set(MAX_LOG_LEVEL 2 CACHE STRING "Available log levels: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF") +set(TEST_MAX_LOG_LEVEL 2 CACHE STRING "Available log levels: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF") set(UTEMPL_URL "https://sha512sum.xyz/git/sha512sum/utempl" CACHE STRING "utempl repository URL") @@ -260,7 +261,7 @@ if(ENABLE_TESTS) target_sources(larra_xmpp_tests PUBLIC ${SOURCES}) target_link_libraries(larra_xmpp_tests GTest::gtest_main larra_xmpp) - target_compile_definitions(larra_xmpp_tests PRIVATE SPDLOG_ACTIVE_LEVEL=0) # SPDLOG_LEVEL_TRACE=0. Check LOG_LEVEL variable and spdlog documentation for more details + target_compile_definitions(larra_xmpp_tests PRIVATE SPDLOG_ACTIVE_LEVEL=${TEST_MAX_LOG_LEVEL}) set_property(TARGET larra_xmpp_tests PROPERTY CXX_STANDARD 23) include(GoogleTest) gtest_discover_tests(larra_xmpp_tests) @@ -272,7 +273,7 @@ if(ENABLE_EXAMPLES) get_filename_component(EXAMPLE_NAME ${EXAMPLE_SRC} NAME_WE) add_executable(${EXAMPLE_NAME} ${EXAMPLE_SRC}) target_link_libraries(${EXAMPLE_NAME} larra_xmpp) - target_compile_definitions(${EXAMPLE_NAME} PRIVATE SPDLOG_ACTIVE_LEVEL=${LOG_LEVEL}) + target_compile_definitions(${EXAMPLE_NAME} PRIVATE SPDLOG_ACTIVE_LEVEL=${TEST_MAX_LOG_LEVEL}) set_property(TARGET ${EXAMPLE_NAME} PROPERTY CXX_STANDARD 23) set_target_properties(${EXAMPLE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/examples/output") diff --git a/tests/iq.cpp b/tests/iq.cpp index 4638ffe..3545e51 100644 --- a/tests/iq.cpp +++ b/tests/iq.cpp @@ -1,10 +1,23 @@ #include +#include #include +#include namespace { -static constexpr auto kExpectedData = "\n37\n"; +static constexpr auto kForbiddenErrorData = R"( + +)"; + +static constexpr auto kExpectedSetData = R"( +37 +)"; + +static constexpr auto kExpectedData = R"( +37 +)"; + struct SomeStruct { int value; constexpr auto operator==(const SomeStruct&) const -> bool = default; @@ -43,4 +56,101 @@ TEST(IQ, Parse) { EXPECT_EQ(S::Parse(node), iq); } +TEST(IQ, ParseForbiddenError) { + std::istringstream xml_stream(kForbiddenErrorData); + + xmlpp::DomParser parser; + parser.parse_stream(xml_stream); + + using S = Serialization>; + xmlpp::Document* doc = parser.get_document(); + auto iqRes = S::Parse(doc->get_root_node()); + + ASSERT_TRUE(std::holds_alternative(iqRes)); + + auto errorRes = std::get(iqRes); + ASSERT_TRUE(std::holds_alternative(errorRes.payload)); +} + +TEST(IQ, IqErrThrowVisitorThrow) { + std::istringstream xml_stream(kForbiddenErrorData); + + xmlpp::DomParser parser; + parser.parse_stream(xml_stream); + + using S = Serialization>; + xmlpp::Document* doc = parser.get_document(); + auto iqRes = S::Parse(doc->get_root_node()); + ASSERT_TRUE(std::holds_alternative(iqRes)); + + static constexpr auto visitorErrMsg = "Test Error"; + static constexpr auto throwErrMsg = "Stanza IQ Error: Forbidden"; + try { + std::visit(utempl::Overloaded([](iq::Result r) {}, IqErrThrowVisitor{visitorErrMsg}), std::move(iqRes)); + } catch(const stanza::error::StanzaBaseError& err) { + ASSERT_STREQ(throwErrMsg, err.what()); + return; + } catch(const std::runtime_error& err) { + ASSERT_TRUE(false) << "Invalid throw type throw"; + } catch(...) { + ASSERT_TRUE(false) << "Unexpected throw"; + } + + ASSERT_TRUE(false) << "Expected throwing an exception due to an error in output"; +} + +TEST(IQ, IqErrThrowVisitorThrowGet) { + std::istringstream xml_stream(kExpectedData); + + xmlpp::DomParser parser; + parser.parse_stream(xml_stream); + + using S = Serialization>; + xmlpp::Document* doc = parser.get_document(); + auto iqRes = S::Parse(doc->get_root_node()); + ASSERT_TRUE(std::holds_alternative>(iqRes)) << "\tERROR: Unexpected parse result"; + + static constexpr auto visitorErrMsg = "Test Error"; + static constexpr auto throwErrMsg = "Test Error: 'Get' is an invalid type for IQ result. Expected 'Result' or 'Error'"; + try { + std::visit(utempl::Overloaded([](iq::Result r) {}, IqErrThrowVisitor{"Test Error"}), std::move(iqRes)); + } catch(const stanza::error::StanzaBaseError& err) { + ASSERT_TRUE(false) << "\tERROR: Invalid throw type throw"; + } catch(const std::runtime_error& err) { + ASSERT_STREQ(throwErrMsg, err.what()); + return; + } catch(...) { + ASSERT_TRUE(false) << "\tERROR: Unexpected throw"; + } + + ASSERT_TRUE(false) << "\tERROR: Expected throwing an exception due to an error in output"; +} + +TEST(IQ, IqErrThrowVisitorThrowSet) { + std::istringstream xml_stream(kExpectedSetData); + + xmlpp::DomParser parser; + parser.parse_stream(xml_stream); + + using S = Serialization>; + xmlpp::Document* doc = parser.get_document(); + auto iqRes = S::Parse(doc->get_root_node()); + ASSERT_TRUE(std::holds_alternative>(iqRes)) << "\tERROR: Unexpected parse result"; + + static constexpr auto visitorErrMsg = "Test Error"; + static constexpr auto throwErrMsg = "Test Error: 'Set' is an invalid type for IQ result. Expected 'Result' or 'Error'"; + try { + std::visit(utempl::Overloaded([](iq::Result r) {}, IqErrThrowVisitor{"Test Error"}), std::move(iqRes)); + } catch(const stanza::error::StanzaBaseError& err) { + ASSERT_TRUE(false) << "\tERROR: Invalid throw type throw"; + } catch(const std::runtime_error& err) { + ASSERT_STREQ(throwErrMsg, err.what()); + return; + } catch(...) { + ASSERT_TRUE(false) << "\tERROR: Unexpected throw"; + } + + ASSERT_TRUE(false) << "\tERROR: Expected throwing an exception due to an error in output"; +} + } // namespace larra::xmpp -- 2.47.1 From b908baf7948c95c3e4f8ab8824e89c9b210be555 Mon Sep 17 00:00:00 2001 From: Ivan-lis Date: Mon, 23 Dec 2024 21:08:25 +0000 Subject: [PATCH 5/5] Added cmd arg handling to set log level --- .forgejo/workflows/pr_check.yaml | 4 +-- .vscode/tasks.json | 6 ++-- CMakeLists.txt | 6 ++-- examples/src/connect.cpp | 52 +++++++++++++++++++++++++-- tests/main.cpp | 60 ++++++++++++++++++++++++++------ 5 files changed, 108 insertions(+), 20 deletions(-) diff --git a/.forgejo/workflows/pr_check.yaml b/.forgejo/workflows/pr_check.yaml index cd9c23a..a9f6f7c 100644 --- a/.forgejo/workflows/pr_check.yaml +++ b/.forgejo/workflows/pr_check.yaml @@ -144,7 +144,7 @@ jobs: continue-on-error: true run: | cd ${{ github.workspace }}/build_gcc - ./larra_xmpp_tests + ./larra_xmpp_tests --log_level=0 - name: Clang build (only configuring) id: clang_build @@ -204,4 +204,4 @@ jobs: # export LLVM_VER=19.1.3 # export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}" # cd ${{ github.workspace }}/build_clang - # ASAN_SYMBOLIZER_PATH=llvm-symbolizer ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=1:atexit=1:abort_on_error=1 ./larra_xmpp_tests + # ASAN_SYMBOLIZER_PATH=llvm-symbolizer ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=1:atexit=1:abort_on_error=1 ./larra_xmpp_tests --log_level=0 diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 99fb723..1007e88 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -193,7 +193,7 @@ "command": [ "cd ${workspaceFolder} &&", "mkdir -p build_clang && cd build_clang &&", - "ASAN_SYMBOLIZER_PATH=llvm-symbolizer ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=1:atexit=1:abort_on_error=1 ./larra_xmpp_tests ; echo \"exit code: $?\"", + "ASAN_SYMBOLIZER_PATH=llvm-symbolizer ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=1:atexit=1:abort_on_error=1 ./larra_xmpp_tests --log_level=0 ; echo \"exit code: $?\"", ], "presentation": { "clear": true @@ -239,7 +239,7 @@ "command": [ "cd ${workspaceFolder} &&", "mkdir -p build_clang && cd build_clang &&", - "MSAN_SYMBOLIZER_PATH=llvm-symbolizer MSAN_OPTIONS=abort_on_error=1 ./larra_xmpp_tests ; echo \"exit code: $?\"", + "MSAN_SYMBOLIZER_PATH=llvm-symbolizer MSAN_OPTIONS=abort_on_error=1 ./larra_xmpp_tests --log_level=0 ; echo \"exit code: $?\"", ], "presentation": { "clear": true @@ -273,7 +273,7 @@ "command": [ "cd ${workspaceFolder} &&", "mkdir -p build_clang && cd build_clang &&", - "MSAN_SYMBOLIZER_PATH=llvm-symbolizer MSAN_OPTIONS=abort_on_error=1 ./larra_xmpp_tests ; echo \"exit code: $?\"", + "MSAN_SYMBOLIZER_PATH=llvm-symbolizer MSAN_OPTIONS=abort_on_error=1 ./larra_xmpp_tests --log_level=0 ; echo \"exit code: $?\"", ], "presentation": { "clear": true diff --git a/CMakeLists.txt b/CMakeLists.txt index 389da96..e1822bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(FMT_MODULE OFF) set(UTEMPL_MODULE OFF) set(CXX_EXTENSIONS NO) -set(BOOST_INCLUDE_LIBRARIES "pfr;asio;serialization") +set(BOOST_INCLUDE_LIBRARIES "pfr;asio;serialization;program_options") option(CPM_USE_LOCAL_PACKAGES "Use local packages" ON) option(UTEMPL_USE_LOCAL_PACKAGE "Use utempl local package" OFF) option(BUILD_EXECUTABLE ON) @@ -177,11 +177,11 @@ target_include_directories(larra_xmpp PUBLIC if(TARGET Boost::pfr) target_link_libraries(larra_xmpp PUBLIC - Boost::asio Boost::serialization utempl::utempl + Boost::asio Boost::serialization Boost::program_options utempl::utempl OpenSSL::SSL nameof::nameof fmt::fmt OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES}) else() - find_package(Boost 1.85.0 COMPONENTS serialization REQUIRED) + find_package(Boost 1.85.0 COMPONENTS serialization program_options REQUIRED) target_link_libraries(larra_xmpp PUBLIC utempl::utempl ${Boost_LIBRARIES} OpenSSL::SSL nameof::nameof fmt::fmt diff --git a/examples/src/connect.cpp b/examples/src/connect.cpp index 9f24137..2a8fd7b 100644 --- a/examples/src/connect.cpp +++ b/examples/src/connect.cpp @@ -3,11 +3,28 @@ #include #include +#include #include #include #include #include +// clang-format off +constexpr auto ToString(spdlog::level::level_enum e) { + switch (e) { + case spdlog::level::trace: return "TRACE"; + case spdlog::level::debug: return "DEBUG"; + case spdlog::level::info: return "INFO"; + case spdlog::level::warn: return "WARNING"; + case spdlog::level::err: return "ERROR"; + case spdlog::level::critical: return "CRITICAL"; + case spdlog::level::off: return "OFF"; + default: + return "INVALID"; + } +} +// clang-format on + namespace iq = larra::xmpp::iq; auto Coroutine() -> boost::asio::awaitable { @@ -50,8 +67,39 @@ auto Coroutine() -> boost::asio::awaitable { SPDLOG_INFO("Done connecting client!"); } -auto main() -> int { - spdlog::set_level(spdlog::level::trace); +namespace po = boost::program_options; + +auto main(int argc, char** argv) -> int { + // Define options + po::options_description desc("Allowed options"); + desc.add_options()("help,h", "Print help message")("log_level,l", + po::value()->default_value(SPDLOG_LEVEL_INFO), + "Set log level: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF"); + + // Parse command-line arguments + po::variables_map vm; + try { + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if(vm["log_level"].as() < spdlog::level::level_enum::trace || vm["log_level"].as() > spdlog::level::level_enum::off) { + throw std::invalid_argument{ + std::format("Invalid argument value for '--log_level' option. Check option description for more details")}; + } + if(vm["log_level"].as() < SPDLOG_ACTIVE_LEVEL) { + SPDLOG_WARN("Specified log_level '{}' is lower than max available one '{}'. Log level will be changed according to the maximum one", + vm["log_level"].as(), + SPDLOG_ACTIVE_LEVEL); + } + } catch(const std::exception& e) { + SPDLOG_CRITICAL("Cmd parse error: {}", e.what()); + return 1; + } + + // Cmd options handling + spdlog::set_level(static_cast(vm["log_level"].as())); + std::println("\nEnvironment setup:\n\tCurrently set log level: {}\n", ToString(spdlog::get_level())); + boost::asio::io_context io_context; boost::asio::co_spawn(io_context, Coroutine(), boost::asio::detached); io_context.run(); diff --git a/tests/main.cpp b/tests/main.cpp index 538b54b..45ab76b 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,21 +1,61 @@ #include #include +#include +#include #include -#include +#include -class PreconfigureEnvironment : public ::testing::Environment { - public: - void SetUp() override { - spdlog::set_level(spdlog::level::trace); - std::println("\nPreconfigureEnvironment setup:\n\tCompiled max availabel log level: {}\n\tCurrently set log level: {}", - SPDLOG_ACTIVE_LEVEL, - std::to_underlying(spdlog::get_level())); +// clang-format off +constexpr auto ToString(spdlog::level::level_enum e) { + switch (e) { + case spdlog::level::trace: return "TRACE"; + case spdlog::level::debug: return "DEBUG"; + case spdlog::level::info: return "INFO"; + case spdlog::level::warn: return "WARNING"; + case spdlog::level::err: return "ERROR"; + case spdlog::level::critical: return "CRITICAL"; + case spdlog::level::off: return "OFF"; + default: + return "INVALID"; } -}; +} +// clang-format on + +namespace po = boost::program_options; auto main(int argc, char** argv) -> int { ::testing::InitGoogleTest(&argc, argv); - ::testing::AddGlobalTestEnvironment(new PreconfigureEnvironment); // NOLINT GTest takes ownership + + // Define options + po::options_description desc("Allowed options"); + desc.add_options()("help,h", "Print help message")("log_level,l", + po::value()->default_value(SPDLOG_LEVEL_INFO), + "Set log level: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF"); + + // Parse command-line arguments + po::variables_map vm; + try { + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if(vm["log_level"].as() < spdlog::level::level_enum::trace || vm["log_level"].as() > spdlog::level::level_enum::off) { + throw std::invalid_argument{ + std::format("Invalid argument value for '--log_level' option. Check option description for more details")}; + } + if(vm["log_level"].as() < SPDLOG_ACTIVE_LEVEL) { + SPDLOG_WARN("Specified log_level '{}' is lower than max available one '{}'. Log level will be changed according to the maximum one", + vm["log_level"].as(), + SPDLOG_ACTIVE_LEVEL); + } + } catch(const std::exception& e) { + SPDLOG_CRITICAL("Cmd parse error: {}", e.what()); + return 1; + } + + // Cmd options handling + spdlog::set_level(static_cast(vm["log_level"].as())); + std::println("\nEnvironment setup:\n\tCurrently set log level: {}\n", ToString(spdlog::get_level())); + return RUN_ALL_TESTS(); } \ No newline at end of file -- 2.47.1