Adds the Logger class and tests.

C++20 support is best effort until I build a compiler and stdlib that support it.
This commit is contained in:
2023-05-07 00:54:32 -07:00
parent a446806e14
commit a1e28c1db7
3 changed files with 1149 additions and 0 deletions

229
logger.cpp Normal file
View File

@@ -0,0 +1,229 @@
/**********************************************************************************************************************
* *
* @file logger.cpp *
* *
* @brief Defines the Logger class declared in logger.h. *
* @copyright Copyright (C) 2023 by Tom Hicks <headhunter3@gmail.com> *
* *
* Licensed under the MIT license see below for details. *
* *
* MIT License *
* *
* Copyright (c) 2023 Tom Hicks <headhunter3@gmail.com> *
* *
* 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 <algorithm>
#include <exception>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
#if __cplusplus >= 202002L
#include <source_location>
#include <sstream>
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<Logger> shared_logger_ = nullptr;
} // End namespace
Logger::Destination::Destination() {}
Logger::Destination::~Destination() {}
Logger::Logger() {}
Logger::~Logger() {}
shared_ptr<Logger> Logger::GetShared() {
if (shared_logger_ == nullptr) {
shared_logger_ = shared_ptr<Logger>(new Logger());
}
return shared_logger_;
}
shared_ptr<Logger> Logger::GetUnique() {
return shared_ptr<Logger>(new Logger());
}
void Logger::AddDestination(shared_ptr<Logger::Destination> 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

113
logger.h Normal file
View File

@@ -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 <headhunter3@gmail.com> *
* *
* Licensed under the MIT license see below for details. *
* *
* MIT License *
* *
* Copyright (c) 2023 Tom Hicks <headhunter3@gmail.com> *
* *
* 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 <exception>
#include <memory>
#include <string>
#include <vector>
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<Logger> GetShared();
static std::shared_ptr<Logger> GetUnique();
virtual void AddDestination(std::shared_ptr<Logger::Destination> 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<std::shared_ptr<Logger::Destination>> 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__)

807
logger_test.cpp Normal file
View File

@@ -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 <headhunter3@gmail.com> *
* *
* Licensed under the MIT license see below for details. *
* *
* MIT License *
* *
* Copyright (c) 2023 Tom Hicks <headhunter3@gmail.com> *
* *
* 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 <iostream>
#include <memory>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <vector>
#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<Logger::MessageType, optional<string>, optional<string>> 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<LogEntry> log;
};
template <typename T>
ostream& PrintOptional(ostream& os, optional<T> 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<string> expected, const LogEntry& log_entry) {
optional<string> 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<string> expected, const LogEntry& log_entry) {
optional<string> 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<SpyLoggerDestination> 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<SpyLoggerDestination>();
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<SpyLoggerDestination>();
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<SpyLoggerDestination>();
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<SpyLoggerDestination>();
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<SpyLoggerDestination>();
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<string>& message_text,
const optional<std::exception>& ex) -> string {
ostringstream error_message;
auto logger = Logger::GetUnique();
auto spy = make_shared<SpyLoggerDestination>();
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<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log what a terrible failure with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Wtf,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)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<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log an error with a message",
no_errors,
make_tuple(Logger::MessageType::Error,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log an error with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Error,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log an error with a message",
no_errors,
make_tuple(Logger::MessageType::Error,
(const optional<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a warning with a message",
no_errors,
make_tuple(Logger::MessageType::Warning,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log a warning with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Warning,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a warning with a message",
no_errors,
make_tuple(Logger::MessageType::Warning,
(const optional<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log an info with a message",
no_errors,
make_tuple(Logger::MessageType::Info,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log an info with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Info,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log an info with a message",
no_errors,
make_tuple(Logger::MessageType::Info,
(const optional<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a debug with a message",
no_errors,
make_tuple(Logger::MessageType::Debug,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log a debug with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Debug,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a debug with a message",
no_errors,
make_tuple(Logger::MessageType::Debug,
(const optional<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a verbose with a message",
no_errors,
make_tuple(Logger::MessageType::Verbose,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log a verbose with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Verbose,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a verbose with a message",
no_errors,
make_tuple(Logger::MessageType::Verbose,
(const optional<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
}));
}
TestResults test_Logger_Log() {
auto log = [](const Logger::MessageType& type,
const optional<string>& message_text,
const optional<std::exception>& ex) -> string {
ostringstream error_message;
auto logger = Logger::GetUnique();
auto spy = make_shared<SpyLoggerDestination>();
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<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log what a terrible failure with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Wtf,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)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<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log an error with a message",
no_errors,
make_tuple(Logger::MessageType::Error,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log an error with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Error,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log an error with a message",
no_errors,
make_tuple(Logger::MessageType::Error,
(const optional<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a warning with a message",
no_errors,
make_tuple(Logger::MessageType::Warning,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log a warning with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Warning,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a warning with a message",
no_errors,
make_tuple(Logger::MessageType::Warning,
(const optional<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log an info with a message",
no_errors,
make_tuple(Logger::MessageType::Info,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log an info with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Info,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log an info with a message",
no_errors,
make_tuple(Logger::MessageType::Info,
(const optional<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a debug with a message",
no_errors,
make_tuple(Logger::MessageType::Debug,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log a debug with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Debug,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a debug with a message",
no_errors,
make_tuple(Logger::MessageType::Debug,
(const optional<string>&)nullopt,
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a verbose with a message",
no_errors,
make_tuple(Logger::MessageType::Verbose,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)nullopt)),
make_test("should log a verbose with a message and an exception",
no_errors,
make_tuple(Logger::MessageType::Verbose,
(const optional<string>&)"this should never happen",
(const optional<std::exception>&)runtime_error("bad thing happen"))),
make_test("should log a verbose with a message",
no_errors,
make_tuple(Logger::MessageType::Verbose,
(const optional<string>&)nullopt,
(const optional<std::exception>&)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<SpyLoggerDestination>();
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<SpyLoggerDestination>();
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();
}