mirror of
https://github.com/KjellKod/g3log.git
synced 2024-12-12 10:23:50 +01:00
Mostly a refactor of tests (#513)
This commit is contained in:
parent
055f5e4d21
commit
626191a62d
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@ -12,10 +12,9 @@
|
||||
"name": "(gdb) Start",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/g3log-FATAL-contract",
|
||||
"program": "${workspaceFolder}/build/test_signal",
|
||||
"MIMode": "gdb",
|
||||
"cwd": "${workspaceFolder}/build"
|
||||
"setupCommands": [
|
||||
"cwd": "${workspaceFolder}/build""setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
@ -28,6 +27,5 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -1,4 +1,7 @@
|
||||
{
|
||||
"cmake.configureOnOpen": false,
|
||||
"editor.formatOnSave": true
|
||||
"editor.formatOnSave": true,
|
||||
"files.associations": {
|
||||
"ostream": "cpp"
|
||||
}
|
||||
}
|
@ -242,6 +242,30 @@ namespace g3 {
|
||||
exit(signal_number);
|
||||
}
|
||||
|
||||
// This function is intended to be async-signal-safe. Using write and STDERR_FILENO should be
|
||||
// safe in a signal handler. ref: http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
|
||||
|
||||
// This function is intended to be async-signal-safe.
|
||||
// It writes an error message to stderr using the write system call,
|
||||
// which is listed as async-signal-safe by POSIX.
|
||||
size_t writeErrorMessage(const char* message) {
|
||||
if (message == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate the length of the message without using std library strlen or similar
|
||||
// this is to ensure async-signal-safe by POSIX
|
||||
size_t length = 0;
|
||||
for (const char* p = message; *p != '\0'; ++p) {
|
||||
++length;
|
||||
}
|
||||
|
||||
// Write the message to STDERR_FILENO in a single call.
|
||||
// This assumes that the message is not too large for a single write.
|
||||
auto bytes_written = write(STDERR_FILENO, message, length);
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
// restores the signal handler back to default
|
||||
void restoreFatalHandlingToDefault() {
|
||||
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
|
||||
@ -274,7 +298,8 @@ namespace g3 {
|
||||
|
||||
if (sigaction(signal_number, &(old_action_it->second), nullptr) < 0) {
|
||||
auto signalname = std::string("sigaction - ") + signalToStr(signal_number);
|
||||
perror(signalname.c_str());
|
||||
// https://man7.org/linux/man-pages/man7/signal-safety.7.html (see signal-safety)
|
||||
internal::writeErrorMessage(signalname.c_str());
|
||||
}
|
||||
|
||||
gSavedSigActions.erase(old_action_it);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <windows.h>
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include "g3log/crashhandler.hpp"
|
||||
#include "g3log/g3log.hpp"
|
||||
@ -171,6 +172,13 @@ namespace g3 {
|
||||
raise(signal_number);
|
||||
}
|
||||
|
||||
// FYI: Concept of async-signal-safe operations does not exist on windows
|
||||
// we stick to perror for lack of better alternatives.
|
||||
size_t writeErrorMessage(const char* message) {
|
||||
perror(message);
|
||||
return std::strlen(message);
|
||||
}
|
||||
|
||||
// Restore back to default fatal event handling
|
||||
void restoreFatalHandlingToDefault() {
|
||||
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
|
||||
@ -181,19 +189,19 @@ namespace g3 {
|
||||
#endif
|
||||
|
||||
if (SIG_ERR == signal(SIGABRT, SIG_DFL))
|
||||
perror("signal - SIGABRT");
|
||||
internal::writeErrorMessage("signal - SIGABRT");
|
||||
|
||||
if (SIG_ERR == signal(SIGFPE, SIG_DFL))
|
||||
perror("signal - SIGABRT");
|
||||
internal::writeErrorMessage("signal - SIGABRT");
|
||||
|
||||
if (SIG_ERR == signal(SIGSEGV, SIG_DFL))
|
||||
perror("signal - SIGABRT");
|
||||
internal::writeErrorMessage("signal - SIGABRT");
|
||||
|
||||
if (SIG_ERR == signal(SIGILL, SIG_DFL))
|
||||
perror("signal - SIGABRT");
|
||||
internal::writeErrorMessage("signal - SIGABRT");
|
||||
|
||||
if (SIG_ERR == signal(SIGTERM, SIG_DFL))
|
||||
perror("signal - SIGABRT");
|
||||
internal::writeErrorMessage("signal - SIGABRT");
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -212,15 +220,15 @@ namespace g3 {
|
||||
if (!g_installed_thread_signal_handler) {
|
||||
g_installed_thread_signal_handler = true;
|
||||
if (SIG_ERR == signal(SIGTERM, signalHandler))
|
||||
perror("signal - SIGTERM");
|
||||
internal::writeErrorMessage("signal - SIGTERM");
|
||||
if (SIG_ERR == signal(SIGABRT, signalHandler))
|
||||
perror("signal - SIGABRT");
|
||||
internal::writeErrorMessage("signal - SIGABRT");
|
||||
if (SIG_ERR == signal(SIGFPE, signalHandler))
|
||||
perror("signal - SIGFPE");
|
||||
internal::writeErrorMessage("signal - SIGFPE");
|
||||
if (SIG_ERR == signal(SIGSEGV, signalHandler))
|
||||
perror("signal - SIGSEGV");
|
||||
internal::writeErrorMessage("signal - SIGSEGV");
|
||||
if (SIG_ERR == signal(SIGILL, signalHandler))
|
||||
perror("signal - SIGILL");
|
||||
internal::writeErrorMessage("signal - SIGILL");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -148,13 +148,13 @@ namespace g3 {
|
||||
message.get()->setExpression(boolean_expression);
|
||||
|
||||
if (internal::wasFatal(level)) {
|
||||
saveFatalMessage(level, stack_trace, message, fatal_signal);
|
||||
saveFatalMessage(stack_trace, message, fatal_signal);
|
||||
} else {
|
||||
pushMessageToLogger(message);
|
||||
}
|
||||
}
|
||||
|
||||
void saveFatalMessage(const LEVELS& level, const char* stack_trace, g3::LogMessagePtr& message, int& fatal_signal) {
|
||||
void saveFatalMessage(const char* stack_trace, g3::LogMessagePtr& message, int& fatal_signal) {
|
||||
auto fatalhook = g_fatal_pre_logging_hook;
|
||||
// In case the fatal_pre logging actually will cause a crash in its turn
|
||||
// let's not do recursive crashing!
|
||||
|
@ -77,5 +77,6 @@ namespace g3 {
|
||||
* This is an internal only function. Do not use it elsewhere. It is triggered
|
||||
* from g3log, g3LogWorker after flushing messages to file */
|
||||
void exitWithDefaultSignalHandler(const LEVELS& level, g3::SignalType signal_number);
|
||||
size_t writeErrorMessage(const char* message);
|
||||
} // namespace internal
|
||||
} // namespace g3
|
||||
|
@ -106,7 +106,7 @@ namespace g3 {
|
||||
void saveMessage(const char* message, const char* file, int line, const char* function, const LEVELS& level,
|
||||
const char* boolean_expression, int fatal_signal, const char* stack_trace);
|
||||
|
||||
void saveFatalMessage(const LEVELS& level, const char* stack_trace, g3::LogMessagePtr& message, int& fatal_signal);
|
||||
void saveFatalMessage(const char* stack_trace, g3::LogMessagePtr& message, int& fatal_signal);
|
||||
|
||||
// forwards the message to all sinks
|
||||
void pushMessageToLogger(LogMessagePtr log_entry);
|
||||
|
@ -62,7 +62,7 @@
|
||||
SET(OS_SPECIFIC_TEST test_crashhandler_windows)
|
||||
ENDIF(MSVC OR MINGW)
|
||||
|
||||
SET(tests_to_run test_message test_filechange test_io test_cpp_future_concepts test_concept_sink test_sink ${OS_SPECIFIC_TEST})
|
||||
SET(tests_to_run test_message test_filechange test_io test_fatal test_signal test_cpp_future_concepts test_concept_sink test_sink ${OS_SPECIFIC_TEST})
|
||||
SET(helper ${DIR_UNIT_TEST}/testing_helpers.h ${DIR_UNIT_TEST}/testing_helpers.cpp)
|
||||
include_directories(${DIR_UNIT_TEST})
|
||||
|
||||
|
349
test_unit/test_fatal.cpp
Normal file
349
test_unit/test_fatal.cpp
Normal file
@ -0,0 +1,349 @@
|
||||
/** ==========================================================================re
|
||||
* 2011 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes
|
||||
* with no warranties. This code is yours to share, use and modify with no
|
||||
* strings attached and no restrictions or obligations.
|
||||
*
|
||||
* For more information see g3log/LICENSE or refer refer to http://unlicense.org
|
||||
* ============================================================================*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "g3log/g3log.hpp"
|
||||
#include "g3log/generated_definitions.hpp"
|
||||
#include "g3log/loglevels.hpp"
|
||||
#include "g3log/logworker.hpp"
|
||||
#include "testing_helpers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
const std::string log_directory = "./";
|
||||
const std::string t_info = "test INFO ";
|
||||
const std::string t_info2 = "test INFO 123";
|
||||
const std::string t_debug = "test DEBUG ";
|
||||
const std::string t_debug3 = "test DEBUG 1.123456";
|
||||
const std::string t_warning = "test WARNING ";
|
||||
const std::string t_warning3 = "test WARNING yello";
|
||||
|
||||
std::atomic<size_t> g_fatal_counter = {0};
|
||||
void fatalCounter() {
|
||||
++g_fatal_counter;
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
using namespace testing_helpers;
|
||||
TEST(LogTest, LOGF__FATAL) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
LOGF(FATAL, "This message should throw %d", 0);
|
||||
EXPECT_TRUE(mockFatalWasCalled());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "This message should throw 0")) << "\n****" << mockFatalMessage();
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "FATAL"));
|
||||
|
||||
auto file_content = logger.resetAndRetrieveContent();
|
||||
EXPECT_TRUE(verifyContent(file_content, "This message should throw 0")) << "\n****" << file_content;
|
||||
EXPECT_TRUE(verifyContent(file_content, "FATAL"));
|
||||
}
|
||||
|
||||
#ifndef DISABLE_FATAL_SIGNALHANDLING
|
||||
|
||||
TEST(LogTest, FatalSIGTERM__UsingDefaultHandler) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
g_fatal_counter.store(0);
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
raise(SIGTERM);
|
||||
logger.reset();
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{1});
|
||||
}
|
||||
|
||||
#if !(defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
|
||||
namespace {
|
||||
std::atomic<size_t> customFatalCounter = {0};
|
||||
std::atomic<int> lastEncounteredSignal = {0};
|
||||
void customSignalHandler(int signal_number, siginfo_t* info, void* unused_context) {
|
||||
lastEncounteredSignal.store(signal_number);
|
||||
++customFatalCounter;
|
||||
}
|
||||
void installCustomSIGTERM() {
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_sigaction = &customSignalHandler;
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGTERM, &action, nullptr);
|
||||
}
|
||||
|
||||
std::atomic<bool> oldSigTermCheck = {false};
|
||||
void customOldSignalHandler(int signal_number, siginfo_t* info, void* unused_context) {
|
||||
lastEncounteredSignal.store(signal_number);
|
||||
oldSigTermCheck.store(true);
|
||||
}
|
||||
void installCustomOldSIGTERM() {
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_sigaction = &customOldSignalHandler;
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGTERM, &action, nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Override of signal handling and testing of it should be fairly easy to port to windows
|
||||
// ref: https://github.com/KjellKod/g3log/blob/master/src/crashhandler_windows.cpp
|
||||
// what is missing is the override of signals and custom installation of signals
|
||||
// ref: https://github.com/KjellKod/g3log/blob/master/src/crashhandler_unix.cpp
|
||||
// functions: void restoreFatalHandlingToDefault()
|
||||
// void overrideSetupSignals(const std::map<int, std::string> overrideSignals)
|
||||
// void restoreSignalHandler(int signal_number)
|
||||
//
|
||||
// It would require some adding of unit test (see the test below)
|
||||
// and good Windows experience. Since I am not currently working much on the Windows
|
||||
// side I am reaching out to the community for this one:
|
||||
//
|
||||
//
|
||||
// For the test to work the following code should be added in this test
|
||||
//void customSignalHandler(int signal_number) {
|
||||
// lastEncounteredSignal.store(signal_number);
|
||||
// ++customFatalCounter;
|
||||
//}
|
||||
//
|
||||
//void installCustomSIGTERM() {
|
||||
// ASSERT_TRUE(SIG_ERR != signal(SIGTERM, customSignalHandler));
|
||||
//}
|
||||
|
||||
TEST(LogTest, FatalSIGTERM__UsingCustomHandler) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
g_fatal_counter.store(0);
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
installCustomSIGTERM();
|
||||
g3::overrideSetupSignals({{SIGABRT, "SIGABRT"}, {SIGFPE, "SIGFPE"}, {SIGILL, "SIGILL"}});
|
||||
|
||||
installCustomSIGTERM();
|
||||
EXPECT_EQ(customFatalCounter.load(), size_t{0});
|
||||
EXPECT_EQ(lastEncounteredSignal.load(), 0);
|
||||
|
||||
raise(SIGTERM);
|
||||
logger.reset();
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{0});
|
||||
EXPECT_EQ(lastEncounteredSignal.load(), SIGTERM);
|
||||
EXPECT_EQ(customFatalCounter.load(), size_t{1});
|
||||
}
|
||||
|
||||
TEST(LogTest, FatalSIGTERM__VerifyingOldCustomHandler) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
g_fatal_counter.store(0);
|
||||
customFatalCounter.store(0);
|
||||
lastEncounteredSignal.store(0);
|
||||
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
installCustomOldSIGTERM();
|
||||
g3::overrideSetupSignals({{SIGABRT, "SIGABRT"}, {SIGFPE, "SIGFPE"}, {SIGILL, "SIGILL"}, {SIGTERM, "SIGTERM"}});
|
||||
g3::restoreSignalHandler(SIGTERM); // revert SIGTERM installation
|
||||
|
||||
EXPECT_EQ(customFatalCounter.load(), size_t{0});
|
||||
EXPECT_EQ(lastEncounteredSignal.load(), 0);
|
||||
EXPECT_FALSE(oldSigTermCheck.load());
|
||||
raise(SIGTERM);
|
||||
logger.reset();
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{0});
|
||||
EXPECT_EQ(lastEncounteredSignal.load(), SIGTERM);
|
||||
EXPECT_TRUE(oldSigTermCheck.load());
|
||||
}
|
||||
|
||||
#endif // DISABLE_FATAL_SIGNALHANDLING
|
||||
#endif // !(defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
|
||||
|
||||
TEST(LogTest, LOG_preFatalLogging_hook) {
|
||||
{
|
||||
RestoreFileLogger logger(log_directory);
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
g_fatal_counter.store(0);
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
LOG(FATAL) << "This message is fatal";
|
||||
logger.reset();
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{1});
|
||||
}
|
||||
{
|
||||
// Now with no fatal pre-logging-hook
|
||||
RestoreFileLogger logger(log_directory);
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
g_fatal_counter.store(0);
|
||||
LOG(FATAL) << "This message is fatal";
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{0});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LogTest, LOG_FATAL) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
|
||||
LOG(FATAL) << "This message is fatal";
|
||||
EXPECT_TRUE(mockFatalWasCalled());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "This message is fatal"))
|
||||
<< "\ncontent: [[" << mockFatalMessage() << "]]";
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "FATAL"));
|
||||
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(file_content, "This message is fatal"));
|
||||
EXPECT_TRUE(verifyContent(file_content, "FATAL"));
|
||||
EXPECT_TRUE(verifyContent(file_content, "EXIT trigger caused by "));
|
||||
}
|
||||
TEST(LogTest, LOGF_IF__FATAL) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
EXPECT_FALSE(mockFatalWasCalled());
|
||||
LOGF_IF(FATAL, (2 < 3), "This message %s be worse", "could");
|
||||
EXPECT_TRUE(mockFatalWasCalled());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by ")) << "\n"
|
||||
<< mockFatalMessage();
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "FATAL"));
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "This message could be worse"));
|
||||
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(file_content, "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(file_content, "FATAL"));
|
||||
EXPECT_TRUE(verifyContent(file_content, "This message could be worse"));
|
||||
}
|
||||
TEST(LogTest, LOG_IF__FATAL) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
LOG_IF(WARNING, (0 != t_info.compare(t_info))) << "This message should NOT be written";
|
||||
EXPECT_FALSE(mockFatalWasCalled());
|
||||
LOG_IF(FATAL, (0 != t_info.compare(t_info2))) << "This message should throw. xyz ";
|
||||
EXPECT_TRUE(mockFatalWasCalled());
|
||||
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "FATAL"));
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "This message should throw. xyz "));
|
||||
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(file_content, "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(file_content, "FATAL"));
|
||||
EXPECT_TRUE(verifyContent(file_content, "This message should throw. xyz "));
|
||||
}
|
||||
TEST(LogTest, LOG_IF__FATAL__NO_THROW) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
LOG_IF(FATAL, (2 > 3)) << "This message%sshould NOT throw";
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
}
|
||||
|
||||
// CHECK_F
|
||||
TEST(CheckTest, CHECK_F__thisWILL_PrintErrorMsg) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
EXPECT_TRUE(mockFatalMessage().empty());
|
||||
EXPECT_FALSE(mockFatalWasCalled());
|
||||
|
||||
CHECK(1 == 2);
|
||||
EXPECT_FALSE(mockFatalMessage().empty());
|
||||
EXPECT_TRUE(mockFatalWasCalled());
|
||||
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(file_content, "CONTRACT")) << "**** " << mockFatalMessage();
|
||||
}
|
||||
|
||||
TEST(CHECK_F_Test, CHECK_F__thisWILL_PrintErrorMsg) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
std::string msg = "This message is added to throw %s and %s";
|
||||
std::string arg1 = "message";
|
||||
std::string arg3 = "log";
|
||||
|
||||
CHECK_F(1 >= 2, msg.c_str(), arg1.c_str(), arg3.c_str());
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(file_content, "CONTRACT"));
|
||||
}
|
||||
|
||||
TEST(CHECK_Test, CHECK__thisWILL_PrintErrorMsg) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
std::string msg = "This message is added to throw message and log";
|
||||
CHECK(1 >= 2) << msg;
|
||||
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(file_content, "CONTRACT"));
|
||||
EXPECT_TRUE(verifyContent(file_content, msg));
|
||||
}
|
||||
TEST(CHECK, CHECK_ThatWontThrow) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
std::string msg = "This %s should never appear in the %s";
|
||||
std::string msg3 = "This message should never appear in the log";
|
||||
CHECK(1 == 1);
|
||||
CHECK_F(1 == 1, msg.c_str(), "message", "log");
|
||||
logger.reset();
|
||||
EXPECT_FALSE(mockFatalWasCalled());
|
||||
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_FALSE(verifyContent(file_content, msg3));
|
||||
EXPECT_FALSE(verifyContent(mockFatalMessage(), msg3));
|
||||
}
|
||||
|
||||
TEST(CHECK, CHECK_runtimeError) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
|
||||
g3::setFatalExitHandler([](g3::FatalMessagePtr msg) {
|
||||
throw std::runtime_error("fatal test handler");
|
||||
});
|
||||
|
||||
class dynamic_int_array {
|
||||
std::unique_ptr<int[]> data_;
|
||||
const int size_;
|
||||
|
||||
public:
|
||||
explicit dynamic_int_array(int size) :
|
||||
data_{std::make_unique<int[]>(size)},
|
||||
size_(size) {}
|
||||
|
||||
int& at(int i) {
|
||||
CHECK(i < size_);
|
||||
|
||||
// unreachable if i >= size_
|
||||
return data_[i];
|
||||
}
|
||||
};
|
||||
|
||||
dynamic_int_array arr{3};
|
||||
|
||||
EXPECT_THROW(arr.at(3) = 1, std::runtime_error);
|
||||
}
|
||||
|
||||
// see also test_io -- AddNonFatal
|
||||
TEST(CustomLogLevels, AddFatal) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
const LEVELS DEADLY{FATAL.value + 1, {"DEADLY"}};
|
||||
EXPECT_TRUE(g3::internal::wasFatal(DEADLY));
|
||||
g_fatal_counter.store(0);
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
#ifdef G3_DYNAMIC_LOGGING
|
||||
g3::only_change_at_initialization::addLogLevel(DEADLY, true);
|
||||
#endif
|
||||
// clang-format off
|
||||
LOG(DEADLY) << "Testing my own custom level"; auto line = __LINE__;
|
||||
// clang-format on
|
||||
logger.reset();
|
||||
ASSERT_TRUE(mockFatalWasCalled());
|
||||
EXPECT_EQ(size_t{1}, g_fatal_counter.load());
|
||||
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
std::string expected;
|
||||
expected += "DEADLY [test_fatal.cpp->" + std::string(G3LOG_PRETTY_FUNCTION) + ":" + std::to_string(line);
|
||||
EXPECT_TRUE(verifyContent(file_content, expected)) << file_content
|
||||
<< "\n\nExpected: \n"
|
||||
<< expected;
|
||||
g_fatal_counter.store(0); // restore
|
||||
}
|
@ -327,288 +327,6 @@ TEST(LogTest, LOG_IF) {
|
||||
EXPECT_TRUE(verifyContent(file_content, t_info2));
|
||||
EXPECT_FALSE(verifyContent(file_content, t_debug3));
|
||||
}
|
||||
TEST(LogTest, LOGF__FATAL) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
LOGF(FATAL, "This message should throw %d", 0);
|
||||
EXPECT_TRUE(mockFatalWasCalled());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "This message should throw 0")) << "\n****" << mockFatalMessage();
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "FATAL"));
|
||||
|
||||
auto file_content = logger.resetAndRetrieveContent();
|
||||
EXPECT_TRUE(verifyContent(file_content, "This message should throw 0")) << "\n****" << file_content;
|
||||
EXPECT_TRUE(verifyContent(file_content, "FATAL"));
|
||||
}
|
||||
|
||||
#ifndef DISABLE_FATAL_SIGNALHANDLING
|
||||
|
||||
TEST(LogTest, FatalSIGTERM__UsingDefaultHandler) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
g_fatal_counter.store(0);
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
raise(SIGTERM);
|
||||
logger.reset();
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{1});
|
||||
}
|
||||
|
||||
#if !(defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
|
||||
namespace {
|
||||
std::atomic<size_t> customFatalCounter = {0};
|
||||
std::atomic<int> lastEncounteredSignal = {0};
|
||||
void customSignalHandler(int signal_number, siginfo_t* info, void* unused_context) {
|
||||
lastEncounteredSignal.store(signal_number);
|
||||
++customFatalCounter;
|
||||
}
|
||||
void installCustomSIGTERM() {
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_sigaction = &customSignalHandler;
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGTERM, &action, nullptr);
|
||||
}
|
||||
|
||||
std::atomic<bool> oldSigTermCheck = {false};
|
||||
void customOldSignalHandler(int signal_number, siginfo_t* info, void* unused_context) {
|
||||
lastEncounteredSignal.store(signal_number);
|
||||
oldSigTermCheck.store(true);
|
||||
}
|
||||
void installCustomOldSIGTERM() {
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_sigaction = &customOldSignalHandler;
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGTERM, &action, nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Override of signal handling and testing of it should be fairly easy to port to windows
|
||||
// ref: https://github.com/KjellKod/g3log/blob/master/src/crashhandler_windows.cpp
|
||||
// what is missing is the override of signals and custom installation of signals
|
||||
// ref: https://github.com/KjellKod/g3log/blob/master/src/crashhandler_unix.cpp
|
||||
// functions: void restoreFatalHandlingToDefault()
|
||||
// void overrideSetupSignals(const std::map<int, std::string> overrideSignals)
|
||||
// void restoreSignalHandler(int signal_number)
|
||||
//
|
||||
// It would require some adding of unit test (see the test below)
|
||||
// and good Windows experience. Since I am not currently working much on the Windows
|
||||
// side I am reaching out to the community for this one:
|
||||
//
|
||||
//
|
||||
// For the test to work the following code should be added in this test
|
||||
//void customSignalHandler(int signal_number) {
|
||||
// lastEncounteredSignal.store(signal_number);
|
||||
// ++customFatalCounter;
|
||||
//}
|
||||
//
|
||||
//void installCustomSIGTERM() {
|
||||
// ASSERT_TRUE(SIG_ERR != signal(SIGTERM, customSignalHandler));
|
||||
//}
|
||||
|
||||
TEST(LogTest, FatalSIGTERM__UsingCustomHandler) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
g_fatal_counter.store(0);
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
installCustomSIGTERM();
|
||||
g3::overrideSetupSignals({{SIGABRT, "SIGABRT"}, {SIGFPE, "SIGFPE"}, {SIGILL, "SIGILL"}});
|
||||
|
||||
installCustomSIGTERM();
|
||||
EXPECT_EQ(customFatalCounter.load(), size_t{0});
|
||||
EXPECT_EQ(lastEncounteredSignal.load(), 0);
|
||||
|
||||
raise(SIGTERM);
|
||||
logger.reset();
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{0});
|
||||
EXPECT_EQ(lastEncounteredSignal.load(), SIGTERM);
|
||||
EXPECT_EQ(customFatalCounter.load(), size_t{1});
|
||||
}
|
||||
|
||||
TEST(LogTest, FatalSIGTERM__VerifyingOldCustomHandler) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
g_fatal_counter.store(0);
|
||||
customFatalCounter.store(0);
|
||||
lastEncounteredSignal.store(0);
|
||||
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
installCustomOldSIGTERM();
|
||||
g3::overrideSetupSignals({{SIGABRT, "SIGABRT"}, {SIGFPE, "SIGFPE"}, {SIGILL, "SIGILL"}, {SIGTERM, "SIGTERM"}});
|
||||
g3::restoreSignalHandler(SIGTERM); // revert SIGTERM installation
|
||||
|
||||
EXPECT_EQ(customFatalCounter.load(), size_t{0});
|
||||
EXPECT_EQ(lastEncounteredSignal.load(), 0);
|
||||
EXPECT_FALSE(oldSigTermCheck.load());
|
||||
raise(SIGTERM);
|
||||
logger.reset();
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{0});
|
||||
EXPECT_EQ(lastEncounteredSignal.load(), SIGTERM);
|
||||
EXPECT_TRUE(oldSigTermCheck.load());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
TEST(LogTest, LOG_preFatalLogging_hook) {
|
||||
{
|
||||
RestoreFileLogger logger(log_directory);
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
g_fatal_counter.store(0);
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
LOG(FATAL) << "This message is fatal";
|
||||
logger.reset();
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{1});
|
||||
}
|
||||
{
|
||||
// Now with no fatal pre-logging-hook
|
||||
RestoreFileLogger logger(log_directory);
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
g_fatal_counter.store(0);
|
||||
LOG(FATAL) << "This message is fatal";
|
||||
EXPECT_EQ(g_fatal_counter.load(), size_t{0});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LogTest, LOG_FATAL) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
|
||||
LOG(FATAL) << "This message is fatal";
|
||||
EXPECT_TRUE(mockFatalWasCalled());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "This message is fatal"))
|
||||
<< "\ncontent: [[" << mockFatalMessage() << "]]";
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "FATAL"));
|
||||
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(file_content, "This message is fatal"));
|
||||
EXPECT_TRUE(verifyContent(file_content, "FATAL"));
|
||||
EXPECT_TRUE(verifyContent(file_content, "EXIT trigger caused by "));
|
||||
}
|
||||
TEST(LogTest, LOGF_IF__FATAL) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
EXPECT_FALSE(mockFatalWasCalled());
|
||||
LOGF_IF(FATAL, (2 < 3), "This message %s be worse", "could");
|
||||
EXPECT_TRUE(mockFatalWasCalled());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by ")) << "\n"
|
||||
<< mockFatalMessage();
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "FATAL"));
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "This message could be worse"));
|
||||
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(file_content, "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(file_content, "FATAL"));
|
||||
EXPECT_TRUE(verifyContent(file_content, "This message could be worse"));
|
||||
}
|
||||
TEST(LogTest, LOG_IF__FATAL) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
LOG_IF(WARNING, (0 != t_info.compare(t_info))) << "This message should NOT be written";
|
||||
EXPECT_FALSE(mockFatalWasCalled());
|
||||
LOG_IF(FATAL, (0 != t_info.compare(t_info2))) << "This message should throw. xyz ";
|
||||
EXPECT_TRUE(mockFatalWasCalled());
|
||||
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "FATAL"));
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "This message should throw. xyz "));
|
||||
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(file_content, "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(file_content, "FATAL"));
|
||||
EXPECT_TRUE(verifyContent(file_content, "This message should throw. xyz "));
|
||||
}
|
||||
TEST(LogTest, LOG_IF__FATAL__NO_THROW) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
LOG_IF(FATAL, (2 > 3)) << "This message%sshould NOT throw";
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
}
|
||||
|
||||
// CHECK_F
|
||||
TEST(CheckTest, CHECK_F__thisWILL_PrintErrorMsg) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
EXPECT_TRUE(mockFatalMessage().empty());
|
||||
EXPECT_FALSE(mockFatalWasCalled());
|
||||
|
||||
CHECK(1 == 2);
|
||||
EXPECT_FALSE(mockFatalMessage().empty());
|
||||
EXPECT_TRUE(mockFatalWasCalled());
|
||||
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(file_content, "CONTRACT")) << "**** " << mockFatalMessage();
|
||||
}
|
||||
|
||||
TEST(CHECK_F_Test, CHECK_F__thisWILL_PrintErrorMsg) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
std::string msg = "This message is added to throw %s and %s";
|
||||
std::string arg1 = "message";
|
||||
std::string arg3 = "log";
|
||||
|
||||
CHECK_F(1 >= 2, msg.c_str(), arg1.c_str(), arg3.c_str());
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(file_content, "CONTRACT"));
|
||||
}
|
||||
|
||||
TEST(CHECK_Test, CHECK__thisWILL_PrintErrorMsg) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
std::string msg = "This message is added to throw message and log";
|
||||
CHECK(1 >= 2) << msg;
|
||||
|
||||
logger.reset();
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_TRUE(verifyContent(mockFatalMessage(), "EXIT trigger caused by "));
|
||||
EXPECT_TRUE(verifyContent(file_content, "CONTRACT"));
|
||||
EXPECT_TRUE(verifyContent(file_content, msg));
|
||||
}
|
||||
TEST(CHECK, CHECK_ThatWontThrow) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
std::string msg = "This %s should never appear in the %s";
|
||||
std::string msg3 = "This message should never appear in the log";
|
||||
CHECK(1 == 1);
|
||||
CHECK_F(1 == 1, msg.c_str(), "message", "log");
|
||||
logger.reset();
|
||||
EXPECT_FALSE(mockFatalWasCalled());
|
||||
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
EXPECT_FALSE(verifyContent(file_content, msg3));
|
||||
EXPECT_FALSE(verifyContent(mockFatalMessage(), msg3));
|
||||
}
|
||||
|
||||
TEST(CHECK, CHECK_runtimeError) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
|
||||
g3::setFatalExitHandler([](g3::FatalMessagePtr msg) {
|
||||
throw std::runtime_error("fatal test handler");
|
||||
});
|
||||
|
||||
class dynamic_int_array {
|
||||
std::unique_ptr<int[]> data_;
|
||||
const int size_;
|
||||
|
||||
public:
|
||||
explicit dynamic_int_array(int size) :
|
||||
data_{std::make_unique<int[]>(size)},
|
||||
size_(size) {}
|
||||
|
||||
int& at(int i) {
|
||||
CHECK(i < size_);
|
||||
|
||||
// unreachable if i >= size_
|
||||
return data_[i];
|
||||
}
|
||||
};
|
||||
|
||||
dynamic_int_array arr{3};
|
||||
|
||||
EXPECT_THROW(arr.at(3) = 1, std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(CustomLogLevels, AddANonFatal) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
@ -628,32 +346,6 @@ TEST(CustomLogLevels, AddANonFatal) {
|
||||
<< expected;
|
||||
}
|
||||
|
||||
TEST(CustomLogLevels, AddFatal) {
|
||||
RestoreFileLogger logger(log_directory);
|
||||
const LEVELS DEADLY{FATAL.value + 1, {"DEADLY"}};
|
||||
EXPECT_TRUE(g3::internal::wasFatal(DEADLY));
|
||||
g_fatal_counter.store(0);
|
||||
ASSERT_FALSE(mockFatalWasCalled());
|
||||
g3::setFatalPreLoggingHook(fatalCounter);
|
||||
#ifdef G3_DYNAMIC_LOGGING
|
||||
g3::only_change_at_initialization::addLogLevel(DEADLY, true);
|
||||
#endif
|
||||
// clang-format off
|
||||
LOG(DEADLY) << "Testing my own custom level"; auto line = __LINE__;
|
||||
// clang-format on
|
||||
logger.reset();
|
||||
ASSERT_TRUE(mockFatalWasCalled());
|
||||
EXPECT_EQ(size_t{1}, g_fatal_counter.load());
|
||||
|
||||
std::string file_content = readFileToText(logger.logFile());
|
||||
std::string expected;
|
||||
expected += "DEADLY [test_io.cpp->" + std::string(G3LOG_PRETTY_FUNCTION) + ":" + std::to_string(line);
|
||||
EXPECT_TRUE(verifyContent(file_content, expected)) << file_content
|
||||
<< "\n\nExpected: \n"
|
||||
<< expected;
|
||||
g_fatal_counter.store(0); // restore
|
||||
}
|
||||
|
||||
#ifdef G3_DYNAMIC_LOGGING
|
||||
namespace {
|
||||
// Restore dynamic levels if turned off
|
||||
|
62
test_unit/test_signal.cpp
Normal file
62
test_unit/test_signal.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "g3log/crashhandler.hpp"
|
||||
|
||||
#if !(defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
class SignalHandlingTest : public ::testing::Test {
|
||||
protected:
|
||||
int original_stderr;
|
||||
int pipefd[2];
|
||||
FILE* temp_stderr;
|
||||
|
||||
void SetUp() override {
|
||||
// Redirect stderr to a pipe
|
||||
ASSERT_EQ(pipe(pipefd), 0);
|
||||
original_stderr = dup(STDERR_FILENO);
|
||||
ASSERT_NE(original_stderr, -1);
|
||||
ASSERT_NE(dup2(pipefd[1], STDERR_FILENO), -1);
|
||||
temp_stderr = fdopen(pipefd[1], "w");
|
||||
setvbuf(temp_stderr, NULL, _IONBF, 0); // Disable buffering
|
||||
|
||||
// Set the read end of the pipe to non-blocking mode
|
||||
// so we can verify when buffer is empty
|
||||
int flags = fcntl(pipefd[0], F_GETFL, 0);
|
||||
fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// Restore the original stderr
|
||||
fclose(temp_stderr);
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
dup2(original_stderr, STDERR_FILENO);
|
||||
close(original_stderr);
|
||||
}
|
||||
|
||||
std::string ReadStderr() {
|
||||
char buffer[1024];
|
||||
ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1);
|
||||
if (bytes_read >= 0) {
|
||||
buffer[bytes_read] = '\0'; // Null-terminate the string
|
||||
return std::string(buffer);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SignalHandlingTest, WriteErrorMessage_WritesToStderr) {
|
||||
const char* test_message = "Test error message";
|
||||
g3::internal::writeErrorMessage(test_message);
|
||||
std::string output = ReadStderr();
|
||||
ASSERT_EQ(output, test_message);
|
||||
}
|
||||
|
||||
TEST_F(SignalHandlingTest, WriteErrorMessage_Nullptr_DoesNotWriteToStderr) {
|
||||
g3::internal::writeErrorMessage(nullptr);
|
||||
std::string output = ReadStderr();
|
||||
ASSERT_TRUE(output.empty());
|
||||
}
|
||||
|
||||
#endif // #if !(defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
|
Loading…
Reference in New Issue
Block a user