Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c3ec538d authored by Bernardo Rufino's avatar Bernardo Rufino
Browse files

Add transport tests

Some tests that were lacking.

Test: m -j RunFrameworksServicesRoboTests
Change-Id: Ia6800e950e96d7481d5c74f62147ad3b9b1493ba
parent 0f0d1ab1
Loading
Loading
Loading
Loading
+29 −2
Original line number Diff line number Diff line
@@ -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";

@@ -71,16 +72,31 @@ public class TransportClientManagerTest {
                .thenReturn(true);
    }

    @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)) {
+107 −18
Original line number Diff line number Diff line
@@ -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,15 +299,75 @@ 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);
        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0);
    }

    @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");
@@ -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();
    }
+48 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}