states, Duration timeout) throws TimeoutException {
+ waitFor(() -> states.contains(getState()), timeout);
}
/**
diff --git a/thread/tests/integration/src/android/net/thread/InfraNetworkDevice.java b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
similarity index 92%
rename from thread/tests/integration/src/android/net/thread/InfraNetworkDevice.java
rename to thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
index 43a800dafa..3081f9fc14 100644
--- a/thread/tests/integration/src/android/net/thread/InfraNetworkDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.net.thread;
+package android.net.thread.utils;
-import static android.net.thread.IntegrationTestUtils.getRaPios;
-import static android.net.thread.IntegrationTestUtils.readPacketFrom;
-import static android.net.thread.IntegrationTestUtils.waitFor;
+import static android.net.thread.utils.IntegrationTestUtils.getRaPios;
+import static android.net.thread.utils.IntegrationTestUtils.readPacketFrom;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
@@ -34,6 +34,7 @@ import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.time.Duration;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeoutException;
@@ -100,8 +101,8 @@ public final class InfraNetworkDevice {
* @param timeoutSeconds the number of seconds to wait for.
* @throws TimeoutException when the device fails to generate a SLAAC address in given timeout.
*/
- public void runSlaac(int timeoutSeconds) throws TimeoutException {
- waitFor(() -> (ipv6Addr = runSlaac()) != null, timeoutSeconds, 5 /* intervalSeconds */);
+ public void runSlaac(Duration timeout) throws TimeoutException {
+ waitFor(() -> (ipv6Addr = runSlaac()) != null, timeout);
}
private Inet6Address runSlaac() {
diff --git a/thread/tests/integration/src/android/net/thread/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
similarity index 80%
rename from thread/tests/integration/src/android/net/thread/IntegrationTestUtils.java
rename to thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index c465d572ec..f223367e80 100644
--- a/thread/tests/integration/src/android/net/thread/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.net.thread;
+package android.net.thread.utils;
import static android.system.OsConstants.IPPROTO_ICMPV6;
@@ -23,6 +23,7 @@ import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_AD
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import android.net.TestNetworkInterface;
+import android.net.thread.ThreadNetworkController;
import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -39,6 +40,7 @@ import com.google.common.util.concurrent.SettableFuture;
import java.io.FileDescriptor;
import java.nio.ByteBuffer;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -49,6 +51,14 @@ import java.util.function.Supplier;
/** Static utility methods relating to Thread integration tests. */
public final class IntegrationTestUtils {
+ // The timeout of join() after restarting ot-daemon. The device needs to send 6 Link Request
+ // every 5 seconds, followed by 4 Parent Request every second. So this value needs to be 40
+ // seconds to be safe
+ public static final Duration RESTART_JOIN_TIMEOUT = Duration.ofSeconds(40);
+ public static final Duration JOIN_TIMEOUT = Duration.ofSeconds(30);
+ public static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
+ public static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
+
private IntegrationTestUtils() {}
/** Returns whether the device supports simulated Thread radio. */
@@ -60,49 +70,33 @@ public final class IntegrationTestUtils {
/**
* Waits for the given {@link Supplier} to be true until given timeout.
*
- * It checks the condition once every second.
- *
- * @param condition the condition to check.
- * @param timeoutSeconds the number of seconds to wait for.
- * @throws TimeoutException if the condition is not met after the timeout.
+ * @param condition the condition to check
+ * @param timeout the time to wait for the condition before throwing
+ * @throws TimeoutException if the condition is still not met when the timeout expires
*/
- public static void waitFor(Supplier condition, int timeoutSeconds)
+ public static void waitFor(Supplier condition, Duration timeout)
throws TimeoutException {
- waitFor(condition, timeoutSeconds, 1);
- }
+ final long intervalMills = 1000;
+ final long timeoutMills = timeout.toMillis();
- /**
- * Waits for the given {@link Supplier} to be true until given timeout.
- *
- * It checks the condition once every {@code intervalSeconds}.
- *
- * @param condition the condition to check.
- * @param timeoutSeconds the number of seconds to wait for.
- * @param intervalSeconds the period to check the {@code condition}.
- * @throws TimeoutException if the condition is still not met when the timeout expires.
- */
- public static void waitFor(Supplier condition, int timeoutSeconds, int intervalSeconds)
- throws TimeoutException {
- for (int i = 0; i < timeoutSeconds; i += intervalSeconds) {
+ for (long i = 0; i < timeoutMills; i += intervalMills) {
if (condition.get()) {
return;
}
- SystemClock.sleep(intervalSeconds * 1000L);
+ SystemClock.sleep(intervalMills);
}
if (condition.get()) {
return;
}
- throw new TimeoutException(
- String.format(
- "The condition failed to become true in %d seconds.", timeoutSeconds));
+ throw new TimeoutException("The condition failed to become true in " + timeout);
}
/**
* Creates a {@link TapPacketReader} given the {@link TestNetworkInterface} and {@link Handler}.
*
- * @param testNetworkInterface the TUN interface of the test network.
- * @param handler the handler to process the packets.
- * @return the {@link TapPacketReader}.
+ * @param testNetworkInterface the TUN interface of the test network
+ * @param handler the handler to process the packets
+ * @return the {@link TapPacketReader}
*/
public static TapPacketReader newPacketReader(
TestNetworkInterface testNetworkInterface, Handler handler) {
@@ -117,16 +111,16 @@ public final class IntegrationTestUtils {
/**
* Waits for the Thread module to enter any state of the given {@code deviceRoles}.
*
- * @param controller the {@link ThreadNetworkController}.
+ * @param controller the {@link ThreadNetworkController}
* @param deviceRoles the desired device roles. See also {@link
- * ThreadNetworkController.DeviceRole}.
- * @param timeoutSeconds the number of seconds ot wait for.
- * @return the {@link ThreadNetworkController.DeviceRole} after waiting.
+ * ThreadNetworkController.DeviceRole}
+ * @param timeout the time to wait for the expected state before throwing
+ * @return the {@link ThreadNetworkController.DeviceRole} after waiting
* @throws TimeoutException if the device hasn't become any of expected roles until the timeout
- * expires.
+ * expires
*/
public static int waitForStateAnyOf(
- ThreadNetworkController controller, List deviceRoles, int timeoutSeconds)
+ ThreadNetworkController controller, List deviceRoles, Duration timeout)
throws TimeoutException {
SettableFuture future = SettableFuture.create();
ThreadNetworkController.StateCallback callback =
@@ -137,24 +131,24 @@ public final class IntegrationTestUtils {
};
controller.registerStateCallback(directExecutor(), callback);
try {
- int role = future.get(timeoutSeconds, TimeUnit.SECONDS);
- controller.unregisterStateCallback(callback);
- return role;
+ return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException e) {
throw new TimeoutException(
String.format(
- "The device didn't become an expected role in %d seconds.",
- timeoutSeconds));
+ "The device didn't become an expected role in %s: %s",
+ timeout, e.getMessage()));
+ } finally {
+ controller.unregisterStateCallback(callback);
}
}
/**
* Reads a packet from a given {@link TapPacketReader} that satisfies the {@code filter}.
*
- * @param packetReader a TUN packet reader.
- * @param filter the filter to be applied on the packet.
+ * @param packetReader a TUN packet reader
+ * @param filter the filter to be applied on the packet
* @return the first IPv6 packet that satisfies the {@code filter}. If it has waited for more
- * than 3000ms to read the next packet, the method will return null.
+ * than 3000ms to read the next packet, the method will return null
*/
public static byte[] readPacketFrom(TapPacketReader packetReader, Predicate filter) {
byte[] packet;
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
new file mode 100644
index 0000000000..4a06fe8676
--- /dev/null
+++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.net.InetAddresses;
+import android.os.SystemClock;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.net.Inet6Address;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Wrapper of the "/system/bin/ot-ctl" which can be used to send CLI commands to ot-daemon to
+ * control its behavior.
+ *
+ * Note that this class takes root privileged to run.
+ */
+public final class OtDaemonController {
+ private static final String OT_CTL = "/system/bin/ot-ctl";
+
+ /**
+ * Factory resets ot-daemon.
+ *
+ *
This will erase all persistent data written into apexdata/com.android.apex/ot-daemon and
+ * restart the ot-daemon service.
+ */
+ public void factoryReset() {
+ executeCommand("factoryreset");
+
+ // TODO(b/323164524): ot-ctl is a separate process so that the tests can't depend on the
+ // time sequence. Here needs to wait for system server to receive the ot-daemon death
+ // signal and take actions.
+ // A proper fix is to replace "ot-ctl" with "cmd thread_network ot-ctl" which is
+ // synchronized with the system server
+ SystemClock.sleep(500);
+ }
+
+ /** Returns the list of IPv6 addresses on ot-daemon. */
+ public List getAddresses() {
+ String output = executeCommand("ipaddr");
+ return Arrays.asList(output.split("\n")).stream()
+ .map(String::trim)
+ .filter(str -> !str.equals("Done"))
+ .map(addr -> InetAddresses.parseNumericAddress(addr))
+ .map(inetAddr -> (Inet6Address) inetAddr)
+ .toList();
+ }
+
+ public String executeCommand(String cmd) {
+ return SystemUtil.runShellCommand(OT_CTL + " " + cmd);
+ }
+}