diff --git a/src/input_output/FGLog.cpp b/src/input_output/FGLog.cpp index 35b7ff9d6..f1c00cc4c 100644 --- a/src/input_output/FGLog.cpp +++ b/src/input_output/FGLog.cpp @@ -1,9 +1,9 @@ /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - Module: FGOutputType.cpp + Module: FGLog.cpp Author: Bertrand Coconnier Date started: 05/03/24 - Purpose: Manage output of sim parameters to file or stdout + Purpose: Manage the logging of messages ------------- Copyright (C) 2024 Bertrand Coconnier ------------- @@ -26,8 +26,8 @@ FUNCTIONAL DESCRIPTION -------------------------------------------------------------------------------- -This is the place where you create output routines to dump data for perusal -later. +This is the place where the logging of messages is managed. The messages can be +sent to the console, to a file, etc. HISTORY -------------------------------------------------------------------------------- diff --git a/src/input_output/FGLog.h b/src/input_output/FGLog.h index 05e068a7b..efee734f9 100644 --- a/src/input_output/FGLog.h +++ b/src/input_output/FGLog.h @@ -97,7 +97,7 @@ class JSBSIM_API FGLogger { public: virtual ~FGLogger() {} - virtual void SetLevel(LogLevel level) { level = level; } + virtual void SetLevel(LogLevel l) { level = l; } virtual void FileLocation(const std::string& filename, int line) {} void SetMinLevel(LogLevel level) { min_level = level; } virtual void Message(const std::string& message) = 0; @@ -148,7 +148,7 @@ class JSBSIM_API FGLogConsole : public FGLogger { public: void FileLocation(const std::string& filename, int line) override - { buffer << std::endl << "In file " << filename << ": line" << line << std::endl; } + { buffer << std::endl << "In file " << filename << ": line " << line << std::endl; } void Format(LogFormat format) override; void Flush(void) override; diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 8a737be5f..bd5a8c4b7 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -19,7 +19,8 @@ set(UNIT_TESTS FGColumnVector3Test FGPropertyManagerTest FGAtmosphereTest FGAuxiliaryTest - FGMSISTest) + FGMSISTest + FGLogTest) foreach(test ${UNIT_TESTS}) diff --git a/tests/unit_tests/FGLogTest.h b/tests/unit_tests/FGLogTest.h new file mode 100644 index 000000000..1c82e44f7 --- /dev/null +++ b/tests/unit_tests/FGLogTest.h @@ -0,0 +1,422 @@ +#include +#include +#include + +class DummyLogger : public JSBSim::FGLogger +{ +public: + JSBSim::LogLevel GetLogLevel() const { return level; } + void Message(const std::string& message) override { buffer.append(message); } + void FileLocation(const std::string& filename, int line) override { + buffer.append(filename); + buffer.append(":"); + buffer.append(std::to_string(line)); + } + void Format(JSBSim::LogFormat format) override { + switch (format) + { + case JSBSim::LogFormat::NORMAL: + buffer.append("NORMAL"); + break; + default: + buffer.append("UNKNOWN"); + break; + } + } + void Flush(void) override { flushed = true; } + + std::string buffer; + bool flushed = false; +}; + +class FGLogTest : public CxxTest::TestSuite +{ +public: +void testConstructor() { + auto logger = std::make_shared(); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + TS_ASSERT_EQUALS(logger->GetLogLevel(), JSBSim::LogLevel::BULK); + + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + TS_ASSERT(log.str().empty()); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + TS_ASSERT_EQUALS(logger->GetLogLevel(), JSBSim::LogLevel::INFO); +} + +void testDestructor() { + auto logger = std::make_shared(); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + TS_ASSERT(log.str().empty()); + TS_ASSERT(!logger->flushed); + } + TS_ASSERT(logger->buffer.empty()); + TS_ASSERT(logger->flushed); +} + +void testCharMessage() { + auto logger = std::make_shared(); + const char* message = "Hello, World!"; + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log <flushed); + TS_ASSERT(logger->buffer.empty()); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, message); +} + +void testStringMessage() { + auto logger = std::make_shared(); + std::string message = "Hello, World!"; + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << message; + TS_ASSERT_EQUALS(log.str(), message); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, message); +} + +void testConcatenatedMessages() { + auto logger = std::make_shared(); + std::string message1 = "Hello"; + std::string message2 = "World!"; + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << message1 << " " << message2; + TS_ASSERT_EQUALS(log.str(), message1 + " " + message2); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, message1 + " " + message2); +} + +void testEndl() { + auto logger = std::make_shared(); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << "Hello" << std::endl << "World!"; + TS_ASSERT_EQUALS(log.str(), "Hello\nWorld!"); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "Hello\nWorld!"); +} + +void testNumbers() { + auto logger = std::make_shared(); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << 1 << 2.1 << -3.4f; + TS_ASSERT_EQUALS(log.str(), "12.1-3.4"); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "12.1-3.4"); +} + +void testSetPrecision() { + auto logger = std::make_shared(); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << std::setprecision(3) << 1.23456789; + TS_ASSERT_EQUALS(log.str(), "1.23"); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "1.23"); +} + +void testSetWidthRight() { + auto logger = std::make_shared(); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << std::setw(5) << 123; + TS_ASSERT_EQUALS(log.str(), " 123"); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, " 123"); +} + +void testSetWidthLeft() { + auto logger = std::make_shared(); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << std::left << std::setw(5) << 123; + TS_ASSERT_EQUALS(log.str(), "123 "); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "123 "); +} + +void testPath() { + auto logger = std::make_shared(); + SGPath path("path/to"); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << (path/"file"); + TS_ASSERT_EQUALS(log.str(), "Path \"path/to/file\""); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "Path \"path/to/file\""); +} + +void testColumnVector3() { + auto logger = std::make_shared(); + JSBSim::FGColumnVector3 vec(1, 2, 3); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << vec; + TS_ASSERT_EQUALS(log.str(), "1 , 2 , 3"); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "1 , 2 , 3"); +} + +void testFormatOnly() { + auto logger = std::make_shared(); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + log << JSBSim::LogFormat::NORMAL; + TS_ASSERT(log.str().empty()); + TS_ASSERT(!logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "NORMAL"); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "NORMAL"); +} + +void testClosingFormat() { + auto logger = std::make_shared(); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << "Hello,"; + TS_ASSERT_EQUALS(log.str(), "Hello,"); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + log << JSBSim::LogFormat::NORMAL; + TS_ASSERT(log.str().empty()); + TS_ASSERT(!logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "Hello,NORMAL"); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "Hello,NORMAL"); +} + +void testMidFormat() { + auto logger = std::make_shared(); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << "Hello,"; + TS_ASSERT_EQUALS(log.str(), "Hello,"); + TS_ASSERT(!logger->flushed); + TS_ASSERT(logger->buffer.empty()); + log << JSBSim::LogFormat::NORMAL; + TS_ASSERT(log.str().empty()); + TS_ASSERT(!logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "Hello,NORMAL"); + log << " World!"; + TS_ASSERT_EQUALS(log.str(), " World!"); + TS_ASSERT(!logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "Hello,NORMAL"); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "Hello,NORMAL World!"); +} + +void testXMLLogging() { + auto logger = std::make_shared(); + JSBSim::Element el("element"); + el.SetFileName("file.xml"); + el.SetLineNumber(42); + { + JSBSim::FGXMLLogging log(logger, &el, JSBSim::LogLevel::DEBUG); + TS_ASSERT(log.str().empty()); + TS_ASSERT_EQUALS(logger->buffer, "file.xml:42"); + TS_ASSERT(!logger->flushed); + TS_ASSERT_EQUALS(logger->GetLogLevel(), JSBSim::LogLevel::DEBUG); + } + TS_ASSERT(logger->flushed); + TS_ASSERT_EQUALS(logger->buffer, "file.xml:42"); +} +}; + +class FGLogConsoleTest : public CxxTest::TestSuite +{ +public: +void testNormalMessage() { + auto logger = std::make_shared(); + std::ostringstream buffer; + auto cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(buffer.rdbuf()); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::DEBUG); + log << "Hello, World!"; + } + std::cout.rdbuf(cout_buffer); + TS_ASSERT_EQUALS(buffer.str(), "Hello, World!"); +} + +void testErrorMessage() { + auto logger = std::make_shared(); + std::ostringstream buffer; + auto cerr_buffer = std::cerr.rdbuf(); + std::cerr.rdbuf(buffer.rdbuf()); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::ERROR); + log << "Hello, World!"; + } + std::cerr.rdbuf(cerr_buffer); + TS_ASSERT_EQUALS(buffer.str(), "Hello, World!"); +} + +void testXMLLogging() { + auto logger = std::make_shared(); + std::ostringstream buffer; + auto cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(buffer.rdbuf()); + JSBSim::Element el("element"); + el.SetFileName("name.xml"); + el.SetLineNumber(42); + { + JSBSim::FGXMLLogging log(logger, &el, JSBSim::LogLevel::DEBUG); + } + std::cout.rdbuf(cout_buffer); + TS_ASSERT_EQUALS(buffer.str(), "\nIn file name.xml: line 42\n"); +} + +void testRedFormat() { + auto logger = std::make_shared(); + std::ostringstream buffer; + auto cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(buffer.rdbuf()); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << JSBSim::LogFormat::RED; + log << "Hello, World!"; + log << JSBSim::LogFormat::RESET; + } + std::cout.rdbuf(cout_buffer); +#ifdef _MSC_VER + TS_ASSERT_EQUALS(buffer.str(), "Hello, World!"); +#else + TS_ASSERT_EQUALS(buffer.str(), "\033[31mHello, World!\033[0m"); +#endif +} + +void testCyanFormat() { + auto logger = std::make_shared(); + std::ostringstream buffer; + auto cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(buffer.rdbuf()); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << JSBSim::LogFormat::BLUE; + log << "Hello, World!"; + log << JSBSim::LogFormat::RESET; + } + std::cout.rdbuf(cout_buffer); +#ifdef _MSC_VER + TS_ASSERT_EQUALS(buffer.str(), "Hello, World!"); +#else + TS_ASSERT_EQUALS(buffer.str(), "\033[34mHello, World!\033[0m"); +#endif +} + +void testBoldFormat() { + auto logger = std::make_shared(); + std::ostringstream buffer; + auto cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(buffer.rdbuf()); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << JSBSim::LogFormat::BOLD; + log << "Hello, World!"; + log << JSBSim::LogFormat::RESET; + } + std::cout.rdbuf(cout_buffer); +#ifdef _MSC_VER + TS_ASSERT_EQUALS(buffer.str(), "Hello, World!"); +#else + TS_ASSERT_EQUALS(buffer.str(), "\033[1mHello, World!\033[0m"); +#endif +} + +void testNormalFormat() { + auto logger = std::make_shared(); + std::ostringstream buffer; + auto cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(buffer.rdbuf()); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << JSBSim::LogFormat::NORMAL; + log << "Hello, World!"; + log << JSBSim::LogFormat::RESET; + } + std::cout.rdbuf(cout_buffer); +#ifdef _MSC_VER + TS_ASSERT_EQUALS(buffer.str(), "Hello, World!"); +#else + TS_ASSERT_EQUALS(buffer.str(), "\033[22mHello, World!\033[0m"); +#endif +} + +void testUnderlineFormat() { + auto logger = std::make_shared(); + std::ostringstream buffer; + auto cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(buffer.rdbuf()); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << JSBSim::LogFormat::UNDERLINE_ON; + log << "Hello, World!"; + log << JSBSim::LogFormat::UNDERLINE_OFF; + } + std::cout.rdbuf(cout_buffer); +#ifdef _MSC_VER + TS_ASSERT_EQUALS(buffer.str(), "Hello, World!"); +#else + TS_ASSERT_EQUALS(buffer.str(), "\033[4mHello, World!\033[24m"); +#endif +} + +void testDefaultFormat() { + auto logger = std::make_shared(); + std::ostringstream buffer; + auto cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(buffer.rdbuf()); + { + JSBSim::FGLogging log(logger, JSBSim::LogLevel::INFO); + log << JSBSim::LogFormat::DEFAULT; + log << "Hello, World!"; + log << JSBSim::LogFormat::RESET; + } + std::cout.rdbuf(cout_buffer); +#ifdef _MSC_VER + TS_ASSERT_EQUALS(buffer.str(), "Hello, World!"); +#else + TS_ASSERT_EQUALS(buffer.str(), "\033[39mHello, World!\033[0m"); +#endif +} +};