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

Commit 5e0fa4b1 authored by Yasin Kilicdere's avatar Yasin Kilicdere
Browse files

Add test method UserLifecycleStressTest.switchToExistingGuestAndStartOverStressTest

This test method tests the following scenario consecutively 10 times:
1. Switch to existing guest
2. Mark it for deletion
3. Create and switch to a new guest
4. Remove previous guest
5. Switch to owner

Bug: 228022618
Test: atest com.android.server.pm.UserLifecycleStressTest
Change-Id: I38c3f57e8b008ca406813f0a5c82d9afa27aba6c
parent 66642324
Loading
Loading
Loading
Loading
+142 −32
Original line number Original line Diff line number Diff line
@@ -15,11 +15,15 @@
 */
 */
package com.android.server.pm;
package com.android.server.pm;


import static org.junit.Assert.assertNotNull;
import static android.os.UserHandle.USER_NULL;
import static org.junit.Assert.assertTrue;
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.ActivityManager;
import android.app.IStopUserCallback;
import android.app.IStopUserCallback;
import android.app.UserSwitchObserver;
import android.content.Context;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.RemoteException;
@@ -31,17 +35,18 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.runner.AndroidJUnit4;


import com.android.internal.util.FunctionalUtils;

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


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


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


    private Context mContext;
    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
     * Create and stop user {@link #NUM_ITERATIONS} times in a row. Check stop user can be finished
     * amount of time.
     * in a reasonable amount of time.
     */
     */
    @Test
    @Test
    public void stopManagedProfileStressTest()
    public void stopManagedProfileStressTest() throws RemoteException, InterruptedException {
            throws IOException, RemoteException, InterruptedException {
        for (int i = 0; i < NUM_ITERATIONS; i++) {
        for (int i = 0; i < NUM_ITERATIONS_STOP_USER; i++) {
            final UserInfo userInfo = mUserManager.createProfileForUser("TestUser",
            final UserInfo userInfo = mUserManager.createProfileForUser("TestUser",
                    UserManager.USER_TYPE_PROFILE_MANAGED, 0, mActivityManager.getCurrentUser());
                    UserManager.USER_TYPE_PROFILE_MANAGED, 0, mActivityManager.getCurrentUser());
            assertNotNull(userInfo);
            assertThat(userInfo).isNotNull();
            try {
            try {
                assertTrue(
                assertWithMessage("Failed to start the profile")
                        "Failed to start the profile",
                        .that(ActivityManager.getService().startUserInBackground(userInfo.id))
                        ActivityManager.getService().startUserInBackground(userInfo.id));
                        .isTrue();
                // Seems the broadcast queue is getting more busy if we wait a few seconds before
                // Seems the broadcast queue is getting more busy if we wait a few seconds before
                // stopping the user.
                // stopping the user.
                TimeUnit.SECONDS.sleep(WAIT_BEFORE_STOP_USER_IN_SECOND);
                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 {
    private void stopUser(int userId) throws RemoteException, InterruptedException {
        final long startTime = System.currentTimeMillis();
        runWithLatch("stop user", countDownLatch -> {
        CountDownLatch countDownLatch = new CountDownLatch(1);
            ActivityManager.getService()
        ActivityManager.getService().
                    .stopUser(userId, /* force= */ true, new IStopUserCallback.Stub() {
                stopUser(userId, true,
                        new IStopUserCallback.Stub() {
                        @Override
                        @Override
                            public void userStopped(int userId) throws RemoteException {
                        public void userStopped(int userId) {
                            countDownLatch.countDown();
                            countDownLatch.countDown();
                        }
                        }


                        @Override
                        @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",
     * Calls the given consumer with a CountDownLatch parameter, and expects it's countDown() method
                stoppedBeforeTimeout);
     * to be called before timeout, or fails the test otherwise.
        Log.d(TAG, "stopUser takes " + (System.currentTimeMillis() - startTime) + " ms");
     */
    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");
    }
    }
}
}