diff --git a/wpilibc/src/main/native/cpp/IterativeRobotBase.cpp b/wpilibc/src/main/native/cpp/IterativeRobotBase.cpp index d783139aa1..0bccb0b49f 100644 --- a/wpilibc/src/main/native/cpp/IterativeRobotBase.cpp +++ b/wpilibc/src/main/native/cpp/IterativeRobotBase.cpp @@ -94,6 +94,18 @@ void IterativeRobotBase::SetNetworkTablesFlushEnabled(bool enabled) { m_ntFlushEnabled = enabled; } +void IterativeRobotBase::EnableLiveWindowInTest(bool testLW) { + if (IsTest()) { + throw FRC_MakeError(err::IncompatibleMode, + "Can't configure test mode while in test mode!"); + } + m_lwEnabledInTest = testLW; +} + +bool IterativeRobotBase::IsLiveWindowEnabledInTest() { + return m_lwEnabledInTest; +} + units::second_t IterativeRobotBase::GetPeriod() const { return m_period; } @@ -125,8 +137,10 @@ void IterativeRobotBase::LoopFunc() { } else if (m_lastMode == Mode::kTeleop) { TeleopExit(); } else if (m_lastMode == Mode::kTest) { - LiveWindow::SetEnabled(false); - Shuffleboard::DisableActuatorWidgets(); + if (m_lwEnabledInTest) { + LiveWindow::SetEnabled(false); + Shuffleboard::DisableActuatorWidgets(); + } TestExit(); } @@ -141,8 +155,10 @@ void IterativeRobotBase::LoopFunc() { TeleopInit(); m_watchdog.AddEpoch("TeleopInit()"); } else if (mode == Mode::kTest) { - LiveWindow::SetEnabled(true); - Shuffleboard::EnableActuatorWidgets(); + if (m_lwEnabledInTest) { + LiveWindow::SetEnabled(true); + Shuffleboard::EnableActuatorWidgets(); + } TestInit(); m_watchdog.AddEpoch("TestInit()"); } diff --git a/wpilibc/src/main/native/include/frc/IterativeRobotBase.h b/wpilibc/src/main/native/include/frc/IterativeRobotBase.h index 7392e673ce..77ed197479 100644 --- a/wpilibc/src/main/native/include/frc/IterativeRobotBase.h +++ b/wpilibc/src/main/native/include/frc/IterativeRobotBase.h @@ -201,6 +201,19 @@ class IterativeRobotBase : public RobotBase { */ void SetNetworkTablesFlushEnabled(bool enabled); + /** + * Sets whether LiveWindow operation is enabled during test mode. + * + * @param testLW True to enable, false to disable. Defaults to true. + * @throws if called in test mode. + */ + void EnableLiveWindowInTest(bool testLW); + + /** + * Whether LiveWindow operation is enabled during test mode. + */ + bool IsLiveWindowEnabledInTest(); + /** * Gets time period between calls to Periodic() functions. */ @@ -228,6 +241,7 @@ class IterativeRobotBase : public RobotBase { units::second_t m_period; Watchdog m_watchdog; bool m_ntFlushEnabled = true; + bool m_lwEnabledInTest = true; void PrintLoopOverrunMessage(); }; diff --git a/wpilibc/src/test/native/cpp/TimedRobotTest.cpp b/wpilibc/src/test/native/cpp/TimedRobotTest.cpp index 4ccce1b92d..08c262bc41 100644 --- a/wpilibc/src/test/native/cpp/TimedRobotTest.cpp +++ b/wpilibc/src/test/native/cpp/TimedRobotTest.cpp @@ -9,6 +9,7 @@ #include #include +#include "frc/livewindow/LiveWindow.h" #include "frc/simulation/DriverStationSim.h" #include "frc/simulation/SimHooks.h" #include "gtest/gtest.h" @@ -16,7 +17,7 @@ using namespace frc; namespace { -class TimedRobotTest : public ::testing::Test { +class TimedRobotTest : public ::testing::TestWithParam { protected: void SetUp() override { frc::sim::PauseTiming(); } @@ -304,8 +305,11 @@ TEST_F(TimedRobotTest, TeleopMode) { robotThread.join(); } -TEST_F(TimedRobotTest, TestMode) { +TEST_P(TimedRobotTest, TestMode) { + bool isTestLW = GetParam(); + MockRobot robot; + robot.EnableLiveWindowInTest(isTestLW); std::thread robotThread{[&] { robot.StartCompetition(); }}; @@ -321,6 +325,7 @@ TEST_F(TimedRobotTest, TestMode) { EXPECT_EQ(0u, robot.m_autonomousInitCount); EXPECT_EQ(0u, robot.m_teleopInitCount); EXPECT_EQ(0u, robot.m_testInitCount); + EXPECT_FALSE(frc::LiveWindow::IsEnabled()); EXPECT_EQ(0u, robot.m_robotPeriodicCount); EXPECT_EQ(0u, robot.m_simulationPeriodicCount); @@ -342,6 +347,9 @@ TEST_F(TimedRobotTest, TestMode) { EXPECT_EQ(0u, robot.m_autonomousInitCount); EXPECT_EQ(0u, robot.m_teleopInitCount); EXPECT_EQ(1u, robot.m_testInitCount); + EXPECT_EQ(isTestLW, frc::LiveWindow::IsEnabled()); + + EXPECT_THROW(robot.EnableLiveWindowInTest(isTestLW), std::runtime_error); EXPECT_EQ(1u, robot.m_robotPeriodicCount); EXPECT_EQ(1u, robot.m_simulationPeriodicCount); @@ -376,6 +384,32 @@ TEST_F(TimedRobotTest, TestMode) { EXPECT_EQ(0u, robot.m_teleopExitCount); EXPECT_EQ(0u, robot.m_testExitCount); + frc::sim::DriverStationSim::SetEnabled(false); + frc::sim::DriverStationSim::SetAutonomous(false); + frc::sim::DriverStationSim::SetTest(false); + frc::sim::DriverStationSim::NotifyNewData(); + frc::sim::StepTiming(20_ms); // Wait for Notifiers + + EXPECT_EQ(1u, robot.m_robotInitCount); + EXPECT_EQ(1u, robot.m_simulationInitCount); + EXPECT_EQ(1u, robot.m_disabledInitCount); + EXPECT_EQ(0u, robot.m_autonomousInitCount); + EXPECT_EQ(0u, robot.m_teleopInitCount); + EXPECT_EQ(1u, robot.m_testInitCount); + EXPECT_FALSE(frc::LiveWindow::IsEnabled()); + + EXPECT_EQ(3u, robot.m_robotPeriodicCount); + EXPECT_EQ(3u, robot.m_simulationPeriodicCount); + EXPECT_EQ(1u, robot.m_disabledPeriodicCount); + EXPECT_EQ(0u, robot.m_autonomousPeriodicCount); + EXPECT_EQ(0u, robot.m_teleopPeriodicCount); + EXPECT_EQ(2u, robot.m_testPeriodicCount); + + EXPECT_EQ(0u, robot.m_disabledExitCount); + EXPECT_EQ(0u, robot.m_autonomousExitCount); + EXPECT_EQ(0u, robot.m_teleopExitCount); + EXPECT_EQ(1u, robot.m_testExitCount); + robot.EndCompetition(); robotThread.join(); } @@ -572,3 +606,5 @@ TEST_F(TimedRobotTest, AddPeriodicWithOffset) { robot.EndCompetition(); robotThread.join(); } + +INSTANTIATE_TEST_SUITE_P(TimedRobotTests, TimedRobotTest, testing::Bool()); diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/IterativeRobotBase.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/IterativeRobotBase.java index 1115130a61..e0fe0344c0 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/IterativeRobotBase.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/IterativeRobotBase.java @@ -10,6 +10,7 @@ import edu.wpi.first.networktables.NetworkTableInstance; import edu.wpi.first.wpilibj.livewindow.LiveWindow; import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import java.util.ConcurrentModificationException; /** * IterativeRobotBase implements a specific type of robot program framework, extending the RobotBase @@ -67,6 +68,7 @@ public abstract class IterativeRobotBase extends RobotBase { private final double m_period; private final Watchdog m_watchdog; private boolean m_ntFlushEnabled = true; + private boolean m_lwEnabledInTest = true; /** * Constructor for IterativeRobotBase. @@ -244,6 +246,28 @@ public abstract class IterativeRobotBase extends RobotBase { m_ntFlushEnabled = enabled; } + /** + * Sets whether LiveWindow operation is enabled during test mode. Calling + * + * @param testLW True to enable, false to disable. Defaults to true. + * @throws ConcurrentModificationException if this is called during test mode. + */ + public void enableLiveWindowInTest(boolean testLW) { + if (isTest()) { + throw new ConcurrentModificationException("Can't configure test mode while in test mode!"); + } + m_lwEnabledInTest = testLW; + } + + /** + * Whether LiveWindow operation is enabled during test mode. + * + * @return whether LiveWindow should be enabled in test mode. + */ + public boolean isLiveWindowEnabledInTest() { + return m_lwEnabledInTest; + } + /** * Gets time period between calls to Periodic() functions. * @@ -281,8 +305,10 @@ public abstract class IterativeRobotBase extends RobotBase { } else if (m_lastMode == Mode.kTeleop) { teleopExit(); } else if (m_lastMode == Mode.kTest) { - LiveWindow.setEnabled(false); - Shuffleboard.disableActuatorWidgets(); + if (m_lwEnabledInTest) { + LiveWindow.setEnabled(false); + Shuffleboard.disableActuatorWidgets(); + } testExit(); } @@ -297,8 +323,10 @@ public abstract class IterativeRobotBase extends RobotBase { teleopInit(); m_watchdog.addEpoch("teleopInit()"); } else if (mode == Mode.kTest) { - LiveWindow.setEnabled(true); - Shuffleboard.enableActuatorWidgets(); + if (m_lwEnabledInTest) { + LiveWindow.setEnabled(true); + Shuffleboard.enableActuatorWidgets(); + } testInit(); m_watchdog.addEpoch("testInit()"); } diff --git a/wpilibj/src/test/java/edu/wpi/first/wpilibj/TimedRobotTest.java b/wpilibj/src/test/java/edu/wpi/first/wpilibj/TimedRobotTest.java index de5fd1cc0d..bf143e79ab 100644 --- a/wpilibj/src/test/java/edu/wpi/first/wpilibj/TimedRobotTest.java +++ b/wpilibj/src/test/java/edu/wpi/first/wpilibj/TimedRobotTest.java @@ -5,14 +5,20 @@ 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.assertThrows; +import edu.wpi.first.wpilibj.livewindow.LiveWindow; import edu.wpi.first.wpilibj.simulation.DriverStationSim; import edu.wpi.first.wpilibj.simulation.SimHooks; +import java.util.ConcurrentModificationException; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class TimedRobotTest { static class MockRobot extends TimedRobot { @@ -119,6 +125,7 @@ class TimedRobotTest { @BeforeEach void setup() { SimHooks.pauseTiming(); + DriverStationSim.resetData(); } @AfterEach @@ -391,10 +398,12 @@ class TimedRobotTest { robot.close(); } - @Test + @ValueSource(booleans = {true, false}) + @ParameterizedTest @ResourceLock("timing") - void testModeTest() { + void testModeTest(boolean isLW) { MockRobot robot = new MockRobot(); + robot.enableLiveWindowInTest(isLW); Thread robotThread = new Thread( @@ -415,6 +424,7 @@ class TimedRobotTest { assertEquals(0, robot.m_autonomousInitCount.get()); assertEquals(0, robot.m_teleopInitCount.get()); assertEquals(0, robot.m_testInitCount.get()); + assertFalse(LiveWindow.isEnabled()); assertEquals(0, robot.m_robotPeriodicCount.get()); assertEquals(0, robot.m_simulationPeriodicCount.get()); @@ -457,6 +467,9 @@ class TimedRobotTest { assertEquals(0, robot.m_autonomousInitCount.get()); assertEquals(0, robot.m_teleopInitCount.get()); assertEquals(1, robot.m_testInitCount.get()); + assertEquals(isLW, LiveWindow.isEnabled()); + + assertThrows(ConcurrentModificationException.class, () -> robot.enableLiveWindowInTest(isLW)); assertEquals(2, robot.m_robotPeriodicCount.get()); assertEquals(2, robot.m_simulationPeriodicCount.get()); @@ -470,6 +483,33 @@ class TimedRobotTest { assertEquals(0, robot.m_teleopExitCount.get()); assertEquals(0, robot.m_testExitCount.get()); + DriverStationSim.setEnabled(false); + DriverStationSim.setAutonomous(false); + DriverStationSim.setTest(false); + DriverStationSim.notifyNewData(); + + SimHooks.stepTiming(0.02); + + assertEquals(1, robot.m_robotInitCount.get()); + assertEquals(1, robot.m_simulationInitCount.get()); + assertEquals(1, robot.m_disabledInitCount.get()); + assertEquals(0, robot.m_autonomousInitCount.get()); + assertEquals(0, robot.m_teleopInitCount.get()); + assertEquals(1, robot.m_testInitCount.get()); + assertFalse(LiveWindow.isEnabled()); + + assertEquals(3, robot.m_robotPeriodicCount.get()); + assertEquals(3, robot.m_simulationPeriodicCount.get()); + assertEquals(1, robot.m_disabledPeriodicCount.get()); + assertEquals(0, robot.m_autonomousPeriodicCount.get()); + assertEquals(0, robot.m_teleopPeriodicCount.get()); + assertEquals(2, robot.m_testPeriodicCount.get()); + + assertEquals(0, robot.m_disabledExitCount.get()); + assertEquals(0, robot.m_autonomousExitCount.get()); + assertEquals(0, robot.m_teleopExitCount.get()); + assertEquals(1, robot.m_testExitCount.get()); + robot.endCompetition(); try { robotThread.interrupt();