From 581dd6c0452bb2f9d62bf3af61d6aa54f94584d8 Mon Sep 17 00:00:00 2001 From: magiblot Date: Sun, 15 Oct 2023 23:56:57 +0200 Subject: [PATCH] platform: add SignalHandler tests --- include/tvision/internal/sighandl.h | 4 +- source/platform/sighandl.cpp | 5 +- test/platform/sighandl.test.cpp | 251 ++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 test/platform/sighandl.test.cpp diff --git a/include/tvision/internal/sighandl.h b/include/tvision/internal/sighandl.h index 2542cfaf..320eb189 100644 --- a/include/tvision/internal/sighandl.h +++ b/include/tvision/internal/sighandl.h @@ -54,7 +54,7 @@ class SignalHandler static void handleSignal(int, siginfo_t *, void *); static HandlerInfo &getHandlerInfo(int) noexcept; - static bool invokeHandlerOrDefault(int, struct sigaction &, siginfo_t *, void *) noexcept; + static bool invokeHandlerOrDefault(int, const struct sigaction &, siginfo_t *, void *) noexcept; static bool invokeDefault(int, siginfo_t *) noexcept; public: @@ -70,7 +70,7 @@ class SignalHandler { public: - static void enable(SignalHandlerCallback &aCallback) noexcept {} + static void enable(SignalHandlerCallback &) noexcept {} static void disable() noexcept {} }; diff --git a/source/platform/sighandl.cpp b/source/platform/sighandl.cpp index 68bd77f4..bf8b839b 100644 --- a/source/platform/sighandl.cpp +++ b/source/platform/sighandl.cpp @@ -1,8 +1,9 @@ #include -#include #ifdef _TV_UNIX +#include + namespace tvision { @@ -97,7 +98,7 @@ void SignalHandler::handleSignal(int signo, siginfo_t *info, void *context) } } -bool SignalHandler::invokeHandlerOrDefault( int signo, struct sigaction &action, +bool SignalHandler::invokeHandlerOrDefault( int signo, const struct sigaction &action, siginfo_t *info, void *context ) noexcept { // If the handler is a custom one, invoke it directly. diff --git a/test/platform/sighandl.test.cpp b/test/platform/sighandl.test.cpp new file mode 100644 index 00000000..ed7850e6 --- /dev/null +++ b/test/platform/sighandl.test.cpp @@ -0,0 +1,251 @@ +#include + +#ifdef __linux__ + +#include + +#include +#include + +namespace tvision +{ + +struct SignalHandlerTest : public ::testing::Test +{ + struct Counters + { + size_t handlerInvocations; + size_t callbackEnterInvocations; + size_t callbackExitInvocations; + size_t wrapperInvocations; + }; + + // Counters stored in shared memory. + static Counters *counters; + + SignalHandlerTest() noexcept + { + counters = (Counters *) mmap(nullptr, sizeof(Counters), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + EXPECT_NE(counters, nullptr) + << "Test bug: 'mmap' failed."; + *counters = {}; + } + + ~SignalHandlerTest() + { + int ret = munmap(counters, sizeof(Counters)); + EXPECT_NE(ret, -1) + << "Test bug: 'munmap' failed."; + + SignalHandler::disable(); + for (int signo : SignalHandler::handledSignals) + signal(signo, SIG_DFL); + } +}; + +SignalHandlerTest::Counters *SignalHandlerTest::counters; + +static bool signalKillsSubprocess(int signo) +{ + auto pid = fork(); + if (pid == 0) + { + raise(signo); + _Exit(0); + } + else if (pid > 0) + { + int status; + while (waitpid(pid, &status, 0) != pid); + if (WIFSIGNALED(status)) + return WTERMSIG(status) == signo; + return false; + } + return false; +} + +static void signalHandlerThatDoesNotKillTheProcess(int) +{ + ++SignalHandlerTest::counters->handlerInvocations; +} + +static void extendedSignalHandlerThatDoesNotKillTheProcess(int signo, siginfo_t *info, void *context) +{ + ++SignalHandlerTest::counters->handlerInvocations; +} + +static void signalCallbackThatTemporarilyDisablesSignalHandler(bool enter) noexcept +{ + // Disable and re-enable SignalHandler because this is also what is done in + // practice by Platform::signalCallback. + if (enter) + { + ++SignalHandlerTest::counters->callbackEnterInvocations; + SignalHandler::disable(); + } + else + { + ++SignalHandlerTest::counters->callbackExitInvocations; + SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler); + } +} + +static void signalCallbackThatAborts(bool enter) noexcept +{ + if (enter) + { + ++SignalHandlerTest::counters->callbackEnterInvocations; + abort(); + } + else + ++SignalHandlerTest::counters->callbackExitInvocations; +} + +static void signalCallbackThatSegfaults(bool enter) noexcept +{ + if (enter) + { + ++SignalHandlerTest::counters->callbackEnterInvocations; + *(volatile int *) nullptr = 0; + } + else + ++SignalHandlerTest::counters->callbackExitInvocations; +} + +static void installSignalHandler(int signo, void (*handler)(int)) noexcept +{ + auto ret = signal(signo, handler); + EXPECT_NE(ret, SIG_ERR) + << "Test bug: failed to install signal handler."; +} + +static void installExtendedSignalHandler(int signo, void (*handler)(int, siginfo_t *, void *)) noexcept +{ + struct sigaction sa {}; + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = handler; + int ret = sigaction(signo, &sa, nullptr); + EXPECT_NE(ret, -1) + << "Test bug: failed to install extended signal handler."; +} + +static struct sigaction wrappedSa; + +static void signalWrapper(int signo, siginfo_t *info, void *context) +{ + ++SignalHandlerTest::counters->wrapperInvocations; + if (wrappedSa.sa_flags & SA_SIGINFO) + wrappedSa.sa_sigaction(signo, info, context); + else + wrappedSa.sa_handler(signo); +} + +static void installSignalWrapper(int signo) +{ + struct sigaction sa {}; + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = &signalWrapper; + int ret = sigaction(signo, &sa, &wrappedSa); + EXPECT_NE(ret, -1) + << "Test bug: failed to install signal wrapper."; +} + +TEST_F(SignalHandlerTest, ShouldOnlyInvokeCallbackOnEnterWhenSignalKillsTheProcess) +{ + SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler); + ASSERT_TRUE(signalKillsSubprocess(SIGINT)); + ASSERT_EQ(counters->callbackEnterInvocations, 1); + ASSERT_EQ(counters->callbackExitInvocations, 0); +} + +TEST_F(SignalHandlerTest, ShouldFallBackToDefaultHandlerWhenCallbackAborts) +{ + installSignalHandler(SIGABRT, signalHandlerThatDoesNotKillTheProcess); + SignalHandler::enable(signalCallbackThatAborts); + ASSERT_TRUE(signalKillsSubprocess(SIGABRT)); + ASSERT_EQ(counters->handlerInvocations, 0); + ASSERT_EQ(counters->callbackEnterInvocations, 1); + ASSERT_EQ(counters->callbackExitInvocations, 0); +} + +TEST_F(SignalHandlerTest, ShouldFallBackToDefaultHandlerWhenCallbackSegfaults) +{ + installSignalHandler(SIGSEGV, signalHandlerThatDoesNotKillTheProcess); + SignalHandler::enable(signalCallbackThatSegfaults); + ASSERT_TRUE(signalKillsSubprocess(SIGSEGV)); + ASSERT_EQ(counters->handlerInvocations, 0); + ASSERT_EQ(counters->callbackEnterInvocations, 1); + ASSERT_EQ(counters->callbackExitInvocations, 0); +} + +TEST_F(SignalHandlerTest, ShouldInvokeAPreviouslyInstalledHandler) +{ + installSignalHandler(SIGINT, signalHandlerThatDoesNotKillTheProcess); + SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler); + for (int i = 1; i <= 2; ++i) + { + ASSERT_NE(raise(SIGINT), -1); + ASSERT_EQ(counters->handlerInvocations, i); + ASSERT_EQ(counters->callbackEnterInvocations, i); + ASSERT_EQ(counters->callbackExitInvocations, i); + } +} + +TEST_F(SignalHandlerTest, ShouldInvokeAPreviouslyInstalledExtendedHandler) +{ + installExtendedSignalHandler(SIGINT, extendedSignalHandlerThatDoesNotKillTheProcess); + SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler); + for (int i = 1; i <= 2; ++i) + { + ASSERT_NE(raise(SIGINT), -1); + ASSERT_EQ(counters->handlerInvocations, i); + ASSERT_EQ(counters->callbackEnterInvocations, i); + ASSERT_EQ(counters->callbackExitInvocations, i); + } +} + +TEST_F(SignalHandlerTest, ShouldPreserveACustomHandlerInstalledBetween_enable_And_disable_) +{ + SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler); + installSignalHandler(SIGINT, signalHandlerThatDoesNotKillTheProcess); + SignalHandler::disable(); + for (int i = 1; i <= 2; ++i) + { + ASSERT_NE(raise(SIGINT), -1); + ASSERT_EQ(counters->handlerInvocations, i); + ASSERT_EQ(counters->callbackEnterInvocations, 0); + ASSERT_EQ(counters->callbackExitInvocations, 0); + } +} + +TEST_F(SignalHandlerTest, ShouldPreserveAPreviouslyInstalledHandlerInstalledAfter_enable_) +{ + installSignalHandler(SIGINT, signalHandlerThatDoesNotKillTheProcess); + SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler); + installSignalWrapper(SIGINT); + for (int i = 1; i <= 2; ++i) + { + ASSERT_NE(raise(SIGINT), -1); + ASSERT_EQ(counters->handlerInvocations, i); + ASSERT_EQ(counters->callbackEnterInvocations, i); + ASSERT_EQ(counters->callbackExitInvocations, i); + ASSERT_EQ(counters->wrapperInvocations, i); + } +} + +TEST_F(SignalHandlerTest, ShouldNotInvokeNeitherAPreviouslyInstalledHandlerNorTheCallbackIfAWrapperIsInstalledBetween_enable_And_disable_) +{ + installSignalHandler(SIGINT, signalHandlerThatDoesNotKillTheProcess); + SignalHandler::enable(signalCallbackThatTemporarilyDisablesSignalHandler); + installSignalWrapper(SIGINT); + SignalHandler::disable(); + ASSERT_TRUE(signalKillsSubprocess(SIGINT)); + ASSERT_EQ(counters->handlerInvocations, 0); + ASSERT_EQ(counters->callbackEnterInvocations, 0); + ASSERT_EQ(counters->callbackExitInvocations, 0); + ASSERT_EQ(counters->wrapperInvocations, 1); +} + +} // namespace tvision + +#endif // __linux__