From 79f565191eaa04f8b1ef1c1910b29e792f68b09e Mon Sep 17 00:00:00 2001 From: Starlight220 <53231611+Starlight220@users.noreply.github.com> Date: Mon, 9 Jan 2023 02:33:53 +0200 Subject: [PATCH] [examples] DigitalCommunication, I2CCommunication: Add tests (#4865) --- .../DigitalCommunication/cpp/Robot.cpp | 49 ++---- .../DigitalCommunication/include/Robot.h | 31 ++++ .../examples/I2CCommunication/cpp/Robot.cpp | 55 +++--- .../examples/I2CCommunication/include/Robot.h | 25 +++ .../cpp/DigitalCommunicationTest.cpp | 153 +++++++++++++++++ .../DigitalCommunication/cpp/main.cpp | 17 ++ .../cpp/I2CCommunicationTest.cpp | 160 ++++++++++++++++++ .../examples/I2CCommunication/cpp/main.cpp | 17 ++ .../examples/digitalcommunication/Robot.java | 18 +- .../wpi/first/wpilibj/examples/examples.json | 3 +- .../examples/i2ccommunication/Robot.java | 15 +- .../DigitalCommunicationTest.java | 114 +++++++++++++ .../I2CCommunicationTest.java | 126 ++++++++++++++ 13 files changed, 709 insertions(+), 74 deletions(-) create mode 100644 wpilibcExamples/src/main/cpp/examples/DigitalCommunication/include/Robot.h create mode 100644 wpilibcExamples/src/main/cpp/examples/I2CCommunication/include/Robot.h create mode 100644 wpilibcExamples/src/test/cpp/examples/DigitalCommunication/cpp/DigitalCommunicationTest.cpp create mode 100644 wpilibcExamples/src/test/cpp/examples/DigitalCommunication/cpp/main.cpp create mode 100644 wpilibcExamples/src/test/cpp/examples/I2CCommunication/cpp/I2CCommunicationTest.cpp create mode 100644 wpilibcExamples/src/test/cpp/examples/I2CCommunication/cpp/main.cpp create mode 100644 wpilibjExamples/src/test/java/edu/wpi/first/wpilibj/examples/digitalcommunication/DigitalCommunicationTest.java create mode 100644 wpilibjExamples/src/test/java/edu/wpi/first/wpilibj/examples/i2ccommunication/I2CCommunicationTest.java diff --git a/wpilibcExamples/src/main/cpp/examples/DigitalCommunication/cpp/Robot.cpp b/wpilibcExamples/src/main/cpp/examples/DigitalCommunication/cpp/Robot.cpp index 037fb1dbc1..fa43e98d8d 100644 --- a/wpilibcExamples/src/main/cpp/examples/DigitalCommunication/cpp/Robot.cpp +++ b/wpilibcExamples/src/main/cpp/examples/DigitalCommunication/cpp/Robot.cpp @@ -2,45 +2,28 @@ // 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 +#include "Robot.h" + #include -#include -/** - * This is a sample program demonstrating how to communicate to a light - * controller from the robot code using the roboRIO's DIO ports. - */ -class Robot : public frc::TimedRobot { - public: - void RobotPeriodic() override { - // pull alliance port high if on red alliance, pull low if on blue alliance - m_allianceOutput.Set(frc::DriverStation::GetAlliance() == - frc::DriverStation::kRed); +void Robot::RobotPeriodic() { + // pull alliance port high if on red alliance, pull low if on blue alliance + m_allianceOutput.Set(frc::DriverStation::GetAlliance() == + frc::DriverStation::kRed); - // pull enabled port high if enabled, low if disabled - m_enabledOutput.Set(frc::DriverStation::IsEnabled()); + // pull enabled port high if enabled, low if disabled + m_enabledOutput.Set(frc::DriverStation::IsEnabled()); - // pull auto port high if in autonomous, low if in teleop (or disabled) - m_autonomousOutput.Set(frc::DriverStation::IsAutonomous()); + // pull auto port high if in autonomous, low if in teleop (or disabled) + m_autonomousOutput.Set(frc::DriverStation::IsAutonomous()); - // pull alert port high if match time remaining is between 30 and 25 seconds - auto matchTime = frc::DriverStation::GetMatchTime(); - m_alertOutput.Set(matchTime <= 30 && matchTime >= 25); - } - - private: - // define ports for communication with light controller - static constexpr int kAlliancePort = 0; - static constexpr int kEnabledPort = 1; - static constexpr int kAutonomousPort = 2; - static constexpr int kAlertPort = 3; - - frc::DigitalOutput m_allianceOutput{kAlliancePort}; - frc::DigitalOutput m_enabledOutput{kEnabledPort}; - frc::DigitalOutput m_autonomousOutput{kAutonomousPort}; - frc::DigitalOutput m_alertOutput{kAlertPort}; -}; + // pull alert port high if match time remaining is between 30 and 25 seconds + auto matchTime = frc::DriverStation::GetMatchTime(); + m_alertOutput.Set(matchTime <= 30 && matchTime >= 25); +} +#ifndef RUNNING_FRC_TESTS int main() { return frc::StartRobot(); } +#endif diff --git a/wpilibcExamples/src/main/cpp/examples/DigitalCommunication/include/Robot.h b/wpilibcExamples/src/main/cpp/examples/DigitalCommunication/include/Robot.h new file mode 100644 index 0000000000..17ec6805ed --- /dev/null +++ b/wpilibcExamples/src/main/cpp/examples/DigitalCommunication/include/Robot.h @@ -0,0 +1,31 @@ +// 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. + +#pragma once + +#include + +#include +#include + +/** + * This is a sample program demonstrating how to communicate to a light + * controller from the robot code using the roboRIO's DIO ports. + */ +class Robot : public frc::TimedRobot { + public: + // define ports for communication with light controller + static constexpr int kAlliancePort = 0; + static constexpr int kEnabledPort = 1; + static constexpr int kAutonomousPort = 2; + static constexpr int kAlertPort = 3; + + void RobotPeriodic() override; + + private: + frc::DigitalOutput m_allianceOutput{kAlliancePort}; + frc::DigitalOutput m_enabledOutput{kEnabledPort}; + frc::DigitalOutput m_autonomousOutput{kAutonomousPort}; + frc::DigitalOutput m_alertOutput{kAlertPort}; +}; diff --git a/wpilibcExamples/src/main/cpp/examples/I2CCommunication/cpp/Robot.cpp b/wpilibcExamples/src/main/cpp/examples/I2CCommunication/cpp/Robot.cpp index fad7581ff8..c927fb4d2b 100644 --- a/wpilibcExamples/src/main/cpp/examples/I2CCommunication/cpp/Robot.cpp +++ b/wpilibcExamples/src/main/cpp/examples/I2CCommunication/cpp/Robot.cpp @@ -2,45 +2,34 @@ // 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 "Robot.h" + #include #include -#include -#include #include -/** - * This is a sample program demonstrating how to communicate to a light - * controller from the robot code using the roboRIO's I2C port. - */ -class Robot : public frc::TimedRobot { - public: - void RobotPeriodic() override { - // Creates a string to hold current robot state information, including - // alliance, enabled state, operation mode, and match time. The message - // is sent in format "AEM###" where A is the alliance color, (R)ed or - // (B)lue, E is the enabled state, (E)nabled or (D)isabled, M is the - // operation mode, (A)utonomous or (T)eleop, and ### is the zero-padded - // time remaining in the match. - // - // For example, "RET043" would indicate that the robot is on the red - // alliance, enabled in teleop mode, with 43 seconds left in the match. - auto string = fmt::format( - "{}{}{}{:03}", - frc::DriverStation::GetAlliance() == frc::DriverStation::Alliance::kRed - ? "R" - : "B", - frc::DriverStation::IsEnabled() ? "E" : "D", - frc::DriverStation::IsAutonomous() ? "A" : "T", - static_cast(frc::Timer::GetMatchTime().value())); +void Robot::RobotPeriodic() { + // Creates a string to hold current robot state information, including + // alliance, enabled state, operation mode, and match time. The message + // is sent in format "AEM###" where A is the alliance color, (R)ed or + // (B)lue, E is the enabled state, (E)nabled or (D)isabled, M is the + // operation mode, (A)utonomous or (T)eleop, and ### is the zero-padded + // time remaining in the match. + // + // For example, "RET043" would indicate that the robot is on the red + // alliance, enabled in teleop mode, with 43 seconds left in the match. + auto string = fmt::format( + "{}{}{}{:03}", + frc::DriverStation::GetAlliance() == frc::DriverStation::Alliance::kRed + ? "R" + : "B", + frc::DriverStation::IsEnabled() ? "E" : "D", + frc::DriverStation::IsAutonomous() ? "A" : "T", + static_cast(frc::Timer::GetMatchTime().value())); - arduino.WriteBulk(reinterpret_cast(string.data()), string.size()); - } - - private: - static constexpr int deviceAddress = 4; - frc::I2C arduino{frc::I2C::Port::kOnboard, deviceAddress}; -}; + arduino.WriteBulk(reinterpret_cast(string.data()), string.size()); +} #ifndef RUNNING_FRC_TESTS int main() { diff --git a/wpilibcExamples/src/main/cpp/examples/I2CCommunication/include/Robot.h b/wpilibcExamples/src/main/cpp/examples/I2CCommunication/include/Robot.h new file mode 100644 index 0000000000..ede2624cd8 --- /dev/null +++ b/wpilibcExamples/src/main/cpp/examples/I2CCommunication/include/Robot.h @@ -0,0 +1,25 @@ +// 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. + +#pragma once + +#include + +#include +#include + +/** + * This is a sample program demonstrating how to communicate to a light + * controller from the robot code using the roboRIO's I2C port. + */ +class Robot : public frc::TimedRobot { + public: + void RobotPeriodic() override; + + static constexpr frc::I2C::Port kPort = frc::I2C::Port::kOnboard; + + private: + static constexpr int deviceAddress = 4; + frc::I2C arduino{kPort, deviceAddress}; +}; diff --git a/wpilibcExamples/src/test/cpp/examples/DigitalCommunication/cpp/DigitalCommunicationTest.cpp b/wpilibcExamples/src/test/cpp/examples/DigitalCommunication/cpp/DigitalCommunicationTest.cpp new file mode 100644 index 0000000000..4422453f8a --- /dev/null +++ b/wpilibcExamples/src/test/cpp/examples/DigitalCommunication/cpp/DigitalCommunicationTest.cpp @@ -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 + +#include +#include + +#include +#include +#include +#include + +#include "Robot.h" + +template +class DigitalCommunicationTest : public testing::TestWithParam { + public: + frc::sim::DIOSim m_allianceOutput{Robot::kAlliancePort}; + frc::sim::DIOSim m_enabledOutput{Robot::kEnabledPort}; + frc::sim::DIOSim m_autonomousOutput{Robot::kAutonomousPort}; + frc::sim::DIOSim m_alertOutput{Robot::kAlertPort}; + Robot m_robot; + std::optional m_thread; + + void SetUp() override { + frc::sim::PauseTiming(); + frc::sim::DriverStationSim::ResetData(); + + m_thread = std::thread([&] { m_robot.StartCompetition(); }); + frc::sim::StepTiming(0.0_ms); + // SimHooks.stepTiming(0.0); // Wait for Notifiers + } + + void TearDown() override { + m_robot.EndCompetition(); + m_thread->join(); + m_allianceOutput.ResetData(); + m_enabledOutput.ResetData(); + m_autonomousOutput.ResetData(); + m_alertOutput.ResetData(); + } +}; + +class AllianceTest : public DigitalCommunicationTest {}; + +TEST_P(AllianceTest, Alliance) { + auto alliance = GetParam(); + frc::sim::DriverStationSim::SetAllianceStationId(alliance); + frc::sim::DriverStationSim::NotifyNewData(); + + EXPECT_TRUE(m_allianceOutput.GetInitialized()); + EXPECT_FALSE(m_allianceOutput.GetIsInput()); + + frc::sim::StepTiming(20_ms); + + bool isRed; + switch (alliance) { + case HAL_AllianceStationID_kBlue1: + case HAL_AllianceStationID_kBlue2: + case HAL_AllianceStationID_kBlue3: + isRed = false; + break; + case HAL_AllianceStationID_kRed1: + case HAL_AllianceStationID_kRed2: + case HAL_AllianceStationID_kRed3: + isRed = true; + break; + } + EXPECT_EQ(isRed, m_allianceOutput.GetValue()); +} + +INSTANTIATE_TEST_SUITE_P( + DigitalCommunicationTests, AllianceTest, + testing::Values( + HAL_AllianceStationID_kRed1, HAL_AllianceStationID_kRed2, + HAL_AllianceStationID_kRed3, HAL_AllianceStationID_kBlue1, + HAL_AllianceStationID_kBlue2, HAL_AllianceStationID_kBlue3), + [](const testing::TestParamInfo& info) { + switch (info.param) { + case HAL_AllianceStationID_kBlue1: + return std::string{"Blue1"}; + case HAL_AllianceStationID_kBlue2: + return std::string{"Blue2"}; + case HAL_AllianceStationID_kBlue3: + return std::string{"Blue3"}; + case HAL_AllianceStationID_kRed1: + return std::string{"Red1"}; + case HAL_AllianceStationID_kRed2: + return std::string{"Red2"}; + case HAL_AllianceStationID_kRed3: + return std::string{"Red3"}; + } + return std::string{"Error"}; + }); + +class EnabledTest : public DigitalCommunicationTest {}; + +TEST_P(EnabledTest, Enabled) { + auto enabled = GetParam(); + frc::sim::DriverStationSim::SetEnabled(enabled); + frc::sim::DriverStationSim::NotifyNewData(); + + EXPECT_TRUE(m_enabledOutput.GetInitialized()); + EXPECT_FALSE(m_enabledOutput.GetIsInput()); + + frc::sim::StepTiming(20_ms); + + EXPECT_EQ(enabled, m_enabledOutput.GetValue()); +} + +INSTANTIATE_TEST_SUITE_P(DigitalCommunicationTests, EnabledTest, + testing::Bool(), testing::PrintToStringParamName()); + +class AutonomousTest : public DigitalCommunicationTest {}; + +TEST_P(AutonomousTest, Autonomous) { + auto autonomous = GetParam(); + frc::sim::DriverStationSim::SetAutonomous(autonomous); + frc::sim::DriverStationSim::NotifyNewData(); + + EXPECT_TRUE(m_autonomousOutput.GetInitialized()); + EXPECT_FALSE(m_autonomousOutput.GetIsInput()); + + frc::sim::StepTiming(20_ms); + + EXPECT_EQ(autonomous, m_autonomousOutput.GetValue()); +} + +INSTANTIATE_TEST_SUITE_P(DigitalCommunicationTests, AutonomousTest, + testing::Bool(), testing::PrintToStringParamName()); + +class AlertTest : public DigitalCommunicationTest {}; + +TEST_P(AlertTest, Alert) { + auto matchTime = GetParam(); + frc::sim::DriverStationSim::SetMatchTime(matchTime); + frc::sim::DriverStationSim::NotifyNewData(); + + EXPECT_TRUE(m_alertOutput.GetInitialized()); + EXPECT_FALSE(m_alertOutput.GetIsInput()); + + frc::sim::StepTiming(20_ms); + + EXPECT_EQ(matchTime <= 30 && matchTime >= 25, m_alertOutput.GetValue()); +} + +INSTANTIATE_TEST_SUITE_P( + DigitalCommunicationTests, AlertTest, testing::Values(45.0, 27.0, 23.0), + [](const testing::TestParamInfo& info) { + return testing::PrintToString(info.param).append("_s"); + }); diff --git a/wpilibcExamples/src/test/cpp/examples/DigitalCommunication/cpp/main.cpp b/wpilibcExamples/src/test/cpp/examples/DigitalCommunication/cpp/main.cpp new file mode 100644 index 0000000000..285c1d5267 --- /dev/null +++ b/wpilibcExamples/src/test/cpp/examples/DigitalCommunication/cpp/main.cpp @@ -0,0 +1,17 @@ +// 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 + +#include "gtest/gtest.h" + +/** + * Runs all unit tests. + */ +int main(int argc, char** argv) { + HAL_Initialize(500, 0); + ::testing::InitGoogleTest(&argc, argv); + int ret = RUN_ALL_TESTS(); + return ret; +} diff --git a/wpilibcExamples/src/test/cpp/examples/I2CCommunication/cpp/I2CCommunicationTest.cpp b/wpilibcExamples/src/test/cpp/examples/I2CCommunication/cpp/I2CCommunicationTest.cpp new file mode 100644 index 0000000000..f78fab60c9 --- /dev/null +++ b/wpilibcExamples/src/test/cpp/examples/I2CCommunication/cpp/I2CCommunicationTest.cpp @@ -0,0 +1,160 @@ +// 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 + +#include +#include + +#include +#include +#include +#include + +#include "Robot.h" + +static std::string gString; + +void callback(const char* name, void* param, const unsigned char* buffer, + unsigned int count) { + gString.assign(reinterpret_cast(buffer), + static_cast(count)); +} + +template +class I2CCommunicationTest : public testing::TestWithParam { + public: + Robot m_robot; + std::optional m_thread; + int32_t m_callback; + int32_t m_port; + + void SetUp() override { + gString = std::string(); + frc::sim::PauseTiming(); + frc::sim::DriverStationSim::ResetData(); + m_port = static_cast(Robot::kPort); + + m_callback = HALSIM_RegisterI2CWriteCallback(m_port, &callback, nullptr); + + m_thread = std::thread([&] { m_robot.StartCompetition(); }); + frc::sim::StepTiming(0.0_ms); // Wait for Notifiers + } + + void TearDown() override { + m_robot.EndCompetition(); + m_thread->join(); + + HALSIM_CancelI2CWriteCallback(m_port, m_callback); + HALSIM_ResetI2CData(m_port); + } +}; + +class AllianceTest : public I2CCommunicationTest {}; + +TEST_P(AllianceTest, Alliance) { + auto alliance = GetParam(); + frc::sim::DriverStationSim::SetAllianceStationId(alliance); + frc::sim::DriverStationSim::NotifyNewData(); + + EXPECT_TRUE(HALSIM_GetI2CInitialized(m_port)); + + frc::sim::StepTiming(20_ms); + + char expected; + switch (alliance) { + case HAL_AllianceStationID_kBlue1: + case HAL_AllianceStationID_kBlue2: + case HAL_AllianceStationID_kBlue3: + expected = 'B'; + break; + case HAL_AllianceStationID_kRed1: + case HAL_AllianceStationID_kRed2: + case HAL_AllianceStationID_kRed3: + expected = 'R'; + break; + } + EXPECT_EQ(expected, gString.at(0)); +} + +INSTANTIATE_TEST_SUITE_P( + I2CCommunicationTests, AllianceTest, + testing::Values( + HAL_AllianceStationID_kRed1, HAL_AllianceStationID_kRed2, + HAL_AllianceStationID_kRed3, HAL_AllianceStationID_kBlue1, + HAL_AllianceStationID_kBlue2, HAL_AllianceStationID_kBlue3), + [](const testing::TestParamInfo& info) { + switch (info.param) { + case HAL_AllianceStationID_kBlue1: + return std::string{"Blue1"}; + case HAL_AllianceStationID_kBlue2: + return std::string{"Blue2"}; + case HAL_AllianceStationID_kBlue3: + return std::string{"Blue3"}; + case HAL_AllianceStationID_kRed1: + return std::string{"Red1"}; + case HAL_AllianceStationID_kRed2: + return std::string{"Red2"}; + case HAL_AllianceStationID_kRed3: + return std::string{"Red3"}; + } + return std::string{"Error"}; + }); + +class EnabledTest : public I2CCommunicationTest {}; + +TEST_P(EnabledTest, Enabled) { + auto enabled = GetParam(); + frc::sim::DriverStationSim::SetEnabled(enabled); + frc::sim::DriverStationSim::NotifyNewData(); + + EXPECT_TRUE(HALSIM_GetI2CInitialized(m_port)); + + frc::sim::StepTiming(20_ms); + + char expected = enabled ? 'E' : 'D'; + EXPECT_EQ(expected, gString.at(1)); +} + +INSTANTIATE_TEST_SUITE_P(I2CCommunicationTests, EnabledTest, testing::Bool(), + testing::PrintToStringParamName()); + +class AutonomousTest : public I2CCommunicationTest {}; + +TEST_P(AutonomousTest, Autonomous) { + auto autonomous = GetParam(); + frc::sim::DriverStationSim::SetAutonomous(autonomous); + frc::sim::DriverStationSim::NotifyNewData(); + + EXPECT_TRUE(HALSIM_GetI2CInitialized(m_port)); + + frc::sim::StepTiming(20_ms); + + char expected = autonomous ? 'A' : 'T'; + EXPECT_EQ(expected, gString.at(2)); +} + +INSTANTIATE_TEST_SUITE_P(I2CCommunicationTests, AutonomousTest, testing::Bool(), + testing::PrintToStringParamName()); + +class MatchTimeTest : public I2CCommunicationTest {}; + +TEST_P(MatchTimeTest, Alert) { + auto matchTime = GetParam(); + frc::sim::DriverStationSim::SetMatchTime(matchTime); + frc::sim::DriverStationSim::NotifyNewData(); + + EXPECT_TRUE(HALSIM_GetI2CInitialized(m_port)); + + frc::sim::StepTiming(20_ms); + + std::string expected = fmt::format("{:03}", matchTime); + EXPECT_EQ(expected, gString.substr(3)); +} + +INSTANTIATE_TEST_SUITE_P( + I2CCommunicationTests, MatchTimeTest, testing::Values(112, 45, 27, 23, 3), + [](const testing::TestParamInfo& info) { + return testing::PrintToString(info.param).append("_s"); + }); diff --git a/wpilibcExamples/src/test/cpp/examples/I2CCommunication/cpp/main.cpp b/wpilibcExamples/src/test/cpp/examples/I2CCommunication/cpp/main.cpp new file mode 100644 index 0000000000..285c1d5267 --- /dev/null +++ b/wpilibcExamples/src/test/cpp/examples/I2CCommunication/cpp/main.cpp @@ -0,0 +1,17 @@ +// 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 + +#include "gtest/gtest.h" + +/** + * Runs all unit tests. + */ +int main(int argc, char** argv) { + HAL_Initialize(500, 0); + ::testing::InitGoogleTest(&argc, argv); + int ret = RUN_ALL_TESTS(); + return ret; +} diff --git a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/digitalcommunication/Robot.java b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/digitalcommunication/Robot.java index 85ff8d636a..0eaa0e19ed 100644 --- a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/digitalcommunication/Robot.java +++ b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/digitalcommunication/Robot.java @@ -14,10 +14,10 @@ import edu.wpi.first.wpilibj.TimedRobot; */ public class Robot extends TimedRobot { // define ports for digitalcommunication with light controller - private static final int kAlliancePort = 0; - private static final int kEnabledPort = 1; - private static final int kAutonomousPort = 2; - private static final int kAlertPort = 3; + static final int kAlliancePort = 0; + static final int kEnabledPort = 1; + static final int kAutonomousPort = 2; + static final int kAlertPort = 3; private final DigitalOutput m_allianceOutput = new DigitalOutput(kAlliancePort); private final DigitalOutput m_enabledOutput = new DigitalOutput(kEnabledPort); @@ -39,4 +39,14 @@ public class Robot extends TimedRobot { var matchTime = DriverStation.getMatchTime(); m_alertOutput.set(matchTime <= 30 && matchTime >= 25); } + + /** Close all resources. */ + @Override + public void close() { + m_allianceOutput.close(); + m_enabledOutput.close(); + m_autonomousOutput.close(); + m_alertOutput.close(); + super.close(); + } } diff --git a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/examples.json b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/examples.json index 551348221d..468b1f4d7e 100644 --- a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/examples.json +++ b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/examples.json @@ -803,7 +803,8 @@ "name": "Digital Communication Sample", "description": "An example program that communicates with external devices (such as an Arduino) using the roboRIO's DIO", "tags": [ - "Digital" + "Digital", + "Unit Testing" ], "foldername": "digitalcommunication", "gradlebase": "java", diff --git a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/i2ccommunication/Robot.java b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/i2ccommunication/Robot.java index 9c1e6230c6..cbd9abed54 100644 --- a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/i2ccommunication/Robot.java +++ b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/i2ccommunication/Robot.java @@ -6,6 +6,7 @@ package edu.wpi.first.wpilibj.examples.i2ccommunication; import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.I2C; +import edu.wpi.first.wpilibj.I2C.Port; import edu.wpi.first.wpilibj.TimedRobot; /** @@ -13,9 +14,10 @@ import edu.wpi.first.wpilibj.TimedRobot; * code using the roboRIO's I2C port. */ public class Robot extends TimedRobot { - private static int kDeviceAddress = 4; + static final Port kPort = Port.kOnboard; + private static final int kDeviceAddress = 4; - private static I2C m_arduino = new I2C(I2C.Port.kOnboard, kDeviceAddress); + private final I2C m_arduino = new I2C(kPort, kDeviceAddress); private void writeString(String input) { // Creates a char array from the input string @@ -30,7 +32,7 @@ public class Robot extends TimedRobot { } // Writes bytes over I2C - m_arduino.transaction(data, data.length, null, 0); + m_arduino.transaction(data, data.length, new byte[] {}, 0); } @Override @@ -54,4 +56,11 @@ public class Robot extends TimedRobot { writeString(stateMessage.toString()); } + + /** Close all resources. */ + @Override + public void close() { + m_arduino.close(); + super.close(); + } } diff --git a/wpilibjExamples/src/test/java/edu/wpi/first/wpilibj/examples/digitalcommunication/DigitalCommunicationTest.java b/wpilibjExamples/src/test/java/edu/wpi/first/wpilibj/examples/digitalcommunication/DigitalCommunicationTest.java new file mode 100644 index 0000000000..9f11dc9e3c --- /dev/null +++ b/wpilibjExamples/src/test/java/edu/wpi/first/wpilibj/examples/digitalcommunication/DigitalCommunicationTest.java @@ -0,0 +1,114 @@ +// 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.examples.digitalcommunication; + +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.hal.AllianceStationID; +import edu.wpi.first.hal.HAL; +import edu.wpi.first.wpilibj.simulation.DIOSim; +import edu.wpi.first.wpilibj.simulation.DriverStationSim; +import edu.wpi.first.wpilibj.simulation.SimHooks; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; + +@ResourceLock("timing") +class DigitalCommunicationTest { + private Robot m_robot; + private Thread m_thread; + private final DIOSim m_allianceOutput = new DIOSim(Robot.kAlliancePort); + private final DIOSim m_enabledOutput = new DIOSim(Robot.kEnabledPort); + private final DIOSim m_autonomousOutput = new DIOSim(Robot.kAutonomousPort); + private final DIOSim m_alertOutput = new DIOSim(Robot.kAlertPort); + + @BeforeEach + void startThread() { + HAL.initialize(500, 0); + SimHooks.pauseTiming(); + DriverStationSim.resetData(); + m_robot = new Robot(); + m_thread = new Thread(m_robot::startCompetition); + m_thread.start(); + SimHooks.stepTiming(0.0); // Wait for Notifiers + } + + @AfterEach + void stopThread() { + m_robot.endCompetition(); + try { + m_thread.interrupt(); + m_thread.join(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + m_robot.close(); + m_allianceOutput.resetData(); + m_enabledOutput.resetData(); + m_autonomousOutput.resetData(); + m_alertOutput.resetData(); + } + + @EnumSource(AllianceStationID.class) + @ParameterizedTest(name = "alliance[{index}]: {0}") + void allianceTest(AllianceStationID alliance) { + DriverStationSim.setAllianceStationId(alliance); + DriverStationSim.notifyNewData(); + + assertTrue(m_allianceOutput.getInitialized()); + assertFalse(m_allianceOutput.getIsInput()); + + SimHooks.stepTiming(0.02); + + assertEquals(alliance.name().startsWith("Red"), m_allianceOutput.getValue()); + } + + @ValueSource(booleans = {true, false}) + @ParameterizedTest(name = "enabled[{index}]: {0}") + void enabledTest(boolean enabled) { + DriverStationSim.setEnabled(enabled); + DriverStationSim.notifyNewData(); + + assertTrue(m_enabledOutput.getInitialized()); + assertFalse(m_enabledOutput.getIsInput()); + + SimHooks.stepTiming(0.02); + + assertEquals(enabled, m_enabledOutput.getValue()); + } + + @ValueSource(booleans = {true, false}) + @ParameterizedTest(name = "autonomous[{index}]: {0}") + void autonomousTest(boolean autonomous) { + DriverStationSim.setAutonomous(autonomous); + DriverStationSim.notifyNewData(); + + assertTrue(m_autonomousOutput.getInitialized()); + assertFalse(m_autonomousOutput.getIsInput()); + + SimHooks.stepTiming(0.02); + + assertEquals(autonomous, m_autonomousOutput.getValue()); + } + + @ValueSource(doubles = {45.0, 27.0, 23.0}) + @ParameterizedTest(name = "alert[{index}]: {0}s") + void alertTest(double matchTime) { + DriverStationSim.setMatchTime(matchTime); + DriverStationSim.notifyNewData(); + + assertTrue(m_alertOutput.getInitialized()); + assertFalse(m_alertOutput.getIsInput()); + + SimHooks.stepTiming(0.02); + + assertEquals(matchTime <= 30 && matchTime >= 25, m_alertOutput.getValue()); + } +} diff --git a/wpilibjExamples/src/test/java/edu/wpi/first/wpilibj/examples/i2ccommunication/I2CCommunicationTest.java b/wpilibjExamples/src/test/java/edu/wpi/first/wpilibj/examples/i2ccommunication/I2CCommunicationTest.java new file mode 100644 index 0000000000..9848c11aa4 --- /dev/null +++ b/wpilibjExamples/src/test/java/edu/wpi/first/wpilibj/examples/i2ccommunication/I2CCommunicationTest.java @@ -0,0 +1,126 @@ +// 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.examples.i2ccommunication; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import edu.wpi.first.hal.AllianceStationID; +import edu.wpi.first.hal.HAL; +import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj.simulation.CallbackStore; +import edu.wpi.first.wpilibj.simulation.DriverStationSim; +import edu.wpi.first.wpilibj.simulation.I2CSim; +import edu.wpi.first.wpilibj.simulation.SimHooks; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; + +@ResourceLock("timing") +class I2CCommunicationTest { + private Robot m_robot; + private Thread m_thread; + private final I2CSim m_i2c = new I2CSim(Robot.kPort.value); + private CompletableFuture m_future; + private CallbackStore m_callback; + + @BeforeEach + void startThread() { + HAL.initialize(500, 0); + SimHooks.pauseTiming(); + DriverStationSim.resetData(); + m_future = new CompletableFuture<>(); + m_callback = + m_i2c.registerWriteCallback( + (name, buffer, count) -> m_future.complete(new String(buffer, 0, count))); + m_robot = new Robot(); + m_thread = new Thread(m_robot::startCompetition); + m_thread.start(); + SimHooks.stepTiming(0.0); // Wait for Notifiers + } + + @AfterEach + void stopThread() { + m_robot.endCompetition(); + try { + m_thread.interrupt(); + m_thread.join(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + m_robot.close(); + m_callback.close(); + m_i2c.resetData(); + } + + @EnumSource(AllianceStationID.class) + @ParameterizedTest(name = "alliance[{index}]: {0}") + void allianceTest(AllianceStationID alliance) { + DriverStationSim.setAllianceStationId(alliance); + DriverStationSim.notifyNewData(); + + assertTrue(m_i2c.getInitialized()); + + SimHooks.stepTiming(0.02); + + String str = assertTimeoutPreemptively(Duration.ofMillis(20L), () -> m_future.get()); + char expected = alliance.name().startsWith("Red") ? 'R' : 'B'; + + assertEquals(expected, str.charAt(0)); + } + + @ValueSource(booleans = {true, false}) + @ParameterizedTest(name = "enabled[{index}]: {0}") + void enabledTest(boolean enabled) { + DriverStationSim.setEnabled(enabled); + DriverStationSim.notifyNewData(); + + assertTrue(m_i2c.getInitialized()); + + SimHooks.stepTiming(0.02); + + String str = assertTimeoutPreemptively(Duration.ofMillis(20L), () -> m_future.get()); + char expected = enabled ? 'E' : 'D'; + + assertEquals(expected, str.charAt(1)); + } + + @ValueSource(booleans = {true, false}) + @ParameterizedTest(name = "autonomous[{index}]: {0}") + void autonomousTest(boolean autonomous) { + DriverStationSim.setAutonomous(autonomous); + DriverStationSim.notifyNewData(); + + assertTrue(m_i2c.getInitialized()); + + SimHooks.stepTiming(0.02); + + String str = assertTimeoutPreemptively(Duration.ofMillis(20L), () -> m_future.get()); + char expected = autonomous ? 'A' : 'T'; + + assertEquals(expected, str.charAt(2)); + } + + @ValueSource(doubles = {112.0, 45.0, 27.0, 23.0, 3.0}) + @ParameterizedTest(name = "matchTime[{index}]: {0}s") + void matchTimeTest(double matchTime) { + DriverStationSim.setMatchTime(matchTime); + DriverStationSim.notifyNewData(); + assertTrue(m_i2c.getInitialized()); + + SimHooks.stepTiming(0.02); + + String str = assertTimeoutPreemptively(Duration.ofMillis(20L), () -> m_future.get()); + String expected = String.format("%03d", (int) DriverStation.getMatchTime()); + + assertEquals(expected, str.substring(3)); + } +}