[Thread] add integration tests for aosp/3334156

Bug: 374037595
Change-Id: I1a380067681297c6047d9b0b772c873ecd110754
This commit is contained in:
Kangping Dong 2024-11-05 18:36:08 +08:00
commit 3b6bb1ba4a
4 changed files with 128 additions and 1 deletions

View file

@ -18,8 +18,8 @@ package android.net.thread;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.thread.utils.IntegrationTestUtils.buildIcmpv4EchoReply;
import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
import static android.net.thread.utils.IntegrationTestUtils.buildIcmpv4EchoReply;
import static android.net.thread.utils.IntegrationTestUtils.enableThreadAndJoinNetwork;
import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv4Packet;

View file

@ -29,6 +29,7 @@ import static android.net.thread.utils.IntegrationTestUtils.getPrefixesFromNetDa
import static android.net.thread.utils.IntegrationTestUtils.getThreadNetwork;
import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static android.net.thread.utils.ThreadNetworkControllerWrapper.JOIN_TIMEOUT;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
@ -55,6 +56,7 @@ import android.net.thread.utils.OtDaemonController;
import android.net.thread.utils.ThreadFeatureCheckerRule;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.net.thread.utils.ThreadNetworkControllerWrapper;
import android.net.thread.utils.ThreadStateListener;
import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider;
@ -138,6 +140,8 @@ public class ThreadIntegrationTest {
@After
public void tearDown() throws Exception {
ThreadStateListener.stopAllListeners();
mController.setTestNetworkAsUpstreamAndWait(null);
mController.leaveAndWait();
mController.setConfigurationAndWait(DEFAULT_CONFIG);
@ -267,6 +271,20 @@ public class ThreadIntegrationTest {
assertTunInterfaceMemberOfGroup(GROUP_ADDR_ALL_ROUTERS);
}
@Test
public void joinNetwork_joinTheSameNetworkTwice_neverDetached() throws Exception {
mController.joinAndWait(DEFAULT_DATASET);
mController.waitForRole(DEVICE_ROLE_LEADER, JOIN_TIMEOUT);
var listener = ThreadStateListener.startListener(mController.get());
mController.joinAndWait(DEFAULT_DATASET);
assertThat(
listener.pollForAnyRoleOf(
List.of(DEVICE_ROLE_DETACHED, DEVICE_ROLE_STOPPED), JOIN_TIMEOUT))
.isNull();
}
@Test
public void edPingsMeshLocalAddresses_oneReplyPerRequest() throws Exception {
mController.joinAndWait(DEFAULT_DATASET);

View file

@ -37,6 +37,7 @@ import android.net.thread.ThreadNetworkManager;
import android.os.OutcomeReceiver;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@ -53,6 +54,9 @@ public final class ThreadNetworkControllerWrapper {
private final ThreadNetworkController mController;
private final List<Integer> mDeviceRoleUpdates = new ArrayList<>();
@Nullable private StateCallback mStateCallback;
/**
* Returns a new {@link ThreadNetworkControllerWrapper} instance or {@code null} if Thread
* feature is not supported on this device.
@ -70,6 +74,15 @@ public final class ThreadNetworkControllerWrapper {
mController = controller;
}
/**
* Returns the underlying {@link ThreadNetworkController} object or {@code null} if the current
* platform doesn't support it.
*/
@Nullable
public ThreadNetworkController get() {
return mController;
}
/**
* Returns the Thread enabled state.
*

View file

@ -0,0 +1,96 @@
/*
* 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 android.Manifest.permission.ACCESS_NETWORK_STATE;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import android.annotation.Nullable;
import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkController.StateCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.ArrayTrackRecord;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
/**
* A listener for sequential Thread state updates.
*
* <p>This is a wrapper around {@link ThreadNetworkController#registerStateCallback} to make
* synchronized access to Thread state updates easier.
*/
@VisibleForTesting
public final class ThreadStateListener {
private static final List<ThreadStateListener> sListeners = new ArrayList<>();
private final ArrayTrackRecord<Integer> mDeviceRoleUpdates = new ArrayTrackRecord<>();
private final ArrayTrackRecord<Integer>.ReadHead mReadHead = mDeviceRoleUpdates.newReadHead();
private final ThreadNetworkController mController;
private final StateCallback mCallback =
new ThreadNetworkController.StateCallback() {
@Override
public void onDeviceRoleChanged(int newRole) {
mDeviceRoleUpdates.add(newRole);
}
// Add more state update trackers here
};
/** Creates a new {@link ThreadStateListener} object and starts listening for state updates. */
public static ThreadStateListener startListener(ThreadNetworkController controller) {
var listener = new ThreadStateListener(controller);
sListeners.add(listener);
listener.start();
return listener;
}
/** Stops all listeners created by {@link #startListener}. */
public static void stopAllListeners() {
for (var listener : sListeners) {
listener.stop();
}
sListeners.clear();
}
private ThreadStateListener(ThreadNetworkController controller) {
mController = controller;
}
private void start() {
runAsShell(
ACCESS_NETWORK_STATE,
() -> mController.registerStateCallback(directExecutor(), mCallback));
}
private void stop() {
runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(mCallback));
}
/**
* Polls for any role in {@code roles} starting after call to {@link #startListener}.
*
* <p>Returns the matched device role or {@code null} if timeout.
*/
@Nullable
public Integer pollForAnyRoleOf(List<Integer> roles, Duration timeout) {
return mReadHead.poll(timeout.toMillis(), newRole -> (roles.contains(newRole)));
}
}