From 611c3ce039428aa46c6df5bad4cc4bf8b9ec36be Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sun, 8 Feb 2026 17:38:26 -0800 Subject: [PATCH 1/6] Add annotations for JUnit Jupiter extensions This allows us to add optional parameters that can be specified by users of the annotations. Notably, IsolatedNetworkTables allows the user to specify waitForListenerQueueSeconds. --- .../ParameterizedIntakeSubsystemTest.java | 7 +-- .../testing/junit/jupiter/CommandTester.java | 2 +- .../testing/junit/jupiter/InitWPILib.java | 59 ++++++++++++++++++ ...xtension.java => InitWPILibExtension.java} | 35 +---------- .../ProvideUniqueNetworkTableInstance.java | 48 ++++++++++++++ ...eUniqueNetworkTableInstanceExtension.java} | 62 +++++++++++-------- ...Test.java => InitWPILibExtensionTest.java} | 8 +-- ...queNetworkTableInstanceExtensionTest.java} | 7 +-- .../vision/MultiPhotonPoseEstimatorTest.java | 5 +- .../TimestampedStructPublisherTest.java | 7 +-- 10 files changed, 162 insertions(+), 78 deletions(-) create mode 100644 testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILib.java rename testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/{WPILibExtension.java => InitWPILibExtension.java} (82%) create mode 100644 testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstance.java rename testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/{IsolatedNetworkTablesExtension.java => ProvideUniqueNetworkTableInstanceExtension.java} (71%) rename testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/{WPILibExtensionTest.java => InitWPILibExtensionTest.java} (97%) rename testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/{IsolatedNetworkTablesExtensionTest.java => ProvideUniqueNetworkTableInstanceExtensionTest.java} (91%) diff --git a/lib/src/test/java/com/team2813/lib2813/subsystems/ParameterizedIntakeSubsystemTest.java b/lib/src/test/java/com/team2813/lib2813/subsystems/ParameterizedIntakeSubsystemTest.java index 6da2a7fc..a14ebc9d 100644 --- a/lib/src/test/java/com/team2813/lib2813/subsystems/ParameterizedIntakeSubsystemTest.java +++ b/lib/src/test/java/com/team2813/lib2813/subsystems/ParameterizedIntakeSubsystemTest.java @@ -1,5 +1,5 @@ /* -Copyright 2025 Prospect Robotics SWENext Club +Copyright 2025-2026 Prospect Robotics SWENext Club Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,16 +22,15 @@ import com.team2813.lib2813.control.Motor; import com.team2813.lib2813.testing.FakeMotor; import com.team2813.lib2813.testing.junit.jupiter.CommandTester; -import com.team2813.lib2813.testing.junit.jupiter.WPILibExtension; +import com.team2813.lib2813.testing.junit.jupiter.InitWPILib; import edu.wpi.first.wpilibj2.command.Command; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.EnumSource; @ParameterizedClass @EnumSource(ControlMode.class) -@ExtendWith(WPILibExtension.class) +@InitWPILib public final class ParameterizedIntakeSubsystemTest { private static class ConcreteParameterizedIntakeSubsystem extends ParameterizedIntakeSubsystem { 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 6fbdd517..7eabcf92 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 @@ -20,7 +20,7 @@ /** * Allows tests to run commands. * - *

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

Tests can get an instance by using {@link InitWPILib}. * * @since 2.0.0 */ diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILib.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILib.java new file mode 100644 index 00000000..8ac614fc --- /dev/null +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILib.java @@ -0,0 +1,59 @@ +/* +Copyright 2026 Prospect Robotics SWENext Club + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.team2813.lib2813.testing.junit.jupiter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * JUnit Jupiter annotation used to signal tests that depends on WPILib. + * + *

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

Example use: + * + *

{@code
+ * @InitWPILib
+ * public final class FlightSubsystemTest {
+ *
+ *   @Test
+ *   public void initiallyNotInAir() {
+ *     var flight = new FlightSubsystem();
+ *
+ *     assertThat(flight.inAir()).isFalse();
+ *   }
+ *
+ *   @Test
+ *   public void takesFlight(CommandTester commandTester) {
+ *     var flight = new FlightSubsystem();
+ *     Command takeOff = flight.createTakeOffCommandCommand();
+ *
+ *     commandTester.runUntilComplete(takeOff);
+ *
+ *     assertThat(flight.inAir()).isTrue();
+ *   }
+ * }
+ * }
+ * + * @since 2.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(InitWPILibExtension.class) +public @interface InitWPILib {} 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/InitWPILibExtension.java similarity index 82% rename from testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtension.java rename to testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILibExtension.java index 085d27a1..d3f9bcc0 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/InitWPILibExtension.java @@ -30,39 +30,8 @@ import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; -/** - * JUnit Jupiter extension for testing code that depends on WPILib. - * - *

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

Example use: - * - *

{@code
- * @ExtendWith(WPILibExtension.class)
- * public final class FlightSubsystemTest {
- *
- *   @Test
- *   public void initiallyNotInAir() {
- *     var flight = new FlightSubsystem();
- *
- *     assertThat(flight.inAir()).isFalse();
- *   }
- *
- *   @Test
- *   public void takesFlight(CommandTester commandTester) {
- *     var flight = new FlightSubsystem();
- *     Command takeOff = flight.createTakeOffCommandCommand();
- *
- *     commandTester.runUntilComplete(takeOff);
- *
- *     assertThat(flight.inAir()).isTrue();
- *   }
- * }
- * }
- * - * @since 2.0.0 - */ -public final class WPILibExtension +/** JUnit Jupiter extension for testing code that depends on WPILib. */ +final class InitWPILibExtension implements Extension, AfterAllCallback, AfterEachCallback, diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstance.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstance.java new file mode 100644 index 00000000..82447903 --- /dev/null +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstance.java @@ -0,0 +1,48 @@ +/* +Copyright 2026 Prospect Robotics SWENext Club + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.team2813.lib2813.testing.junit.jupiter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * JUnit Jupiter annotation used to signal tests that need an isolated NetworkTableInstance. + * + *

Example use: + * + *

{@code
+ * @ProvideUniqueNetworkTableInstance
+ * public final class IntakeTest {
+ *
+ *   @Test
+ *   public void intakeCoral(NetworkTableInstance ntInstance)  {
+ *     // Do something with ntInstance
+ *   }
+ * }
+ * }
+ * + * @since 2.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(ProvideUniqueNetworkTableInstanceExtension.class) +public @interface ProvideUniqueNetworkTableInstance { + + double waitForListenerQueueSeconds() default 0.4; +} diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java similarity index 71% rename from testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java rename to testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java index fde01441..557c8b4f 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java @@ -20,6 +20,7 @@ import edu.wpi.first.wpilibj.Preferences; import java.lang.reflect.Field; import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; @@ -28,29 +29,26 @@ import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.support.AnnotationSupport; -/** - * JUnit Jupiter extension for providing an isolated NetworkTableInstance to tests. - * - *

Example use: - * - *

{@code
- * @ExtendWith(IsolatedNetworkTablesExtension.class)
- * public final class IntakeTest {
- *
- *   @Test
- *   public void intakeCoral(NetworkTableInstance ntInstance)  {
- *     // Do something with ntInstance
- *   }
- * }
- * }
- * - * @since 2.0.0 - */ -public final class IsolatedNetworkTablesExtension - implements Extension, BeforeEachCallback, AfterEachCallback, ParameterResolver { - private static final Namespace NAMESPACE = Namespace.create(IsolatedNetworkTablesExtension.class); +/** JUnit Jupiter extension for providing an isolated NetworkTableInstance to tests. */ +final class ProvideUniqueNetworkTableInstanceExtension + implements Extension, + BeforeAllCallback, + BeforeEachCallback, + AfterEachCallback, + ParameterResolver { + private static final Namespace NAMESPACE = + Namespace.create(ProvideUniqueNetworkTableInstanceExtension.class); private static final StoreKey DATA_KEY = StoreKey.of(Data.class); + private static final StoreKey ANNOTATION_KEY = + StoreKey.of(ProvideUniqueNetworkTableInstance.class); + + @Override + public void beforeAll(ExtensionContext context) { + Store store = context.getStore(NAMESPACE); + ANNOTATION_KEY.put(store, getAnnotation(context)); + } @Override public void beforeEach(ExtensionContext context) { @@ -74,10 +72,13 @@ public void afterEach(ExtensionContext context) { // This works around a race condition in WPILib where a listener registered by Preferences can // be called after the NetworkTableInstance was closed (see // https://github.com/wpilibsuite/allwpilib/issues/8215). - if (!data.testInstance.waitForListenerQueue(.4)) { - System.err.println( - "Timed out waiting for the NetworkTableInstance listener queue to empty (waited 400ms);" - + " will not close temporary NetworkTableInstance"); + ProvideUniqueNetworkTableInstance annotation = ANNOTATION_KEY.get(store); + double timeout = annotation.waitForListenerQueueSeconds(); + if (!data.testInstance.waitForListenerQueue(timeout)) { + System.err.printf( + "Timed out waiting for the NetworkTableInstance listener queue to empty (waited" + + " %dms); will not close temporary NetworkTableInstance%n", + Math.round(timeout * 1000)); } else { data.testInstance.close(); } @@ -110,6 +111,17 @@ static Data create() { } } + private static ProvideUniqueNetworkTableInstance getAnnotation(ExtensionContext context) { + return AnnotationSupport.findAnnotation( + context.getRequiredTestClass(), + ProvideUniqueNetworkTableInstance.class, + context.getEnclosingTestClasses()) + .orElseThrow( + () -> + new IllegalStateException( + "Could not find an enclosed class annotated with" + " @IsolatedNetworkTables")); + } + /** * Removes the listener installed by {@link * Preferences#setNetworkTableInstance(NetworkTableInstance)}. diff --git a/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtensionTest.java b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILibExtensionTest.java similarity index 97% rename from testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtensionTest.java rename to testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILibExtensionTest.java index f35b49d0..db7d6b45 100644 --- a/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/WPILibExtensionTest.java +++ b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILibExtensionTest.java @@ -1,5 +1,5 @@ /* -Copyright 2025 Prospect Robotics SWENext Club +Copyright 2025-2026 Prospect Robotics SWENext Club Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -37,8 +37,8 @@ import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; -/** Tests for {@link WPILibExtension}. */ -public class WPILibExtensionTest { +/** Tests for {@link InitWPILibExtension}. */ +public class InitWPILibExtensionTest { static final Command FAKE_COMMAND = new Command() {}; @@ -49,7 +49,7 @@ static void initializeHal() { } } - @ExtendWith(WPILibExtension.class) + @ExtendWith(InitWPILibExtension.class) @Tag("ignore-outside-testkit") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public static class SampleTest { diff --git a/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtensionTest.java b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtensionTest.java similarity index 91% rename from testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtensionTest.java rename to testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtensionTest.java index cc0e0665..8116666c 100644 --- a/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtensionTest.java +++ b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtensionTest.java @@ -25,14 +25,13 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; -/** Tests for {@link IsolatedNetworkTablesExtension}. */ -class IsolatedNetworkTablesExtensionTest { +/** Tests for {@link ProvideUniqueNetworkTableInstanceExtension}. */ +class ProvideUniqueNetworkTableInstanceExtensionTest { - @ExtendWith(IsolatedNetworkTablesExtension.class) + @ProvideUniqueNetworkTableInstance @Tag("ignore-outside-testkit") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public static class SampleTest { diff --git a/vision/src/test/java/com/team2813/lib2813/vision/MultiPhotonPoseEstimatorTest.java b/vision/src/test/java/com/team2813/lib2813/vision/MultiPhotonPoseEstimatorTest.java index 7be00ae9..73f650e6 100644 --- a/vision/src/test/java/com/team2813/lib2813/vision/MultiPhotonPoseEstimatorTest.java +++ b/vision/src/test/java/com/team2813/lib2813/vision/MultiPhotonPoseEstimatorTest.java @@ -17,7 +17,7 @@ import static com.google.common.truth.Truth.assertThat; -import com.team2813.lib2813.testing.junit.jupiter.IsolatedNetworkTablesExtension; +import com.team2813.lib2813.testing.junit.jupiter.ProvideUniqueNetworkTableInstance; import edu.wpi.first.apriltag.AprilTag; import edu.wpi.first.apriltag.AprilTagFieldLayout; import edu.wpi.first.math.geometry.Pose3d; @@ -27,13 +27,12 @@ import edu.wpi.first.math.geometry.Translation3d; import edu.wpi.first.networktables.NetworkTableInstance; import java.util.List; -import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.photonvision.PhotonPoseEstimator.PoseStrategy; /** Tests for {@link MultiPhotonPoseEstimator}. */ -@ExtendWith(IsolatedNetworkTablesExtension.class) +@ProvideUniqueNetworkTableInstance class MultiPhotonPoseEstimatorTest { private static final double FIELD_LENGTH = 17.548; private static final double FIELD_WIDTH = 8.052; diff --git a/vision/src/test/java/com/team2813/lib2813/vision/TimestampedStructPublisherTest.java b/vision/src/test/java/com/team2813/lib2813/vision/TimestampedStructPublisherTest.java index 4c21d839..5057f937 100644 --- a/vision/src/test/java/com/team2813/lib2813/vision/TimestampedStructPublisherTest.java +++ b/vision/src/test/java/com/team2813/lib2813/vision/TimestampedStructPublisherTest.java @@ -1,5 +1,5 @@ /* -Copyright 2025 Prospect Robotics SWENext Club +Copyright 2025-2026 Prospect Robotics SWENext Club Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,17 +19,16 @@ import static com.team2813.lib2813.vision.TimestampedStructPublisher.DEFAULT_PUBLISHED_VALUE_VALID_MICROS; import static com.team2813.lib2813.vision.TimestampedStructPublisher.EXPECTED_UPDATE_FREQUENCY_MICROS; -import com.team2813.lib2813.testing.junit.jupiter.IsolatedNetworkTablesExtension; +import com.team2813.lib2813.testing.junit.jupiter.ProvideUniqueNetworkTableInstance; import edu.wpi.first.math.geometry.*; import edu.wpi.first.networktables.*; import edu.wpi.first.units.Units; import java.util.*; import java.util.function.Supplier; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; /** Tests for {@link TimestampedStructPublisher}. */ -@ExtendWith(IsolatedNetworkTablesExtension.class) +@ProvideUniqueNetworkTableInstance(waitForListenerQueueSeconds = 0.6) public class TimestampedStructPublisherTest { private static final long MICROSECONDS_PER_SECOND = 1_000_000; private static final Translation2d DEFAULT_VALUE = new Translation2d(28, 13); From 8c63ff1702adc677e5d7195c64b430203126cb30 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sun, 8 Feb 2026 20:32:18 -0800 Subject: [PATCH 2/6] Update IsolatedNetworkTablesExtension to not call setNetworkTableInstance) by default --- .../jupiter/ProvideUniqueNetworkTableInstance.java | 13 ++++++++++++- ...ProvideUniqueNetworkTableInstanceExtension.java | 14 ++++++++++---- ...ideUniqueNetworkTableInstanceExtensionTest.java | 4 ++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstance.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstance.java index 82447903..96e3e186 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstance.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstance.java @@ -44,5 +44,16 @@ @ExtendWith(ProvideUniqueNetworkTableInstanceExtension.class) public @interface ProvideUniqueNetworkTableInstance { - double waitForListenerQueueSeconds() default 0.4; + /** + * How long to wait for the listener queue to empty before destroying the temporary network table + * instance. + */ + double waitForListenerQueueSeconds() default 0.6; + + /** + * Whether to call {@link + * edu.wpi.first.wpilibj.Preferences#setNetworkTableInstance(edu.wpi.first.networktables.NetworkTableInstance)} + * with the temporary network table instance before starting each test. + */ + boolean replacePreferencesNetworkTable() default false; } diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java index 557c8b4f..42be1036 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java @@ -55,8 +55,12 @@ public void beforeEach(ExtensionContext context) { Store store = context.getStore(NAMESPACE); NetworkTableInstance ntInstance = DATA_KEY.getOrComputeIfAbsent(store, Data::create).testInstance; - Preferences.setNetworkTableInstance(ntInstance); - removePreferencesListener(); + + ProvideUniqueNetworkTableInstance annotation = ANNOTATION_KEY.get(store); + if (annotation.replacePreferencesNetworkTable()) { + Preferences.setNetworkTableInstance(ntInstance); + removePreferencesListener(); + } } @Override @@ -65,14 +69,16 @@ public void afterEach(ExtensionContext context) { Store store = context.getStore(NAMESPACE); Data data = DATA_KEY.remove(store); if (data != null) { - Preferences.setNetworkTableInstance(data.prevInstance); + ProvideUniqueNetworkTableInstance annotation = ANNOTATION_KEY.get(store); + if (!data.prevInstance.equals(Preferences.getNetworkTable().getInstance())) { + Preferences.setNetworkTableInstance(data.prevInstance); + } // Clear out the listener queue before destroying our temporary NetworkTableInstance. // // This works around a race condition in WPILib where a listener registered by Preferences can // be called after the NetworkTableInstance was closed (see // https://github.com/wpilibsuite/allwpilib/issues/8215). - ProvideUniqueNetworkTableInstance annotation = ANNOTATION_KEY.get(store); double timeout = annotation.waitForListenerQueueSeconds(); if (!data.testInstance.waitForListenerQueue(timeout)) { System.err.printf( diff --git a/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtensionTest.java b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtensionTest.java index 8116666c..6c70e6fb 100644 --- a/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtensionTest.java +++ b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtensionTest.java @@ -44,9 +44,9 @@ public void verifyProvidesNetworkTableParameter(NetworkTableInstance ntInstance) } @Test - public void verifyReplacesPreferencesNetworkTableInstance() { + public void verifyDoesNotReplacePreferencesNetworkTableInstanceByDefault() { assertThat(Preferences.getNetworkTable().getInstance().getHandle()) - .isNotEqualTo(NetworkTableInstance.getDefault().getHandle()); + .isEqualTo(NetworkTableInstance.getDefault().getHandle()); } } // end SampleTest From e0df29e5d5f344eeb75db6887bdb946a0d78850b Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sun, 8 Feb 2026 21:16:12 -0800 Subject: [PATCH 3/6] Fix timeout in call to waitForListenerQueue() --- .../com/team2813/lib2813/preferences/IsolatedPreferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java index cb6ed91b..b82035ee 100644 --- a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java +++ b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java @@ -52,7 +52,7 @@ protected void after() { // This works around a race condition in WPILib where a listener registered by Preferences can // be called after the NetworkTableInstance was closed (see // https://github.com/wpilibsuite/allwpilib/issues/8215). - if (!tempInstance.waitForListenerQueue(4)) { + if (!tempInstance.waitForListenerQueue(.4)) { System.err.println( "Timed out waiting for the NetworkTableInstance listener queue to empty (waited 400ms);" + " will not close temporary NetworkTableInstance"); From 564892b594e53a8e16f911c503a8032e4b52463a Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sun, 8 Feb 2026 21:19:20 -0800 Subject: [PATCH 4/6] Call waitForListenerQueue() before removing the listner, because, why not? --- .../com/team2813/lib2813/preferences/IsolatedPreferences.java | 3 ++- .../jupiter/ProvideUniqueNetworkTableInstanceExtension.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java index b82035ee..d183769f 100644 --- a/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java +++ b/lib/src/test/java/com/team2813/lib2813/preferences/IsolatedPreferences.java @@ -40,6 +40,7 @@ protected void before() { prevInstance = Preferences.getNetworkTable().getInstance(); tempInstance = NetworkTableInstance.create(); Preferences.setNetworkTableInstance(tempInstance); + tempInstance.waitForListenerQueue(1); removePreferencesListener(); } @@ -74,7 +75,7 @@ private static void removePreferencesListener() { NetworkTableListener listener = (NetworkTableListener) listnerField.get(null); listnerField.set(null, null); listener.close(); - } catch (NoSuchFieldException | IllegalAccessException e) { + } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) { } } } diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java index 42be1036..5057dc05 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java @@ -59,6 +59,7 @@ public void beforeEach(ExtensionContext context) { ProvideUniqueNetworkTableInstance annotation = ANNOTATION_KEY.get(store); if (annotation.replacePreferencesNetworkTable()) { Preferences.setNetworkTableInstance(ntInstance); + ntInstance.waitForListenerQueue(1); removePreferencesListener(); } } @@ -141,7 +142,7 @@ private static void removePreferencesListener() { NetworkTableListener listener = (NetworkTableListener) listnerField.get(null); listnerField.set(null, null); listener.close(); - } catch (NoSuchFieldException | IllegalAccessException e) { + } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) { } } } From 460aae9a4cf299da1325831a137cf8f00345613e Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Mon, 9 Feb 2026 18:57:44 -0800 Subject: [PATCH 5/6] Update assertHasNoFailures() to include failures in the exception --- .../junit/jupiter/ExtensionAssertions.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ExtensionAssertions.java b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ExtensionAssertions.java index bb9cb086..9646cbab 100644 --- a/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ExtensionAssertions.java +++ b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ExtensionAssertions.java @@ -1,5 +1,5 @@ /* -Copyright 2025 Prospect Robotics SWENext Club +Copyright 2025-2026 Prospect Robotics SWENext Club Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,8 +15,11 @@ */ package com.team2813.lib2813.testing.junit.jupiter; +import java.util.List; import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.Event; import org.junit.platform.testkit.engine.Events; +import org.opentest4j.MultipleFailuresError; /** * A collection of utility methods that support asserting conditions in tests of JUnit Extensions. @@ -28,7 +31,14 @@ final class ExtensionAssertions { * * @param events Events fired during execution of a test plan on the JUnit Platform. */ - public static void assertHasNoFailures(Events events) { + public static void assertHasNoFailures(Events events, String category) { + if (!events.failed().list().isEmpty()) { + List failures = + events.failed().stream().map(Event::toString).map(AssertionError::new).toList(); + throw new MultipleFailuresError( + String.format("Expected no failed events with category '%s'", category), failures); + } + events.assertStatistics( stats -> { stats.skipped(0); @@ -42,8 +52,8 @@ public static void assertHasNoFailures(Events events) { * @param results Results of executing a test plan on the JUnit Platform. */ public static void assertHasNoFailures(EngineExecutionResults results) { - assertHasNoFailures(results.containerEvents()); - assertHasNoFailures(results.testEvents()); + assertHasNoFailures(results.containerEvents(), "container"); + assertHasNoFailures(results.testEvents(), "test"); } private ExtensionAssertions() { From fc3a3c102a6cf6e420d9390fed98fb460f78e185 Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Mon, 9 Feb 2026 19:04:31 -0800 Subject: [PATCH 6/6] Address code review comments by the one and only cuttestkittensrule --- .../jupiter/ProvideUniqueNetworkTableInstanceExtension.java | 2 +- .../lib2813/testing/junit/jupiter/InitWPILibExtensionTest.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java index 5057dc05..222e4d22 100644 --- a/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/ProvideUniqueNetworkTableInstanceExtension.java @@ -126,7 +126,7 @@ private static ProvideUniqueNetworkTableInstance getAnnotation(ExtensionContext .orElseThrow( () -> new IllegalStateException( - "Could not find an enclosed class annotated with" + " @IsolatedNetworkTables")); + "Could not find an enclosed class annotated with @IsolatedNetworkTables")); } /** diff --git a/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILibExtensionTest.java b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILibExtensionTest.java index db7d6b45..249c8323 100644 --- a/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILibExtensionTest.java +++ b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/InitWPILibExtensionTest.java @@ -33,7 +33,6 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; @@ -49,7 +48,7 @@ static void initializeHal() { } } - @ExtendWith(InitWPILibExtension.class) + @InitWPILib @Tag("ignore-outside-testkit") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public static class SampleTest {