Merge "Fix UserManagerTest waitForUserRemoval timeout."
This commit is contained in:
committed by
Android (Google) Code Review
commit
414c1802e6
@@ -26,7 +26,6 @@ import static org.testng.Assert.assertThrows;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.pm.UserProperties;
|
||||
@@ -46,8 +45,6 @@ import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
@@ -61,10 +58,13 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Test {@link UserManager} functionality. */
|
||||
/**
|
||||
* Test {@link UserManager} functionality.
|
||||
*
|
||||
* atest com.android.server.pm.UserManagerTest
|
||||
*/
|
||||
@Postsubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class UserManagerTest {
|
||||
@@ -88,13 +88,17 @@ public final class UserManagerTest {
|
||||
private PackageManager mPackageManager;
|
||||
private ArraySet<Integer> mUsersToRemove;
|
||||
private UserSwitchWaiter mUserSwitchWaiter;
|
||||
private UserRemovalWaiter mUserRemovalWaiter;
|
||||
private int mOriginalCurrentUserId;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mOriginalCurrentUserId = ActivityManager.getCurrentUser();
|
||||
mUserManager = UserManager.get(mContext);
|
||||
mActivityManager = mContext.getSystemService(ActivityManager.class);
|
||||
mPackageManager = mContext.getPackageManager();
|
||||
mUserSwitchWaiter = new UserSwitchWaiter(TAG, SWITCH_USER_TIMEOUT_SECONDS);
|
||||
mUserRemovalWaiter = new UserRemovalWaiter(mContext, TAG, REMOVE_USER_TIMEOUT_SECONDS);
|
||||
|
||||
mUsersToRemove = new ArraySet<>();
|
||||
removeExistingUsers();
|
||||
@@ -102,10 +106,14 @@ public final class UserManagerTest {
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (mOriginalCurrentUserId != ActivityManager.getCurrentUser()) {
|
||||
switchUser(mOriginalCurrentUserId);
|
||||
}
|
||||
mUserSwitchWaiter.close();
|
||||
|
||||
// Making a copy of mUsersToRemove to avoid ConcurrentModificationException
|
||||
mUsersToRemove.stream().toList().forEach(this::removeUser);
|
||||
mUserRemovalWaiter.close();
|
||||
}
|
||||
|
||||
private void removeExistingUsers() {
|
||||
@@ -394,11 +402,10 @@ public final class UserManagerTest {
|
||||
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
|
||||
asHandle(currentUser));
|
||||
try {
|
||||
runThenWaitForUserRemoval(() -> {
|
||||
assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
|
||||
/* overrideDevicePolicy= */ true))
|
||||
.isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
|
||||
}, user1.id); // wait for user removal
|
||||
assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
|
||||
/* overrideDevicePolicy= */ true))
|
||||
.isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
|
||||
waitForUserRemoval(user1.id);
|
||||
} finally {
|
||||
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false,
|
||||
asHandle(currentUser));
|
||||
@@ -463,11 +470,10 @@ public final class UserManagerTest {
|
||||
assertThat(hasUser(user1.id)).isTrue();
|
||||
assertThat(getUser(user1.id).isEphemeral()).isTrue();
|
||||
|
||||
runThenWaitForUserRemoval(() -> {
|
||||
// Switch back to the starting user.
|
||||
switchUser(startUser);
|
||||
// User will be removed once switch is complete
|
||||
}, user1.id); // wait for user removal
|
||||
// Switch back to the starting user.
|
||||
switchUser(startUser);
|
||||
// User will be removed once switch is complete
|
||||
waitForUserRemoval(user1.id);
|
||||
|
||||
assertThat(hasUser(user1.id)).isFalse();
|
||||
}
|
||||
@@ -480,18 +486,17 @@ public final class UserManagerTest {
|
||||
// Switch to the user just created.
|
||||
switchUser(testUser.id);
|
||||
|
||||
runThenWaitForUserRemoval(() -> {
|
||||
switchUserThenRun(startUser, () -> {
|
||||
// While the switch is happening, call removeUserWhenPossible for the current user.
|
||||
assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(),
|
||||
/* overrideDevicePolicy= */ false))
|
||||
.isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
|
||||
switchUserThenRun(startUser, () -> {
|
||||
// While the switch is happening, call removeUserWhenPossible for the current user.
|
||||
assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(),
|
||||
/* overrideDevicePolicy= */ false))
|
||||
.isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
|
||||
|
||||
assertThat(hasUser(testUser.id)).isTrue();
|
||||
assertThat(getUser(testUser.id).isEphemeral()).isTrue();
|
||||
}); // wait for user switch - startUser
|
||||
// User will be removed once switch is complete
|
||||
}, testUser.id); // wait for user removal
|
||||
assertThat(hasUser(testUser.id)).isTrue();
|
||||
assertThat(getUser(testUser.id).isEphemeral()).isTrue();
|
||||
}); // wait for user switch - startUser
|
||||
// User will be removed once switch is complete
|
||||
waitForUserRemoval(testUser.id);
|
||||
|
||||
assertThat(hasUser(testUser.id)).isFalse();
|
||||
}
|
||||
@@ -512,11 +517,10 @@ public final class UserManagerTest {
|
||||
assertThat(getUser(testUser.id).isEphemeral()).isTrue();
|
||||
}); // wait for user switch - testUser
|
||||
|
||||
runThenWaitForUserRemoval(() -> {
|
||||
// Switch back to the starting user.
|
||||
switchUser(startUser);
|
||||
// User will be removed once switch is complete
|
||||
}, testUser.id); // wait for user removal
|
||||
// Switch back to the starting user.
|
||||
switchUser(startUser);
|
||||
// User will be removed once switch is complete
|
||||
waitForUserRemoval(testUser.id);
|
||||
|
||||
assertThat(hasUser(testUser.id)).isFalse();
|
||||
}
|
||||
@@ -526,11 +530,10 @@ public final class UserManagerTest {
|
||||
public void testRemoveUserWhenPossible_nonCurrentUserRemoved() throws Exception {
|
||||
final UserInfo user1 = createUser("User 1", /* flags= */ 0);
|
||||
|
||||
runThenWaitForUserRemoval(() -> {
|
||||
assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
|
||||
/* overrideDevicePolicy= */ false))
|
||||
.isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
|
||||
}, user1.id); // wait for user removal
|
||||
assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
|
||||
/* overrideDevicePolicy= */ false))
|
||||
.isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
|
||||
waitForUserRemoval(user1.id);
|
||||
|
||||
assertThat(hasUser(user1.id)).isFalse();
|
||||
}
|
||||
@@ -549,11 +552,10 @@ public final class UserManagerTest {
|
||||
UserManager.USER_TYPE_PROFILE_MANAGED,
|
||||
parentUser.id);
|
||||
|
||||
runThenWaitForUserRemoval(() -> {
|
||||
assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(),
|
||||
/* overrideDevicePolicy= */ false))
|
||||
.isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
|
||||
}, parentUser.id); // wait for user removal
|
||||
assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(),
|
||||
/* overrideDevicePolicy= */ false))
|
||||
.isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
|
||||
waitForUserRemoval(parentUser.id);
|
||||
|
||||
assertThat(hasUser(parentUser.id)).isFalse();
|
||||
assertThat(hasUser(cloneProfileUser.id)).isFalse();
|
||||
@@ -1120,7 +1122,7 @@ public final class UserManagerTest {
|
||||
|
||||
@Nullable
|
||||
private UserInfo getUser(int id) {
|
||||
List<UserInfo> list = mUserManager.getUsers();
|
||||
List<UserInfo> list = mUserManager.getAliveUsers();
|
||||
|
||||
for (UserInfo user : list) {
|
||||
if (user.id == id) {
|
||||
@@ -1275,7 +1277,7 @@ public final class UserManagerTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testConcurrentUserCreate() throws Exception {
|
||||
int userCount = mUserManager.getUserCount();
|
||||
int userCount = mUserManager.getAliveUsers().size();
|
||||
int maxSupportedUsers = UserManager.getMaxSupportedUsers();
|
||||
int canBeCreatedCount = maxSupportedUsers - userCount;
|
||||
// Test exceeding the limit while running in parallel
|
||||
@@ -1294,7 +1296,7 @@ public final class UserManagerTest {
|
||||
}
|
||||
es.shutdown();
|
||||
es.awaitTermination(20, TimeUnit.SECONDS);
|
||||
assertThat(mUserManager.getUserCount()).isEqualTo(maxSupportedUsers);
|
||||
assertThat(mUserManager.getAliveUsers().size()).isEqualTo(maxSupportedUsers);
|
||||
assertThat(created.get()).isEqualTo(canBeCreatedCount);
|
||||
}
|
||||
|
||||
@@ -1451,41 +1453,18 @@ public final class UserManagerTest {
|
||||
}
|
||||
|
||||
private void removeUser(UserHandle userHandle) {
|
||||
runThenWaitForUserRemoval(
|
||||
() -> mUserManager.removeUser(userHandle),
|
||||
userHandle == null ? UserHandle.USER_NULL : userHandle.getIdentifier()
|
||||
);
|
||||
mUserManager.removeUser(userHandle);
|
||||
waitForUserRemoval(userHandle.getIdentifier());
|
||||
}
|
||||
|
||||
private void removeUser(int userId) {
|
||||
runThenWaitForUserRemoval(
|
||||
() -> mUserManager.removeUser(userId),
|
||||
userId
|
||||
);
|
||||
mUserManager.removeUser(userId);
|
||||
waitForUserRemoval(userId);
|
||||
}
|
||||
|
||||
private void runThenWaitForUserRemoval(Runnable runnable, int userIdToWaitUntilDeleted) {
|
||||
if (!hasUser(userIdToWaitUntilDeleted)) {
|
||||
runnable.run();
|
||||
mUsersToRemove.remove(userIdToWaitUntilDeleted);
|
||||
return;
|
||||
}
|
||||
|
||||
Function<Intent, Boolean> checker = intent -> {
|
||||
UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
|
||||
return userHandle != null && userHandle.getIdentifier() == userIdToWaitUntilDeleted;
|
||||
};
|
||||
|
||||
BlockingBroadcastReceiver blockingBroadcastReceiver = BlockingBroadcastReceiver.create(
|
||||
mContext, Intent.ACTION_USER_REMOVED, checker);
|
||||
|
||||
blockingBroadcastReceiver.setTimeout(REMOVE_USER_TIMEOUT_SECONDS);
|
||||
blockingBroadcastReceiver.register();
|
||||
|
||||
try (blockingBroadcastReceiver) {
|
||||
runnable.run();
|
||||
}
|
||||
mUsersToRemove.remove(userIdToWaitUntilDeleted);
|
||||
private void waitForUserRemoval(int userId) {
|
||||
mUserRemovalWaiter.waitFor(userId);
|
||||
mUsersToRemove.remove(userId);
|
||||
}
|
||||
|
||||
private UserInfo createUser(String name, int flags) {
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.pm;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UserRemovalWaiter extends BroadcastReceiver implements Closeable {
|
||||
|
||||
private final Context mContext;
|
||||
private final UserManager mUserManager;
|
||||
private final String mTag;
|
||||
private final long mTimeoutMillis;
|
||||
private final Map<Integer, CountDownLatch> mMap = new ConcurrentHashMap<>();
|
||||
|
||||
private CountDownLatch getLatch(final int userId) {
|
||||
return mMap.computeIfAbsent(userId, absentKey -> new CountDownLatch(1));
|
||||
}
|
||||
|
||||
public UserRemovalWaiter(Context context, String tag, int timeoutInSeconds) {
|
||||
mContext = context;
|
||||
mUserManager = UserManager.get(mContext);
|
||||
mTag = tag;
|
||||
mTimeoutMillis = timeoutInSeconds * 1000L;
|
||||
|
||||
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_USER_REMOVED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mContext.unregisterReceiver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
|
||||
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
|
||||
Log.i(mTag, "ACTION_USER_REMOVED received for user " + userId);
|
||||
getLatch(userId).countDown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the removal of the given user, or fails if it times out.
|
||||
*/
|
||||
public void waitFor(int userId) {
|
||||
Log.i(mTag, "Waiting for user " + userId + " to be removed");
|
||||
CountDownLatch latch = getLatch(userId);
|
||||
long startTime = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - startTime < mTimeoutMillis) {
|
||||
if (hasUserGone(userId) || waitLatchForOneSecond(latch)) {
|
||||
Log.i(mTag, "User " + userId + " is removed in "
|
||||
+ (System.currentTimeMillis() - startTime) + " ms");
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail("Timeout waiting for user removal. userId = " + userId);
|
||||
}
|
||||
|
||||
private boolean hasUserGone(int userId) {
|
||||
return mUserManager.getAliveUsers().stream().noneMatch(x -> x.id == userId);
|
||||
}
|
||||
|
||||
private boolean waitLatchForOneSecond(CountDownLatch latch) {
|
||||
try {
|
||||
return latch.await(1, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(mTag, "Thread interrupted unexpectedly.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user