From fb376f403a9cfb6d2c8be82b4e824569a8561c6e Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Thu, 19 Dec 2024 11:07:30 +1100 Subject: [PATCH 1/8] Make string_join a utility and add tests --- src/util/string_join.hpp | 67 +++++++++++++++++ tests/tests/util/string_join.cpp | 121 +++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 src/util/string_join.hpp create mode 100644 tests/tests/util/string_join.cpp diff --git a/src/util/string_join.hpp b/src/util/string_join.hpp new file mode 100644 index 00000000..d5b230b2 --- /dev/null +++ b/src/util/string_join.hpp @@ -0,0 +1,67 @@ +/* + * MIT License + * + * Copyright (c) 2024 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. + * + * 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. + */ + +#ifndef NUCLEAR_UTIL_STRING_JOIN_HPP +#define NUCLEAR_UTIL_STRING_JOIN_HPP + +#include +#include + +namespace NUClear { +namespace util { + + namespace detail { + // No argument base case + inline void do_string_join(std::stringstream& /*out*/, const std::string& /*delimiter*/) {} + + // Single argument case + template + void do_string_join(std::stringstream& out, const std::string& /*delimiter*/, Last&& last) { + out << std::forward(last); + } + + // Two or more arguments case + template + void do_string_join(std::stringstream& out, + const std::string& delimiter, + First&& first, + Second&& second, + Remainder&&... remainder) { + out << std::forward(first) << delimiter; + do_string_join(out, delimiter, std::forward(second), std::forward(remainder)...); + } + } // namespace detail + + /** + * Join a list of arguments into a single string with a delimiter between each argument. + */ + template + std::string string_join(const std::string& delimiter, Arguments&&... args) { + std::stringstream out; + detail::do_string_join(out, delimiter, std::forward(args)...); + return out.str(); + } + +} // namespace util +} // namespace NUClear + +#endif // NUCLEAR_UTIL_STRING_JOIN_HPP diff --git a/tests/tests/util/string_join.cpp b/tests/tests/util/string_join.cpp new file mode 100644 index 00000000..7f6aa5c8 --- /dev/null +++ b/tests/tests/util/string_join.cpp @@ -0,0 +1,121 @@ +/* + * MIT License + * + * Copyright (c) 2023 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. + * + * 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. + */ + +#include "util/string_join.hpp" + +#include +#include +#include +#include + +struct TestSymbol { + friend std::ostream& operator<<(std::ostream& os, const TestSymbol&) { + return os << typeid(TestSymbol).name(); + } +}; + + +SCENARIO("Test string_join correctly joins strings", "[util][string_join]") { + + GIVEN("An empty set of arguments") { + std::string delimiter = GENERATE("", ",", " "); + + WHEN("string_join is called") { + const std::string result = NUClear::util::string_join(delimiter); + + THEN("It should return an empty string") { + REQUIRE(result.empty()); + } + } + } + + GIVEN("A single string argument") { + std::string delimiter = GENERATE("", ",", " "); + std::string arg = GENERATE("test", "string", "join"); + + WHEN("string_join is called") { + const std::string result = NUClear::util::string_join(delimiter, arg); + + THEN("It should return the argument") { + REQUIRE(result == arg); + } + } + } + + GIVEN("Two string arguments") { + std::string delimiter = GENERATE("", ",", " "); + std::string arg1 = GENERATE("test", "string", "join"); + std::string arg2 = GENERATE("test", "string", "join"); + + WHEN("string_join is called") { + const std::string result = NUClear::util::string_join(delimiter, arg1, arg2); + + THEN("It should return the two arguments joined by the delimiter") { + REQUIRE(result == arg1 + delimiter + arg2); + } + } + } + + GIVEN("Three string arguments") { + std::string delimiter = GENERATE("", ",", " "); + std::string arg1 = GENERATE("test", "string", "join"); + std::string arg2 = GENERATE("test", "string", "join"); + std::string arg3 = GENERATE("test", "string", "join"); + + WHEN("string_join is called") { + const std::string result = NUClear::util::string_join(delimiter, arg1, arg2, arg3); + + THEN("It should return the three arguments joined by the delimiter") { + REQUIRE(result == arg1 + delimiter + arg2 + delimiter + arg3); + } + } + } + + GIVEN("A mix of string and non-string arguments") { + std::string delimiter = GENERATE("", ",", " "); + std::string arg1 = GENERATE("test", "string", "join"); + std::string arg2 = GENERATE("test", "string", "join"); + int arg3 = GENERATE(1, 2, 3); + + WHEN("string_join is called") { + const std::string result = NUClear::util::string_join(delimiter, arg1, arg2, arg3); + + THEN("It should return the arguments joined by the delimiter") { + REQUIRE(result == arg1 + delimiter + arg2 + delimiter + std::to_string(arg3)); + } + } + } + + GIVEN("A class with an overloaded operator<<") { + std::string delimiter = GENERATE("", ",", " "); + TestSymbol arg1; + TestSymbol arg2; + + WHEN("string_join is called") { + const std::string result = NUClear::util::string_join(delimiter, arg1, arg2); + + THEN("It should return the arguments joined by the delimiter") { + REQUIRE(result == typeid(TestSymbol).name() + delimiter + typeid(TestSymbol).name()); + } + } + } +} From 9a86e19d42e417a0eb1b994b52326ce4dafd7335 Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Thu, 19 Dec 2024 11:32:37 +1100 Subject: [PATCH 2/8] Swap to using a logger class to allow more control over what is logged and how --- src/PowerPlant.cpp | 17 +------ src/PowerPlant.hpp | 49 +++++++++++-------- src/Reactor.hpp | 14 +++--- src/message/LogMessage.hpp | 7 +++ src/util/Logger.cpp | 49 +++++++++++++++++++ src/util/Logger.hpp | 96 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 188 insertions(+), 44 deletions(-) create mode 100644 src/util/Logger.cpp create mode 100644 src/util/Logger.hpp diff --git a/src/PowerPlant.cpp b/src/PowerPlant.cpp index 89f30ea6..deabde45 100644 --- a/src/PowerPlant.cpp +++ b/src/PowerPlant.cpp @@ -49,7 +49,7 @@ PowerPlant* PowerPlant::powerplant = nullptr; // This is taking argc and argv as given by main so this should not take an array // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) PowerPlant::PowerPlant(Configuration config, int argc, const char* argv[]) - : scheduler(config.default_pool_concurrency) { + : scheduler(config.default_pool_concurrency), logger(*this) { // Stop people from making more then one powerplant if (powerplant != nullptr) { @@ -103,21 +103,6 @@ void PowerPlant::submit(std::unique_ptr&& task) noexcep scheduler.submit(std::move(task)); } -void PowerPlant::log(const LogLevel& level, std::string message) { - // Get the current task - const auto* current_task = threading::ReactionTask::get_current_task(); - - // Inline emit the log message to default handlers to pause the current task until the log message is processed - emit(std::make_unique( - level, - current_task != nullptr ? current_task->parent->reactor.log_level : LogLevel::UNKNOWN, - std::move(message), - current_task != nullptr ? current_task->statistics : nullptr)); -} -void PowerPlant::log(const LogLevel& level, std::stringstream& message) { - log(level, message.str()); -} - void PowerPlant::shutdown(bool force) { // Emit our shutdown event diff --git a/src/PowerPlant.hpp b/src/PowerPlant.hpp index c7dc6969..2b783c01 100644 --- a/src/PowerPlant.hpp +++ b/src/PowerPlant.hpp @@ -38,6 +38,7 @@ #include "threading/ReactionTask.hpp" #include "threading/scheduler/Scheduler.hpp" #include "util/FunctionFusion.hpp" +#include "util/Logger.hpp" #include "util/demangle.hpp" namespace NUClear { @@ -188,32 +189,41 @@ class PowerPlant { * Logs a message through the system so the various log handlers can access it. * The arguments being logged should be able to be added into a stringstream. * - * @tparam level The level to log at (defaults to DEBUG) + * @tparam level The level to log at * @tparam Arguments The types of the arguments we are logging * * @param args The arguments we are logging */ template void log(Arguments&&... args) { - log(level, std::forward(args)...); + logger.log(nullptr, level, std::forward(args)...); } template void log(const LogLevel& level, Arguments&&... args) { - std::stringstream ss; - log(level, ss, std::forward(args)...); + logger.log(nullptr, level, std::forward(args)...); } - template - void log(const LogLevel& level, std::stringstream& ss, First&& first, Arguments&&... args) { - ss << std::forward(first) << " "; - log(level, ss, std::forward(args)...); + + /** + * Log a message through NUClear's system. + * + * This version of log takes a pointer to a reactor as an argument. + * When logging from a reactor's log function, even if it's not logging within a reactor needs the context to add + * the extra information about the reactors name. + * + * @tparam level The level to log at (defaults to DEBUG) + * @tparam Arguments The types of the arguments we are logging + * + * @param reactor The reactor that is logging + * @param args The arguments we are logging + */ + template + void log(const Reactor* reactor, Arguments&&... args) { + logger.log(reactor, level, std::forward(args)...); } - template - void log(const LogLevel& level, std::stringstream& ss, Last&& last) { - ss << std::forward(last); - log(level, ss); + template + void log(const Reactor* reactor, const LogLevel& level, Arguments&&... args) { + logger.log(reactor, level, std::forward(args)...); } - void log(const LogLevel& level, std::stringstream& message); - void log(const LogLevel& level, std::string message); /** * Emits data to the system and routes it to the other systems that use it. @@ -235,8 +245,7 @@ class PowerPlant { emit(std::move(data)); } template