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/IsolatedNetworkTablesExtension.java new file mode 100644 index 00000000..2de4a5fa --- /dev/null +++ b/testing/src/main/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtension.java @@ -0,0 +1,80 @@ +package com.team2813.lib2813.testing.junit.jupiter; + +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.wpilibj.Preferences; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +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 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
+ * }
+ * }
+ * }
+ */
+public final class IsolatedNetworkTablesExtension
+ implements Extension, AfterEachCallback, ParameterResolver {
+ private static final StoreKeyValues returned from this method should almost always be stored in static final fields.
+ */
+ public static If no value is stored in the current {@link ExtensionContext} for this key, ancestors of the
+ * context will be queried for a value with this key in the {@code Namespace} used to create this
+ * store.
+ *
+ * @param store the store to get data from.
+ * @see #getOrDefault(Store, V)
+ */
+ public V get(Store store) {
+ return store.get(this, requiredType);
+ }
+
+ /**
+ * Gets the value of the specified required type that is stored under this key, or the supplied
+ * {@code defaultValue} if no value is found for this key in this store or in an ancestor.
+ *
+ * If no value is stored in the current {@link ExtensionContext} for this, ancestors of the
+ * context will be queried for a value with this key in the {@code Namespace} used to create this
+ * store.
+ *
+ * @param store the store to get data from.
+ * @param defaultValue the default value.
+ * @return the value; potentially {@code null}.
+ * @see #get(Store)
+ */
+ public V getOrDefault(Store store, V defaultValue) {
+ return store.getOrDefault(this, requiredType, defaultValue);
+ }
+
+ /**
+ * Gets the value of the specified required type that is stored under this key.
+ *
+ * If no value is stored in the current {@link ExtensionContext} for this key, ancestors of the
+ * context will be queried for a value with this key in the {@code Namespace} used to create this
+ * store. If no value is found for this key a new value will be computed by the {@code
+ * valueSupplier}, stored, and returned.
+ *
+ * If {@code requiredType} implements {@link Store.CloseableResource} or {@link AutoCloseable}
+ * (unless the {@code junit.jupiter.extensions.store.close.autocloseable.enabled} configuration
+ * parameter is set to {@code false}), then the {@code close()} method will be invoked on the
+ * stored object when the store is closed.
+ *
+ * @param store the store to use to get and store the data.
+ * @param valueSupplier the function called to create a new value; never {@code null} but may
+ * return {@code null}.
+ * @return the value; potentially {@code null}.
+ * @see Store.CloseableResource
+ * @see AutoCloseable
+ */
+ public V getOrComputeIfAbsent(Store store, Supplier A stored {@code value} is visible in child {@link ExtensionContext ExtensionContexts} for
+ * the store's {@code Namespace} unless they overwrite it.
+ *
+ * If the {@code value} is an instance of {@link Store.CloseableResource} or {@link
+ * AutoCloseable} (unless the {@code junit.jupiter.extensions.store.close.autocloseable.enabled}
+ * configuration parameter is set to {@code false}), then the {@code close()} method will be
+ * invoked on the stored object when the store is closed.
+ *
+ * @param store the store to put data into.
+ * @param value the value to store; may be {@code null}.
+ * @see Store.CloseableResource
+ * @see AutoCloseable
+ */
+ public void put(Store store, V value) {
+ store.put(this, value);
+ }
+
+ /**
+ * Removes the value of the specified required type that was previously stored under this key.
+ *
+ * The value will only be removed in the current {@link ExtensionContext}, not in ancestors. In
+ * addition, the {@link Store.CloseableResource} and {@link AutoCloseable} API will not be honored
+ * for values that are manually removed via this method.
+ *
+ * @param store the store to remove data from.
+ * @return the previous value or {@code null} if no value was present for the specified key.
+ */
+ public V remove(Store store) {
+ return store.remove(this, requiredType);
+ }
+}
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
new file mode 100644
index 00000000..696d4e29
--- /dev/null
+++ b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/ExtensionAssertions.java
@@ -0,0 +1,37 @@
+package com.team2813.lib2813.testing.junit.jupiter;
+
+import org.junit.platform.testkit.engine.EngineExecutionResults;
+import org.junit.platform.testkit.engine.Events;
+
+/**
+ * A collection of utility methods that support asserting conditions in tests of JUnit Extensions.
+ */
+final class ExtensionAssertions {
+
+ /**
+ * Asserts that the supplied {@code events} do not contain any failures.
+ *
+ * @param events Events fired during execution of a test plan on the JUnit Platform.
+ */
+ public static void assertHasNoFailures(Events events) {
+ events.assertStatistics(
+ stats -> {
+ stats.skipped(0);
+ stats.failed(0);
+ });
+ }
+
+ /**
+ * Asserts that the supplied {@code results} do not contain any failures.
+ *
+ * @param results Results of executing a test plan on the JUnit Platform.
+ */
+ public static void assertHasNoFailures(EngineExecutionResults results) {
+ assertHasNoFailures(results.containerEvents());
+ assertHasNoFailures(results.testEvents());
+ }
+
+ private ExtensionAssertions() {
+ throw new AssertionError("Not instantiable");
+ }
+}
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/IsolatedNetworkTablesExtensionTest.java
new file mode 100644
index 00000000..505054da
--- /dev/null
+++ b/testing/src/test/java/com/team2813/lib2813/testing/junit/jupiter/IsolatedNetworkTablesExtensionTest.java
@@ -0,0 +1,41 @@
+package com.team2813.lib2813.testing.junit.jupiter;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.team2813.lib2813.testing.junit.jupiter.ExtensionAssertions.assertHasNoFailures;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
+
+import edu.wpi.first.networktables.NetworkTableInstance;
+import org.junit.jupiter.api.MethodOrderer;
+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 {
+
+ @ExtendWith(IsolatedNetworkTablesExtension.class)
+ @Tag("ignore-outside-testkit")
+ @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+ public static class SampleTest {
+
+ @Test
+ public void verifyProvidesNetworkTableParameter(NetworkTableInstance ntInstance) {
+ assertThat(ntInstance).isNotNull();
+ assertThat(ntInstance.getHandle())
+ .isNotEqualTo(NetworkTableInstance.getDefault().getHandle());
+ }
+ } // end SampleTest
+
+ @Test
+ void verifyExtension() {
+ // Act - Run tests in SampleTest
+ EngineExecutionResults results =
+ EngineTestKit.engine("junit-jupiter").selectors(selectClass(SampleTest.class)).execute();
+
+ // Assert - All tests in SampleTest pass
+ assertHasNoFailures(results);
+ }
+}
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/WPILibExtensionTest.java
index 3fd1cfe5..9c7301ca 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/WPILibExtensionTest.java
@@ -2,6 +2,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static com.team2813.lib2813.testing.junit.jupiter.ExtensionAssertions.assertHasNoFailures;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import edu.wpi.first.hal.HAL;
@@ -20,7 +21,6 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.platform.testkit.engine.EngineExecutionResults;
import org.junit.platform.testkit.engine.EngineTestKit;
-import org.junit.platform.testkit.engine.Events;
/** Tests for {@link WPILibExtension}. */
public class WPILibExtensionTest {
@@ -129,19 +129,6 @@ private void withDriverStationTemporarilyEnabled(Runnable runnable) {
}
}
- private void assertHasNoFailures(EngineExecutionResults results) {
- assertHasNoFailures(results.containerEvents());
- assertHasNoFailures(results.testEvents());
- }
-
- private void assertHasNoFailures(Events events) {
- events.assertStatistics(
- stats -> {
- stats.skipped(0);
- stats.failed(0);
- });
- }
-
private static class VerifiableCommand extends Command {
private static final int EXPECTED_EXECUTION_COUNT = 4;
private int initializedCount = 0;