[Thread] use TestRule to filter Thread tests
It's error-prone to check dependent feature in both setUp() and
tearDown() to filter specific Thread tests. This commit adds
`ThreadFeatureCheckRule` and refactor the Thread tests to use the new
test rule to filter test cases.
Test: manually verified the annonated tests are skipped with
ASSUMPTION_FAILED when the dependency feature is not supported
Change-Id: I7a5efe6bda53fc1a6e204f6f40dd3ab2c3940988
diff --git a/thread/framework/java/android/net/thread/ActiveOperationalDataset.java b/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
index b74a15a..4a7d3a7 100644
--- a/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
+++ b/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
@@ -74,42 +74,61 @@
public final class ActiveOperationalDataset implements Parcelable {
/** The maximum length of the Active Operational Dataset TLV array in bytes. */
public static final int LENGTH_MAX_DATASET_TLVS = 254;
+
/** The length of Extended PAN ID in bytes. */
public static final int LENGTH_EXTENDED_PAN_ID = 8;
+
/** The minimum length of Network Name as UTF-8 bytes. */
public static final int LENGTH_MIN_NETWORK_NAME_BYTES = 1;
+
/** The maximum length of Network Name as UTF-8 bytes. */
public static final int LENGTH_MAX_NETWORK_NAME_BYTES = 16;
+
/** The length of Network Key in bytes. */
public static final int LENGTH_NETWORK_KEY = 16;
+
/** The length of Mesh-Local Prefix in bits. */
public static final int LENGTH_MESH_LOCAL_PREFIX_BITS = 64;
+
/** The length of PSKc in bytes. */
public static final int LENGTH_PSKC = 16;
+
/** The 2.4 GHz channel page. */
public static final int CHANNEL_PAGE_24_GHZ = 0;
+
/** The minimum 2.4GHz channel. */
public static final int CHANNEL_MIN_24_GHZ = 11;
+
/** The maximum 2.4GHz channel. */
public static final int CHANNEL_MAX_24_GHZ = 26;
+
/** @hide */
@VisibleForTesting public static final int TYPE_CHANNEL = 0;
+
/** @hide */
@VisibleForTesting public static final int TYPE_PAN_ID = 1;
+
/** @hide */
@VisibleForTesting public static final int TYPE_EXTENDED_PAN_ID = 2;
+
/** @hide */
@VisibleForTesting public static final int TYPE_NETWORK_NAME = 3;
+
/** @hide */
@VisibleForTesting public static final int TYPE_PSKC = 4;
+
/** @hide */
@VisibleForTesting public static final int TYPE_NETWORK_KEY = 5;
+
/** @hide */
@VisibleForTesting public static final int TYPE_MESH_LOCAL_PREFIX = 7;
+
/** @hide */
@VisibleForTesting public static final int TYPE_SECURITY_POLICY = 12;
+
/** @hide */
@VisibleForTesting public static final int TYPE_ACTIVE_TIMESTAMP = 14;
+
/** @hide */
@VisibleForTesting public static final int TYPE_CHANNEL_MASK = 53;
@@ -975,8 +994,10 @@
public static final class SecurityPolicy {
/** The default Rotation Time in hours. */
public static final int DEFAULT_ROTATION_TIME_HOURS = 672;
+
/** The minimum length of Security Policy flags in bytes. */
public static final int LENGTH_MIN_SECURITY_POLICY_FLAGS = 1;
+
/** The length of Rotation Time TLV value in bytes. */
private static final int LENGTH_SECURITY_POLICY_ROTATION_TIME = 2;
diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
index aba4193..0277528 100644
--- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
+++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
@@ -46,10 +46,13 @@
*/
public class ThreadPersistentSettings {
private static final String TAG = "ThreadPersistentSettings";
+
/** File name used for storing settings. */
private static final String FILE_NAME = "ThreadPersistentSettings.xml";
+
/** Current config store data version. This will be incremented for any additions. */
private static final int CURRENT_SETTINGS_STORE_DATA_VERSION = 1;
+
/**
* Stores the version of the data. This can be used to handle migration of data if some
* non-backward compatible change introduced.
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 36ce4d5..48e0c26 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -40,7 +40,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeNotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -60,7 +59,8 @@
import android.net.thread.ThreadNetworkException;
import android.net.thread.ThreadNetworkManager;
import android.net.thread.utils.TapTestNetworkTracker;
-import android.os.Build;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.os.HandlerThread;
import android.os.OutcomeReceiver;
@@ -69,16 +69,12 @@
import androidx.test.filters.LargeTest;
import com.android.net.module.util.ArrayTrackRecord;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.FunctionalUtils.ThrowingRunnable;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
@@ -99,8 +95,7 @@
/** CTS tests for {@link ThreadNetworkController}. */
@LargeTest
-@RunWith(DevSdkIgnoreRunner.class)
-@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) // Thread is available on only U+
+@RequiresThreadFeature
public class ThreadNetworkControllerTest {
private static final int JOIN_TIMEOUT_MILLIS = 30 * 1000;
private static final int LEAVE_TIMEOUT_MILLIS = 2_000;
@@ -113,7 +108,7 @@
private static final String THREAD_NETWORK_PRIVILEGED =
"android.permission.THREAD_NETWORK_PRIVILEGED";
- @Rule public DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private ExecutorService mExecutor;
@@ -126,14 +121,10 @@
@Before
public void setUp() throws Exception {
- ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
- if (manager != null) {
- mController = manager.getAllThreadNetworkControllers().get(0);
- }
-
- // TODO: we will also need it in tearDown(), it's better to have a Rule to skip
- // tests if a feature is not available.
- assumeNotNull(mController);
+ mController =
+ mContext.getSystemService(ThreadNetworkManager.class)
+ .getAllThreadNetworkControllers()
+ .get(0);
mGrantedPermissions = new HashSet<String>();
mExecutor = Executors.newSingleThreadExecutor();
@@ -146,9 +137,6 @@
@After
public void tearDown() throws Exception {
- if (mController == null) {
- return;
- }
dropAllPermissions();
leaveAndWait(mController);
tearDownTestNetwork();
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 88ee47e..b40685c 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -24,8 +24,6 @@
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source;
import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
-import static android.net.thread.utils.IntegrationTestUtils.isMulticastRoutingSupported;
-import static android.net.thread.utils.IntegrationTestUtils.isSimulatedThreadRadioSupported;
import static android.net.thread.utils.IntegrationTestUtils.isToIpv6Destination;
import static android.net.thread.utils.IntegrationTestUtils.newPacketReader;
import static android.net.thread.utils.IntegrationTestUtils.pollForPacket;
@@ -44,8 +42,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assume.assumeNotNull;
-import static org.junit.Assume.assumeTrue;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -56,6 +52,10 @@
import android.net.thread.utils.FullThreadDevice;
import android.net.thread.utils.InfraNetworkDevice;
import android.net.thread.utils.OtDaemonController;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresIpv6MulticastRouting;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.os.Handler;
import android.os.HandlerThread;
@@ -68,6 +68,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -82,19 +83,11 @@
/** Integration test cases for Thread Border Routing feature. */
@RunWith(AndroidJUnit4.class)
+@RequiresThreadFeature
+@RequiresSimulationThreadDevice
@LargeTest
public class BorderRoutingTest {
private static final String TAG = BorderRoutingTest.class.getSimpleName();
- private final Context mContext = ApplicationProvider.getApplicationContext();
- private ThreadNetworkController mController;
- private OtDaemonController mOtCtl;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private TestNetworkTracker mInfraNetworkTracker;
- private List<FullThreadDevice> mFtds;
- private TapPacketReader mInfraNetworkReader;
- private InfraNetworkDevice mInfraDevice;
-
private static final int NUM_FTD = 2;
private static final Inet6Address GROUP_ADDR_SCOPE_5 =
(Inet6Address) InetAddresses.parseNumericAddress("ff05::1234");
@@ -114,17 +107,25 @@
private static final ActiveOperationalDataset DEFAULT_DATASET =
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ThreadNetworkController mController;
+ private OtDaemonController mOtCtl;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private TestNetworkTracker mInfraNetworkTracker;
+ private List<FullThreadDevice> mFtds;
+ private TapPacketReader mInfraNetworkReader;
+ private InfraNetworkDevice mInfraDevice;
+
@Before
public void setUp() throws Exception {
- assumeTrue(isSimulatedThreadRadioSupported());
final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
if (manager != null) {
mController = manager.getAllThreadNetworkControllers().get(0);
}
- // Run the tests on only devices where the Thread feature is available
- assumeNotNull(mController);
-
// TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
mOtCtl = new OtDaemonController();
mOtCtl.factoryReset();
@@ -151,10 +152,6 @@
@After
public void tearDown() throws Exception {
- if (mController == null) {
- return;
- }
-
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
NETWORK_SETTINGS,
@@ -225,8 +222,6 @@
@Test
public void unicastRouting_borderRouterSendsUdpToThreadDevice_datagramReceived()
throws Exception {
- assumeTrue(isSimulatedThreadRadioSupported());
-
/*
* <pre>
* Topology:
@@ -260,9 +255,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_ftdSubscribedMulticastAddress_infraLinkJoinsMulticastGroup()
throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -281,10 +276,10 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void
multicastRouting_ftdSubscribedScope3MulticastAddress_infraLinkNotJoinMulticastGroup()
throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -303,9 +298,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_ftdSubscribedMulticastAddress_canPingfromInfraLink()
throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -325,9 +320,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_inboundForwarding_afterBrRejoinFtdRepliesSubscribedAddress()
throws Exception {
- assumeTrue(isMulticastRoutingSupported());
// TODO (b/327311034): Testing bbr state switch from primary mode to secondary mode and back
// to primary mode requires an additional BR in the Thread network. This is not currently
@@ -335,9 +330,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_ftdSubscribedScope3MulticastAddress_cannotPingfromInfraLink()
throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -357,9 +352,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_ftdNotSubscribedMulticastAddress_cannotPingFromInfraDevice()
throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -378,9 +373,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_multipleFtdsSubscribedDifferentAddresses_canPingFromInfraDevice()
throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -414,9 +409,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_multipleFtdsSubscribedSameAddress_canPingFromInfraDevice()
throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -449,8 +444,8 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_outboundForwarding_scopeLargerThan3IsForwarded() throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -474,9 +469,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_outboundForwarding_scopeSmallerThan4IsNotForwarded()
throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -497,8 +492,8 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_outboundForwarding_llaToScope4IsNotForwarded() throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -520,8 +515,8 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_outboundForwarding_mlaToScope4IsNotForwarded() throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -546,9 +541,9 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_infraNetworkSwitch_ftdRepliesToSubscribedAddress()
throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
@@ -575,8 +570,8 @@
}
@Test
+ @RequiresIpv6MulticastRouting
public void multicastRouting_infraNetworkSwitch_outboundPacketIsForwarded() throws Exception {
- assumeTrue(isMulticastRoutingSupported());
/*
* <pre>
* Topology:
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
index 3493d9f..4e6ee5f 100644
--- a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -24,7 +24,6 @@
import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.discoverForServiceLost;
import static android.net.thread.utils.IntegrationTestUtils.discoverService;
-import static android.net.thread.utils.IntegrationTestUtils.isSimulatedThreadRadioSupported;
import static android.net.thread.utils.IntegrationTestUtils.resolveService;
import static android.net.thread.utils.IntegrationTestUtils.resolveServiceUntil;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
@@ -36,8 +35,6 @@
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeNotNull;
-import static org.junit.Assume.assumeTrue;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -47,6 +44,9 @@
import android.net.nsd.NsdServiceInfo;
import android.net.thread.utils.FullThreadDevice;
import android.net.thread.utils.TapTestNetworkTracker;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.os.HandlerThread;
import androidx.test.core.app.ApplicationProvider;
@@ -58,6 +58,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,18 +74,13 @@
/** Integration test cases for Service Discovery feature. */
@RunWith(AndroidJUnit4.class)
+@RequiresThreadFeature
+@RequiresSimulationThreadDevice
@LargeTest
@Ignore("TODO: b/328527773 - enable the test when it's stable")
public class ServiceDiscoveryTest {
private static final String TAG = ServiceDiscoveryTest.class.getSimpleName();
private static final int NUM_FTD = 3;
- private final Context mContext = ApplicationProvider.getApplicationContext();
-
- private HandlerThread mHandlerThread;
- private ThreadNetworkController mController;
- private NsdManager mNsdManager;
- private TapTestNetworkTracker mTestNetworkTracker;
- private List<FullThreadDevice> mFtds;
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
private static final byte[] DEFAULT_DATASET_TLVS =
@@ -100,6 +96,16 @@
private static final Correspondence<byte[], byte[]> BYTE_ARRAY_EQUALITY =
Correspondence.from(Arrays::equals, "is equivalent to");
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ private HandlerThread mHandlerThread;
+ private ThreadNetworkController mController;
+ private NsdManager mNsdManager;
+ private TapTestNetworkTracker mTestNetworkTracker;
+ private List<FullThreadDevice> mFtds;
+
@Before
public void setUp() throws Exception {
final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
@@ -107,12 +113,6 @@
mController = manager.getAllThreadNetworkControllers().get(0);
}
- // Run the tests on only devices where the Thread feature is available.
- assumeNotNull(mController);
-
- // Run the tests only when the device uses simulated Thread radio.
- assumeTrue(isSimulatedThreadRadioSupported());
-
// BR forms a network.
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
runAsShell(
@@ -150,12 +150,6 @@
@After
public void tearDown() throws Exception {
- if (mController == null) {
- return;
- }
- if (!isSimulatedThreadRadioSupported()) {
- return;
- }
for (FullThreadDevice ftd : mFtds) {
// Clear registered SRP hosts and services
if (ftd.isSrpHostRegistered()) {
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index 580a83a..9585d7d 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -33,14 +33,14 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-import static org.junit.Assume.assumeNotNull;
-
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.annotation.Nullable;
import android.content.Context;
import android.net.thread.ThreadNetworkController.StateCallback;
import android.net.thread.utils.OtDaemonController;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider;
@@ -49,6 +49,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,12 +59,9 @@
/** Tests for E2E Android Thread integration with ot-daemon, ConnectivityService, etc.. */
@LargeTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public class ThreadIntegrationTest {
- private final Context mContext = ApplicationProvider.getApplicationContext();
- private ThreadNetworkController mController;
- private OtDaemonController mOtCtl;
-
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
private static final byte[] DEFAULT_DATASET_TLVS =
base16().decode(
@@ -75,16 +73,18 @@
private static final ActiveOperationalDataset DEFAULT_DATASET =
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ThreadNetworkController mController;
+ private OtDaemonController mOtCtl;
+
@Before
public void setUp() throws Exception {
- final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
- if (manager != null) {
- mController = manager.getAllThreadNetworkControllers().get(0);
- }
-
- // Run the tests on only devices where the Thread feature is available
- assumeNotNull(mController);
-
+ mController =
+ mContext.getSystemService(ThreadNetworkManager.class)
+ .getAllThreadNetworkControllers()
+ .get(0);
mOtCtl = new OtDaemonController();
leaveAndWait(mController);
@@ -94,10 +94,6 @@
@After
public void tearDown() throws Exception {
- if (mController == null) {
- return;
- }
-
setTestUpStreamNetworkAndWait(mController, null);
leaveAndWait(mController);
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index bb2d973..2237e65 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -20,7 +20,6 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
@@ -32,8 +31,6 @@
import android.net.thread.ThreadNetworkController;
import android.os.Handler;
import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.VintfRuntimeInfo;
import androidx.annotation.NonNull;
@@ -65,8 +62,6 @@
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
import java.util.function.Supplier;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/** Static utility methods relating to Thread integration tests. */
public final class IntegrationTestUtils {
@@ -79,35 +74,8 @@
public static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
public static final Duration SERVICE_DISCOVERY_TIMEOUT = Duration.ofSeconds(20);
- private static final String KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED = "5.15.0";
- private static final int KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED = 14;
-
private IntegrationTestUtils() {}
- /** Returns whether the device supports simulated Thread radio. */
- public static boolean isSimulatedThreadRadioSupported() {
- // The integration test uses SIMULATION Thread radio so that it only supports CuttleFish.
- return SystemProperties.get("ro.product.model").startsWith("Cuttlefish");
- }
-
- public static boolean isMulticastRoutingSupported() {
- return isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)
- && isKernelAndroidVersionAtLeast(
- KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED);
- }
-
- private static boolean isKernelAndroidVersionAtLeast(int n) {
- final String osRelease = VintfRuntimeInfo.getOsRelease();
- final Pattern pattern = Pattern.compile("android(\\d+)");
- Matcher matcher = pattern.matcher(osRelease);
-
- if (matcher.find()) {
- int version = Integer.parseInt(matcher.group(1));
- return (version >= n);
- }
- return false;
- }
-
/**
* Waits for the given {@link Supplier} to be true until given timeout.
*
diff --git a/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java b/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
new file mode 100644
index 0000000..bee9ceb
--- /dev/null
+++ b/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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 android.net.thread.utils;
+
+import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.net.thread.ThreadNetworkManager;
+import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A rule used to skip Thread tests when the device doesn't support a specific feature indicated by
+ * {@code ThreadFeatureCheckerRule.Requires*}.
+ */
+public final class ThreadFeatureCheckerRule implements TestRule {
+ private static final String KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED = "5.15.0";
+ private static final int KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED = 14;
+
+ /**
+ * Annotates a test class or method requires the Thread feature to run.
+ *
+ * <p>In Absence of the Thread feature, the test class or method will be ignored.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface RequiresThreadFeature {}
+
+ /**
+ * Annotates a test class or method requires the kernel IPv6 multicast routing feature to run.
+ *
+ * <p>In Absence of the multicast routing feature, the test class or method will be ignored.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface RequiresIpv6MulticastRouting {}
+
+ /**
+ * Annotates a test class or method requires the simulation Thread device (i.e. ot-cli-ftd) to
+ * run.
+ *
+ * <p>In Absence of the simulation device, the test class or method will be ignored.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface RequiresSimulationThreadDevice {}
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ if (hasAnnotation(RequiresThreadFeature.class, description)) {
+ assumeTrue(
+ "Skipping test because the Thread feature is unavailable",
+ hasThreadFeature());
+ }
+
+ if (hasAnnotation(RequiresIpv6MulticastRouting.class, description)) {
+ assumeTrue(
+ "Skipping test because kernel IPv6 multicast routing is unavailable",
+ hasIpv6MulticastRouting());
+ }
+
+ if (hasAnnotation(RequiresSimulationThreadDevice.class, description)) {
+ assumeTrue(
+ "Skipping test because simulation Thread device is unavailable",
+ hasSimulationThreadDevice());
+ }
+
+ base.evaluate();
+ }
+ };
+ }
+
+ /** Returns {@code true} if a test method or the test class is annotated with annotation. */
+ private <T extends Annotation> boolean hasAnnotation(
+ Class<T> annotationClass, Description description) {
+ // Method annotation
+ boolean hasAnnotation = description.getAnnotation(annotationClass) != null;
+
+ // Class annotation
+ Class<?> clazz = description.getTestClass();
+ while (!hasAnnotation && clazz != Object.class) {
+ hasAnnotation |= clazz.getAnnotation(annotationClass) != null;
+ clazz = clazz.getSuperclass();
+ }
+
+ return hasAnnotation;
+ }
+
+ /** Returns {@code true} if this device has the Thread feature supported. */
+ private static boolean hasThreadFeature() {
+ final Context context = ApplicationProvider.getApplicationContext();
+ return context.getSystemService(ThreadNetworkManager.class) != null;
+ }
+
+ /**
+ * Returns {@code true} if this device has the kernel IPv6 multicast routing feature enabled.
+ */
+ private static boolean hasIpv6MulticastRouting() {
+ // The kernel IPv6 multicast routing (i.e. IPV6_MROUTE) is enabled on kernel version
+ // android14-5.15.0 and later
+ return isKernelVersionAtLeast(KERNEL_VERSION_MULTICAST_ROUTING_SUPPORTED)
+ && isKernelAndroidVersionAtLeast(
+ KERNEL_ANDROID_VERSION_MULTICAST_ROUTING_SUPPORTED);
+ }
+
+ /**
+ * Returns {@code true} if the android version in the kernel version of this device is equal to
+ * or larger than the given {@code minVersion}.
+ */
+ private static boolean isKernelAndroidVersionAtLeast(int minVersion) {
+ final String osRelease = VintfRuntimeInfo.getOsRelease();
+ final Pattern pattern = Pattern.compile("android(\\d+)");
+ Matcher matcher = pattern.matcher(osRelease);
+
+ if (matcher.find()) {
+ int version = Integer.parseInt(matcher.group(1));
+ return (version >= minVersion);
+ }
+ return false;
+ }
+
+ /** Returns {@code true} if the simulation Thread device is supported. */
+ private static boolean hasSimulationThreadDevice() {
+ // Simulation radio is supported on only Cuttlefish
+ return SystemProperties.get("ro.product.model").startsWith("Cuttlefish");
+ }
+}