Add unit tests for radio service implementations

Bug: 241118988
Test: atest com.android.server.broadcastradio
Change-Id: I7c039bafe9cb466afd1a2a9af62052a1222598cc
This commit is contained in:
Weilin Xu
2022-10-31 16:18:13 +00:00
parent 58e1c9fba4
commit f05be09584
8 changed files with 353 additions and 20 deletions

View File

@@ -48,7 +48,7 @@ android_test {
],
// mockito-target-inline dependency
jni_libs: [
"libcarservicejni",
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
],
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2022 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.
-->
<configuration description="Runs Broadcast Radio Tests.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="BroadcastRadioTests.apk" />
</target_preparer>
<option name="test-tag" value="BroadcastRadioTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.hardware.radio.tests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
</test>
</configuration>

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2022 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.broadcastradio;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import android.util.Log;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import org.junit.After;
import org.junit.Before;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
/**
* Base class to make it easier to write tests that uses {@code ExtendedMockito} for radio.
*
*/
public abstract class ExtendedRadioMockitoTestCase {
private static final String TAG = "RadioMockitoTestCase";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private MockitoSession mSession;
@Before
public void startSession() {
StaticMockitoSessionBuilder builder = mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT);
initializeSession(builder);
mSession = builder.startMocking();
}
/**
* Initializes the mockito session for radio test.
*
* <p>Typically used to define which classes should have static methods mocked or spied.
*/
protected void initializeSession(StaticMockitoSessionBuilder builder) {
if (DEBUG) {
Log.d(TAG, "initializeSession()");
}
}
@After
public final void finishSession() {
if (mSession == null) {
Log.w(TAG, "finishSession(): no session");
return;
}
try {
if (DEBUG) {
Log.d(TAG, "finishSession()");
}
} finally {
// mSession.finishMocking() must ALWAYS be called (hence the over-protective try/finally
// statements), otherwise it would cause failures on future tests as mockito
// cannot start a session when a previous one is not finished
mSession.finishMocking();
}
}
}

View File

