From 26728aefc2baed2a1a82901dace2196b187cadc3 Mon Sep 17 00:00:00 2001 From: Brian Stack Date: Tue, 10 Nov 2020 16:22:14 -0800 Subject: [PATCH] Add tests for UWB AdapterStateListener Bug: 170323306 Test: atest UwbManagerTests Change-Id: I1f880aa6df30405960d4fa7442c23bc87eed5aaa --- core/tests/uwbtests/Android.bp | 1 + .../android/uwb/AdapterStateListenerTest.java | 311 ++++++++++++++++++ 2 files changed, 312 insertions(+) create mode 100644 core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java diff --git a/core/tests/uwbtests/Android.bp b/core/tests/uwbtests/Android.bp index c41c346b131a0..8ee86f470c9e3 100644 --- a/core/tests/uwbtests/Android.bp +++ b/core/tests/uwbtests/Android.bp @@ -17,6 +17,7 @@ android_test { static_libs: [ "androidx.test.ext.junit", "androidx.test.rules", + "mockito-target-minus-junit4", ], libs: [ "android.test.runner", diff --git a/core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java b/core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java new file mode 100644 index 0000000000000..ce67ef7868e89 --- /dev/null +++ b/core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java @@ -0,0 +1,311 @@ +/* + * Copyright 2020 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.uwb; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + + +import android.os.RemoteException; +import android.uwb.UwbManager.AdapterStateCallback; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Test of {@link AdapterStateListener}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AdapterStateListenerTest { + + IUwbAdapter mUwbAdapter = mock(IUwbAdapter.class); + + Answer mRegisterSuccessAnswer = new Answer() { + public Object answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + IUwbAdapterStateCallbacks cb = (IUwbAdapterStateCallbacks) args[0]; + try { + cb.onAdapterStateChanged(false, StateChangeReason.UNKNOWN); + } catch (RemoteException e) { + // Nothing to do + } + return new Object(); + } + }; + + Throwable mThrowRemoteException = new RemoteException("RemoteException"); + + private static Executor getExecutor() { + return new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + } + }; + } + + private static void verifyCallbackStateChangedInvoked( + AdapterStateCallback callback, int numTimes) { + verify(callback, times(numTimes)).onStateChanged(anyBoolean(), anyInt()); + } + + @Test + public void testRegister_RegisterUnregister() throws RemoteException { + doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any()); + + AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter); + AdapterStateCallback callback1 = mock(AdapterStateCallback.class); + AdapterStateCallback callback2 = mock(AdapterStateCallback.class); + + // Verify that the adapter state listener registered with the UWB Adapter + adapterStateListener.register(getExecutor(), callback1); + verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any()); + verifyCallbackStateChangedInvoked(callback1, 1); + verifyCallbackStateChangedInvoked(callback2, 0); + + // Register a second client and no new call to UWB Adapter + adapterStateListener.register(getExecutor(), callback2); + verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any()); + verifyCallbackStateChangedInvoked(callback1, 1); + verifyCallbackStateChangedInvoked(callback2, 1); + + // Unregister first callback + adapterStateListener.unregister(callback1); + verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any()); + verify(mUwbAdapter, times(0)).unregisterAdapterStateCallbacks(any()); + verifyCallbackStateChangedInvoked(callback1, 1); + verifyCallbackStateChangedInvoked(callback2, 1); + + // Unregister second callback + adapterStateListener.unregister(callback2); + verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any()); + verify(mUwbAdapter, times(1)).unregisterAdapterStateCallbacks(any()); + verifyCallbackStateChangedInvoked(callback1, 1); + verifyCallbackStateChangedInvoked(callback2, 1); + } + + @Test + public void testRegister_FirstRegisterFails() throws RemoteException { + AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter); + AdapterStateCallback callback1 = mock(AdapterStateCallback.class); + AdapterStateCallback callback2 = mock(AdapterStateCallback.class); + + // Throw a remote exception whenever first registering + doThrow(mThrowRemoteException).when(mUwbAdapter).registerAdapterStateCallbacks(any()); + + adapterStateListener.register(getExecutor(), callback1); + verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any()); + + // No longer throw an exception, instead succeed + doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any()); + + // Register a different callback + adapterStateListener.register(getExecutor(), callback2); + verify(mUwbAdapter, times(2)).registerAdapterStateCallbacks(any()); + + // Ensure first callback was invoked again + verifyCallbackStateChangedInvoked(callback1, 2); + verifyCallbackStateChangedInvoked(callback2, 1); + } + + @Test + public void testRegister_RegisterSameCallbackTwice() throws RemoteException { + AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter); + AdapterStateCallback callback = mock(AdapterStateCallback.class); + doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any()); + + adapterStateListener.register(getExecutor(), callback); + verifyCallbackStateChangedInvoked(callback, 1); + + adapterStateListener.register(getExecutor(), callback); + verifyCallbackStateChangedInvoked(callback, 1); + + // Invoke a state change and ensure the callback is only called once + adapterStateListener.onAdapterStateChanged(false, StateChangeReason.UNKNOWN); + verifyCallbackStateChangedInvoked(callback, 2); + } + + @Test + public void testCallback_RunViaExecutor_Success() throws RemoteException { + // Verify that the callbacks are invoked on the executor when successful + doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any()); + runViaExecutor(); + } + + @Test + public void testCallback_RunViaExecutor_Failure() throws RemoteException { + // Verify that the callbacks are invoked on the executor when there is a remote exception + doThrow(mThrowRemoteException).when(mUwbAdapter).registerAdapterStateCallbacks(any()); + runViaExecutor(); + } + + private void runViaExecutor() { + AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter); + AdapterStateCallback callback = mock(AdapterStateCallback.class); + + Executor executor = mock(Executor.class); + + // Do not run commands received and ensure that the callback is not invoked + doAnswer(new ExecutorAnswer(false)).when(executor).execute(any()); + adapterStateListener.register(executor, callback); + verify(executor, times(1)).execute(any()); + verifyCallbackStateChangedInvoked(callback, 0); + + // Manually invoke the callback and ensure callback is not invoked + adapterStateListener.onAdapterStateChanged(false, StateChangeReason.UNKNOWN); + verify(executor, times(2)).execute(any()); + verifyCallbackStateChangedInvoked(callback, 0); + + // Run the command that the executor receives + doAnswer(new ExecutorAnswer(true)).when(executor).execute(any()); + adapterStateListener.onAdapterStateChanged(false, StateChangeReason.UNKNOWN); + verify(executor, times(3)).execute(any()); + verifyCallbackStateChangedInvoked(callback, 1); + } + + class ExecutorAnswer implements Answer { + + final boolean mShouldRun; + ExecutorAnswer(boolean shouldRun) { + mShouldRun = shouldRun; + } + + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + if (mShouldRun) { + ((Runnable) invocation.getArgument(0)).run(); + } + return null; + } + } + + @Test + public void testNotify_AllCallbacksNotified() throws RemoteException { + doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any()); + + AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter); + List callbacks = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + AdapterStateCallback callback = mock(AdapterStateCallback.class); + adapterStateListener.register(getExecutor(), callback); + callbacks.add(callback); + } + + // Ensure every callback got the initial state + for (AdapterStateCallback callback : callbacks) { + verifyCallbackStateChangedInvoked(callback, 1); + } + + // Invoke a state change and ensure all callbacks are invoked + adapterStateListener.onAdapterStateChanged(true, StateChangeReason.ALL_SESSIONS_CLOSED); + for (AdapterStateCallback callback : callbacks) { + verifyCallbackStateChangedInvoked(callback, 2); + } + } + + @Test + public void testStateChange_CorrectValue() { + AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter); + + AdapterStateCallback callback = mock(AdapterStateCallback.class); + + adapterStateListener.register(getExecutor(), callback); + + runStateChangeValue(StateChangeReason.ALL_SESSIONS_CLOSED, + AdapterStateCallback.STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED); + + runStateChangeValue(StateChangeReason.SESSION_STARTED, + AdapterStateCallback.STATE_CHANGED_REASON_SESSION_STARTED); + + runStateChangeValue(StateChangeReason.SYSTEM_BOOT, + AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_BOOT); + + runStateChangeValue(StateChangeReason.SYSTEM_POLICY, + AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); + + runStateChangeValue(StateChangeReason.UNKNOWN, + AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN); + } + + private void runStateChangeValue(@StateChangeReason int reasonIn, + @AdapterStateCallback.StateChangedReason int reasonOut) { + AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter); + AdapterStateCallback callback = mock(AdapterStateCallback.class); + adapterStateListener.register(getExecutor(), callback); + + adapterStateListener.onAdapterStateChanged(false, reasonIn); + verify(callback, times(1)).onStateChanged(false, reasonOut); + + adapterStateListener.onAdapterStateChanged(true, reasonIn); + verify(callback, times(1)).onStateChanged(true, reasonOut); + } + + @Test + public void testStateChange_FirstRegisterGetsCorrectState() throws RemoteException { + AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter); + AdapterStateCallback callback = mock(AdapterStateCallback.class); + + Answer registerAnswer = new Answer() { + public Object answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + IUwbAdapterStateCallbacks cb = (IUwbAdapterStateCallbacks) args[0]; + try { + cb.onAdapterStateChanged(true, StateChangeReason.SESSION_STARTED); + } catch (RemoteException e) { + // Nothing to do + } + return new Object(); + } + }; + + doAnswer(registerAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any()); + + adapterStateListener.register(getExecutor(), callback); + verify(callback).onStateChanged(true, + AdapterStateCallback.STATE_CHANGED_REASON_SESSION_STARTED); + } + + @Test + public void testStateChange_SecondRegisterGetsCorrectState() { + AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter); + AdapterStateCallback callback1 = mock(AdapterStateCallback.class); + AdapterStateCallback callback2 = mock(AdapterStateCallback.class); + + adapterStateListener.register(getExecutor(), callback1); + adapterStateListener.onAdapterStateChanged(true, StateChangeReason.SYSTEM_BOOT); + + adapterStateListener.register(getExecutor(), callback2); + verify(callback2).onStateChanged(true, + AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_BOOT); + } +}