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

Commit e1062bc7 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add test method...

Merge "Add test method UserLifecycleStressTest.switchToExistingGuestAndStartOverStressTest" into tm-dev
parents e0363130 5e0fa4b1
Loading
Loading
Loading
Loading
+142 −32
Original line number Diff line number Diff line
@@ -15,11 +15,15 @@
 */
package com.android.server.pm;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import android.app.ActivityManager;
import android.app.IStopUserCallback;
import android.app.UserSwitchObserver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.RemoteException;
@@ -31,17 +35,18 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.util.FunctionalUtils;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * To run the test:
 * bit FrameworksServicesTests:com.android.server.pm.UserLifecycleStressTest
 * atest FrameworksServicesTests:com.android.server.pm.UserLifecycleStressTest
 */
@Postsubmit
@RunWith(AndroidJUnit4.class)
@@ -49,8 +54,8 @@ import java.util.concurrent.TimeUnit;
public class UserLifecycleStressTest {
    private static final String TAG = "UserLifecycleStressTest";
    // TODO: Make this smaller once we have improved it.
    private static final int MAX_TIME_STOP_USER_IN_SECOND = 30;
    private static final int NUM_ITERATIONS_STOP_USER = 10;
    private static final int TIMEOUT_IN_SECOND = 40;
    private static final int NUM_ITERATIONS = 10;
    private static final int WAIT_BEFORE_STOP_USER_IN_SECOND = 3;

    private Context mContext;
@@ -65,20 +70,19 @@ public class UserLifecycleStressTest {
    }

