[hal,wpilib] Move Alert to HAL (#8646)

SystemCore implementation is not yet connected to MRCComm.
This commit is contained in:
Peter Johnson
2026-03-03 21:58:47 -07:00
committed by GitHub
parent f4935a2ea9
commit 733cfa4b07
33 changed files with 1719 additions and 1121 deletions

View File

@@ -1,250 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/util/Alert.hpp"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include <gtest/gtest.h>
#include "wpi/nt/NetworkTableInstance.hpp"
#include "wpi/nt/StringArrayTopic.hpp"
#include "wpi/simulation/SimHooks.hpp"
#include "wpi/smartdashboard/SmartDashboard.hpp"
using namespace wpi;
using enum Alert::AlertType;
class AlertsTest : public ::testing::Test {
public:
~AlertsTest() override {
// test all destructors
Update();
EXPECT_EQ(GetSubscriberForType(kError).Get().size(), 0ul);
EXPECT_EQ(GetSubscriberForType(kWarning).Get().size(), 0ul);
EXPECT_EQ(GetSubscriberForType(kInfo).Get().size(), 0ul);
}
std::string GetGroupName() {
const ::testing::TestInfo* testInfo =
::testing::UnitTest::GetInstance()->current_test_info();
return fmt::format("{}_{}", testInfo->test_suite_name(), testInfo->name());
}
template <typename... Args>
Alert MakeAlert(Args&&... args) {
return Alert(GetGroupName(), std::forward<Args>(args)...);
}
std::vector<std::string> GetActiveAlerts(Alert::AlertType type) {
Update();
return GetSubscriberForType(type).Get();
}
bool IsAlertActive(std::string_view text, Alert::AlertType type) {
auto activeAlerts = GetActiveAlerts(type);
return std::find(activeAlerts.begin(), activeAlerts.end(), text) !=
activeAlerts.end();
}
void Update() { wpi::SmartDashboard::UpdateValues(); }
private:
std::string GetSubtableName(Alert::AlertType type) {
switch (type) {
case kError:
return "errors";
case kWarning:
return "warnings";
case kInfo:
return "infos";
default:
return "unknown";
}
}
const wpi::nt::StringArraySubscriber GetSubscriberForType(
Alert::AlertType type) {
return wpi::nt::NetworkTableInstance::GetDefault()
.GetStringArrayTopic(fmt::format("/SmartDashboard/{}/{}",
GetGroupName(), GetSubtableName(type)))
.Subscribe({});
}
};
#define EXPECT_STATE(type, ...) \
EXPECT_EQ(GetActiveAlerts(type), (std::vector<std::string>{__VA_ARGS__}))
TEST_F(AlertsTest, SetUnsetSingle) {
auto one = MakeAlert("one", kInfo);
EXPECT_FALSE(IsAlertActive("one", kInfo));
one.Set(true);
EXPECT_TRUE(IsAlertActive("one", kInfo));
one.Set(false);
EXPECT_FALSE(IsAlertActive("one", kInfo));
}
TEST_F(AlertsTest, SetUnsetMultiple) {
auto one = MakeAlert("one", kError);
auto two = MakeAlert("two", kInfo);
EXPECT_FALSE(IsAlertActive("one", kError));
EXPECT_FALSE(IsAlertActive("two", kInfo));
one.Set(true);
EXPECT_TRUE(IsAlertActive("one", kError));
EXPECT_FALSE(IsAlertActive("two", kInfo));
one.Set(true);
two.Set(true);
EXPECT_TRUE(IsAlertActive("one", kError));
EXPECT_TRUE(IsAlertActive("two", kInfo));
one.Set(false);
EXPECT_FALSE(IsAlertActive("one", kError));
EXPECT_TRUE(IsAlertActive("two", kInfo));
}
TEST_F(AlertsTest, SetIsIdempotent) {
auto a = MakeAlert("A", kInfo);
auto b = MakeAlert("B", kInfo);
auto c = MakeAlert("C", kInfo);
a.Set(true);
b.Set(true);
c.Set(true);
const auto startState = GetActiveAlerts(kInfo);
b.Set(true);
EXPECT_STATE(kInfo, startState);
a.Set(true);
EXPECT_STATE(kInfo, startState);
}
TEST_F(AlertsTest, DestructorUnsetsAlert) {
{
auto alert = MakeAlert("alert", kWarning);
alert.Set(true);
EXPECT_TRUE(IsAlertActive("alert", kWarning));
}
EXPECT_FALSE(IsAlertActive("alert", kWarning));
}
TEST_F(AlertsTest, SetTextWhileUnset) {
auto alert = MakeAlert("BEFORE", kInfo);
EXPECT_EQ("BEFORE", alert.GetText());
alert.Set(true);
EXPECT_TRUE(IsAlertActive("BEFORE", kInfo));
alert.Set(false);
EXPECT_FALSE(IsAlertActive("BEFORE", kInfo));
alert.SetText("AFTER");
EXPECT_EQ("AFTER", alert.GetText());
alert.Set(true);
EXPECT_FALSE(IsAlertActive("BEFORE", kInfo));
EXPECT_TRUE(IsAlertActive("AFTER", kInfo));
}
TEST_F(AlertsTest, SetTextWhileSet) {
auto alert = MakeAlert("BEFORE", kInfo);
EXPECT_EQ("BEFORE", alert.GetText());
alert.Set(true);
EXPECT_TRUE(IsAlertActive("BEFORE", kInfo));
alert.SetText("AFTER");
EXPECT_EQ("AFTER", alert.GetText());
EXPECT_FALSE(IsAlertActive("BEFORE", kInfo));
EXPECT_TRUE(IsAlertActive("AFTER", kInfo));
}
TEST_F(AlertsTest, SetTextDoesNotAffectFirstOrderSort) {
wpi::sim::PauseTiming();
auto a = MakeAlert("A", kError);
auto b = MakeAlert("B", kError);
auto c = MakeAlert("C", kError);
a.Set(true);
wpi::sim::StepTiming(1_s);
b.Set(true);
wpi::sim::StepTiming(1_s);
c.Set(true);
auto expectedEndState = GetActiveAlerts(kError);
std::replace(expectedEndState.begin(), expectedEndState.end(),
std::string("B"), std::string("AFTER"));
b.SetText("AFTER");
EXPECT_STATE(kError, expectedEndState);
wpi::sim::ResumeTiming();
}
TEST_F(AlertsTest, MoveAssign) {
auto outer = MakeAlert("outer", kInfo);
outer.Set(true);
EXPECT_TRUE(IsAlertActive("outer", kInfo));
{
auto inner = MakeAlert("inner", kWarning);
inner.Set(true);
EXPECT_TRUE(IsAlertActive("inner", kWarning));
outer = std::move(inner);
// Assignment target should be unset and invalidated as part of move, before
// destruction
EXPECT_FALSE(IsAlertActive("outer", kInfo));
}
EXPECT_TRUE(IsAlertActive("inner", kWarning));
}
TEST_F(AlertsTest, MoveConstruct) {
auto a = MakeAlert("A", kInfo);
a.Set(true);
EXPECT_TRUE(IsAlertActive("A", kInfo));
Alert b{std::move(a)};
EXPECT_TRUE(IsAlertActive("A", kInfo));
b.Set(false);
EXPECT_FALSE(IsAlertActive("A", kInfo));
b.Set(true);
EXPECT_TRUE(IsAlertActive("A", kInfo));
}
TEST_F(AlertsTest, SortOrder) {
wpi::sim::PauseTiming();
auto a = MakeAlert("A", kInfo);
auto b = MakeAlert("B", kInfo);
auto c = MakeAlert("C", kInfo);
a.Set(true);
EXPECT_STATE(kInfo, "A");
wpi::sim::StepTiming(1_s);
b.Set(true);
EXPECT_STATE(kInfo, "B", "A");
wpi::sim::StepTiming(1_s);
c.Set(true);
EXPECT_STATE(kInfo, "C", "B", "A");
wpi::sim::StepTiming(1_s);
c.Set(false);
EXPECT_STATE(kInfo, "B", "A");
wpi::sim::StepTiming(1_s);
c.Set(true);
EXPECT_STATE(kInfo, "C", "B", "A");
wpi::sim::StepTiming(1_s);
a.Set(false);
EXPECT_STATE(kInfo, "C", "B");
wpi::sim::StepTiming(1_s);
b.Set(false);
EXPECT_STATE(kInfo, "C");
wpi::sim::StepTiming(1_s);
b.Set(true);
EXPECT_STATE(kInfo, "B", "C");
wpi::sim::StepTiming(1_s);
a.Set(true);
EXPECT_STATE(kInfo, "A", "B", "C");
wpi::sim::ResumeTiming();
}

View File

@@ -0,0 +1,153 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/simulation/AlertSim.hpp"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include <gtest/gtest.h>
#include "wpi/driverstation/Alert.hpp"
#include "wpi/hal/HALBase.h"
namespace wpi::sim {
class AlertSimTest : public ::testing::Test {
public:
AlertSimTest() { HAL_Initialize(500, 0); }
~AlertSimTest() override { AlertSim::ResetData(); }
std::string GetGroupName() {
const ::testing::TestInfo* testInfo =
::testing::UnitTest::GetInstance()->current_test_info();
return fmt::format("{}_{}", testInfo->test_suite_name(), testInfo->name());
}
template <typename... Args>
Alert MakeAlert(Args&&... args) {
return Alert(GetGroupName(), std::forward<Args>(args)...);
}
std::vector<std::string> GetActiveAlerts(Alert::Level type) {
auto alerts = AlertSim::GetAll();
std::vector<std::string> activeAlerts;
for (const auto& alert : alerts) {
if (alert.isActive() && alert.level == type) {
activeAlerts.emplace_back(std::move(alert.text));
}
}
return activeAlerts;
}
bool IsAlertActive(std::string_view text, Alert::Level type) {
auto alerts = AlertSim::GetAll();
return std::any_of(alerts.begin(), alerts.end(),
[text, type](const AlertSim::AlertInfo& alert) {
return alert.isActive() && alert.level == type &&
alert.text == text;
});
}
};
#define EXPECT_STATE(type, ...) \
EXPECT_EQ(GetActiveAlerts(type), (std::vector<std::string>{__VA_ARGS__}))
TEST_F(AlertSimTest, NoAlertsInitially) {
EXPECT_EQ(AlertSim::GetCount(), 0);
EXPECT_TRUE(AlertSim::GetAll().empty());
}
TEST_F(AlertSimTest, NoAlertsAfterReset) {
auto alert = MakeAlert("alert", Alert::Level::HIGH);
alert.Set(true);
EXPECT_TRUE(IsAlertActive("alert", Alert::Level::HIGH));
AlertSim::ResetData();
EXPECT_EQ(AlertSim::GetCount(), 0);
EXPECT_TRUE(AlertSim::GetAll().empty());
}
TEST_F(AlertSimTest, SetUnsetSingle) {
auto one = MakeAlert("one", Alert::Level::LOW);
EXPECT_FALSE(IsAlertActive("one", Alert::Level::LOW));
one.Set(true);
EXPECT_TRUE(IsAlertActive("one", Alert::Level::LOW));
one.Set(false);
EXPECT_FALSE(IsAlertActive("one", Alert::Level::LOW));
}
TEST_F(AlertSimTest, SetUnsetMultiple) {
auto one = MakeAlert("one", Alert::Level::HIGH);
auto two = MakeAlert("two", Alert::Level::LOW);
EXPECT_FALSE(IsAlertActive("one", Alert::Level::HIGH));
EXPECT_FALSE(IsAlertActive("two", Alert::Level::LOW));
one.Set(true);
EXPECT_TRUE(IsAlertActive("one", Alert::Level::HIGH));
EXPECT_FALSE(IsAlertActive("two", Alert::Level::LOW));
one.Set(true);
two.Set(true);
EXPECT_TRUE(IsAlertActive("one", Alert::Level::HIGH));
EXPECT_TRUE(IsAlertActive("two", Alert::Level::LOW));
one.Set(false);
EXPECT_FALSE(IsAlertActive("one", Alert::Level::HIGH));
EXPECT_TRUE(IsAlertActive("two", Alert::Level::LOW));
}
TEST_F(AlertSimTest, SetIsIdempotent) {
auto a = MakeAlert("A", Alert::Level::LOW);
auto b = MakeAlert("B", Alert::Level::LOW);
auto c = MakeAlert("C", Alert::Level::LOW);
a.Set(true);
b.Set(true);
c.Set(true);
const auto startState = GetActiveAlerts(Alert::Level::LOW);
b.Set(true);
EXPECT_STATE(Alert::Level::LOW, startState);
a.Set(true);
EXPECT_STATE(Alert::Level::LOW, startState);
}
TEST_F(AlertSimTest, DestructorUnsetsAlert) {
{
auto alert = MakeAlert("alert", Alert::Level::MEDIUM);
alert.Set(true);
EXPECT_TRUE(IsAlertActive("alert", Alert::Level::MEDIUM));
}
EXPECT_FALSE(IsAlertActive("alert", Alert::Level::MEDIUM));
}
TEST_F(AlertSimTest, SetTextWhileUnset) {
auto alert = MakeAlert("BEFORE", Alert::Level::LOW);
EXPECT_EQ("BEFORE", alert.GetText());
alert.Set(true);
EXPECT_TRUE(IsAlertActive("BEFORE", Alert::Level::LOW));
alert.Set(false);
EXPECT_FALSE(IsAlertActive("BEFORE", Alert::Level::LOW));
alert.SetText("AFTER");
EXPECT_EQ("AFTER", alert.GetText());
alert.Set(true);
EXPECT_FALSE(IsAlertActive("BEFORE", Alert::Level::LOW));
EXPECT_TRUE(IsAlertActive("AFTER", Alert::Level::LOW));
}
TEST_F(AlertSimTest, SetTextWhileSet) {
auto alert = MakeAlert("BEFORE", Alert::Level::LOW);
EXPECT_EQ("BEFORE", alert.GetText());
alert.Set(true);
EXPECT_TRUE(IsAlertActive("BEFORE", Alert::Level::LOW));
alert.SetText("AFTER");
EXPECT_EQ("AFTER", alert.GetText());
EXPECT_FALSE(IsAlertActive("BEFORE", Alert::Level::LOW));
EXPECT_TRUE(IsAlertActive("AFTER", Alert::Level::LOW));
}
} // namespace wpi::sim

View File

@@ -2,222 +2,140 @@ import typing as T
import pytest
from ntcore import NetworkTableInstance
from wpilib import Alert, SmartDashboard
from wpilib.simulation import pauseTiming, resumeTiming, stepTiming
from wpilib import Alert
from wpilib.simulation import AlertSim
AlertType = Alert.AlertType
Level = Alert.Level
@pytest.fixture(scope="function")
def group_name(nt, request):
def group_name(request):
group_name = f"AlertTest_{request.node.name}"
yield group_name
SmartDashboard.updateValues()
assert len(get_active_alerts(nt, group_name, AlertType.kError)) == 0
assert len(get_active_alerts(nt, group_name, AlertType.kWarning)) == 0
assert len(get_active_alerts(nt, group_name, AlertType.kInfo)) == 0
def get_subscriber_for_type(
nt: NetworkTableInstance, group_name: str, alert_type: AlertType
):
subtable_name = {
AlertType.kError: "errors",
AlertType.kWarning: "warnings",
AlertType.kInfo: "infos",
}.get(alert_type, "unknown")
topic = f"/SmartDashboard/{group_name}/{subtable_name}"
return nt.getStringArrayTopic(topic).subscribe([])
AlertSim.resetData()
def get_active_alerts(
nt: NetworkTableInstance, group_name: str, alert_type: AlertType
group_name: str, level: Alert.Level
) -> T.List[str]:
SmartDashboard.updateValues()
with get_subscriber_for_type(nt, group_name, alert_type) as sub:
return sub.get()
return [
a.text
for a in AlertSim.getAll()
if a.group == group_name and a.level == level and a.isActive()
]
def is_alert_active(
nt: NetworkTableInstance, group_name: str, text: str, alert_type: AlertType
group_name: str, text: str, level: Alert.Level
):
active_alerts = get_active_alerts(nt, group_name, alert_type)
return text in active_alerts
matches = [
a
for a in AlertSim.getAll()
if a.group == group_name and a.level == level and a.text == text and a.isActive()
]
return len(matches) > 0
def assert_state(
nt: NetworkTableInstance,
group_name: str,
alert_type: AlertType,
level: Alert.Level,
expected_state: T.List[str],
):
assert expected_state == get_active_alerts(nt, group_name, alert_type)
assert expected_state == get_active_alerts(group_name, level)
def test_set_unset_single(nt, group_name):
with Alert(group_name, "one", AlertType.kError) as one:
def test_set_unset_single(group_name):
with Alert(group_name, "one", Alert.Level.HIGH) as one:
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
assert not is_alert_active(nt, group_name, "two", AlertType.kInfo)
assert not is_alert_active(group_name, "one", Alert.Level.HIGH)
assert not is_alert_active(group_name, "two", Alert.Level.LOW)
one.set(True)
assert is_alert_active(nt, group_name, "one", AlertType.kError)
assert is_alert_active(group_name, "one", Alert.Level.HIGH)
one.set(True)
assert is_alert_active(nt, group_name, "one", AlertType.kError)
assert is_alert_active(group_name, "one", Alert.Level.HIGH)
one.set(False)
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
assert not is_alert_active(group_name, "one", Alert.Level.HIGH)
def test_set_unset_multiple(nt, group_name):
def test_set_unset_multiple(group_name):
with (
Alert(group_name, "one", AlertType.kError) as one,
Alert(group_name, "two", AlertType.kInfo) as two,
Alert(group_name, "one", Alert.Level.HIGH) as one,
Alert(group_name, "two", Alert.Level.LOW) as two,
):
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
assert not is_alert_active(nt, group_name, "two", AlertType.kInfo)
assert not is_alert_active(group_name, "one", Alert.Level.HIGH)
assert not is_alert_active(group_name, "two", Alert.Level.LOW)
one.set(True)
assert is_alert_active(nt, group_name, "one", AlertType.kError)
assert not is_alert_active(nt, group_name, "two", AlertType.kInfo)
assert is_alert_active(group_name, "one", Alert.Level.HIGH)
assert not is_alert_active(group_name, "two", Alert.Level.LOW)
one.set(True)
two.set(True)
assert is_alert_active(nt, group_name, "one", AlertType.kError)
assert is_alert_active(nt, group_name, "two", AlertType.kInfo)
assert is_alert_active(group_name, "one", Alert.Level.HIGH)
assert is_alert_active(group_name, "two", Alert.Level.LOW)
one.set(False)
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
assert is_alert_active(nt, group_name, "two", AlertType.kInfo)
assert not is_alert_active(group_name, "one", Alert.Level.HIGH)
assert is_alert_active(group_name, "two", Alert.Level.LOW)
def test_set_is_idempotent(nt, group_name):
def test_set_is_idempotent(group_name):
group_name = group_name
with (
Alert(group_name, "A", AlertType.kInfo) as a,
Alert(group_name, "B", AlertType.kInfo) as b,
Alert(group_name, "C", AlertType.kInfo) as c,
Alert(group_name, "A", Alert.Level.LOW) as a,
Alert(group_name, "B", Alert.Level.LOW) as b,
Alert(group_name, "C", Alert.Level.LOW) as c,
):
a.set(True)
b.set(True)
c.set(True)
start_state = get_active_alerts(nt, group_name, AlertType.kInfo)
start_state = get_active_alerts(group_name, Alert.Level.LOW)
assert set(start_state) == {"A", "B", "C"}
b.set(True)
assert_state(nt, group_name, AlertType.kInfo, start_state)
assert_state(group_name, Alert.Level.LOW, start_state)
a.set(True)
assert_state(nt, group_name, AlertType.kInfo, start_state)
assert_state(group_name, Alert.Level.LOW, start_state)
def test_close_unsets_alert(nt, group_name):
def test_close_unsets_alert(group_name):
group_name = group_name
with Alert(group_name, "alert", AlertType.kWarning) as alert:
with Alert(group_name, "alert", Alert.Level.MEDIUM) as alert:
alert.set(True)
assert is_alert_active(nt, group_name, "alert", AlertType.kWarning)
assert not is_alert_active(nt, group_name, "alert", AlertType.kWarning)
assert is_alert_active(group_name, "alert", Alert.Level.MEDIUM)
assert not is_alert_active(group_name, "alert", Alert.Level.MEDIUM)
def test_set_text_while_unset(nt, group_name):
def test_set_text_while_unset(group_name):
group_name = group_name
with Alert(group_name, "BEFORE", AlertType.kInfo) as alert:
with Alert(group_name, "BEFORE", Alert.Level.LOW) as alert:
assert alert.getText() == "BEFORE"
alert.set(True)
assert is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
assert is_alert_active(group_name, "BEFORE", Alert.Level.LOW)
alert.set(False)
assert not is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
assert not is_alert_active(group_name, "BEFORE", Alert.Level.LOW)
alert.setText("AFTER")
assert alert.getText() == "AFTER"
alert.set(True)
assert not is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
assert is_alert_active(nt, group_name, "AFTER", AlertType.kInfo)
assert not is_alert_active(group_name, "BEFORE", Alert.Level.LOW)
assert is_alert_active(group_name, "AFTER", Alert.Level.LOW)
def test_set_text_while_set(nt, group_name):
with Alert(group_name, "BEFORE", AlertType.kInfo) as alert:
def test_set_text_while_set(group_name):
with Alert(group_name, "BEFORE", Alert.Level.LOW) as alert:
assert alert.getText() == "BEFORE"
alert.set(True)
assert is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
assert is_alert_active(group_name, "BEFORE", Alert.Level.LOW)
alert.setText("AFTER")
assert alert.getText() == "AFTER"
assert not is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
assert is_alert_active(nt, group_name, "AFTER", AlertType.kInfo)
def test_set_text_does_not_affect_sort(nt, group_name):
pauseTiming()
try:
with (
Alert(group_name, "A", AlertType.kInfo) as a,
Alert(group_name, "B", AlertType.kInfo) as b,
Alert(group_name, "C", AlertType.kInfo) as c,
):
a.set(True)
stepTiming(1)
b.set(True)
stepTiming(1)
c.set(True)
expected_state = get_active_alerts(nt, group_name, AlertType.kInfo)
expected_state[expected_state.index("B")] = "AFTER"
b.setText("AFTER")
assert_state(nt, group_name, AlertType.kInfo, expected_state)
finally:
resumeTiming()
def test_sort_order(nt, group_name):
pauseTiming()
try:
with (
Alert(group_name, "A", AlertType.kInfo) as a,
Alert(group_name, "B", AlertType.kInfo) as b,
Alert(group_name, "C", AlertType.kInfo) as c,
):
a.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["A"])
stepTiming(1)
b.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["B", "A"])
stepTiming(1)
c.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["C", "B", "A"])
stepTiming(1)
c.set(False)
assert_state(nt, group_name, AlertType.kInfo, ["B", "A"])
stepTiming(1)
c.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["C", "B", "A"])
stepTiming(1)
a.set(False)
assert_state(nt, group_name, AlertType.kInfo, ["C", "B"])
stepTiming(1)
b.set(False)
assert_state(nt, group_name, AlertType.kInfo, ["C"])
stepTiming(1)
b.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["B", "C"])
stepTiming(1)
a.set(True)
assert_state(nt, group_name, AlertType.kInfo, ["A", "B", "C"])
finally:
resumeTiming()
assert not is_alert_active(group_name, "BEFORE", Alert.Level.LOW)
assert is_alert_active(group_name, "AFTER", Alert.Level.LOW)