Files
allwpilib/wpilibj/src/test/java/org/wpilib/util/AlertTest.java

244 lines
7.1 KiB
Java
Raw Normal View History

[wpilib] Refactor Alert (#7279) This refactors Alert in both c++ and java to fix the issues with the current c++ implementation and improve performance. Currently, constructing an Alert adds it to a list of Alerts with the same group and type. Activating an alert sets a flag on the alert. When the SendableAlerts is polled (GetStrings), the entire list is iterated over, filtered, and the filtered list is sorted by timestamp. This leads to a worst case O(m + nlog(n)) time complexity for GetStrings, where m and n are the count of total constructed alerts active alerts respectively. It also allocates intermediate data structures to hold the active alert strings for sorting. This changes the implementation to improve the performance of GetStrings, by shifting the sorting overhead to Alert.Set Constructing the Alert only initializes the alert's initial state, and initializes the SendableAlerts for the group if it is not already initialized. Activating or deactivating an alert sets an internal flag for state tracking, and inserts or removes a structure containing the timestamp and text into a self-sorting data structure (std::set, TreeSet) containing other active alerts with the same group and type. (worst case O(log(n)) Now, SendableAlerts.GetStrings only has to iterate over the structure and copy the strings to the returned array. (amortized O(n)) This also fixes the c++ implementation by removing the need for SendableAlerts to directly access the Alert. This also adds a helper method to SendableRegistry to force initialization of the instance to prevent static initialization ordering issues.
2024-11-06 18:09:06 -05:00
// 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.
package edu.wpi.first.wpilibj;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringArraySubscriber;
import edu.wpi.first.wpilibj.Alert.AlertType;
import edu.wpi.first.wpilibj.simulation.SimHooks;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.parallel.ResourceLock;
class AlertTest {
private NetworkTableInstance m_inst;
private String m_groupName;
[wpilib] Refactor Alert (#7279) This refactors Alert in both c++ and java to fix the issues with the current c++ implementation and improve performance. Currently, constructing an Alert adds it to a list of Alerts with the same group and type. Activating an alert sets a flag on the alert. When the SendableAlerts is polled (GetStrings), the entire list is iterated over, filtered, and the filtered list is sorted by timestamp. This leads to a worst case O(m + nlog(n)) time complexity for GetStrings, where m and n are the count of total constructed alerts active alerts respectively. It also allocates intermediate data structures to hold the active alert strings for sorting. This changes the implementation to improve the performance of GetStrings, by shifting the sorting overhead to Alert.Set Constructing the Alert only initializes the alert's initial state, and initializes the SendableAlerts for the group if it is not already initialized. Activating or deactivating an alert sets an internal flag for state tracking, and inserts or removes a structure containing the timestamp and text into a self-sorting data structure (std::set, TreeSet) containing other active alerts with the same group and type. (worst case O(log(n)) Now, SendableAlerts.GetStrings only has to iterate over the structure and copy the strings to the returned array. (amortized O(n)) This also fixes the c++ implementation by removing the need for SendableAlerts to directly access the Alert. This also adds a helper method to SendableRegistry to force initialization of the instance to prevent static initialization ordering issues.
2024-11-06 18:09:06 -05:00
@BeforeEach
void setup(TestInfo info) {
m_groupName = "AlertTest_" + info.getDisplayName();
m_inst = NetworkTableInstance.create();
SmartDashboard.setNetworkTableInstance(m_inst);
[wpilib] Refactor Alert (#7279) This refactors Alert in both c++ and java to fix the issues with the current c++ implementation and improve performance. Currently, constructing an Alert adds it to a list of Alerts with the same group and type. Activating an alert sets a flag on the alert. When the SendableAlerts is polled (GetStrings), the entire list is iterated over, filtered, and the filtered list is sorted by timestamp. This leads to a worst case O(m + nlog(n)) time complexity for GetStrings, where m and n are the count of total constructed alerts active alerts respectively. It also allocates intermediate data structures to hold the active alert strings for sorting. This changes the implementation to improve the performance of GetStrings, by shifting the sorting overhead to Alert.Set Constructing the Alert only initializes the alert's initial state, and initializes the SendableAlerts for the group if it is not already initialized. Activating or deactivating an alert sets an internal flag for state tracking, and inserts or removes a structure containing the timestamp and text into a self-sorting data structure (std::set, TreeSet) containing other active alerts with the same group and type. (worst case O(log(n)) Now, SendableAlerts.GetStrings only has to iterate over the structure and copy the strings to the returned array. (amortized O(n)) This also fixes the c++ implementation by removing the need for SendableAlerts to directly access the Alert. This also adds a helper method to SendableRegistry to force initialization of the instance to prevent static initialization ordering issues.
2024-11-06 18:09:06 -05:00
}
@AfterEach
void checkClean() {
update();
assertEquals(0, getActiveAlerts(AlertType.kError).length);
assertEquals(0, getActiveAlerts(AlertType.kWarning).length);
assertEquals(0, getActiveAlerts(AlertType.kInfo).length);
m_inst.close();
SmartDashboard.setNetworkTableInstance(NetworkTableInstance.getDefault());
[wpilib] Refactor Alert (#7279) This refactors Alert in both c++ and java to fix the issues with the current c++ implementation and improve performance. Currently, constructing an Alert adds it to a list of Alerts with the same group and type. Activating an alert sets a flag on the alert. When the SendableAlerts is polled (GetStrings), the entire list is iterated over, filtered, and the filtered list is sorted by timestamp. This leads to a worst case O(m + nlog(n)) time complexity for GetStrings, where m and n are the count of total constructed alerts active alerts respectively. It also allocates intermediate data structures to hold the active alert strings for sorting. This changes the implementation to improve the performance of GetStrings, by shifting the sorting overhead to Alert.Set Constructing the Alert only initializes the alert's initial state, and initializes the SendableAlerts for the group if it is not already initialized. Activating or deactivating an alert sets an internal flag for state tracking, and inserts or removes a structure containing the timestamp and text into a self-sorting data structure (std::set, TreeSet) containing other active alerts with the same group and type. (worst case O(log(n)) Now, SendableAlerts.GetStrings only has to iterate over the structure and copy the strings to the returned array. (amortized O(n)) This also fixes the c++ implementation by removing the need for SendableAlerts to directly access the Alert. This also adds a helper method to SendableRegistry to force initialization of the instance to prevent static initialization ordering issues.
2024-11-06 18:09:06 -05:00
}
private String getSubtableName(Alert.AlertType type) {
switch (type) {
case kError:
return "errors";
case kWarning:
return "warnings";
case kInfo:
return "infos";
default:
return "unknown";
}
}
private StringArraySubscriber getSubscriberForType(Alert.AlertType type) {
return m_inst
[wpilib] Refactor Alert (#7279) This refactors Alert in both c++ and java to fix the issues with the current c++ implementation and improve performance. Currently, constructing an Alert adds it to a list of Alerts with the same group and type. Activating an alert sets a flag on the alert. When the SendableAlerts is polled (GetStrings), the entire list is iterated over, filtered, and the filtered list is sorted by timestamp. This leads to a worst case O(m + nlog(n)) time complexity for GetStrings, where m and n are the count of total constructed alerts active alerts respectively. It also allocates intermediate data structures to hold the active alert strings for sorting. This changes the implementation to improve the performance of GetStrings, by shifting the sorting overhead to Alert.Set Constructing the Alert only initializes the alert's initial state, and initializes the SendableAlerts for the group if it is not already initialized. Activating or deactivating an alert sets an internal flag for state tracking, and inserts or removes a structure containing the timestamp and text into a self-sorting data structure (std::set, TreeSet) containing other active alerts with the same group and type. (worst case O(log(n)) Now, SendableAlerts.GetStrings only has to iterate over the structure and copy the strings to the returned array. (amortized O(n)) This also fixes the c++ implementation by removing the need for SendableAlerts to directly access the Alert. This also adds a helper method to SendableRegistry to force initialization of the instance to prevent static initialization ordering issues.
2024-11-06 18:09:06 -05:00
.getStringArrayTopic("/SmartDashboard/" + m_groupName + "/" + getSubtableName(type))
.subscribe(new String[] {});
}
private String[] getActiveAlerts(AlertType type) {
update();
try (var sub = getSubscriberForType(type)) {
return sub.get();
}
}
private void update() {
SmartDashboard.updateValues();
}
private boolean isAlertActive(String text, Alert.AlertType type) {
return Arrays.asList(getActiveAlerts(type)).contains(text);
}
private void assertState(Alert.AlertType type, List<String> state) {
assertEquals(state, Arrays.asList(getActiveAlerts(type)));
}
private Alert makeAlert(String text, Alert.AlertType type) {
return new Alert(m_groupName, text, type);
}
@Test
void setUnsetSingle() {
try (var one = makeAlert("one", AlertType.kInfo)) {
assertFalse(isAlertActive("one", AlertType.kInfo));
one.set(true);
assertTrue(isAlertActive("one", AlertType.kInfo));
one.set(false);
assertFalse(isAlertActive("one", AlertType.kInfo));
}
}
@Test
void setUnsetMultiple() {
[wpilib] Refactor Alert (#7279) This refactors Alert in both c++ and java to fix the issues with the current c++ implementation and improve performance. Currently, constructing an Alert adds it to a list of Alerts with the same group and type. Activating an alert sets a flag on the alert. When the SendableAlerts is polled (GetStrings), the entire list is iterated over, filtered, and the filtered list is sorted by timestamp. This leads to a worst case O(m + nlog(n)) time complexity for GetStrings, where m and n are the count of total constructed alerts active alerts respectively. It also allocates intermediate data structures to hold the active alert strings for sorting. This changes the implementation to improve the performance of GetStrings, by shifting the sorting overhead to Alert.Set Constructing the Alert only initializes the alert's initial state, and initializes the SendableAlerts for the group if it is not already initialized. Activating or deactivating an alert sets an internal flag for state tracking, and inserts or removes a structure containing the timestamp and text into a self-sorting data structure (std::set, TreeSet) containing other active alerts with the same group and type. (worst case O(log(n)) Now, SendableAlerts.GetStrings only has to iterate over the structure and copy the strings to the returned array. (amortized O(n)) This also fixes the c++ implementation by removing the need for SendableAlerts to directly access the Alert. This also adds a helper method to SendableRegistry to force initialization of the instance to prevent static initialization ordering issues.
2024-11-06 18:09:06 -05:00
try (var one = makeAlert("one", AlertType.kError);
var two = makeAlert("two", AlertType.kInfo)) {
assertFalse(isAlertActive("one", AlertType.kError));
assertFalse(isAlertActive("two", AlertType.kInfo));
one.set(true);
assertTrue(isAlertActive("one", AlertType.kError));
assertFalse(isAlertActive("two", AlertType.kInfo));
one.set(true);
two.set(true);
assertTrue(isAlertActive("one", AlertType.kError));
assertTrue(isAlertActive("two", AlertType.kInfo));
one.set(false);
assertFalse(isAlertActive("one", AlertType.kError));
assertTrue(isAlertActive("two", AlertType.kInfo));
}
}
@Test
void setIsIdempotent() {
try (var a = makeAlert("A", AlertType.kInfo);
var b = makeAlert("B", AlertType.kInfo);
var c = makeAlert("C", AlertType.kInfo)) {
a.set(true);
b.set(true);
c.set(true);
var startState = List.of(getActiveAlerts(AlertType.kInfo));
b.set(true);
assertState(AlertType.kInfo, startState);
a.set(true);
assertState(AlertType.kInfo, startState);
}
}
@Test
void closeUnsetsAlert() {
try (var alert = makeAlert("alert", AlertType.kWarning)) {
alert.set(true);
assertTrue(isAlertActive("alert", AlertType.kWarning));
}
assertFalse(isAlertActive("alert", AlertType.kWarning));
}
@Test
void setTextWhileUnset() {
try (var alert = makeAlert("BEFORE", AlertType.kInfo)) {
assertEquals("BEFORE", alert.getText());
alert.set(true);
assertTrue(isAlertActive("BEFORE", AlertType.kInfo));
alert.set(false);
assertFalse(isAlertActive("BEFORE", AlertType.kInfo));
alert.setText("AFTER");
assertEquals("AFTER", alert.getText());
alert.set(true);
assertFalse(isAlertActive("BEFORE", AlertType.kInfo));
assertTrue(isAlertActive("AFTER", AlertType.kInfo));
}
}
@Test
void setTextWhileSet() {
try (var alert = makeAlert("BEFORE", AlertType.kInfo)) {
assertEquals("BEFORE", alert.getText());
alert.set(true);
assertTrue(isAlertActive("BEFORE", AlertType.kInfo));
alert.setText("AFTER");
assertEquals("AFTER", alert.getText());
assertFalse(isAlertActive("BEFORE", AlertType.kInfo));
assertTrue(isAlertActive("AFTER", AlertType.kInfo));
}
}
@ResourceLock("timing")
@Test
void setTextDoesNotAffectFirstOrderSort() {
SimHooks.pauseTiming();
try (var a = makeAlert("A", AlertType.kInfo);
var b = makeAlert("B", AlertType.kInfo);
var c = makeAlert("C", AlertType.kInfo)) {
a.set(true);
SimHooks.stepTiming(1);
b.set(true);
SimHooks.stepTiming(1);
c.set(true);
var expectedEndState = new ArrayList<>(List.of(getActiveAlerts(AlertType.kInfo)));
expectedEndState.replaceAll(s -> "B".equals(s) ? "AFTER" : s);
b.setText("AFTER");
assertState(AlertType.kInfo, expectedEndState);
} finally {
SimHooks.resumeTiming();
}
}
@ResourceLock("timing")
@Test
void sortOrder() {
SimHooks.pauseTiming();
try (var a = makeAlert("A", AlertType.kInfo);
var b = makeAlert("B", AlertType.kInfo);
var c = makeAlert("C", AlertType.kInfo)) {
a.set(true);
assertState(AlertType.kInfo, List.of("A"));
SimHooks.stepTiming(1);
b.set(true);
assertState(AlertType.kInfo, List.of("B", "A"));
SimHooks.stepTiming(1);
c.set(true);
assertState(AlertType.kInfo, List.of("C", "B", "A"));
SimHooks.stepTiming(1);
c.set(false);
assertState(AlertType.kInfo, List.of("B", "A"));
SimHooks.stepTiming(1);
c.set(true);
assertState(AlertType.kInfo, List.of("C", "B", "A"));
SimHooks.stepTiming(1);
a.set(false);
assertState(AlertType.kInfo, List.of("C", "B"));
SimHooks.stepTiming(1);
b.set(false);
assertState(AlertType.kInfo, List.of("C"));
SimHooks.stepTiming(1);
b.set(true);
assertState(AlertType.kInfo, List.of("B", "C"));
SimHooks.stepTiming(1);
a.set(true);
assertState(AlertType.kInfo, List.of("A", "B", "C"));
} finally {
SimHooks.resumeTiming();
}
}
}