diff --git a/logger.cpp b/logger.cpp new file mode 100644 index 0000000..6215ba2 --- /dev/null +++ b/logger.cpp @@ -0,0 +1,229 @@ +/********************************************************************************************************************** + * * + * @file logger.cpp * + * * + * @brief Defines the Logger class declared in logger.h. * + * @copyright Copyright (C) 2023 by Tom Hicks * + * * + * Licensed under the MIT license see below for details. * + * * + * MIT License * + * * + * Copyright (c) 2023 Tom Hicks * + * * + * 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 "logger.h" + +#include +#include +#include +#include +#include +#include +#include +#if __cplusplus >= 202002L +#include +#include + +namespace CPPUtils { +namespace { +using std::ostringstream; +using std::source_location; +} // End namespace +} // End namespace CPPUtils +#endif + +namespace CPPUtils { +namespace { +using std::exception; +using std::shared_ptr; +using std::string; +shared_ptr shared_logger_ = nullptr; +} // End namespace + +Logger::Destination::Destination() {} + +Logger::Destination::~Destination() {} + +Logger::Logger() {} + +Logger::~Logger() {} + +shared_ptr Logger::GetShared() { + if (shared_logger_ == nullptr) { + shared_logger_ = shared_ptr(new Logger()); + } + return shared_logger_; +} + +shared_ptr Logger::GetUnique() { + return shared_ptr(new Logger()); +} + +void Logger::AddDestination(shared_ptr destination) { + destinations_.push_back(destination); +} + +void Logger::ClearDestinations() { + destinations_.clear(); +} + +void Logger::Destination::SetMinType(const MessageType& type) { + if (type >= MessageType::Unknown && type <= MessageType::Wtf) { + min_type_ = type; + } else { + min_type_ = MessageType::Unknown; + } +} + +void Logger::Destination::SetMaxType(const MessageType& type) { + if (type >= MessageType::Unknown && type <= MessageType::Wtf) { + max_type_ = type; + } else { + max_type_ = MessageType::Unknown; + } +} + +Logger::MessageType Logger::Destination::GetMinType() const { + return min_type_; +} + +Logger::MessageType Logger::Destination::GetMaxType() const { + return max_type_; +} + +#if __cplusplus >= 202002L +void Logger::LogUnimplementedMethod(source_location location) { + ostringstream error_message; + error_message << "Unimplemented method: \"" << location.function_name() << "\" at " << location.file_name() << " (" + << location.line() << ":" << location.column() << ")"; + LogDebug(error_message.str()); +} +#else +void Logger::LogUnimplementedMethodReal(string method_name) { + LogDebug("Unimplemented method: " + method_name); +} +#endif + +void Logger::LogUnhandledError(const exception& ex) { + LogDebug("Unhandled exception", ex); +} + +void Logger::LogUnimplementedFeature(const string& feature) { + LogDebug("Unimplemented feature: " + feature); +} + +void Logger::LogWtf(const string& message) { + Log(Wtf, message); +} + +void Logger::LogWtf(const exception& ex) { + Log(Wtf, ex); +} + +void Logger::LogWtf(const string& message, const exception& ex) { + Log(Wtf, message, ex); +} + +void Logger::LogError(const string& message) { + Log(Error, message); +} + +void Logger::LogError(const exception& ex) { + Log(Error, ex); +} + +void Logger::LogError(const string& message, const exception& ex) { + Log(Error, message, ex); +} + +void Logger::LogWarning(const string& message) { + Log(Warning, message); +} + +void Logger::LogWarning(const exception& ex) { + Log(Warning, ex); +} + +void Logger::LogWarning(const string& message, const exception& ex) { + Log(Warning, message, ex); +} + +void Logger::LogInfo(const string& message) { + Log(Info, message); +} + +void Logger::LogInfo(const exception& ex) { + Log(Info, ex); +} + +void Logger::LogInfo(const string& message, const exception& ex) { + Log(Info, message, ex); +} + +void Logger::LogDebug(const string& message) { + Log(Debug, message); +} + +void Logger::LogDebug(const exception& ex) { + Log(Debug, ex); +} + +void Logger::LogDebug(const string& message, const exception& ex) { + Log(Debug, message, ex); +} + +void Logger::LogVerbose(const string& message) { + Log(Verbose, message); +} + +void Logger::LogVerbose(const exception& ex) { + Log(Verbose, ex); +} + +void Logger::LogVerbose(const string& message, const exception& ex) { + Log(Verbose, message, ex); +} + +void Logger::Log(const MessageType& type, const string& message) { + for (const auto& destination : destinations_) { + if (type >= destination->GetMinType() && type <= destination->GetMaxType()) { + destination->LogMessage(type, message); + } + } +} + +void Logger::Log(const MessageType& type, const exception& ex) { + for (const auto& destination : destinations_) { + if (type >= destination->GetMinType() && type <= destination->GetMaxType()) { + destination->LogError(type, ex); + } + } +} + +void Logger::Log(const MessageType& type, const string& message, const exception& ex) { + for (const auto& destination : destinations_) { + if (type >= destination->GetMinType() && type <= destination->GetMaxType()) { + destination->LogError(type, message, ex); + } + } +} + +void Logger::LogToDo(const string& message) { + LogDebug("TODO: " + message); +} +} // End namespace CPPUtils diff --git a/logger.h b/logger.h new file mode 100644 index 0000000..a50c11f --- /dev/null +++ b/logger.h @@ -0,0 +1,113 @@ +/********************************************************************************************************************** + * * + * @file logger.h * + * * + * @brief Declares the Logger class used as a generic interface to log to logging destinations. * + * Multiple logging destinations can be registered with different thresholds. * + * @copyright Copyright (C) 2023 by Tom Hicks * + * * + * Licensed under the MIT license see below for details. * + * * + * MIT License * + * * + * Copyright (c) 2023 Tom Hicks * + * * + * 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 CPPUtils__Logger_h__ +#define CPPUtils__Logger_h__ + +#include +#include +#include +#include + +namespace CPPUtils { +class Logger { + public: + enum MessageType { Unknown = 0, Debug, Verbose, Info, Warning, Error, Wtf }; + + class Destination { + public: + Destination(); + virtual ~Destination(); + virtual void LogMessage(const MessageType& type, const std::string& message) const = 0; + virtual void LogError(const MessageType& type, const std::exception& ex) const = 0; + virtual void LogError(const MessageType& type, const std::string& message, const std::exception& ex) const = 0; + virtual void SetMinType(const MessageType& type); + virtual void SetMaxType(const MessageType& type); + virtual MessageType GetMinType() const; + virtual MessageType GetMaxType() const; + + protected: + MessageType min_type_; + MessageType max_type_; + }; + + virtual ~Logger(); + static std::shared_ptr GetShared(); + static std::shared_ptr GetUnique(); + virtual void AddDestination(std::shared_ptr destination); + virtual void ClearDestinations(); + + // This one is special and annoying because it requires macros until the minimum standard is c++20. +#if __cplusplus >= 202002L + void LogUnimplementedMethod(std::source_location = std::source_location::current()); +#else + void LogUnimplementedMethodReal(std::string method_name); +#endif + + void LogUnhandledError(const std::exception& ex); + void LogUnimplementedFeature(const std::string& feature); + void LogWtf(const std::string& message); + void LogWtf(const std::exception& ex); + void LogWtf(const std::string& message, const std::exception& ex); + void LogError(const std::string& message); + void LogError(const std::exception& ex); + void LogError(const std::string& message, const std::exception& ex); + void LogWarning(const std::string& message); + void LogWarning(const std::exception& ex); + void LogWarning(const std::string& message, const std::exception& ex); + void LogInfo(const std::string& message); + void LogInfo(const std::exception& ex); + void LogInfo(const std::string& message, const std::exception& ex); + void LogDebug(const std::string& message); + void LogDebug(const std::exception& ex); + void LogDebug(const std::string& message, const std::exception& ex); + void LogVerbose(const std::string& message); + void LogVerbose(const std::exception& ex); + void LogVerbose(const std::string& message, const std::exception& ex); + void Log(const MessageType& type, const std::string& message); + void Log(const MessageType& type, const std::exception& ex); + void Log(const MessageType& type, const std::string& message, const std::exception& ex); + void LogToDo(const std::string& message); + + protected: + Logger(); + + private: + std::vector> destinations_; +}; +} // End namespace CPPUtils + +#if __cplusplus >= 202002L +#elif defined __GNUC__ +#define LogUnimplementedMethod Logger::LogUnimplementedMethodReal(__PRETTY_FUNCTION__); +#else +#define LogUnimplementedMethod Logger::LogUnimplementedMethodReal(__FUNCTION__); +#endif + +#endif // End !defined(CPPUtils__Logger_h__) diff --git a/logger_test.cpp b/logger_test.cpp new file mode 100644 index 0000000..f4a9d1d --- /dev/null +++ b/logger_test.cpp @@ -0,0 +1,807 @@ +/********************************************************************************************************************** + * * + * @file logger_test.h * + * * + * @brief Defines tests for the Logger class declared in logger.h. * + * @copyright Copyright (C) 2023 by Tom Hicks * + * * + * Licensed under the MIT license see below for details. * + * * + * MIT License * + * * + * Copyright (c) 2023 Tom Hicks * + * * + * 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 "logger.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tinytest.h" + +namespace { +using CPPUtils::Logger; +using std::cout; +using std::endl; +using std::get; +using std::make_optional; +using std::make_shared; +using std::make_tuple; +using std::nullopt; +using std::optional; +using std::ostream; +using std::ostringstream; +using std::runtime_error; +using std::shared_ptr; +using std::string; +using std::tuple; +using std::vector; +using TinyTest::execute_suite; +using TinyTest::make_test; +using TinyTest::make_test_suite; +using TinyTest::TestResults; + +string no_errors = "no errors"; +typedef tuple, optional> LogEntry; + +class SpyLoggerDestination : public Logger::Destination { + public: + SpyLoggerDestination() { + min_type_ = Logger::MessageType::Unknown; + max_type_ = Logger::MessageType::Wtf; + } + + virtual ~SpyLoggerDestination() {} + + virtual void LogMessage(const Logger::MessageType& type, const std::string& message) const { + log.push_back(make_tuple(type, message, nullopt)); + } + + virtual void LogError(const Logger::MessageType& type, const std::exception& ex) const { + log.push_back(make_tuple(type, nullopt, ex.what())); + } + + virtual void LogError(const Logger::MessageType& type, const std::string& message, const std::exception& ex) const { + log.push_back(make_tuple(type, message, ex.what())); + } + + mutable vector log; +}; + +template +ostream& PrintOptional(ostream& os, optional op) { + if (op.has_value()) { + os << op.value(); + } else { + os << "nullopt"; + } + return os; +} + +void ExpectMessageType(ostream& error_message, const Logger::MessageType& expected, const LogEntry& log_entry) { + Logger::MessageType actual = get<0>(log_entry); + if (actual != expected) { + error_message << "Unexpected MessageType expected: " << expected << ", actual: " << actual << endl; + } +} + +void ExpectMessageText(ostream& error_message, optional expected, const LogEntry& log_entry) { + optional actual = get<1>(log_entry); + if (actual != expected) { + error_message << "Unexpected message text. Expected: "; + PrintOptional(error_message, expected) << ", Actual: "; + PrintOptional(error_message, actual) << endl; + } +} + +void ExpectMessageException(ostream& error_message, optional expected, const LogEntry& log_entry) { + optional actual = get<2>(log_entry); + if (actual != expected) { + error_message << "Unexpected message exception. Expected: "; + PrintOptional(error_message, expected) << ", Actual: "; + PrintOptional(error_message, actual) << endl; + } +} + +void ExpectLogSize(ostream& error_message, size_t expected, shared_ptr destination) { + size_t actual = destination->log.size(); + if (actual != expected) { + error_message << "Unexpected number of events logged. Expected: " << expected << ", Actual: " << actual << endl; + } +} + +string GetError(ostringstream& error_message) { + string error = error_message.str(); + if (error.size() > 0) { + return error; + } + return no_errors; +} +} // End namespace + +TestResults test_Destination_TypeRangeGettersAndSetters() { + TestResults results; + auto set_min_type = [](const Logger::MessageType& type) { + auto spy = make_shared(); + if (type == Logger::MessageType::Unknown) { + spy->SetMinType(Logger::MessageType::Debug); + } else { + spy->SetMinType(Logger::MessageType::Unknown); + } + spy->SetMinType(type); + return spy->GetMinType(); + }; + results += execute_suite(make_test_suite( + "CPPUtils::Logger::SetMinType(const MessageType&)", + set_min_type, + { + make_test( + "should set min type to Unknown", Logger::MessageType::Unknown, make_tuple(Logger::MessageType::Unknown)), + make_test("should set min type to Debug", Logger::MessageType::Debug, make_tuple(Logger::MessageType::Debug)), + make_test( + "should set min type to Verbose", Logger::MessageType::Verbose, make_tuple(Logger::MessageType::Verbose)), + make_test("should set min type to Info", Logger::MessageType::Info, make_tuple(Logger::MessageType::Info)), + make_test( + "should set min type to Warning", Logger::MessageType::Warning, make_tuple(Logger::MessageType::Warning)), + make_test("should set min type to Error", Logger::MessageType::Error, make_tuple(Logger::MessageType::Error)), + make_test("should set min type to Wtf", Logger::MessageType::Wtf, make_tuple(Logger::MessageType::Wtf)), + make_test("should set min type to Unknown for an invalid MessageType", + Logger::MessageType::Unknown, + (make_tuple((Logger::MessageType)-1))), + })); + auto set_max_type = [](const Logger::MessageType& type) { + auto spy = make_shared(); + if (type == Logger::MessageType::Unknown) { + spy->SetMaxType(Logger::MessageType::Debug); + } else { + spy->SetMaxType(Logger::MessageType::Unknown); + } + spy->SetMaxType(type); + return spy->GetMaxType(); + }; + results += execute_suite(make_test_suite( + "CPPUtils::Logger::SetMaxType(const MessageType&)", + set_max_type, + { + make_test( + "should set max type to Unknown", Logger::MessageType::Unknown, make_tuple(Logger::MessageType::Unknown)), + make_test("should set max type to Debug", Logger::MessageType::Debug, make_tuple(Logger::MessageType::Debug)), + make_test( + "should set max type to Verbose", Logger::MessageType::Verbose, make_tuple(Logger::MessageType::Verbose)), + make_test("should set max type to Info", Logger::MessageType::Info, make_tuple(Logger::MessageType::Info)), + make_test( + "should set max type to Warning", Logger::MessageType::Warning, make_tuple(Logger::MessageType::Warning)), + make_test("should set max type to Error", Logger::MessageType::Error, make_tuple(Logger::MessageType::Error)), + make_test("should set max type to Wtf", Logger::MessageType::Wtf, make_tuple(Logger::MessageType::Wtf)), + make_test("should set max type to Unknown for an invalid MessageType", + Logger::MessageType::Unknown, + (make_tuple((Logger::MessageType)-1))), + })); + return results; +} + +TestResults test_Logger_GetShared() { + auto function_to_test = []() { + auto first = Logger::GetShared(); + auto second = Logger::GetShared(); + return first == second; + }; + return execute_suite(make_test_suite("CPPUtils::Logger::GetShared()", + function_to_test, + { + make_test("should get the same instance twice", true, make_tuple()), + })); +} + +TestResults test_Logger_GetUnique() { + auto get_unique_is_different = []() { + auto first = Logger::GetUnique(); + auto second = Logger::GetUnique(); + return first != second; + }; + + auto get_unique_is_not_get_shared = []() { + auto shared = Logger::GetShared(); + auto unique = Logger::GetUnique(); + return unique != shared; + }; + + TestResults results; + + results += execute_suite(make_test_suite("CPPUtils::Logger::GetUnique()", + get_unique_is_different, + { + make_test("should get two different instances", true, make_tuple()), + })); + + results += execute_suite( + make_test_suite("CPPUtils::Logger::GetUnique()", + get_unique_is_not_get_shared, + { + make_test("should get and instance that is not the shared instance", true, make_tuple()), + })); + + return results; +} + +TestResults test_LogUnimplementedMethod() { +#if __cplusplus >= 202002L + return TestResults().fail("Not implemented for c++20 yet."); + + ostringstream error_message; + error_message << "Unimplemented method: \"" << location.function_name() << "\" at " << location.file_name() << " (" + << location.line() << ":" << location.column() << ")"; + LogDebug(error_message.str()); + // TODO: see what this results in when run in c++20 and test for those values. +#else + auto function_to_test = [](string method_name) -> string { + auto spy = make_shared(); + auto logger = Logger::GetUnique(); + logger->AddDestination(spy); +#if __cplusplus >= 202002L + std::source_location loc; + logger->LogUnimplementedMethod() +#else + logger->LogUnimplementedMethodReal(method_name); +#endif + ostringstream error_message; + ExpectLogSize(error_message, 1, spy); + if (spy->log.size() > 0) { + auto message = spy->log.at(0); + ExpectMessageType(error_message, Logger::MessageType::Debug, message); + ExpectMessageText(error_message, "Unimplemented method: int main(int argc, char* argv[])", message); + ExpectMessageException(error_message, nullopt, message); + } + return GetError(error_message); + }; + return execute_suite(make_test_suite( + "CPPUtils::Logger::LogUnimplementedMethod", + function_to_test, + { + make_test("should log a debug message", no_errors, make_tuple("int main(int argc, char* argv[])")), + })); +#endif +} + +TestResults test_Logger_LogUnhandledError() { + auto log_unhandled_error = []() { + auto logger = Logger::GetUnique(); + auto spy = make_shared(); + logger->AddDestination(spy); + std::exception ex; + logger->LogUnhandledError(ex); + ostringstream error_message; + ExpectLogSize(error_message, 1, spy); + if (spy->log.size() > 0) { + auto message = spy->log.at(0); + ExpectMessageType(error_message, Logger::MessageType::Debug, message); + ExpectMessageText(error_message, "Unhandled exception", message); + ExpectMessageException(error_message, ex.what(), message); + } + return GetError(error_message); + }; + return execute_suite(make_test_suite("CPPUtils::Logger::LogUnhandledError(const std::exception&)", + log_unhandled_error, + { + make_test("should log an error", no_errors, make_tuple()), + })); +} + +TestResults test_Logger_LogUnimplementedFeature() { + auto log_unimplemented_feature = []() { + auto logger = Logger::GetUnique(); + auto spy = make_shared(); + logger->AddDestination(spy); + string feature_name = "rolling over"; + logger->LogUnimplementedFeature(feature_name); + ostringstream error_message; + ExpectLogSize(error_message, 1, spy); + if (spy->log.size() > 0) { + auto message = spy->log.at(0); + ExpectMessageType(error_message, Logger::MessageType::Debug, message); + ExpectMessageText(error_message, "Unimplemented feature: " + feature_name, message); + ExpectMessageException(error_message, nullopt, message); + } + return GetError(error_message); + }; + return execute_suite(make_test_suite("CPPUtils::Logger::LogUnimplementedFeature()", + log_unimplemented_feature, + { + make_test("should log an unimplemented feature", no_errors, make_tuple()), + })); +} + +TestResults test_Logger_Log_Level() { + auto log = [](const Logger::MessageType& type, + const optional& message_text, + const optional& ex) -> string { + ostringstream error_message; + auto logger = Logger::GetUnique(); + auto spy = make_shared(); + logger->AddDestination(spy); + switch (type) { + case Logger::MessageType::Debug: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->LogDebug(message_text.value(), ex.value()); + } else { + logger->LogDebug(message_text.value()); + } + } else { + if (ex.has_value()) { + logger->LogDebug(ex.value()); + } + } + break; + case Logger::MessageType::Verbose: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->LogVerbose(message_text.value(), ex.value()); + } else { + logger->LogVerbose(message_text.value()); + } + } else { + if (ex.has_value()) { + logger->LogVerbose(ex.value()); + } + } + break; + case Logger::MessageType::Info: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->LogInfo(message_text.value(), ex.value()); + } else { + logger->LogInfo(message_text.value()); + } + } else { + if (ex.has_value()) { + logger->LogInfo(ex.value()); + } + } + break; + case Logger::MessageType::Warning: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->LogWarning(message_text.value(), ex.value()); + } else { + logger->LogWarning(message_text.value()); + } + } else { + if (ex.has_value()) { + logger->LogWarning(ex.value()); + } + } + break; + case Logger::MessageType::Error: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->LogError(message_text.value(), ex.value()); + } else { + logger->LogError(message_text.value()); + } + } else { + if (ex.has_value()) { + logger->LogError(ex.value()); + } + } + break; + case Logger::MessageType::Wtf: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->LogWtf(message_text.value(), ex.value()); + } else { + logger->LogWtf(message_text.value()); + } + } else { + if (ex.has_value()) { + logger->LogWtf(ex.value()); + } + } + break; + default: + break; + } + ExpectLogSize(error_message, 1, spy); + if (spy->log.size() > 0) { + auto message = spy->log.at(0); + ExpectMessageType(error_message, type, message); + ExpectMessageText(error_message, message_text, message); + ExpectMessageException(error_message, ex.has_value() ? make_optional(ex.value().what()) : nullopt, message); + } + return GetError(error_message); + }; + return execute_suite( + make_test_suite("CPPUtils::Logger::Log*(...)", + log, + { + make_test("should log what a terrible failure with a message", + no_errors, + make_tuple(Logger::MessageType::Wtf, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log what a terrible failure with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Wtf, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log what a terrible failure with a message", + no_errors, + make_tuple(Logger::MessageType::Wtf, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + make_test("should log an error with a message", + no_errors, + make_tuple(Logger::MessageType::Error, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log an error with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Error, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log an error with a message", + no_errors, + make_tuple(Logger::MessageType::Error, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a warning with a message", + no_errors, + make_tuple(Logger::MessageType::Warning, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log a warning with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Warning, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a warning with a message", + no_errors, + make_tuple(Logger::MessageType::Warning, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + make_test("should log an info with a message", + no_errors, + make_tuple(Logger::MessageType::Info, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log an info with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Info, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log an info with a message", + no_errors, + make_tuple(Logger::MessageType::Info, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a debug with a message", + no_errors, + make_tuple(Logger::MessageType::Debug, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log a debug with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Debug, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a debug with a message", + no_errors, + make_tuple(Logger::MessageType::Debug, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a verbose with a message", + no_errors, + make_tuple(Logger::MessageType::Verbose, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log a verbose with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Verbose, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a verbose with a message", + no_errors, + make_tuple(Logger::MessageType::Verbose, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + + })); +} + +TestResults test_Logger_Log() { + auto log = [](const Logger::MessageType& type, + const optional& message_text, + const optional& ex) -> string { + ostringstream error_message; + auto logger = Logger::GetUnique(); + auto spy = make_shared(); + logger->AddDestination(spy); + switch (type) { + case Logger::MessageType::Debug: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Debug, message_text.value(), ex.value()); + } else { + logger->Log(Logger::MessageType::Debug, message_text.value()); + } + } else { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Debug, ex.value()); + } + } + break; + case Logger::MessageType::Verbose: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Verbose, message_text.value(), ex.value()); + } else { + logger->Log(Logger::MessageType::Verbose, message_text.value()); + } + } else { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Verbose, ex.value()); + } + } + break; + case Logger::MessageType::Info: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Info, message_text.value(), ex.value()); + } else { + logger->Log(Logger::MessageType::Info, message_text.value()); + } + } else { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Info, ex.value()); + } + } + break; + case Logger::MessageType::Warning: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Warning, message_text.value(), ex.value()); + } else { + logger->Log(Logger::MessageType::Warning, message_text.value()); + } + } else { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Warning, ex.value()); + } + } + break; + case Logger::MessageType::Error: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Error, message_text.value(), ex.value()); + } else { + logger->Log(Logger::MessageType::Error, message_text.value()); + } + } else { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Error, ex.value()); + } + } + break; + case Logger::MessageType::Wtf: + if (message_text.has_value()) { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Wtf, message_text.value(), ex.value()); + } else { + logger->Log(Logger::MessageType::Wtf, message_text.value()); + } + } else { + if (ex.has_value()) { + logger->Log(Logger::MessageType::Wtf, ex.value()); + } + } + break; + default: + break; + } + ExpectLogSize(error_message, 1, spy); + if (spy->log.size() > 0) { + auto message = spy->log.at(0); + ExpectMessageType(error_message, type, message); + ExpectMessageText(error_message, message_text, message); + ExpectMessageException(error_message, ex.has_value() ? make_optional(ex.value().what()) : nullopt, message); + } + return GetError(error_message); + }; + return execute_suite( + make_test_suite("CPPUtils::Logger::Log(const MessageType&, ...)", + log, + { + make_test("should log what a terrible failure with a message", + no_errors, + make_tuple(Logger::MessageType::Wtf, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log what a terrible failure with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Wtf, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log what a terrible failure with a message", + no_errors, + make_tuple(Logger::MessageType::Wtf, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + make_test("should log an error with a message", + no_errors, + make_tuple(Logger::MessageType::Error, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log an error with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Error, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log an error with a message", + no_errors, + make_tuple(Logger::MessageType::Error, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a warning with a message", + no_errors, + make_tuple(Logger::MessageType::Warning, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log a warning with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Warning, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a warning with a message", + no_errors, + make_tuple(Logger::MessageType::Warning, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + make_test("should log an info with a message", + no_errors, + make_tuple(Logger::MessageType::Info, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log an info with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Info, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log an info with a message", + no_errors, + make_tuple(Logger::MessageType::Info, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a debug with a message", + no_errors, + make_tuple(Logger::MessageType::Debug, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log a debug with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Debug, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a debug with a message", + no_errors, + make_tuple(Logger::MessageType::Debug, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a verbose with a message", + no_errors, + make_tuple(Logger::MessageType::Verbose, + (const optional&)"this should never happen", + (const optional&)nullopt)), + make_test("should log a verbose with a message and an exception", + no_errors, + make_tuple(Logger::MessageType::Verbose, + (const optional&)"this should never happen", + (const optional&)runtime_error("bad thing happen"))), + make_test("should log a verbose with a message", + no_errors, + make_tuple(Logger::MessageType::Verbose, + (const optional&)nullopt, + (const optional&)runtime_error("bad thing happen"))), + + })); +} + +TestResults test_Logger_LogToDo() { + auto log_to_do = [](const string& todo_message) { + auto logger = Logger::GetUnique(); + auto spy = make_shared(); + logger->AddDestination(spy); + logger->LogToDo(todo_message); + ostringstream error_message; + ExpectLogSize(error_message, 1, spy); + if (spy->log.size() > 0) { + auto message = spy->log.at(0); + ExpectMessageType(error_message, Logger::MessageType::Debug, message); + ExpectMessageText(error_message, "TODO: " + todo_message, message); + ExpectMessageException(error_message, nullopt, message); + } + return GetError(error_message); + }; + return execute_suite(make_test_suite( + "CPPUtils::Logger::LogToDo(const std:;string&)", + log_to_do, + { + make_test("should log a TODO for \"fill in this function\"", no_errors, make_tuple("fill in this function")), + make_test("should log a TODO for \"delete this after fixing bug:2048\"", + no_errors, + make_tuple("delete this after fixing bug:2048")), + make_test("should log a TODO for \"refactor this\"", no_errors, make_tuple("refactor this")), + })); +} + +TestResults test_Logger_AddDestination_and_ClearDestinations() { + auto add_destination = []() { + ostringstream error_message; + auto logger = Logger::GetUnique(); + auto spy = make_shared(); + ExpectLogSize(error_message, 0, spy); + logger->LogInfo("first message"); + ExpectLogSize(error_message, 0, spy); + logger->AddDestination(spy); + ExpectLogSize(error_message, 0, spy); + logger->LogInfo("second message"); + ExpectLogSize(error_message, 1, spy); + logger->ClearDestinations(); + ExpectLogSize(error_message, 1, spy); + logger->LogInfo("third message"); + ExpectLogSize(error_message, 1, spy); + if (spy->log.size() > 0) { + auto message = spy->log.at(0); + ExpectMessageType(error_message, Logger::MessageType::Info, message); + ExpectMessageText(error_message, "second message", message); + ExpectMessageException(error_message, nullopt, message); + } + return GetError(error_message); + }; + return execute_suite(make_test_suite("", + add_destination, + { + make_test("should add and clear destinations", no_errors, make_tuple()), + })); +} + +// AddDestination +// ClearDestinations + +int main(int argc, char* argv[]) { + TestResults results; + + results += test_Destination_TypeRangeGettersAndSetters(); + results += test_Logger_GetShared(); + results += test_Logger_GetUnique(); + results += test_LogUnimplementedMethod(); + results += test_Logger_LogUnhandledError(); + results += test_Logger_LogUnimplementedFeature(); + results += test_Logger_Log_Level(); + results += test_Logger_Log(); + results += test_Logger_LogToDo(); + results += test_Logger_AddDestination_and_ClearDestinations(); + + PrintResults(cout, results); + + return results.failed() + results.errors(); +}