@@ -16,6 +16,8 @@
package com.android.server.broadcastradio;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -31,30 +33,36 @@ import android.hardware.radio.ICloseHandle;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
import android.os.IBinder;
import android.os.ServiceManager;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.List;
/**
* Tests for {@link android.hardware.radio.IRadioService} with AIDL HAL implementation
*/
@RunWith(MockitoJUnitRunner.class)
public final class IRadioServiceAidlImplTest {
public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCase {
private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
private static final String AM_FM_SERVICE_NAME =
"android.hardware.broadcastradio.IBroadcastRadio/amfm";
private static final String DAB_SERVICE_NAME =
"android.hardware.broadcastradio.IBroadcastRadio/dab";
private IRadioServiceAidlImpl mAidlImpl;
@Mock
private BroadcastRadioService mServiceMock;
@Mock
private IBinder mServiceBinderMock;
@Mock
private BroadcastRadioServiceImpl mHalMock;
@Mock
private RadioManager.ModuleProperties mModuleMock;
@@ -73,7 +81,7 @@ public final class IRadioServiceAidlImplTest {
public void setUp() throws Exception {
doNothing().when(mServiceMock).enforcePolicyAccess();
when(mHalMock.listModules()).thenReturn(Arrays.asList(mModuleMock));
when(mHalMock.listModules()).thenReturn(List.of(mModuleMock));
when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any()))
.thenReturn(mTunerMock);
when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle);
@@ -81,11 +89,26 @@ public final class IRadioServiceAidlImplTest {
mAidlImpl = new IRadioServiceAidlImpl(mServiceMock, mHalMock);
}
@Override
protected void initializeSession(StaticMockitoSessionBuilder builder) {
builder.spyStatic(ServiceManager.class);
}
@Test
public void getServicesNames_forAidlImpl() {
doReturn(null).when(() -> ServiceManager.waitForDeclaredService(
AM_FM_SERVICE_NAME));
doReturn(mServiceBinderMock).when(() -> ServiceManager.waitForDeclaredService(
DAB_SERVICE_NAME));
assertWithMessage("Names of services available")
.that(IRadioServiceAidlImpl.getServicesNames()).containsExactly(DAB_SERVICE_NAME);
}
@Test
public void loadModules_forAidlImpl() {
assertWithMessage("Modules loaded in AIDL HAL")
.that(mAidlImpl.listModules())
.containsExactly(mModuleMock);
.that(mAidlImpl.listModules()).containsExactly(mModuleMock);
}
@Test
@@ -105,5 +128,4 @@ public final class IRadioServiceAidlImplTest {
assertWithMessage("Close handle of announcement listener for HAL 2")
.that(closeHandle).isEqualTo(mICloseHandle);
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright (C) 2022 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.broadcastradio.aidl;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.hardware.broadcastradio.IBroadcastRadio;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
import android.os.IBinder;
import android.os.IServiceCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTestCase {
private static final int FM_RADIO_MODULE_ID = 0;
private static final int DAB_RADIO_MODULE_ID = 1;
private static final ArrayList<String> SERVICE_LIST =
new ArrayList<>(Arrays.asList("FmService", "DabService"));
private BroadcastRadioServiceImpl mBroadcastRadioService;
private IBinder.DeathRecipient mFmDeathRecipient;
@Mock
private RadioManager.ModuleProperties mFmModuleMock;
@Mock
private RadioManager.ModuleProperties mDabModuleMock;
@Mock
private RadioModule mFmRadioModuleMock;
@Mock
private RadioModule mDabRadioModuleMock;
@Mock
private IBroadcastRadio mFmHalServiceMock;
@Mock
private IBroadcastRadio mDabHalServiceMock;
@Mock
private IBinder mFmBinderMock;
@Mock
private IBinder mDabBinderMock;
@Mock
private TunerSession mFmTunerSessionMock;
@Mock
private ITunerCallback mTunerCallbackMock;
@Override
protected void initializeSession(StaticMockitoSessionBuilder builder) {
builder.spyStatic(ServiceManager.class)
.spyStatic(RadioModule.class);
}
@Test
public void listModules_withMultipleServiceNames() throws Exception {
createBroadcastRadioService();
assertWithMessage("Radio modules in AIDL broadcast radio HAL client")
.that(mBroadcastRadioService.listModules())
.containsExactly(mFmModuleMock, mDabModuleMock);
}
@Test
public void hasModules_withIdFoundInModules() throws Exception {
createBroadcastRadioService();
assertWithMessage("DAB radio module in AIDL broadcast radio HAL client")
.that(mBroadcastRadioService.hasModule(DAB_RADIO_MODULE_ID)).isTrue();
}
@Test
public void hasModules_withIdNotFoundInModules() throws Exception {
createBroadcastRadioService();
assertWithMessage("Radio module of id not found in AIDL broadcast radio HAL client")
.that(mBroadcastRadioService.hasModule(DAB_RADIO_MODULE_ID + 1)).isFalse();
}
@Test
public void hasAnyModules_withModulesExist() throws Exception {
createBroadcastRadioService();
assertWithMessage("Any radio module in AIDL broadcast radio HAL client")
.that(mBroadcastRadioService.hasAnyModules()).isTrue();
}
@Test
public void openSession_withIdFound() throws Exception {
createBroadcastRadioService();
ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
/*legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
assertWithMessage("Session opened in FM radio module")
.that(session).isEqualTo(mFmTunerSessionMock);
}
@Test
public void openSession_withIdNotFound() throws Exception {
createBroadcastRadioService();
ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1,
/*legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
assertWithMessage("Session opened with id not found").that(session).isNull();
}
@Test
public void binderDied_forDeathRecipient() throws Exception {
createBroadcastRadioService();
mFmDeathRecipient.binderDied();
verify(mFmRadioModuleMock).closeSessions(eq(RadioTuner.ERROR_HARDWARE_FAILURE));
assertWithMessage("FM radio module after FM broadcast radio HAL service died")
.that(mBroadcastRadioService.hasModule(FM_RADIO_MODULE_ID)).isFalse();
}
private void createBroadcastRadioService() throws RemoteException {
mockServiceManager();
mBroadcastRadioService = new BroadcastRadioServiceImpl(SERVICE_LIST);
}
private void mockServiceManager() throws RemoteException {
doAnswer((Answer<Void>) invocation -> {
String serviceName = (String) invocation.getArguments()[0];
IServiceCallback serviceCallback = (IServiceCallback) invocation.getArguments()[1];
IBinder mockBinder = serviceName.equals("FmService") ? mFmBinderMock : mDabBinderMock;
serviceCallback.onRegistration(serviceName, mockBinder);
return null;
}).when(() -> ServiceManager.registerForNotifications(anyString(),
any(IServiceCallback.class)));
doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
eq(FM_RADIO_MODULE_ID), anyString(), any(IBinder.class), any(Object.class)));
doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule(
eq(DAB_RADIO_MODULE_ID), anyString(), any(IBinder.class), any(Object.class)));
when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock);
when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock);
when(mFmRadioModuleMock.getService()).thenReturn(mFmHalServiceMock);
when(mDabRadioModuleMock.getService()).thenReturn(mDabHalServiceMock);
when(mFmHalServiceMock.asBinder()).thenReturn(mFmBinderMock);
when(mDabHalServiceMock.asBinder()).thenReturn(mDabBinderMock);
doAnswer(invocation -> {
mFmDeathRecipient = (IBinder.DeathRecipient) invocation.getArguments()[0];
return null;
}).when(mFmBinderMock).linkToDeath(any(), anyInt());
when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock)))
.thenReturn(mFmTunerSessionMock);
}
}

View File

@@ -47,6 +47,15 @@ import org.mockito.junit.MockitoJUnitRunner;
public final class RadioModuleTest {
private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EVENT;
private static final RadioManager.ModuleProperties TEST_MODULE_PROPERTIES =
new RadioManager.ModuleProperties(/* id= */ 0, /* serviceName= */ "", /* classId= */ 0,
/* implementor= */ "", /* product= */ "", /* version= */ "",
/* serial= */ "", /* numTuners= */ 0, /* numAudioSources= */ 0,
/* isInitializationRequired= */ false, /* isCaptureSupported= */ false,
/* bands= */ null, /* isBgScanSupported= */ false,
/* supportedProgramTypes= */ new int[]{},
/* supportedIdentifierTypes */ new int[]{},
/* dabFrequencyTable= */ null, /* vendorInfo= */ null);
// Mocks
@Mock
@@ -63,14 +72,7 @@ public final class RadioModuleTest {
@Before
public void setup() throws RemoteException {
mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
/* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
/* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
/* numAudioSources= */ 0, /* isInitializationRequired= */ false,
/* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false,
/* supportedProgramTypes= */ new int[]{},
/* supportedIdentifierTypes */ new int[]{},
/* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock);
mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES, mLock);
// TODO(b/241118988): test non-null image for getImage method
when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
@@ -87,6 +89,12 @@ public final class RadioModuleTest {
.that(mRadioModule.getService()).isEqualTo(mBroadcastRadioMock);
}
@Test
public void getProperties() {
assertWithMessage("Module properties of radio module")
.that(mRadioModule.getProperties()).isEqualTo(TEST_MODULE_PROPERTIES);
}
@Test
public void setInternalHalCallback_callbackSetInHal() throws Exception {
mRadioModule.setInternalHalCallback();

View File

@@ -169,7 +169,7 @@ public final class BroadcastRadioServiceImpl {
synchronized (mLock) {
List<RadioManager.ModuleProperties> moduleList = new ArrayList<>(mModules.size());
for (int i = 0; i < mModules.size(); i++) {
moduleList.add(mModules.valueAt(i).mProperties);
moduleList.add(mModules.valueAt(i).getProperties());
}
return moduleList;
}

View File

@@ -54,11 +54,11 @@ final class RadioModule {
private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
private final IBroadcastRadio mService;
public final RadioManager.ModuleProperties mProperties;
private final Object mLock;
private final Handler mHandler;
private final RadioLogger mLogger;
private final RadioManager.ModuleProperties mProperties;
/**
* Tracks antenna state reported by HAL (if any).
@@ -217,6 +217,10 @@ final class RadioModule {
return mService;
}
public RadioManager.ModuleProperties getProperties() {
return mProperties;
}
void setInternalHalCallback() throws RemoteException {
synchronized (mLock) {
mService.setTunerCallback(mHalTunerCallback);