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

Commit 49b1eb17 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Test removing a user immediately after starting it in background." into main

parents e0ee14bb 2f864fe4
Loading
Loading
Loading
Loading
+114 −5
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import android.app.ActivityManager;
import android.app.IStopUserCallback;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.UserManager;
@@ -35,6 +36,8 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.compatibility.common.util.BlockingBroadcastReceiver;
import com.android.compatibility.common.util.ShellUtils;
import com.android.internal.util.FunctionalUtils;

import org.junit.After;
@@ -46,6 +49,7 @@ import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * To run the test:
@@ -58,9 +62,14 @@ public class UserLifecycleStressTest {
    private static final String TAG = "UserLifecycleStressTest";
    // TODO: Make this smaller once we have improved it.
    private static final int TIMEOUT_IN_SECOND = 40;
    private static final int CHECK_USER_REMOVED_INTERVAL_MS = 200;

    private static final int NUM_ITERATIONS = 8;
    private static final int WAIT_BEFORE_STOP_USER_IN_SECOND = 3;

    /** Name of users/profiles in the test. Users with this name may be freely removed. */
    private static final String TEST_USER_NAME = "UserLifecycleStressTest_test_user";

    private Context mContext;
    private UserManager mUserManager;
    private ActivityManager mActivityManager;
@@ -75,6 +84,7 @@ public class UserLifecycleStressTest {
        mUserSwitchWaiter = new UserSwitchWaiter(TAG, TIMEOUT_IN_SECOND);
        mRemoveGuestOnExitOriginalValue = Settings.Global.getString(mContext.getContentResolver(),
                Settings.Global.REMOVE_GUEST_ON_EXIT);
        waitForBroadcastBarrier(); // isolate tests from each other
    }

    @After
@@ -82,6 +92,7 @@ public class UserLifecycleStressTest {
        mUserSwitchWaiter.close();
        Settings.Global.putString(mContext.getContentResolver(),
                Settings.Global.REMOVE_GUEST_ON_EXIT, mRemoveGuestOnExitOriginalValue);
        waitForBroadcastBarrier(); // isolate tests from each other
    }

    /**
@@ -91,8 +102,10 @@ public class UserLifecycleStressTest {
    @Test
    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());
            logIteration(i, "stopManagedProfileStressTest");

            final UserInfo userInfo = mUserManager.createProfileForUser(TEST_USER_NAME,
                    UserManager.USER_TYPE_PROFILE_MANAGED, 0, ActivityManager.getCurrentUser());
            assertThat(userInfo).isNotNull();
            try {
                assertWithMessage("Failed to start the profile")
@@ -108,6 +121,35 @@ public class UserLifecycleStressTest {
        }
    }

    /**
     * Create a user, and then remove it immediately after starting it in background
     * {@link #NUM_ITERATIONS} times in a row.
     * Check device is not crashed when user data directory is deleted while some other processes
     * might still be trying to access those deleted files.
     */
    @Test
    public void removeRecentlyStartedUserStressTest() throws RemoteException, InterruptedException {
        for (int i = 0; i < NUM_ITERATIONS; i++) {
            logIteration(i, "removeRecentlyStartedUserStressTest");

            Log.d(TAG, "Creating a new user");
            final UserInfo userInfo = mUserManager.createUser(TEST_USER_NAME,
                    UserManager.USER_TYPE_FULL_SECONDARY, 0);
            assertWithMessage("Failed to create the user")
                    .that(userInfo)
                    .isNotNull();
            try {
                Log.d(TAG, "Starting user " + userInfo.id);
                startUserInBackgroundAndWaitForUserStartedBroadcast(userInfo.id);
            } finally {
                Log.d(TAG, "Removing user " + userInfo.id);
                assertWithMessage("Failed to remove the user " + userInfo.id)
                        .that(removeUser(userInfo.id))
                        .isTrue();
            }
        }
    }

    /**
     * Starts over the guest user {@link #NUM_ITERATIONS} times in a row.
     *
@@ -130,10 +172,9 @@ public class UserLifecycleStressTest {
        int nextGuestId = guestUsers.isEmpty() ? USER_NULL : guestUsers.get(0).id;

        for (int i = 0; i < NUM_ITERATIONS; i++) {
            final int currentGuestId = nextGuestId;
            logIteration(i, "switchToExistingGuestAndStartOverStressTest");

            Log.d(TAG, "switchToExistingGuestAndStartOverStressTest"
                    + " - Run " + (i + 1) + " / " + NUM_ITERATIONS);
            final int currentGuestId = nextGuestId;

            if (currentGuestId != USER_NULL) {
                Log.d(TAG, "Switching to the existing guest");
@@ -173,6 +214,25 @@ public class UserLifecycleStressTest {
        Log.d(TAG, "testSwitchToExistingGuestAndStartOver - End");
    }

    private boolean removeUser(int userId) {
        if (!mUserManager.removeUser(userId)) {
            return false;
        }
        try {
            final long startTime = System.currentTimeMillis();
            final long timeoutInMs = TIMEOUT_IN_SECOND * 1000;
            while (mUserManager.getUserInfo(userId) != null
                    && System.currentTimeMillis() - startTime < timeoutInMs) {
                TimeUnit.MILLISECONDS.sleep(CHECK_USER_REMOVED_INTERVAL_MS);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            // Ignore
        }
        return mUserManager.getUserInfo(userId) == null;
    }

    /** Stops the given user and waits for the stop to finish. */
    private void stopUser(int userId) throws RemoteException, InterruptedException {
        runWithLatch("stop user", countDownLatch -> {
@@ -203,6 +263,40 @@ public class UserLifecycleStressTest {
        });
    }

    /**
     * Start user in background and wait for {@link Intent#ACTION_USER_STARTED} broadcast.
     * <p> To start in foreground instead, see {@link #switchUser(int)}.
     * <p> This should always be used for profiles since profiles cannot be started in foreground.
     */
    private void startUserInBackgroundAndWaitForUserStartedBroadcast(int userId) {
        runWithBlockingBroadcastReceiver("start user and wait for ACTION_USER_STARTED broadcast",
                userId, Intent.ACTION_USER_STARTED,
                () -> ActivityManager.getService().startUserInBackground(userId));
    }

    /**
     * Calls the given runnable and expects the given broadcast to be received before timeout,
     * or fails the test otherwise.
     * @param tag tag for logging
     * @param userId id of the user to register the broadcast receiver with
     *               see {@link Context#registerReceiverAsUser}
     * @param action action of the broadcast intent filter i.e. {@link Intent#ACTION_USER_STARTED}
     * @param runnable this will be called after registering the broadcast receiver
     */
    private void runWithBlockingBroadcastReceiver(String tag, int userId, String action,
            FunctionalUtils.ThrowingRunnable runnable) {
        try (BlockingBroadcastReceiver blockingBroadcastReceiver = new BlockingBroadcastReceiver(
                mContext, action,
                intent -> intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL) == userId)) {
            blockingBroadcastReceiver.setTimeout(TIMEOUT_IN_SECOND);
            blockingBroadcastReceiver.registerForAllUsers();
            runnable.run();
            assertWithMessage("Took more than " + TIMEOUT_IN_SECOND + "s to " + tag)
                    .that(blockingBroadcastReceiver.awaitForBroadcast())
                    .isNotNull();
        }
    }

    /**
     * Calls the given consumer with a CountDownLatch parameter, and expects it's countDown() method
     * to be called before timeout, or fails the test otherwise.
@@ -222,5 +316,20 @@ public class UserLifecycleStressTest {
        final long elapsedTime = System.currentTimeMillis() - startTime;
        Log.d(TAG, tag + " takes " + elapsedTime + " ms");
    }

    private void logIteration(int iteration, String testMethodName) {
        Log.d(TAG, testMethodName + " - Iteration " + (iteration + 1) + " / " + NUM_ITERATIONS);
    }

    private static void waitForBroadcastBarrier() {
        try {
            Log.d(TAG, "Starting to waitForBroadcastBarrier");
            ShellUtils.runShellCommandWithTimeout("am wait-for-broadcast-barrier",
                    TIMEOUT_IN_SECOND);
            Log.d(TAG, "waitForBroadcastBarrier is finished");
        } catch (TimeoutException e) {
            Log.e(TAG, "Timeout while running waitForBroadcastBarrier", e);
        }
    }
}