icinga2/test/notification-notificationcomponent.cpp

638 lines
22 KiB
C++

/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
#include <BoostTestTargetConfig.h>
#include "base/defer.hpp"
#include "remote/apilistener.hpp"
#include "test/base-testloggerfixture.hpp"
#include "config/configcompiler.hpp"
#include "notification/notificationcomponent.hpp"
using namespace icinga;
namespace {
/**
* Gets the pointer to the private NotificationTimerHandler() by using Friend-Injection.
*
* This uses the exception the standard makes to private member access for explicit template
* instantiation by instantiating a type that defines an accessor to the member function pointer
* in the surrounding anonymous namespace.
*
* The reason for the anonymous namespace is that it doesn't violate the ODR if other
* instantiations are made to this template in other translation units.
* This isn't actually an issue here because the name is very specific to the single use-case of
* obtaining access to NotificationTimerHandler().
*/
template<auto privateMemberFnPtr>
struct InvokeTimerHandlerImpl
{
friend void InvokeTimerHandler(const NotificationComponent::Ptr& nc) { (*nc.*privateMemberFnPtr)(); }
};
void InvokeTimerHandler(const NotificationComponent::Ptr& nc);
template struct InvokeTimerHandlerImpl<&NotificationComponent::NotificationTimerHandler>;
} // namespace
class NotificationComponentFixture : public TestLoggerFixture
{
public:
NotificationComponentFixture()
{
auto createObjects = []() {
String config = R"CONFIG({
object CheckCommand "dummy" {
command = "/bin/echo"
}
object Host "h1" {
address = "h1"
check_command = "dummy"
enable_notifications = true
enable_active_checks = false
enable_passive_checks = true
}
object NotificationCommand "send" {
command = ["true"]
}
apply Notification "n1" to Host {
interval = 0
command = "send"
period = "tp1"
users = [ "u1" ]
assign where host.enable_notifications == true
}
object User "u1" {
enable_notifications = true
}
object TimePeriod "tp1" {
display_name = "Test TimePeriod"
ranges = {
"monday" = "00:00-24:00"
"tuesday" = "00:00-24:00"
"wednesday" = "00:00-24:00"
"thursday" = "00:00-24:00"
"friday" = "00:00-24:00"
"saturday" = "00:00-24:00"
"sunday" = "00:00-24:00"
}
}
object NotificationComponent "nc" {}
})CONFIG";
std::unique_ptr<Expression> expr = ConfigCompiler::CompileText("<test>", config);
expr->Evaluate(*ScriptFrame::GetCurrentFrame());
};
auto ret = ConfigItem::RunWithActivationContext(new Function("CreateTestObjects", createObjects));
BOOST_REQUIRE(ret);
m_Host = Host::GetByName("h1");
BOOST_REQUIRE(m_Host);
m_Notification = Notification::GetByName("h1!n1");
BOOST_REQUIRE(m_Notification);
m_TimePeriod = TimePeriod::GetByName("tp1");
BOOST_REQUIRE(m_TimePeriod);
ApiListener::UpdateObjectAuthority();
BOOST_REQUIRE(ApiListener::UpdatedObjectAuthority());
// Store the old periods from the config snippets to reuse them later.
m_AllTimePeriod = m_TimePeriod->GetRanges();
Checkable::OnNotificationSentToUser.connect(NotificationSentToUserHandler);
}
static void NotificationSentToUserHandler(
const Notification::Ptr&,
const Checkable::Ptr&,
const User::Ptr&,
const NotificationType& type,
const CheckResult::Ptr&,
const String&,
const String&,
const String&,
const MessageOrigin::Ptr&
)
{
std::unique_lock lock(m_NotificationMutex);
m_LastNotification = type;
m_NumNotifications++;
lock.unlock();
m_NotificationCv.notify_all();
}
auto WaitForExpectedNotificationCount(std::size_t expectedCount, std::chrono::milliseconds timeout = 5s)
{
Defer clearLog{[this]() { ClearTestLogger(); }};
std::unique_lock lock(m_NotificationMutex);
m_NotificationCv.wait_for(lock, timeout, [&]() { return m_NumNotifications >= expectedCount; });
boost::test_tools::assertion_result res{m_NumNotifications == expectedCount};
res.message() << "(" << m_NumNotifications << " == " << expectedCount << ")";
return res;
}
boost::test_tools::assertion_result AssertNoAttemptedSendLogPattern()
{
auto result = ExpectLogPattern("^(Sending|Attempting to (re-)?send).*?notification.*$", 0s);
ClearTestLogger();
return !result;
}
void BeginTimePeriod()
{
ObjectLock lock{m_TimePeriod};
m_TimePeriod->SetRanges(m_AllTimePeriod);
auto now = Utility::GetTime();
m_TimePeriod->UpdateRegion(now, now + 1e3, true);
BOOST_REQUIRE(m_TimePeriod->IsInside(now));
}
void EndTimePeriod()
{
ObjectLock lock{m_TimePeriod};
m_TimePeriod->SetRanges(new Dictionary);
auto now = Utility::GetTime();
m_TimePeriod->UpdateRegion(now, now + 1e3, true);
BOOST_REQUIRE(!m_TimePeriod->IsInside(now));
}
void SetNotificationInverval(double interval) { m_Notification->SetInterval(interval); }
void SetNotificationTimes(double begin, double end)
{
m_Notification->SetTimes(new Dictionary{{"begin", begin}, {"end", end}});
}
void WaitUntilNextReminderScheduled()
{
auto now = Utility::GetTime();
if(now < m_Notification->GetNextNotification()){
Utility::Sleep(m_Notification->GetNextNotification() - now + 0.01);
}
BOOST_REQUIRE_LE(m_Notification->GetNextNotification(), Utility::GetTime());
}
static void NotificationTimerHandler()
{
auto nc = NotificationComponent::GetByName("nc");
InvokeTimerHandler(nc);
}
void ReceiveCheckResults(std::size_t num, ServiceState state)
{
StoppableWaitGroup::Ptr wg = new StoppableWaitGroup();
for (auto i = 0UL; i < num; ++i) {
CheckResult::Ptr cr = new CheckResult();
cr->SetState(state);
double now = Utility::GetTime();
cr->SetActive(false);
cr->SetScheduleStart(now);
cr->SetScheduleEnd(now);
cr->SetExecutionStart(now);
cr->SetExecutionEnd(now);
BOOST_REQUIRE(m_Host->ProcessCheckResult(cr, wg) == Checkable::ProcessingResult::Ok);
}
}
double GetLastNotificationTimestamp() { return m_Notification->GetLastNotification(); }
double GetNextNotificationTimestamp() { return m_Notification->GetNextNotification(); }
void SetNextNotificationTimestamp(double val) { m_Notification->SetNextNotification(val); }
std::uint8_t GetSuppressedNotifications() { return m_Notification->GetSuppressedNotifications(); }
static NotificationType GetLastNotification()
{
std::unique_lock lock(m_NotificationMutex);
return m_LastNotification;
}
static std::size_t GetNotificationCount()
{
std::unique_lock lock(m_NotificationMutex);
return m_NumNotifications;
}
private:
static inline std::mutex m_NotificationMutex;
static inline std::condition_variable m_NotificationCv;
static inline std::size_t m_NumNotifications{};
static inline NotificationType m_LastNotification{};
Host::Ptr m_Host;
Notification::Ptr m_Notification;
TimePeriod::Ptr m_TimePeriod;
Dictionary::Ptr m_AllTimePeriod;
};
BOOST_FIXTURE_TEST_SUITE(notificationcomponent, NotificationComponentFixture,
*boost::unit_test::label("notification"));
/* Test sending out reminder notifications in a given interval.
*/
BOOST_AUTO_TEST_CASE(notify_send_reminders)
{
SetNotificationInverval(0.15);
ReceiveCheckResults(2, ServiceCritical);
// The first run of the timer sets up the next reminder notification.
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
// Rerunning the timer before the next interval should not trigger a reminder notification.
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
// After waiting until the interval has passed, a reminder will be queued.
WaitUntilNextReminderScheduled();
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(2));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
// Now we test that reminders are only sent for Critical states.
// Hard state is switched to OK.
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(3));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
// Now we wait for one interval and check that no reminder has been sent.
WaitUntilNextReminderScheduled();
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 3);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
}
/* Tests if delayed notifications are sent out.
*/
BOOST_AUTO_TEST_CASE(notify_delayed)
{
constexpr double timesBegin = 0.01;
/* We're always inside a time-period for this test-case.
*/
BeginTimePeriod();
/* Set large values to interval and the times window. We're not going to use these,
* but they need to be defined, valid and large so the timer handler doesn't trigger them
* on its own.
*/
SetNotificationInverval(0);
SetNotificationTimes(timesBegin, 20);
/* The notifications need to wait for a delay until they're sent out, so check if
* they haven't been processed in SendNotificationsHandler() but instead delayed.
*/
ReceiveCheckResults(2, ServiceCritical);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
/* When processing the notification, BeginExecuteNotification() sets up the next scheduled
* notification with an additional second to spare, which is an annoying delay for
* unit-testing purposes, so we just verify that it is set correctly here, then reset it
* to be already elapsed further down the line.
*/
BOOST_REQUIRE_CLOSE(GetNextNotificationTimestamp(), Utility::GetTime() + timesBegin + 1, 0.01);
/* Now call the NotificationTimerHandler(), which should also not send the notifications
* out yet, because the times window has not yet been reached
*/
NotificationTimerHandler();
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
/* Now we reset the next scheduled timer run to the past. Since we have verified above
* that it was in fact set correctly, this is fine to do here so we don't have to wait.
*/
SetNextNotificationTimestamp(Utility::GetTime() + timesBegin);
WaitUntilNextReminderScheduled();
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
/* Reset to OK and repeat test with an interval value instead of 0.
*/
SetNotificationInverval(10);
ReceiveCheckResults(1, ServiceOK);
BOOST_REQUIRE(WaitForExpectedNotificationCount(2));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
ReceiveCheckResults(3, ServiceCritical);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 2);
/* Again, no new notifications expected.
*/
BOOST_REQUIRE_CLOSE(GetNextNotificationTimestamp(), Utility::GetTime() + timesBegin + 1, 0.01);
NotificationTimerHandler();
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 2);
/* Again, after the delay has "elapsed", the "reminder" should be sent out.
*/
SetNextNotificationTimestamp(Utility::GetTime() + timesBegin);
WaitUntilNextReminderScheduled();
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(3));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
}
/* Tests simple sending of notifications on each state change.
*/
BOOST_AUTO_TEST_CASE(notify_simple)
{
BeginTimePeriod();
ReceiveCheckResults(2, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
ReceiveCheckResults(1, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(2));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
ReceiveCheckResults(2, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 2);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
}
/* This tests the simplest case where a suppressed notification will be sent after resuming
* a TimePeriod. A single event occurs outside the TimePeriod and the notification should be
* sent as soon as the timer runs after the TimePeriod is resumed.
*/
BOOST_AUTO_TEST_CASE(notify_after_timeperiod_simple)
{
BeginTimePeriod();
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
EndTimePeriod();
ReceiveCheckResults(3, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationProblem);
BeginTimePeriod();
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
}
/* Similar to the test-case above, but has multiple state-changes outside of the TimePeriod
* This is important, since there are multiple places in the code that check on and make modifications
* to the list of suppressed events. A concrete example of a bug like this is #10575.
*/
BOOST_AUTO_TEST_CASE(notify_multiple_state_changes_outside_timeperiod)
{
BOOST_REQUIRE_EQUAL(GetLastNotificationTimestamp(), 0.0);
BeginTimePeriod();
ReceiveCheckResults(2, ServiceCritical);
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
EndTimePeriod();
ReceiveCheckResults(1, ServiceOK);
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
ReceiveCheckResults(1, ServiceCritical);
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
// Third Critical check result will set the Critical hard state.
ReceiveCheckResults(2, ServiceCritical);
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
ReceiveCheckResults(1, ServiceOK);
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
BeginTimePeriod();
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(2));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
}
/* This tests if suppressed notifications of opposite types cancel each other out.
*/
BOOST_AUTO_TEST_CASE(no_notify_suppressed_cancel_out)
{
BOOST_REQUIRE_EQUAL(GetLastNotificationTimestamp(), 0.0);
BeginTimePeriod();
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
EndTimePeriod();
ReceiveCheckResults(3, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationProblem);
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
BeginTimePeriod();
// Ensure no notification is sent after resuming the TimePeriod.
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
// Now repeat the same starting from a Critical state
ReceiveCheckResults(3, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
EndTimePeriod();
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
ReceiveCheckResults(3, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
BeginTimePeriod();
// Ensure no notification is sent after resuming the TimePeriod.
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
}
/* This may look similar to the test-case above, but the critical difference is that here
* the final state change happens inside the TimePeriod again, but before the timer runs.
* The outdated suppressed NotificationProblem will then be subtracted when the timer runs.
*/
BOOST_AUTO_TEST_CASE(no_notify_non_applicable_reason)
{
BOOST_REQUIRE_EQUAL(GetLastNotificationTimestamp(), 0.0);
BeginTimePeriod();
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
EndTimePeriod();
// We queue a suppressed notification.
ReceiveCheckResults(3, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationProblem);
BeginTimePeriod();
// In this scenario a check result that goes against the suppressed notification is processed
// before the timer can run again. No notification should be sent, because the last state
// change the user was notified about was the same.
ReceiveCheckResults(1, ServiceOK);
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationProblem);
// When the timer runs, it should clear the suppressed notification but not send anything.
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
}
/**
* This tests for regressions of races around the NoMoreNotifications flag, like in #10623.
*
* The potential for race-conditions exists in this case because check-results potentially
* leading to notifications are processed synchronously in SendNotificationsHandler() and
* asynchronously in NotificationTimerHandler() when the timer runs out.
*/
BOOST_AUTO_TEST_CASE(no_more_notifications_race)
{
constexpr auto numIterations = 20UL;
BeginTimePeriod();
// Run the handler in a loop to provoke any existing race conditions.
std::atomic_bool stop;
auto timerThread = std::thread{[&stop]() {
while (!stop) {
NotificationTimerHandler();
}
}};
ReceiveCheckResults(1, ServiceOK);
// With interval 0, no reminder notifications should ever be sent.
for (auto i = 0UL; i < numIterations; ++i) {
ReceiveCheckResults(3, ServiceCritical);
ReceiveCheckResults(1, ServiceOK);
}
stop = true;
timerThread.join();
BOOST_REQUIRE(WaitForExpectedNotificationCount(2 * numIterations));
BOOST_REQUIRE(!WaitForExpectedNotificationCount((2 * numIterations) + 1, 10ms));
BOOST_REQUIRE(!ExpectLogPattern("^Sending reminder.*$", 0s));
}
BOOST_AUTO_TEST_SUITE_END()