blob: 2df18db47dd48bc9068700ad39865a3d5fd9c0ae [file] [log] [blame]
// Copyright 2023 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "interrupt_handling.h"
#include "test.h"
#include "util.h"
#ifdef _WIN32
// TODO(digit): Write real test for _WIN32
char dummy_;
#else // !_WIN32
#include <sys/signal.h>
#include <unistd.h>
namespace {
// Retrieve a signal mask for SIGINT/SIGHUP/SIGTERM
sigset_t GetInterruptSignalMask() {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGTERM);
return mask;
}
// Set the signal action for a given |signum|, returning the previous one
// in |*old_action| is |old_action != nullptr|.
void SetSignalAction(int signum, const struct sigaction* action,
struct sigaction* old_action) {
if (sigaction(signum, action, old_action) < 0)
ErrnoFatal("sigaction");
}
// Base class for all tests, used to set the signal mask and actions for
// interrupts to the same base state.
class InterruptHandlingTest : public ::testing::Test {
public:
InterruptHandlingTest() {
// Block signals before changing the action handler + save previous mask.
sigset_t mask = GetInterruptSignalMask();
sigprocmask(SIG_BLOCK, &mask, &prev_mask_);
// Change signal handlers.
struct sigaction sigint_action = {};
sigint_action.sa_handler = [](int) { s_got_sigint = 1; };
SetSignalAction(SIGINT, &sigint_action, &prev_sigint_action_);
struct sigaction sighup_action = {};
sighup_action.sa_handler = [](int) { s_got_sighup = 1; };
SetSignalAction(SIGHUP, &sighup_action, &prev_sighup_action_);
struct sigaction sigterm_action = {};
sigterm_action.sa_handler = [](int) { s_got_sigterm = 1; };
SetSignalAction(SIGTERM, &sigterm_action, &prev_sigterm_action_);
// Unblock signals.
sigprocmask(SIG_UNBLOCK, &mask, nullptr);
}
~InterruptHandlingTest() {
// Block signals before changing handlers.
sigset_t mask = GetInterruptSignalMask();
sigprocmask(SIG_BLOCK, &mask, nullptr);
// Restore previous handlers.
SetSignalAction(SIGINT, &prev_sigint_action_, nullptr);
SetSignalAction(SIGHUP, &prev_sighup_action_, nullptr);
SetSignalAction(SIGTERM, &prev_sigterm_action_, nullptr);
// Clear flags for next test.
Clear();
// Restore previous signal mask.
sigprocmask(SIG_SETMASK, &prev_mask_, nullptr);
}
void Clear() {
s_got_sigint = 0;
s_got_sighup = 0;
s_got_sigterm = 0;
}
void SendSelfSignal(int signum) { ::kill(getpid(), signum); }
sigset_t prev_mask_;
struct sigaction prev_sigint_action_;
struct sigaction prev_sighup_action_;
struct sigaction prev_sigterm_action_;
static volatile sig_atomic_t s_got_sigint;
static volatile sig_atomic_t s_got_sighup;
static volatile sig_atomic_t s_got_sigterm;
};
volatile sig_atomic_t InterruptHandlingTest::s_got_sigint = 0;
volatile sig_atomic_t InterruptHandlingTest::s_got_sighup = 0;
volatile sig_atomic_t InterruptHandlingTest::s_got_sigterm = 0;
} // namespace
TEST_F(InterruptHandlingTest, SendSelfSignals) {
// Verify that the interrupt signals are not blocked
sigset_t empty_mask;
sigemptyset(&empty_mask);
sigset_t cur_mask;
sigprocmask(SIG_BLOCK, &empty_mask, &cur_mask);
ASSERT_FALSE(sigismember(&cur_mask, SIGINT));
ASSERT_FALSE(sigismember(&cur_mask, SIGHUP));
ASSERT_FALSE(sigismember(&cur_mask, SIGTERM));
SendSelfSignal(SIGINT);
ASSERT_TRUE(s_got_sigint);
SendSelfSignal(SIGHUP);
ASSERT_TRUE(s_got_sighup);
SendSelfSignal(SIGTERM);
ASSERT_TRUE(s_got_sigterm);
Clear();
ASSERT_FALSE(s_got_sigint);
ASSERT_FALSE(s_got_sighup);
ASSERT_FALSE(s_got_sigterm);
}
TEST_F(InterruptHandlingTest, InterruptBlocker) {
{
InterruptBlocker blocker;
SendSelfSignal(SIGINT);
ASSERT_FALSE(s_got_sigint);
SendSelfSignal(SIGHUP);
ASSERT_FALSE(s_got_sighup);
SendSelfSignal(SIGTERM);
ASSERT_FALSE(s_got_sigterm);
}
ASSERT_TRUE(s_got_sigint);
ASSERT_TRUE(s_got_sighup);
ASSERT_TRUE(s_got_sigterm);
}
TEST_F(InterruptHandlingTest, InterruptCatcher) {
{
InterruptCatcher catcher;
SendSelfSignal(SIGINT);
ASSERT_FALSE(s_got_sigint);
ASSERT_EQ(SIGINT, catcher.interrupted());
SendSelfSignal(SIGHUP);
ASSERT_FALSE(s_got_sighup);
ASSERT_EQ(SIGHUP, catcher.interrupted());
SendSelfSignal(SIGTERM);
ASSERT_FALSE(s_got_sigterm);
ASSERT_EQ(SIGTERM, catcher.interrupted());
}
// Verify that a second instance does not keep the old interrupted()
// value from a stale global variable.
{
InterruptCatcher catcher;
ASSERT_EQ(0, catcher.interrupted());
}
}
TEST_F(InterruptHandlingTest, InterruptForwarder) {
ASSERT_FALSE(s_got_sigint);
// Create child process with fork.
pid_t child_pid = fork();
ASSERT_GE(child_pid, 0);
if (child_pid == 0) {
// In the child process, do nothing, just wait for an interrupt.
sleep(10);
exit(0);
}
InterruptForwarder forwarder(child_pid);
SendSelfSignal(SIGINT);
ASSERT_FALSE(s_got_sigint);
}
#endif // !_WIN32