From 852ed0ded63765d624aedb1b1ce8dc2f9d102181 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sat, 26 Jul 2025 12:35:28 -0700 Subject: [PATCH 1/3] Add CommandTester and CommandTesterExtension The Java source files were copied from Robot2025. --- lib/build.gradle | 9 +- limelight/build.gradle | 2 - testing/build.gradle | 18 ++-- .../testing/junit/jupiter/CommandTester.java | 14 +++ .../junit/jupiter/CommandTesterExtension.java | 85 +++++++++++++++++++ testing/vendordeps/WPILibNewCommands.json | 38 +++++++++ 6 files changed, 145 insertions(+), 21 deletions(-) create mode 100644 testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTester.java create mode 100644 testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTesterExtension.java create mode 100644 testing/vendordeps/WPILibNewCommands.json diff --git a/lib/build.gradle b/lib/build.gradle index a7a1f7a9..40ce8c1b 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,11 +1,3 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * This generated file contains a sample Java library project to get you started. - * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle - * User Manual available at https://docs.gradle.org/7.3/userguide/building_java_projects.html - */ - plugins { id 'java-common-conventions' id 'edu.wpi.first.GradleRIO' version '2025.1.1' @@ -34,6 +26,7 @@ dependencies { nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop) nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop) simulationRelease wpi.sim.enableRelease() + testImplementation(platform('org.junit:junit-bom:5.13.1')) testImplementation('org.junit.jupiter:junit-jupiter') testRuntimeOnly('org.junit.platform:junit-platform-launcher') diff --git a/limelight/build.gradle b/limelight/build.gradle index 5b39a260..4149a4a8 100644 --- a/limelight/build.gradle +++ b/limelight/build.gradle @@ -2,8 +2,6 @@ plugins { id 'java-common-conventions' id "edu.wpi.first.GradleRIO" version "2025.1.1" id 'idea' - // Spotless code formatter. - id 'com.diffplug.spotless' } idea { diff --git a/testing/build.gradle b/testing/build.gradle index bb4a224b..afb273a6 100644 --- a/testing/build.gradle +++ b/testing/build.gradle @@ -8,12 +8,18 @@ version = '1.0.0-alpha.1' repositories { // Use Maven Central for resolving dependencies. mavenCentral() + maven { + // WPI + url 'https://frcmaven.wpi.edu/artifactory/release/' + } } dependencies { implementation wpi.java.deps.wpilib() implementation wpi.java.vendor.java() implementation 'com.google.truth:truth:1.4.4' + implementation(platform('org.junit:junit-bom:5.13.1')) + implementation('org.junit.jupiter:junit-jupiter') nativeDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.desktop) nativeDebug wpi.java.vendor.jniDebug(wpi.platforms.desktop) @@ -22,8 +28,7 @@ dependencies { nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop) nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop) simulationRelease wpi.sim.enableRelease() - testImplementation(platform('org.junit:junit-bom:5.13.1')) - testImplementation('org.junit.jupiter:junit-jupiter') + testRuntimeOnly('org.junit.platform:junit-platform-launcher') } @@ -45,15 +50,6 @@ javadoc { ] } -// Generates Javadocs for test libraries under /build/docs/testjavadoc -// https://discuss.gradle.org/t/how-configure-gradle-to-generate-javadoc-for-testing-classes-junit/12764/6 -task testJavadoc(type: Javadoc) { - source = sourceSets.test.allJava - classpath = sourceSets.test.compileClasspath - destinationDir = file("${buildDir}/docs/testjavadoc") - println (title == null ? '' : title) -} - task sourceJar(type: Jar) { from sourceSets.main.allJava archiveClassifier = 'sources' diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTester.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTester.java new file mode 100644 index 00000000..ea522a87 --- /dev/null +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTester.java @@ -0,0 +1,14 @@ +package com.team2813.lib2813.testing.junit.jupiter; + +import edu.wpi.first.wpilibj2.command.Command; + +/** + * Allows tests to run commands. + * + *

