Skip to content

Commit

Permalink
platform: add SignalHandler tests
Browse files Browse the repository at this point in the history
  • Loading branch information
magiblot committed Oct 15, 2023
1 parent 3b25264 commit 581dd6c
Show file tree
Hide file tree
Showing 3 changed files with 256 additions and 4 deletions.
4 changes: 2 additions & 2 deletions include/tvision/internal/sighandl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -70,7 +70,7 @@ class SignalHandler
{
public:

static void enable(SignalHandlerCallback &aCallback) noexcept {}
static void enable(SignalHandlerCallback &) noexcept {}
static void disable() noexcept {}
};

Expand Down
5 changes: 3 additions & 2 deletions source/platform/sighandl.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#include <internal/sighandl.h>
#include <stdlib.h>

#ifdef _TV_UNIX

#include <stdlib.h>

namespace tvision
{

Expand Down Expand Up @@ -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.
Expand Down
251 changes: 251 additions & 0 deletions test/platform/sighandl.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
#include <internal/sighandl.h>

#ifdef __linux__

#include <test.h>

#include <sys/wait.h>
#include <sys/mman.h>

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__

0 comments on commit 581dd6c

Please sign in to comment.