Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ protected void before() {

@Override
protected void after() {
if (!tempInstance.waitForListenerQueue(.4)) {
Preferences.setNetworkTableInstance(NetworkTableInstance.getDefault());
if (!tempInstance.waitForListenerQueue(.2)) {
System.err.println(
"Timed out waiting for the NetworkTableInstance listener queue to empty (waited 200ms);"
+ " JVM may crash");
}
Preferences.setNetworkTableInstance(NetworkTableInstance.getDefault());
tempInstance.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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.BeforeEachCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
Expand Down Expand Up @@ -45,53 +46,64 @@
* @since 2.0.0
*/
public final class IsolatedNetworkTablesExtension
implements Extension, AfterEachCallback, ParameterResolver {
private static final StoreKey<NetworkTableInstance> NETWORK_TABLE_INSTANCE_KEY =
StoreKey.of(NetworkTableInstance.class);
implements Extension, BeforeEachCallback, AfterEachCallback, ParameterResolver {
private static final Namespace NAMESPACE = Namespace.create(IsolatedNetworkTablesExtension.class);
private static final StoreKey<Data> DATA_KEY = StoreKey.of(Data.class);

@Override
public void beforeEach(ExtensionContext context) {
Store store = context.getStore(NAMESPACE);
NetworkTableInstance ntInstance =
DATA_KEY.getOrComputeIfAbsent(store, Data::create).testInstance;
Preferences.setNetworkTableInstance(ntInstance);
}

@Override
public void afterEach(ExtensionContext context) {
// If this extension created a temporary NetworkTableInstance, close it.
var ntInstance = NETWORK_TABLE_INSTANCE_KEY.remove(getStore(context));
if (ntInstance != null) {
Store store = context.getStore(NAMESPACE);
Data data = DATA_KEY.remove(store);
if (data != null) {
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).
if (!ntInstance.waitForListenerQueue(.4)) {
if (!data.testInstance.waitForListenerQueue(.2)) {
System.err.println(
"Timed out waiting for the NetworkTableInstance listener queue to empty (waited 200ms);"
+ " JVM may crash");
}

Preferences.setNetworkTableInstance(NetworkTableInstance.getDefault());
ntInstance.close();
data.testInstance.close();
}
}

@Override
public boolean supportsParameter(
ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
if (parameterContext.getTarget().isEmpty()) {
return false; // The extension only supports test method parameter injection.
}
return NetworkTableInstance.class.equals(parameterContext.getParameter().getType());
}

@Override
public NetworkTableInstance resolveParameter(
ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
Store store = getStore(extensionContext);
NetworkTableInstance ntInstance =
NETWORK_TABLE_INSTANCE_KEY.getOrComputeIfAbsent(store, NetworkTableInstance::create);

ntInstance.startLocal();
Preferences.setNetworkTableInstance(ntInstance);
return ntInstance;
Store store = extensionContext.getStore(NAMESPACE);
return DATA_KEY.get(store).testInstance;
}

/** Gets the {@link Store} for this extension. */
private Store getStore(ExtensionContext context) {
return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod()));
private record Data(NetworkTableInstance testInstance, NetworkTableInstance prevInstance) {
static Data create() {
NetworkTableInstance testInstance = NetworkTableInstance.create();
testInstance.startLocal();
NetworkTableInstance prevInstance = Preferences.getNetworkTable().getInstance();
return new Data(testInstance, prevInstance);
}
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -20,6 +20,7 @@
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;

import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.wpilibj.Preferences;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
Expand All @@ -42,6 +43,12 @@ public void verifyProvidesNetworkTableParameter(NetworkTableInstance ntInstance)
assertThat(ntInstance.getHandle())
.isNotEqualTo(NetworkTableInstance.getDefault().getHandle());
}

@Test
public void verifyReplacesPreferencesNetworkTableInstance() {
assertThat(Preferences.getNetworkTable().getInstance().getHandle())
.isNotEqualTo(NetworkTableInstance.getDefault().getHandle());
}
} // end SampleTest

@Test
Expand Down
Loading