diff options
| author | 2018-03-02 09:32:28 +0000 | |
|---|---|---|
| committer | 2018-03-02 14:17:21 +0000 | |
| commit | c3ec538d4ef299bef2b7abd4b99ff84265fd5e8e (patch) | |
| tree | 0cf172f518931ebcee1678437105be19d22897e6 | |
| parent | 0f0d1ab1d370bb3f99a288a85b03f9b551eb1383 (diff) | |
Add transport tests
Some tests that were lacking.
Test: m -j RunFrameworksServicesRoboTests
Change-Id: Ia6800e950e96d7481d5c74f62147ad3b9b1493ba
3 files changed, 184 insertions, 20 deletions
diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientManagerTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientManagerTest.java index 5e3c9741ae70..3d2d8afd4a35 100644 --- a/services/robotests/src/com/android/server/backup/transport/TransportClientManagerTest.java +++ b/services/robotests/src/com/android/server/backup/transport/TransportClientManagerTest.java @@ -20,6 +20,7 @@ import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPOR import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,6 +31,7 @@ import android.content.ServiceConnection; import android.os.Bundle; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; + import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderPackages; import org.junit.Before; @@ -45,7 +47,6 @@ import org.robolectric.annotation.Config; @SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class TransportClientManagerTest { - private static final String PACKAGE_NAME = "random.package.name"; private static final String CLASS_NAME = "random.package.name.transport.Transport"; @@ -72,15 +73,30 @@ public class TransportClientManagerTest { } @Test + public void testGetTransportClient() { + TransportClient transportClient = + mTransportClientManager.getTransportClient(mTransportComponent, "caller"); + + // Connect to be able to extract the intent + transportClient.connectAsync(mTransportConnectionListener, "caller"); + verify(mContext) + .bindServiceAsUser( + argThat(matchesIntentAndExtras(mBindIntent)), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class)); + } + + @Test public void testGetTransportClient_withExtras_createsTransportClientWithCorrectIntent() { Bundle extras = new Bundle(); extras.putBoolean("random_extra", true); - mBindIntent.putExtras(extras); TransportClient transportClient = mTransportClientManager.getTransportClient(mTransportComponent, extras, "caller"); transportClient.connectAsync(mTransportConnectionListener, "caller"); + mBindIntent.putExtras(extras); verify(mContext) .bindServiceAsUser( argThat(matchesIntentAndExtras(mBindIntent)), @@ -89,6 +105,17 @@ public class TransportClientManagerTest { any(UserHandle.class)); } + @Test + public void testDisposeOfTransportClient() { + TransportClient transportClient = + spy(mTransportClientManager.getTransportClient(mTransportComponent, "caller")); + + mTransportClientManager.disposeOfTransportClient(transportClient, "caller"); + + verify(transportClient).unbind(any()); + verify(transportClient).markAsDisposed(); + } + private ArgumentMatcher<Intent> matchesIntentAndExtras(Intent expectedIntent) { return (Intent actualIntent) -> { if (!expectedIntent.filterEquals(actualIntent)) { diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java index ff1644cb75ad..5b65473e0783 100644 --- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java +++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java @@ -26,16 +26,21 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; +import static org.robolectric.shadow.api.Shadow.extract; import static org.testng.Assert.expectThrows; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; @@ -43,10 +48,9 @@ import android.util.Log; import com.android.internal.backup.IBackupTransport; import com.android.server.EventLogTags; -import com.android.server.backup.TransportManager; import com.android.server.testing.FrameworkRobolectricTestRunner; -import com.android.server.testing.SystemLoaderClasses; import com.android.server.testing.SystemLoaderPackages; +import com.android.server.testing.shadows.FrameworkShadowLooper; import com.android.server.testing.shadows.ShadowCloseGuard; import com.android.server.testing.shadows.ShadowEventLog; import com.android.server.testing.shadows.ShadowSlog; @@ -61,11 +65,19 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLooper; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + @RunWith(FrameworkRobolectricTestRunner.class) @Config( manifest = Config.NONE, sdk = 26, - shadows = {ShadowEventLog.class, ShadowCloseGuard.class, ShadowSlog.class} + shadows = { + ShadowEventLog.class, + ShadowCloseGuard.class, + ShadowSlog.class, + FrameworkShadowLooper.class + } ) @SystemLoaderPackages({"com.android.server.backup"}) @Presubmit @@ -80,14 +92,16 @@ public class TransportClientTest { private ComponentName mTransportComponent; private String mTransportString; private Intent mBindIntent; - private ShadowLooper mShadowLooper; + private FrameworkShadowLooper mShadowMainLooper; + private ShadowLooper mShadowWorkerLooper; + private Handler mWorkerHandler; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); Looper mainLooper = Looper.getMainLooper(); - mShadowLooper = shadowOf(mainLooper); + mShadowMainLooper = extract(mainLooper); mTransportComponent = new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport"); mTransportString = mTransportComponent.flattenToShortString(); @@ -107,6 +121,11 @@ public class TransportClientTest { anyInt(), any(UserHandle.class))) .thenReturn(true); + + HandlerThread workerThread = new HandlerThread("worker"); + workerThread.start(); + mShadowWorkerLooper = shadowOf(workerThread.getLooper()); + mWorkerHandler = workerThread.getThreadHandler(); } @Test @@ -129,12 +148,11 @@ public class TransportClientTest { @Test public void testConnectAsync_callsListenerWhenConnected() throws Exception { mTransportClient.connectAsync(mTransportConnectionListener, "caller"); - - // Simulate framework connecting ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(mTransportComponent, mTransportBinder); - mShadowLooper.runToEndOfTasks(); + mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); } @@ -148,8 +166,7 @@ public class TransportClientTest { mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); connection.onServiceConnected(mTransportComponent, mTransportBinder); - - mShadowLooper.runToEndOfTasks(); + mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); verify(mTransportConnectionListener2) @@ -164,7 +181,7 @@ public class TransportClientTest { mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); - mShadowLooper.runToEndOfTasks(); + mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener2) .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); } @@ -180,7 +197,7 @@ public class TransportClientTest { mTransportClient.connectAsync(mTransportConnectionListener, "caller"); - mShadowLooper.runToEndOfTasks(); + mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) .onTransportConnectionResult(isNull(), eq(mTransportClient)); } @@ -233,11 +250,11 @@ public class TransportClientTest { @Test public void testConnectAsync_callsListenerIfBindingDies() throws Exception { mTransportClient.connectAsync(mTransportConnectionListener, "caller"); - ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onBindingDied(mTransportComponent); - mShadowLooper.runToEndOfTasks(); + mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) .onTransportConnectionResult(isNull(), eq(mTransportClient)); } @@ -251,8 +268,7 @@ public class TransportClientTest { mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); connection.onBindingDied(mTransportComponent); - - mShadowLooper.runToEndOfTasks(); + mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) .onTransportConnectionResult(isNull(), eq(mTransportClient)); verify(mTransportConnectionListener2) @@ -271,9 +287,9 @@ public class TransportClientTest { @Test public void testConnectAsync_afterOnServiceConnected_logsBoundAndConnectedTransitions() { ShadowEventLog.setUp(); - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(mTransportComponent, mTransportBinder); assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1); @@ -283,9 +299,9 @@ public class TransportClientTest { @Test public void testConnectAsync_afterOnBindingDied_logsBoundAndUnboundTransitions() { ShadowEventLog.setUp(); - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onBindingDied(mTransportComponent); assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1); @@ -293,6 +309,66 @@ public class TransportClientTest { } @Test + public void testConnect_whenConnected_returnsTransport() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(mTransportComponent, mTransportBinder); + + IBackupTransport transportBinder = + runInWorkerThread(() -> mTransportClient.connect("caller2")); + + assertThat(transportBinder).isNotNull(); + } + + @Test + public void testConnect_afterOnServiceDisconnected_returnsNull() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onServiceConnected(mTransportComponent, mTransportBinder); + connection.onServiceDisconnected(mTransportComponent); + + IBackupTransport transportBinder = + runInWorkerThread(() -> mTransportClient.connect("caller2")); + + assertThat(transportBinder).isNull(); + } + + @Test + public void testConnect_afterOnBindingDied_returnsNull() throws Exception { + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); + connection.onBindingDied(mTransportComponent); + + IBackupTransport transportBinder = + runInWorkerThread(() -> mTransportClient.connect("caller2")); + + assertThat(transportBinder).isNull(); + } + + @Test + public void testConnect_callsThroughToConnectAsync() throws Exception { + // We can't mock bindServiceAsUser() instead of connectAsync() and call the listener inline + // because in our code in TransportClient we assume this is NOT run inline, such that the + // reentrant lock can't be acquired by the listener at the call-site of bindServiceAsUser(), + // which is what would happened if we mocked bindServiceAsUser() to call the listener + // inline. + TransportClient transportClient = spy(mTransportClient); + doAnswer( + invocation -> { + TransportConnectionListener listener = invocation.getArgument(0); + listener.onTransportConnectionResult(mTransportBinder, transportClient); + return null; + }) + .when(transportClient) + .connectAsync(any(), any()); + + IBackupTransport transportBinder = + runInWorkerThread(() -> transportClient.connect("caller")); + + assertThat(transportBinder).isNotNull(); + } + + @Test public void testUnbind_whenConnected_logsDisconnectedAndUnboundTransitions() { mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); @@ -447,6 +523,19 @@ public class TransportClientTest { assertThat(ShadowCloseGuard.hasReported()).isFalse(); } + @Nullable + private <T> T runInWorkerThread(Supplier<T> supplier) throws Exception { + CompletableFuture<T> future = new CompletableFuture<>(); + mWorkerHandler.post(() -> future.complete(supplier.get())); + // Although we are using a separate looper, we are still calling runToEndOfTasks() in the + // main thread (Robolectric only *simulates* multi-thread). The only option left is to fool + // the caller. + mShadowMainLooper.setCurrentThread(false); + mShadowWorkerLooper.runToEndOfTasks(); + mShadowMainLooper.reset(); + return future.get(); + } + private void assertEventLogged(int tag, Object... values) { assertThat(ShadowEventLog.hasEvent(tag, values)).isTrue(); } diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java new file mode 100644 index 000000000000..c0eeb3890a86 --- /dev/null +++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 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.testing.shadows; + +import android.os.Looper; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; +import org.robolectric.shadows.ShadowLooper; + +import java.util.Optional; + +@Implements(value = Looper.class, inheritImplementationMethods = true) +public class FrameworkShadowLooper extends ShadowLooper { + @RealObject private Looper mLooper; + private Optional<Boolean> mIsCurrentThread = Optional.empty(); + + public void setCurrentThread(boolean currentThread) { + mIsCurrentThread = Optional.of(currentThread); + } + + public void reset() { + mIsCurrentThread = Optional.empty(); + } + + @Implementation + public boolean isCurrentThread() { + if (mIsCurrentThread.isPresent()) { + return mIsCurrentThread.get(); + } + return Thread.currentThread() == mLooper.getThread(); + } +} |