Tests can get an instance by using {@link CommandTesterExtension}. + */ +public interface CommandTester { + + /** Schedules the provided command and runs it until it completes. */ + void runUntilComplete(Command command); +} diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTesterExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTesterExtension.java new file mode 100644 index 00000000..056b04e7 --- /dev/null +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTesterExtension.java @@ -0,0 +1,85 @@ +package com.team2813.lib2813.testing.junit.jupiter; + +import edu.wpi.first.hal.HAL; +import edu.wpi.first.wpilibj.simulation.DriverStationSim; +import edu.wpi.first.wpilibj2.command.CommandScheduler; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * JUnit Jupiter extension for allowing a test to schedule commands. + * + *

Example use: + * + *

{@code
+ * @ExtendWith(CommandTesterExtension.class)
+ * public final class FlightSubsystemTest {
+ *
+ *   @Test
+ *   public void takesFlight(CommandTester commandTester) {}
+ *     var flight = new FlightSubsystem();
+ *     Command takeOff = flight.createTakeOffCommandCommand();
+ *     assertThat(flight.inAir()).isFalse();
+ *
+ *     commandTester.runUntilComplete(takeOff);
+ *
+ *     assertThat(flight.inAir()).isTrue();
+ *   }
+ * }
+ * }
+ */ +public final class CommandTesterExtension + implements Extension, + AfterAllCallback, + AfterEachCallback, + BeforeAllCallback, + ParameterResolver { + + @Override + public void beforeAll(ExtensionContext context) { + // See https://www.chiefdelphi.com/t/driverstation-getalliance-in-gradle-test/ + HAL.initialize(500, 0); + DriverStationSim.setEnabled(true); + DriverStationSim.notifyNewData(); + CommandScheduler.getInstance().enable(); + CommandScheduler.getInstance().unregisterAllSubsystems(); + } + + @Override + public void afterEach(ExtensionContext context) { + CommandScheduler.getInstance().unregisterAllSubsystems(); + } + + @Override + public void afterAll(ExtensionContext context) { + CommandScheduler.getInstance().unregisterAllSubsystems(); + CommandScheduler.getInstance().disable(); + DriverStationSim.setEnabled(false); + DriverStationSim.notifyNewData(); + } + + @Override + public boolean supportsParameter( + ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return CommandTester.class.equals(parameterContext.getParameter().getType()); + } + + @Override + public CommandTester resolveParameter( + ParameterContext parameterContext, ExtensionContext extensionContext) { + return command -> { + CommandScheduler scheduler = CommandScheduler.getInstance(); + command.schedule(); + do { + scheduler.run(); + } while (scheduler.isScheduled(command)); + }; + } +} diff --git a/testing/vendordeps/WPILibNewCommands.json b/testing/vendordeps/WPILibNewCommands.json new file mode 100644 index 00000000..b7aedf69 --- /dev/null +++ b/testing/vendordeps/WPILibNewCommands.json @@ -0,0 +1,38 @@ +{ + "fileName": "WPILibNewCommands.json", + "name": "WPILib-New-Commands", + "version": "1.0.0", + "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266", + "frcYear": "2025", + "mavenUrls": [], + "jsonUrl": "", + "javaDependencies": [ + { + "groupId": "edu.wpi.first.wpilibNewCommands", + "artifactId": "wpilibNewCommands-java", + "version": "wpilib" + } + ], + "jniDependencies": [], + "cppDependencies": [ + { + "groupId": "edu.wpi.first.wpilibNewCommands", + "artifactId": "wpilibNewCommands-cpp", + "version": "wpilib", + "libName": "wpilibNewCommands", + "headerClassifier": "headers", + "sourcesClassifier": "sources", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "linuxathena", + "linuxarm32", + "linuxarm64", + "windowsx86-64", + "windowsx86", + "linuxx86-64", + "osxuniversal" + ] + } + ] +} \ No newline at end of file From bc0968ec64d92453eae71579b519e863d1b56826 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sat, 26 Jul 2025 13:05:42 -0700 Subject: [PATCH 2/3] Rename CommandTesterExtension to WPILibExtension --- lib/build.gradle | 9 ++++++++- limelight/build.gradle | 2 ++ .../testing/junit/jupiter/CommandTester.java | 2 +- ...sterExtension.java => WPILibExtension.java} | 18 +++++++++++++----- 4 files changed, 24 insertions(+), 7 deletions(-) rename testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/{CommandTesterExtension.java => WPILibExtension.java} (87%) diff --git a/lib/build.gradle b/lib/build.gradle index 40ce8c1b..a7a1f7a9 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,3 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/7.3/userguide/building_java_projects.html + */ + plugins { id 'java-common-conventions' id 'edu.wpi.first.GradleRIO' version '2025.1.1' @@ -26,7 +34,6 @@ dependencies { nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop) nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop) simulationRelease wpi.sim.enableRelease() - testImplementation(platform('org.junit:junit-bom:5.13.1')) testImplementation('org.junit.jupiter:junit-jupiter') testRuntimeOnly('org.junit.platform:junit-platform-launcher') diff --git a/limelight/build.gradle b/limelight/build.gradle index 4149a4a8..5b39a260 100644 --- a/limelight/build.gradle +++ b/limelight/build.gradle @@ -2,6 +2,8 @@ plugins { id 'java-common-conventions' id "edu.wpi.first.GradleRIO" version "2025.1.1" id 'idea' + // Spotless code formatter. + id 'com.diffplug.spotless' } idea { diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTester.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTester.java index ea522a87..42bc7741 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTester.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTester.java @@ -5,7 +5,7 @@ /** * Allows tests to run commands. * - *

Tests can get an instance by using {@link CommandTesterExtension}. + *

Tests can get an instance by using {@link WPILibExtension}. */ public interface CommandTester { diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTesterExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtension.java similarity index 87% rename from testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTesterExtension.java rename to testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtension.java index 056b04e7..d20fd3da 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/CommandTesterExtension.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtension.java @@ -13,19 +13,27 @@ import org.junit.jupiter.api.extension.ParameterResolver; /** - * JUnit Jupiter extension for allowing a test to schedule commands. + * JUnit Jupiter extension for testing code that depends on WPILib. + * + *

Also provides a {@link CommandTester} for tests. * *

Example use: * *

{@code
- * @ExtendWith(CommandTesterExtension.class)
+ * @ExtendWith(WPILibExtension.class)
  * public final class FlightSubsystemTest {
  *
  *   @Test
- *   public void takesFlight(CommandTester commandTester) {}
+ *   public void initiallyNotInAir() {
  *     var flight = new FlightSubsystem();
- *     Command takeOff = flight.createTakeOffCommandCommand();
+ *
  *     assertThat(flight.inAir()).isFalse();
+ *   }
+ *
+ *   @Test
+ *   public void takesFlight(CommandTester commandTester) {
+ *     var flight = new FlightSubsystem();
+ *     Command takeOff = flight.createTakeOffCommandCommand();
  *
  *     commandTester.runUntilComplete(takeOff);
  *
@@ -34,7 +42,7 @@
  * }
  * }
*/ -public final class CommandTesterExtension +public final class WPILibExtension implements Extension, AfterAllCallback, AfterEachCallback, From 10578f504d15a4b42b7dd707c70c49d707dbeb07 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sat, 23 Aug 2025 13:22:34 -0700 Subject: [PATCH 3/3] In WPILibExtension call cancelAll() before unregisterAllSubsystems() Co-authored-by: cuttestkittensrule --- .../lib2813/testing/junit/jupiter/WPILibExtension.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtension.java index d20fd3da..d6787beb 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtension.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtension.java @@ -56,16 +56,19 @@ public void beforeAll(ExtensionContext context) { DriverStationSim.setEnabled(true); DriverStationSim.notifyNewData(); CommandScheduler.getInstance().enable(); + CommandScheduler.getInstance().cancelAll(); CommandScheduler.getInstance().unregisterAllSubsystems(); } @Override public void afterEach(ExtensionContext context) { + CommandScheduler.getInstance().cancelAll(); CommandScheduler.getInstance().unregisterAllSubsystems(); } @Override public void afterAll(ExtensionContext context) { + CommandScheduler.getInstance().cancelAll(); CommandScheduler.getInstance().unregisterAllSubsystems(); CommandScheduler.getInstance().disable(); DriverStationSim.setEnabled(false);