Skip to content

Commit e5a3370

Browse files
Fix logger tests
1 parent 8aacd3f commit e5a3370

File tree

4 files changed

+168
-11
lines changed

4 files changed

+168
-11
lines changed

heidpi-logger/CMakeLists.txt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ FetchContent_MakeAvailable(json)
2323
FetchContent_Declare(
2424
json-schema-validator
2525
GIT_REPOSITORY https://github.com/pboettch/json-schema-validator.git
26-
GIT_TAG main
26+
GIT_TAG 2.3.0
2727
)
2828
FetchContent_MakeAvailable(json-schema-validator)
2929

@@ -39,9 +39,16 @@ FetchContent_Declare(
3939
)
4040
FetchContent_MakeAvailable(maxminddb)
4141

42+
FetchContent_Declare(
43+
googletest
44+
GIT_REPOSITORY https://github.com/google/googletest.git
45+
GIT_TAG v1.17.0
46+
)
47+
FetchContent_MakeAvailable(googletest)
48+
4249
file(GLOB SOURCES src/*.cpp)
4350
add_executable(heidpi_cpp ${SOURCES})
44-
target_include_directories(heidpi_cpp PRIVATE include)
51+
target_include_directories(heidpi_cpp PRIVATE ${PROJECT_SOURCE_DIR}/include)
4552
target_link_libraries(heidpi_cpp PRIVATE
4653
yaml-cpp
4754
nlohmann_json::nlohmann_json
@@ -54,3 +61,9 @@ include(GNUInstallDirs)
5461
install(TARGETS heidpi_cpp
5562
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
5663

64+
65+
option(BUILD_TESTING "Build the tests" ON)
66+
if(BUILD_TESTING)
67+
enable_testing()
68+
add_subdirectory(test)
69+
endif()

heidpi-logger/include/Logger.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class Logger
1313
static void init(const LoggingConfig &cfg);
1414
static void info(const std::string &msg);
1515
static void error(const std::string &msg);
16+
static void destroy();
1617
~Logger();
1718

1819
private:

heidpi-logger/src/Logger.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
/**
2+
* @file Logger.cpp
3+
* @brief Implements thread-safe logging functionality with console and file output.
4+
*
5+
* This file provides the implementation for the Logger class, which supports logging
6+
* messages with timestamps to both the console and an optional file. It ensures thread
7+
* safety using a mutex and flushes critical error logs immediately to disk.
8+
*/
19
#include "Logger.hpp"
210
#include <iostream>
311
#include <chrono>
@@ -8,15 +16,6 @@
816
std::mutex Logger::mtx;
917
std::ofstream Logger::file;
1018

11-
/**
12-
* @file Logger.cpp
13-
* @brief Implements thread-safe logging functionality with console and file output.
14-
*
15-
* This file provides the implementation for the Logger class, which supports logging
16-
* messages with timestamps to both the console and an optional file. It ensures thread
17-
* safety using a mutex and flushes critical error logs immediately to disk.
18-
*/
19-
2019
/**
2120
* @namespace Logger
2221
* @brief Namespace for logging-related functionality.
@@ -49,6 +48,7 @@ static std::string timestamp()
4948
*/
5049
void Logger::init(const LoggingConfig &cfg)
5150
{
51+
std::lock_guard<std::mutex> lock(mtx);
5252
if (!cfg.filename.empty())
5353
{
5454
file.open(cfg.filename, std::ios::app); // Append mode
@@ -99,13 +99,28 @@ void Logger::error(const std::string &msg)
9999
}
100100
}
101101

102+
/**
103+
* @brief Closes the log file when the Logger object is destroyed.
104+
*
105+
* Ensures the log file is properly closed to release system resources.
106+
*/
107+
void Logger::destroy() {
108+
std::lock_guard<std::mutex> lock(mtx);
109+
if (file.is_open()) {
110+
file.close();
111+
}
112+
file.clear(); // reset flags
113+
}
114+
115+
102116
/**
103117
* @brief Closes the log file when the Logger object is destroyed.
104118
*
105119
* Ensures the log file is properly closed to release system resources.
106120
*/
107121
Logger::~Logger()
108122
{
123+
std::lock_guard<std::mutex> lock(mtx);
109124
if (file.is_open())
110125
{
111126
file.close();

heidpi-logger/test/LoggerTest.cpp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#include "Logger.hpp"
2+
#include <gtest/gtest.h>
3+
#include <fstream>
4+
#include <sstream>
5+
#include <cstdio> // For remove()
6+
#include <thread>
7+
#include <regex>
8+
9+
// Helper to read file contents
10+
std::string readFile(const std::string& filename) {
11+
std::ifstream in(filename);
12+
std::stringstream buffer;
13+
buffer << in.rdbuf();
14+
return buffer.str();
15+
}
16+
17+
// Captures std::cout and std::cerr output
18+
class OutputCapture {
19+
public:
20+
void startCapture() {
21+
oldCout = std::cout.rdbuf(coutStream.rdbuf());
22+
oldCerr = std::cerr.rdbuf(cerrStream.rdbuf());
23+
}
24+
25+
void stopCapture() {
26+
std::cout.rdbuf(oldCout);
27+
std::cerr.rdbuf(oldCerr);
28+
}
29+
30+
std::string getCapturedStdout() const {
31+
return coutStream.str();
32+
}
33+
34+
std::string getCapturedStderr() const {
35+
return cerrStream.str();
36+
}
37+
38+
private:
39+
std::ostringstream coutStream;
40+
std::ostringstream cerrStream;
41+
std::streambuf* oldCout = nullptr;
42+
std::streambuf* oldCerr = nullptr;
43+
};
44+
45+
TEST(LoggerTest, LogsToFileAndConsole) {
46+
std::string testFile = "test_log.txt";
47+
std::remove(testFile.c_str()); // Ensure clean start
48+
49+
LoggingConfig cfg;
50+
cfg.filename = testFile;
51+
Logger::init(cfg);
52+
53+
OutputCapture capture;
54+
capture.startCapture();
55+
56+
Logger::info("This is an info message.");
57+
Logger::error("This is an error message.");
58+
59+
capture.stopCapture();
60+
61+
std::string fileContent = readFile(testFile);
62+
std::string stdoutContent = capture.getCapturedStdout();
63+
std::string stderrContent = capture.getCapturedStderr();
64+
65+
std::regex infoPattern(R"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2} INFO: This is an info message\.\n)");
66+
std::regex errorPattern(R"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2} ERROR: This is an error message\.\n)");
67+
68+
// Console output checks
69+
EXPECT_TRUE(std::regex_match(stdoutContent, infoPattern));
70+
EXPECT_TRUE(std::regex_match(stderrContent, errorPattern));
71+
72+
// File output checks
73+
EXPECT_NE(fileContent.find("INFO: This is an info message."), std::string::npos);
74+
EXPECT_NE(fileContent.find("ERROR: This is an error message."), std::string::npos);
75+
76+
std::remove(testFile.c_str()); // Cleanup
77+
Logger::destroy();
78+
}
79+
80+
TEST(LoggerTest, LogsWithoutFile) {
81+
LoggingConfig cfg;
82+
cfg.filename = ""; // No file
83+
Logger::init(cfg);
84+
85+
OutputCapture capture;
86+
capture.startCapture();
87+
88+
Logger::info("Console only info");
89+
Logger::error("Console only error");
90+
91+
capture.stopCapture();
92+
93+
EXPECT_NE(capture.getCapturedStdout().find("INFO: Console only info"), std::string::npos);
94+
EXPECT_NE(capture.getCapturedStderr().find("ERROR: Console only error"), std::string::npos);
95+
96+
Logger::destroy();
97+
}
98+
99+
TEST(LoggerTest, ThreadSafeLogging) {
100+
std::string testFile = "threaded_log.txt";
101+
std::remove(testFile.c_str());
102+
103+
LoggingConfig cfg;
104+
cfg.filename = testFile;
105+
Logger::init(cfg);
106+
107+
auto logTask = [](int threadId) {
108+
for (int i = 0; i < 10; ++i) {
109+
Logger::info("Thread " + std::to_string(threadId) + " message " + std::to_string(i));
110+
}
111+
};
112+
113+
std::thread t1(logTask, 1);
114+
std::thread t2(logTask, 2);
115+
116+
t1.join();
117+
t2.join();
118+
119+
std::string content = readFile(testFile);
120+
121+
for (int i = 0; i < 10; ++i) {
122+
EXPECT_NE(content.find("Thread 1 message " + std::to_string(i)), std::string::npos);
123+
EXPECT_NE(content.find("Thread 2 message " + std::to_string(i)), std::string::npos);
124+
}
125+
126+
std::remove(testFile.c_str());
127+
Logger::destroy();
128+
}

0 commit comments

Comments
 (0)