    /**
     * Create and stop user 10 times in a row. Check stop user can be finished in a reasonable
     * amount of time.
     * Create and stop user {@link #NUM_ITERATIONS} times in a row. Check stop user can be finished
     * in a reasonable amount of time.
     */
    @Test
    public void stopManagedProfileStressTest()
            throws IOException, RemoteException, InterruptedException {
        for (int i = 0; i < NUM_ITERATIONS_STOP_USER; i++) {
    public void stopManagedProfileStressTest() throws RemoteException, InterruptedException {
        for (int i = 0; i < NUM_ITERATIONS; i++) {
            final UserInfo userInfo = mUserManager.createProfileForUser("TestUser",
                    UserManager.USER_TYPE_PROFILE_MANAGED, 0, mActivityManager.getCurrentUser());
            assertNotNull(userInfo);
            assertThat(userInfo).isNotNull();
            try {
                assertTrue(
                        "Failed to start the profile",
                        ActivityManager.getService().startUserInBackground(userInfo.id));
                assertWithMessage("Failed to start the profile")
                        .that(ActivityManager.getService().startUserInBackground(userInfo.id))
                        .isTrue();
                // Seems the broadcast queue is getting more busy if we wait a few seconds before
                // stopping the user.
                TimeUnit.SECONDS.sleep(WAIT_BEFORE_STOP_USER_IN_SECOND);
@@ -89,28 +93,134 @@ public class UserLifecycleStressTest {
        }
    }

    /**
     * Starts over the guest user {@link #NUM_ITERATIONS} times in a row.
     *
     * Starting over the guest means the following:
     * 1. While the guest user is in foreground, mark it for deletion.
     * 2. Create a new guest. (This wouldn't be possible if the old one wasn't marked for deletion)
     * 3. Switch to newly created guest.
     * 4. Remove the previous guest before waiting for switch to complete.
     **/
    @Test
    public void switchToExistingGuestAndStartOverStressTest() throws Exception {
        if (ActivityManager.getCurrentUser() != USER_SYSTEM) {
            switchUser(USER_SYSTEM);
        }

        final UserInfo foundGuest = mUserManager.findCurrentGuestUser();
        int nextGuestId = foundGuest == null ? USER_NULL : foundGuest.id;

        for (int i = 0; i < NUM_ITERATIONS; i++) {
            final int currentGuestId = nextGuestId;

            Log.d(TAG, "switchToExistingGuestAndStartOverStressTest"
                    + " - Run " + (i + 1) + " / " + NUM_ITERATIONS);

            if (currentGuestId != USER_NULL) {
                Log.d(TAG, "Switching to the existing guest");
                switchUser(currentGuestId);

                Log.d(TAG, "Marking current guest for deletion");
                assertWithMessage("Couldn't mark guest for deletion")
                        .that(mUserManager.markGuestForDeletion(currentGuestId))
                        .isTrue();
            }

            Log.d(TAG, "Creating a new guest");
            final UserInfo newGuest = mUserManager.createGuest(mContext);
            assertWithMessage("Couldn't create new guest")
                    .that(newGuest)
                    .isNotNull();

            Log.d(TAG, "Switching to the new guest");
            switchUserThenRun(newGuest.id, () -> {
                if (currentGuestId != USER_NULL) {
                    Log.d(TAG, "Removing the previous guest before waiting for switch to complete");
                    assertWithMessage("Couldn't remove guest")
                            .that(mUserManager.removeUser(currentGuestId))
                            .isTrue();
                }
            });
            Log.d(TAG, "Switching back to the system user");
            switchUser(USER_SYSTEM);

            nextGuestId = newGuest.id;
        }
        if (nextGuestId != USER_NULL) {
            Log.d(TAG, "Removing the last created guest user");
            mUserManager.removeUser(nextGuestId);
        }
        Log.d(TAG, "testSwitchToExistingGuestAndStartOver - End");
    }

    /** Stops the given user and waits for the stop to finish. */
    private void stopUser(int userId) throws RemoteException, InterruptedException {
        final long startTime = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ActivityManager.getService().
                stopUser(userId, true,
                        new IStopUserCallback.Stub() {
        runWithLatch("stop user", countDownLatch -> {
            ActivityManager.getService()
                    .stopUser(userId, /* force= */ true, new IStopUserCallback.Stub() {
                        @Override
                            public void userStopped(int userId) throws RemoteException {
                        public void userStopped(int userId) {
                            countDownLatch.countDown();
                        }

                        @Override
                            public void userStopAborted(int userId) throws RemoteException {
                        public void userStopAborted(int i) throws RemoteException {

                        }
                    });
        });
    }

    /** Starts the given user in the foreground and waits for the switch to finish. */
    private void switchUser(int userId) throws RemoteException, InterruptedException {
        switchUserThenRun(userId, null);
    }

    /**
     * Starts the given user in the foreground. And runs the given Runnable right after
     * am.switchUser call, before waiting for the actual user switch to be complete.
     **/
    private void switchUserThenRun(int userId, Runnable runAfterSwitchBeforeWait)
            throws RemoteException, InterruptedException {
        runWithLatch("switch user", countDownLatch -> {
            ActivityManager.getService().registerUserSwitchObserver(
                    new UserSwitchObserver() {
                        @Override
                        public void onUserSwitchComplete(int newUserId) {
                            if (userId == newUserId) {
                                countDownLatch.countDown();
                            }
                        }
                    }, TAG);
            Log.d(TAG, "Switching to user " + userId);
            assertWithMessage("Failed to switch to user")
                    .that(mActivityManager.switchUser(userId))
                    .isTrue();
            if (runAfterSwitchBeforeWait != null) {
                runAfterSwitchBeforeWait.run();
            }
        });
        boolean stoppedBeforeTimeout =
                countDownLatch.await(MAX_TIME_STOP_USER_IN_SECOND, TimeUnit.SECONDS);
        assertTrue(
                "Take more than " + MAX_TIME_STOP_USER_IN_SECOND + "s to stop user",
                stoppedBeforeTimeout);
        Log.d(TAG, "stopUser takes " + (System.currentTimeMillis() - startTime) + " ms");
    }

    /**
     * Calls the given consumer with a CountDownLatch parameter, and expects it's countDown() method
     * to be called before timeout, or fails the test otherwise.
     */
    private void runWithLatch(String tag,
            FunctionalUtils.RemoteExceptionIgnoringConsumer<CountDownLatch> consumer)
            throws RemoteException, InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(1);
        final long startTime = System.currentTimeMillis();

        consumer.acceptOrThrow(countDownLatch);
        final boolean doneBeforeTimeout = countDownLatch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
        assertWithMessage("Took more than " + TIMEOUT_IN_SECOND + "s to " + tag)
                .that(doneBeforeTimeout)
                .isTrue();

        final long elapsedTime = System.currentTimeMillis() - startTime;
        Log.d(TAG, tag + " takes " + elapsedTime + " ms");
    }
}