Merge "[Thread] add service unit test skeleton" into main am: 3deb5da6db
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2888486 Change-Id: I92124c04db4e4d0ac432fa3fac5adbf8cfb57538 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
commit
498be80d2e
4 changed files with 225 additions and 40 deletions
|
|
@ -36,7 +36,6 @@ import static android.net.thread.ThreadNetworkException.ERROR_RESOURCE_EXHAUSTED
|
|||
import static android.net.thread.ThreadNetworkException.ERROR_RESPONSE_BAD_FORMAT;
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_TIMEOUT;
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_CHANNEL;
|
||||
import static android.net.thread.ThreadNetworkException.ErrorCode;
|
||||
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
|
||||
|
||||
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_ABORT;
|
||||
|
|
@ -84,6 +83,7 @@ import android.net.thread.OperationalDatasetTimestamp;
|
|||
import android.net.thread.PendingOperationalDataset;
|
||||
import android.net.thread.ThreadNetworkController;
|
||||
import android.net.thread.ThreadNetworkController.DeviceRole;
|
||||
import android.net.thread.ThreadNetworkException.ErrorCode;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
|
|
@ -120,8 +120,8 @@ import java.util.function.Supplier;
|
|||
*
|
||||
* <p>Threading model: This class is not Thread-safe and should only be accessed from the
|
||||
* ThreadNetworkService class. Additional attention should be paid to handle the threading code
|
||||
* correctly: 1. All member fields other than `mHandler` and `mContext` MUST be accessed from
|
||||
* `mHandlerThread` 2. In the @Override methods, the actual work MUST be dispatched to the
|
||||
* correctly: 1. All member fields other than `mHandler` and `mContext` MUST be accessed from the
|
||||
* thread of `mHandler` 2. In the @Override methods, the actual work MUST be dispatched to the
|
||||
* HandlerThread except for arguments or permissions checking
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
|
|
@ -133,11 +133,10 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
|
||||
// Below member fields can only be accessed from the handler thread (`mHandlerThread`). In
|
||||
// Below member fields can only be accessed from the handler thread (`mHandler`). In
|
||||
// particular, the constructor does not run on the handler thread, so it must not touch any of
|
||||
// the non-final fields, nor must it mutate any of the non-final fields inside these objects.
|
||||
|
||||
private final HandlerThread mHandlerThread;
|
||||
private final NetworkProvider mNetworkProvider;
|
||||
private final Supplier<IOtDaemon> mOtDaemonSupplier;
|
||||
private final ConnectivityManager mConnectivityManager;
|
||||
|
|
@ -149,8 +148,10 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
// TODO(b/308310823): read supported channel from Thread dameon
|
||||
private final int mSupportedChannelMask = 0x07FFF800; // from channel 11 to 26
|
||||
|
||||
private IOtDaemon mOtDaemon;
|
||||
private NetworkAgent mNetworkAgent;
|
||||
@Nullable private IOtDaemon mOtDaemon;
|
||||
@Nullable private NetworkAgent mNetworkAgent;
|
||||
@Nullable private NetworkAgent mTestNetworkAgent;
|
||||
|
||||
private MulticastRoutingConfig mUpstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
|
||||
private MulticastRoutingConfig mDownstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
|
||||
private Network mUpstreamNetwork;
|
||||
|
|
@ -164,15 +165,14 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
@VisibleForTesting
|
||||
ThreadNetworkControllerService(
|
||||
Context context,
|
||||
HandlerThread handlerThread,
|
||||
Handler handler,
|
||||
NetworkProvider networkProvider,
|
||||
Supplier<IOtDaemon> otDaemonSupplier,
|
||||
ConnectivityManager connectivityManager,
|
||||
TunInterfaceController tunIfController,
|
||||
InfraInterfaceController infraIfController) {
|
||||
mContext = context;
|
||||
mHandlerThread = handlerThread;
|
||||
mHandler = new Handler(handlerThread.getLooper());
|
||||
mHandler = handler;
|
||||
mNetworkProvider = networkProvider;
|
||||
mOtDaemonSupplier = otDaemonSupplier;
|
||||
mConnectivityManager = connectivityManager;
|
||||
|
|
@ -191,7 +191,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
return new ThreadNetworkControllerService(
|
||||
context,
|
||||
handlerThread,
|
||||
new Handler(handlerThread.getLooper()),
|
||||
networkProvider,
|
||||
() -> IOtDaemon.Stub.asInterface(ServiceManagerWrapper.waitForService("ot_daemon")),
|
||||
context.getSystemService(ConnectivityManager.class),
|
||||
|
|
@ -199,14 +199,6 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
new InfraInterfaceController());
|
||||
}
|
||||
|
||||
private static NetworkCapabilities newNetworkCapabilities() {
|
||||
return new NetworkCapabilities.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
|
||||
try {
|
||||
return (Inet6Address) Inet6Address.getByAddress(addressBytes);
|
||||
|
|
@ -302,6 +294,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
}
|
||||
|
||||
private IOtDaemon getOtDaemon() throws RemoteException {
|
||||
checkOnHandlerThread();
|
||||
|
||||
if (mOtDaemon != null) {
|
||||
return mOtDaemon;
|
||||
}
|
||||
|
|
@ -310,9 +304,9 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
if (otDaemon == null) {
|
||||
throw new RemoteException("Internal error: failed to start OT daemon");
|
||||
}
|
||||
otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
|
||||
otDaemon.initialize(mTunIfController.getTunFd());
|
||||
otDaemon.registerStateCallback(mOtDaemonCallbackProxy, -1);
|
||||
otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
|
||||
mOtDaemon = otDaemon;
|
||||
return mOtDaemon;
|
||||
}
|
||||
|
|
@ -427,27 +421,47 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
mHandler);
|
||||
}
|
||||
|
||||
/** Injects a {@link NetworkAgent} for testing. */
|
||||
@VisibleForTesting
|
||||
void setTestNetworkAgent(@Nullable NetworkAgent testNetworkAgent) {
|
||||
mTestNetworkAgent = testNetworkAgent;
|
||||
}
|
||||
|
||||
private NetworkAgent newNetworkAgent() {
|
||||
if (mTestNetworkAgent != null) {
|
||||
return mTestNetworkAgent;
|
||||
}
|
||||
|
||||
final NetworkCapabilities netCaps =
|
||||
new NetworkCapabilities.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
|
||||
.build();
|
||||
final NetworkScore score =
|
||||
new NetworkScore.Builder()
|
||||
.setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK)
|
||||
.build();
|
||||
return new NetworkAgent(
|
||||
mContext,
|
||||
mHandler.getLooper(),
|
||||
TAG,
|
||||
netCaps,
|
||||
mLinkProperties,
|
||||
newLocalNetworkConfig(),
|
||||
score,
|
||||
new NetworkAgentConfig.Builder().build(),
|
||||
mNetworkProvider) {};
|
||||
}
|
||||
|
||||
private void registerThreadNetwork() {
|
||||
if (mNetworkAgent != null) {
|
||||
return;
|
||||
}
|
||||
NetworkCapabilities netCaps = newNetworkCapabilities();
|
||||
NetworkScore score =
|
||||
new NetworkScore.Builder()
|
||||
.setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK)
|
||||
.build();
|
||||
|
||||
requestThreadNetwork();
|
||||
mNetworkAgent =
|
||||
new NetworkAgent(
|
||||
mContext,
|
||||
mHandlerThread.getLooper(),
|
||||
TAG,
|
||||
netCaps,
|
||||
mLinkProperties,
|
||||
newLocalNetworkConfig(),
|
||||
score,
|
||||
new NetworkAgentConfig.Builder().build(),
|
||||
mNetworkProvider) {};
|
||||
|
||||
mNetworkAgent = newNetworkAgent();
|
||||
mNetworkAgent.register();
|
||||
mNetworkAgent.markConnected();
|
||||
Log.i(TAG, "Registered Thread network");
|
||||
|
|
@ -634,7 +648,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
}
|
||||
|
||||
private void checkOnHandlerThread() {
|
||||
if (Looper.myLooper() != mHandlerThread.getLooper()) {
|
||||
if (Looper.myLooper() != mHandler.getLooper()) {
|
||||
Log.wtf(TAG, "Must be on the handler thread!");
|
||||
}
|
||||
}
|
||||
|
|
@ -950,8 +964,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles and forwards Thread daemon callbacks. This class must be accessed from the {@code
|
||||
* mHandlerThread}.
|
||||
* Handles and forwards Thread daemon callbacks. This class must be accessed from the thread of
|
||||
* {@code mHandler}.
|
||||
*/
|
||||
private final class OtDaemonCallbackProxy extends IOtDaemonCallback.Stub {
|
||||
private final Map<IStateCallback, CallbackMetadata> mStateCallbacks = new HashMap<>();
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ android_test {
|
|||
"guava-android-testlib",
|
||||
"mockito-target-extended-minus-junit4",
|
||||
"net-tests-utils",
|
||||
"ot-daemon-aidl-java",
|
||||
"ot-daemon-testing",
|
||||
"service-connectivity-pre-jarjar",
|
||||
"service-thread-pre-jarjar",
|
||||
"truth",
|
||||
|
|
@ -49,12 +51,15 @@ android_test {
|
|||
"ServiceConnectivityResources",
|
||||
"framework-wifi",
|
||||
],
|
||||
jarjar_rules: ":connectivity-jarjar-rules",
|
||||
jni_libs: [
|
||||
"libservice-thread-jni",
|
||||
|
||||
// these are needed for Extended Mockito
|
||||
"libdexmakerjvmtiagent",
|
||||
"libstaticjvmtiagent",
|
||||
],
|
||||
jni_uses_platform_apis: true,
|
||||
jarjar_rules: ":connectivity-jarjar-rules",
|
||||
// Test coverage system runs on different devices. Need to
|
||||
// compile for all architectures.
|
||||
compile_multilib: "both",
|
||||
|
|
|
|||
|
|
@ -30,5 +30,8 @@
|
|||
<option name="hidden-api-checks" value="false"/>
|
||||
<!-- Ignores tests introduced by guava-android-testlib -->
|
||||
<option name="exclude-annotation" value="org.junit.Ignore"/>
|
||||
<!-- Ignores tests introduced by frameworks-base-testutils -->
|
||||
<option name="exclude-filter" value="android.os.test.TestLooperTest"/>
|
||||
<option name="exclude-filter" value="com.android.test.filters.SelectTestTests"/>
|
||||
</test>
|
||||
</configuration>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Copyright (C) 2023 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 com.android.server.thread;
|
||||
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
|
||||
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
|
||||
|
||||
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
||||
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkAgent;
|
||||
import android.net.NetworkProvider;
|
||||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.IOperationReceiver;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.os.test.TestLooper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.server.thread.openthread.testing.FakeOtDaemon;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** Unit tests for {@link ThreadNetworkControllerService}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class ThreadNetworkControllerServiceTest {
|
||||
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
|
||||
// Active Timestamp: 1
|
||||
// Channel: 19
|
||||
// Channel Mask: 0x07FFF800
|
||||
// Ext PAN ID: ACC214689BC40BDF
|
||||
// Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
|
||||
// Network Key: F26B3153760F519A63BAFDDFFC80D2AF
|
||||
// Network Name: OpenThread-d9a0
|
||||
// PAN ID: 0xD9A0
|
||||
// PSKc: A245479C836D551B9CA557F7B9D351B4
|
||||
// Security Policy: 672 onrcb
|
||||
private static final byte[] DEFAULT_ACTIVE_DATASET_TLVS =
|
||||
base16().decode(
|
||||
"0E080000000000010000000300001335060004001FFFE002"
|
||||
+ "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
|
||||
+ "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
|
||||
+ "642D643961300102D9A00410A245479C836D551B9CA557F7"
|
||||
+ "B9D351B40C0402A0FFF8");
|
||||
private static final ActiveOperationalDataset DEFAULT_ACTIVE_DATASET =
|
||||
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_ACTIVE_DATASET_TLVS);
|
||||
|
||||
@Mock private ConnectivityManager mMockConnectivityManager;
|
||||
@Mock private NetworkAgent mMockNetworkAgent;
|
||||
@Mock private TunInterfaceController mMockTunIfController;
|
||||
@Mock private ParcelFileDescriptor mMockTunFd;
|
||||
@Mock private InfraInterfaceController mMockInfraIfController;
|
||||
private Context mContext;
|
||||
private TestLooper mTestLooper;
|
||||
private FakeOtDaemon mFakeOtDaemon;
|
||||
private ThreadNetworkControllerService mService;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mContext = ApplicationProvider.getApplicationContext();
|
||||
mTestLooper = new TestLooper();
|
||||
final Handler handler = new Handler(mTestLooper.getLooper());
|
||||
NetworkProvider networkProvider =
|
||||
new NetworkProvider(mContext, mTestLooper.getLooper(), "ThreadNetworkProvider");
|
||||
|
||||
mFakeOtDaemon = new FakeOtDaemon(handler);
|
||||
|
||||
when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
|
||||
|
||||
mService =
|
||||
new ThreadNetworkControllerService(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
handler,
|
||||
networkProvider,
|
||||
() -> mFakeOtDaemon,
|
||||
mMockConnectivityManager,
|
||||
mMockTunIfController,
|
||||
mMockInfraIfController);
|
||||
mService.setTestNetworkAgent(mMockNetworkAgent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialize_tunInterfaceSetToOtDaemon() throws Exception {
|
||||
when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
|
||||
|
||||
mService.initialize();
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mMockTunIfController, times(1)).createTunInterface();
|
||||
assertThat(mFakeOtDaemon.getTunFd()).isEqualTo(mMockTunFd);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void join_otDaemonRemoteFailure_returnsInternalError() throws Exception {
|
||||
mService.initialize();
|
||||
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
|
||||
mFakeOtDaemon.setJoinException(new RemoteException("ot-daemon join() throws"));
|
||||
|
||||
runAsShell(
|
||||
PERMISSION_THREAD_NETWORK_PRIVILEGED,
|
||||
() -> mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver));
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mockReceiver, never()).onSuccess();
|
||||
verify(mockReceiver, times(1)).onError(eq(ERROR_INTERNAL_ERROR), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void join_succeed_threadNetworkRegistered() throws Exception {
|
||||
mService.initialize();
|
||||
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
|
||||
|
||||
runAsShell(
|
||||
PERMISSION_THREAD_NETWORK_PRIVILEGED,
|
||||
() -> mService.join(DEFAULT_ACTIVE_DATASET, mockReceiver));
|
||||
// Here needs to call Testlooper#dispatchAll twices because TestLooper#moveTimeForward
|
||||
// operates on only currently enqueued messages but the delayed message is posted from
|
||||
// another Handler task.
|
||||
mTestLooper.dispatchAll();
|
||||
mTestLooper.moveTimeForward(FakeOtDaemon.JOIN_DELAY.toMillis() + 100);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mockReceiver, times(1)).onSuccess();
|
||||
verify(mMockNetworkAgent, times(1)).register();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue