// 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 #include #include #include #include #include #include #include #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 Alert MakeAlert(Args&&... args) { return Alert(GetGroupName(), std::forward(args)...); } std::vector 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{__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(); }