Add UserChecker to System Status Checkers.
This new System Status Checker makes sure that user information has not
changed within a test.
It also allows for setting up users according to a specification.
Fix: 122429516
Test: tapas tradefed-all && make USE_GOMA=true && \
tools/tradefederation/core/tradefed.sh run singleCommand host -n \
--class com.android.tradefed.suite.checker.UserCheckerTest \
--class com.android.tradefed.util.UserUtilTest \
--class com.android.tradefed.device.TestDeviceTest
Change-Id: I4b43f2aeccf7aa8f01c641908e120136c9229475
Merged-In: I4b43f2aeccf7aa8f01c641908e120136c9229475
diff --git a/src/com/android/tradefed/device/ITestDevice.java b/src/com/android/tradefed/device/ITestDevice.java
index 597b7a3..01312d1 100644
--- a/src/com/android/tradefed/device/ITestDevice.java
+++ b/src/com/android/tradefed/device/ITestDevice.java
@@ -399,6 +399,15 @@
public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException;
/**
+ * Create a user with a given name and default flags 0.
+ *
+ * @param name of the user to create on the device
+ * @return the integer for the user id created or -1 for error.
+ * @throws DeviceNotAvailableException
+ */
+ public int createUserNoThrow(String name) throws DeviceNotAvailableException;
+
+ /**
* Create a user with a given name and the provided flags
*
* @param name of the user to create on the device
@@ -503,6 +512,14 @@
public int getUserFlags(int userId) throws DeviceNotAvailableException;
/**
+ * Return whether the specified user is a secondary user according to it's flags.
+ *
+ * @return true if the user is secondary, false otherwise.
+ * @throws DeviceNotAvailableException
+ */
+ public boolean isUserSecondary(int userId) throws DeviceNotAvailableException;
+
+ /**
* Return the serial number associated to the userId if found, -10000 in any other cases.
*
* @throws DeviceNotAvailableException
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index 2a20d12..cfbf3a1 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -3573,6 +3573,12 @@
throw new UnsupportedOperationException("No support for user's feature.");
}
+ /** {@inheritDoc} */
+ @Override
+ public int createUserNoThrow(String name) throws DeviceNotAvailableException {
+ throw new UnsupportedOperationException("No support for user's feature.");
+ }
+
/**
* {@inheritDoc}
*/
@@ -3655,6 +3661,13 @@
throw new UnsupportedOperationException("No support for user's feature.");
}
+ /** {@inheritDoc} */
+ @Override
+ public boolean isUserSecondary(int userId) throws DeviceNotAvailableException {
+ throw new UnsupportedOperationException("No support for user's feature.");
+ }
+
+
/**
* {@inheritDoc}
*/
diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java
index 386db34..bc40082 100644
--- a/src/com/android/tradefed/device/TestDevice.java
+++ b/src/com/android/tradefed/device/TestDevice.java
@@ -29,6 +29,7 @@
import com.android.tradefed.util.KeyguardControllerState;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.StreamUtil;
+import com.android.tradefed.util.UserUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
@@ -853,6 +854,17 @@
return createUser(name, false, false);
}
+ /** {@inheritDoc} */
+ @Override
+ public int createUserNoThrow(String name) throws DeviceNotAvailableException {
+ try {
+ return createUser(name);
+ } catch (IllegalStateException e) {
+ CLog.e("Error creating user: " + e.toString());
+ return -1;
+ }
+ }
+
/**
* {@inheritDoc}
*/
@@ -1000,6 +1012,19 @@
return INVALID_USER_ID;
}
+ /** {@inheritDoc} */
+ @Override
+ public boolean isUserSecondary(int userId) throws DeviceNotAvailableException {
+ if (userId == UserUtil.USER_SYSTEM) {
+ return false;
+ }
+ int flags = getUserFlags(userId);
+ if (flags == INVALID_USER_ID) {
+ return false;
+ }
+ return (flags & UserUtil.FLAGS_NOT_SECONDARY) == 0;
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/com/android/tradefed/suite/checker/UserChecker.java b/src/com/android/tradefed/suite/checker/UserChecker.java
new file mode 100644
index 0000000..d23b88c
--- /dev/null
+++ b/src/com/android/tradefed/suite/checker/UserChecker.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2019 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.tradefed.suite.checker;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.suite.checker.StatusCheckerResult.CheckStatus;
+import com.android.tradefed.util.UserUtil;
+import com.android.tradefed.util.UserUtil.UserSwitchFailedException;
+
+/**
+ * Checks if users have changed during the test.
+ *
+ * <p>Optionally can also setup the current user.
+ */
+@OptionClass(alias = "user-system-checker")
+public class UserChecker implements ISystemStatusChecker {
+
+ @Option(
+ name = "user-type",
+ description = "The type of user to switch to before each module run."
+ )
+ private UserUtil.UserType mUserToSwitchTo = UserUtil.UserType.CURRENT;
+
+ public static final String DEFAULT_NAME = "TFauto";
+
+ private DeviceUserState mPreExecutionUserState;
+
+ /** {@inheritDoc} */
+ @Override
+ public StatusCheckerResult preExecutionCheck(ITestDevice device)
+ throws DeviceNotAvailableException {
+
+ String userSwitchErrorMsg = null;
+ try {
+ switchToExistingOrCreateUserType(device);
+ } catch (UserSwitchFailedException err) {
+ userSwitchErrorMsg = err.toString();
+ }
+
+ mPreExecutionUserState = new DeviceUserState(device);
+ CLog.d("preExecutionUsers=" + mPreExecutionUserState);
+
+ if (userSwitchErrorMsg == null) {
+ return new StatusCheckerResult(CheckStatus.SUCCESS);
+ } else {
+ StatusCheckerResult result = new StatusCheckerResult(CheckStatus.FAILED);
+ result.setErrorMessage(userSwitchErrorMsg);
+ return result;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public StatusCheckerResult postExecutionCheck(ITestDevice device)
+ throws DeviceNotAvailableException {
+ DeviceUserState postDeviceUserState = new DeviceUserState(device);
+ CLog.d("postExecutionUsers=" + postDeviceUserState);
+
+ ArrayList<String> errors = new ArrayList<>();
+
+ for (Integer removedUser : mPreExecutionUserState.findRemovedUsers(postDeviceUserState)) {
+ errors.add(String.format("User %d no longer exists after test", removedUser));
+ }
+
+ for (Integer addedUser : mPreExecutionUserState.findAddedUsers(postDeviceUserState)) {
+ errors.add(
+ String.format(
+ "User %d was created during the test and not deleted", addedUser));
+ }
+
+ if (mPreExecutionUserState.currentUserChanged(postDeviceUserState)) {
+ errors.add(
+ String.format(
+ "User %d was the currentUser before, has changed to %d",
+ mPreExecutionUserState.getCurrentUser(),
+ postDeviceUserState.getCurrentUser()));
+ }
+
+ for (int userId : mPreExecutionUserState.findStoppedUsers(postDeviceUserState)) {
+ CLog.w("User %d was running but is now stopped.", userId);
+ }
+
+ for (int userId : mPreExecutionUserState.findStartedUsers(postDeviceUserState)) {
+ CLog.w("User %d was stopped but is now running.", userId);
+ }
+
+ if (errors.size() > 0) {
+ StatusCheckerResult result = new StatusCheckerResult(CheckStatus.FAILED);
+ result.setErrorMessage(String.join("\n", errors));
+ return result;
+ } else {
+ return new StatusCheckerResult(CheckStatus.SUCCESS);
+ }
+ }
+
+ /**
+ * Switches to the mUserType, creating if necessary.
+ *
+ * <p>Returns null if success, the error string if there is an error.
+ */
+ private void switchToExistingOrCreateUserType(ITestDevice device)
+ throws DeviceNotAvailableException, UserSwitchFailedException {
+ try {
+ UserUtil.switchToUserType(device, mUserToSwitchTo);
+ } catch (UserUtil.SecondaryUserNotFoundException attemptCreate) {
+ CLog.d("No secondary users exist, creating one.");
+ int secondary = device.createUserNoThrow(DEFAULT_NAME);
+ if (secondary <= 0) {
+ throw new UserSwitchFailedException("Failed to create secondary user");
+ }
+ UserUtil.switchToUserType(device, mUserToSwitchTo);
+ }
+ }
+
+ /** Class for monitoring changes to the user state between pre/post check. */
+ static class DeviceUserState {
+ private final int mCurrentUser;
+ private final ArrayList<Integer> mUsers;
+ private final HashMap<Integer, Boolean> mUserRunningStates;
+
+ DeviceUserState(ITestDevice device) throws DeviceNotAvailableException {
+ mCurrentUser = device.getCurrentUser();
+ mUsers = device.listUsers();
+ mUserRunningStates = new HashMap(mUsers.size());
+ for (Integer userId : mUsers) {
+ mUserRunningStates.put(userId, device.isUserRunning(userId));
+ }
+ }
+
+ public int getCurrentUser() {
+ return mCurrentUser;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(String.format("currentUser=%d;", getCurrentUser()));
+ for (Integer userId : mUsers) {
+ String running = mUserRunningStates.get(userId) ? "running" : "stopped";
+ builder.append(String.format(" %d:%s", userId, running));
+ }
+ return builder.toString();
+ }
+
+ List<Integer> findRemovedUsers(DeviceUserState otherState) {
+ ArrayList<Integer> removedUsers = new ArrayList<>();
+ for (Integer userId : mUsers) {
+ if (!otherState.containsUser(userId)) {
+ removedUsers.add(userId);
+ }
+ }
+ return removedUsers;
+ }
+
+ List<Integer> findAddedUsers(DeviceUserState otherState) {
+ ArrayList<Integer> addedUsers = new ArrayList<>();
+ for (Integer userId : otherState.mUsers) {
+ if (!this.containsUser(userId)) {
+ addedUsers.add(userId);
+ }
+ }
+ return addedUsers;
+ }
+
+ boolean currentUserChanged(DeviceUserState otherState) {
+ return this.getCurrentUser() != otherState.getCurrentUser();
+ }
+
+ List<Integer> findStartedUsers(DeviceUserState otherState) {
+ ArrayList<Integer> startedUsers = new ArrayList<>();
+ for (Integer userId : mUsers) {
+ if (!this.isUserRunning(userId) && otherState.isUserRunning(userId)) {
+ startedUsers.add(userId);
+ }
+ }
+ return startedUsers;
+ }
+
+ List<Integer> findStoppedUsers(DeviceUserState otherState) {
+ ArrayList<Integer> stoppedUsers = new ArrayList<>();
+ for (Integer userId : mUsers) {
+ if (this.isUserRunning(userId) && !otherState.isUserRunning(userId)) {
+ stoppedUsers.add(userId);
+ }
+ }
+ return stoppedUsers;
+ }
+
+ private boolean containsUser(int userId) {
+ return mUserRunningStates.containsKey(userId);
+ }
+
+ private boolean isUserRunning(int userId) {
+ return mUserRunningStates.getOrDefault(userId, /* default= */ false);
+ }
+ }
+}
diff --git a/src/com/android/tradefed/targetprep/SwitchUserTargetPreparer.java b/src/com/android/tradefed/targetprep/SwitchUserTargetPreparer.java
index 9b4fe49..891e651 100644
--- a/src/com/android/tradefed/targetprep/SwitchUserTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/SwitchUserTargetPreparer.java
@@ -22,6 +22,7 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.UserUtil;
/**
* A {@link ITargetPreparer} that switches to the specified user kind in setUp. By default it
@@ -31,25 +32,11 @@
*/
@OptionClass(alias = "switch-user-target-preparer")
public class SwitchUserTargetPreparer extends BaseTargetPreparer implements ITargetCleaner {
- private static final int USER_SYSTEM = 0; // From the UserHandle class.
-
- /** Parameters that specify which user to run the test module as. */
- public enum UserType {
- // TODO:(b/123077733) Add support for guest and secondary.
-
- /** current foreground user of the device */
- CURRENT,
- /** user flagged as primary on the device; most often primary = system user = user 0 */
- PRIMARY,
- /** system user = user 0 */
- SYSTEM
- }
-
@Option(
name = "user-type",
description = "The type of user to switch to before the module run."
)
- private UserType mUserToSwitchTo = UserType.CURRENT;
+ private UserUtil.UserType mUserToSwitchTo = UserUtil.UserType.CURRENT;
private int mPreExecutionCurrentUser;
@@ -59,39 +46,25 @@
mPreExecutionCurrentUser = device.getCurrentUser();
- switch (mUserToSwitchTo) {
- case SYSTEM:
- switchToUser(USER_SYSTEM, device);
- break;
- case PRIMARY:
- switchToUser(device.getPrimaryUserId(), device);
- break;
- }
- }
-
- private static void switchToUser(int userId, ITestDevice device)
- throws TargetSetupError, DeviceNotAvailableException {
- if (device.getCurrentUser() == userId) {
- return;
- }
-
- // Otherwise, switch to user with userId.
- if (device.switchUser(userId)) {
- // Successful switch.
- CLog.i("Switched to user %d.", userId);
- } else {
- // Couldn't switch, throw.
+ try {
+ UserUtil.switchToUserType(device, mUserToSwitchTo);
+ } catch (UserUtil.UserSwitchFailedException err) {
throw new TargetSetupError(
- String.format("Failed switch to user %d.", userId),
+ String.format("Failed switch to user type %s", mUserToSwitchTo),
+ err,
device.getDeviceDescriptor());
}
+
+ CLog.d("Successfully switched to user type %s", mUserToSwitchTo);
}
@Override
public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
throws DeviceNotAvailableException {
// Restore the previous user as the foreground.
- if (!device.switchUser(mPreExecutionCurrentUser)) {
+ if (device.switchUser(mPreExecutionCurrentUser)) {
+ CLog.d("Successfully switched back to user id: %d", mPreExecutionCurrentUser);
+ } else {
CLog.w("Could not switch back to the user id: %d", mPreExecutionCurrentUser);
}
}
diff --git a/src/com/android/tradefed/util/UserUtil.java b/src/com/android/tradefed/util/UserUtil.java
new file mode 100644
index 0000000..608b84c
--- /dev/null
+++ b/src/com/android/tradefed/util/UserUtil.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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.tradefed.util;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+public class UserUtil {
+ // From the UserInfo class.
+ public static final int FLAG_PRIMARY = 0x00000001;
+ public static final int FLAG_GUEST = 0x00000004;
+ public static final int FLAG_RESTRICTED = 0x00000008;
+ public static final int FLAG_MANAGED_PROFILE = 0x00000020;
+ public static final int USER_SYSTEM = 0;
+
+ public static final int FLAGS_NOT_SECONDARY =
+ FLAG_PRIMARY | FLAG_MANAGED_PROFILE | FLAG_GUEST | FLAG_RESTRICTED;
+
+ /** Thrown if a user switch could not happen. */
+ public static class UserSwitchFailedException extends Exception {
+ public UserSwitchFailedException(String message) {
+ super(message);
+ }
+ }
+
+ /** Thrown if a user switch could not happen because the secondary user could not be found. */
+ public static class SecondaryUserNotFoundException extends UserSwitchFailedException {
+ public SecondaryUserNotFoundException() {
+ super("Secondary User Not Found");
+ }
+ }
+
+ /** Parameters that specify which user to run the test module as. */
+ public enum UserType {
+ // TODO:(b/123077733) Add support for guest
+
+ /** current foreground user of the device */
+ CURRENT,
+ /** user flagged as primary on the device; most often primary = system user = user 0 */
+ PRIMARY,
+ /** system user = user 0 */
+ SYSTEM,
+ /** secondary user, i.e. non-primary and non-system. */
+ SECONDARY,
+ }
+
+ /**
+ * Attempt to switch to a user type.
+ *
+ * @returns true if successful, false if not.
+ */
+ public static void switchToUserType(ITestDevice device, UserType userType)
+ throws DeviceNotAvailableException, UserSwitchFailedException {
+ switch (userType) {
+ case CURRENT:
+ return; // do nothing
+ case SYSTEM:
+ switchUser(device, USER_SYSTEM);
+ return;
+ case PRIMARY:
+ switchUser(device, device.getPrimaryUserId());
+ return;
+ case SECONDARY:
+ switchToSecondaryUser(device);
+ return;
+ }
+ throw new RuntimeException("userType case not covered: " + userType);
+ }
+
+ /**
+ * Attempt to switch to a secondary user, creating one if necessary.
+ *
+ * @returns true if successful, false if not.
+ */
+ private static void switchToSecondaryUser(ITestDevice device)
+ throws DeviceNotAvailableException, UserSwitchFailedException {
+ int currentUser = device.getCurrentUser();
+ if (device.isUserSecondary(currentUser)) {
+ CLog.d("currentUser is already secondary, no action.");
+ return;
+ }
+
+ int secondary = findExistingSecondary(device);
+ if (secondary <= 0) {
+ throw new SecondaryUserNotFoundException();
+ }
+
+ switchUser(device, secondary);
+ }
+
+ private static void switchUser(ITestDevice device, int userId)
+ throws DeviceNotAvailableException, UserSwitchFailedException {
+ if (!device.switchUser(userId)) {
+ throw new UserSwitchFailedException("Failed to switch to user " + userId);
+ }
+ }
+
+ /**
+ * Finds an arbitrary secondary user and returns the userId.
+ *
+ * <p>TODO: evaluate if a more comprehensive API is needed for this or not.
+ *
+ * @return id of the secondary user or -1 if one could not be found.
+ * @throws DeviceNotAvailableException
+ */
+ private static int findExistingSecondary(ITestDevice device)
+ throws DeviceNotAvailableException {
+ for (int userId : device.listUsers()) {
+ if (device.isUserSecondary(userId)) {
+ return userId;
+ }
+ }
+ // Returns a negative id if we couldn't find a proper existing secondary user.
+ return -1;
+ }
+}
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 2cd4e0b..83b3641 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -135,6 +135,7 @@
import com.android.tradefed.suite.checker.SystemServerFileDescriptorCheckerTest;
import com.android.tradefed.suite.checker.SystemServerStatusCheckerTest;
import com.android.tradefed.suite.checker.TimeStatusCheckerTest;
+import com.android.tradefed.suite.checker.UserCheckerTest;
import com.android.tradefed.targetprep.AllTestAppsInstallSetupTest;
import com.android.tradefed.targetprep.AppSetupTest;
import com.android.tradefed.targetprep.BuildInfoAttributePreparerTest;
@@ -272,6 +273,7 @@
import com.android.tradefed.util.TestMappingTest;
import com.android.tradefed.util.TimeUtilTest;
import com.android.tradefed.util.TimeValTest;
+import com.android.tradefed.util.UserUtilTest;
import com.android.tradefed.util.ZipUtil2Test;
import com.android.tradefed.util.ZipUtilTest;
import com.android.tradefed.util.hostmetric.AbstractHostMonitorTest;
@@ -488,6 +490,7 @@
SystemServerFileDescriptorCheckerTest.class,
SystemServerStatusCheckerTest.class,
TimeStatusCheckerTest.class,
+ UserCheckerTest.class,
// testtype
AndroidJUnitTestTest.class,
@@ -609,6 +612,7 @@
TestMappingTest.class,
TimeUtilTest.class,
TimeValTest.class,
+ UserUtilTest.class,
ZipUtilTest.class,
ZipUtil2Test.class,
diff --git a/tests/src/com/android/tradefed/device/TestDeviceTest.java b/tests/src/com/android/tradefed/device/TestDeviceTest.java
index e570966..9ca9df7 100644
--- a/tests/src/com/android/tradefed/device/TestDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/TestDeviceTest.java
@@ -43,6 +43,7 @@
import com.android.tradefed.util.KeyguardControllerState;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.StreamUtil;
+import com.android.tradefed.util.UserUtil;
import com.android.tradefed.util.ZipUtil2;
import com.google.common.util.concurrent.SettableFuture;
@@ -2165,6 +2166,31 @@
}
/**
+ * Test that successful user creation is handled by {@link
+ * TestDevice#createUserNoThrow(String)}.
+ */
+ public void testCreateUserNoThrow() throws Exception {
+ final String createUserCommand = "pm create-user foo";
+ injectShellResponse(createUserCommand, "Success: created user id 10");
+ replayMocks();
+ assertEquals(10, mTestDevice.createUserNoThrow("foo"));
+ }
+
+ /** Test that {@link TestDevice#createUserNoThrow(String)} fails when bad output */
+ public void testCreateUserNoThrow_wrongOutput() throws Exception {
+ mTestDevice =
+ new TestableTestDevice() {
+ @Override
+ public String executeShellCommand(String command)
+ throws DeviceNotAvailableException {
+ return "Success: created user id WRONG";
+ }
+ };
+
+ assertEquals(-1, mTestDevice.createUserNoThrow("TEST"));
+ }
+
+ /**
* Test that successful user removal is handled by {@link TestDevice#removeUser(int)}.
*/
public void testRemoveUser() throws Exception {
@@ -2443,6 +2469,40 @@
assertEquals(21, flags);
}
+ /** Unit test for {@link TestDevice#isUserSecondary(int)} */
+ public void testIsUserSecondary() throws Exception {
+ mTestDevice =
+ new TestableTestDevice() {
+ @Override
+ public String executeShellCommand(String command)
+ throws DeviceNotAvailableException {
+ return String.format(
+ "Users:\n\tUserInfo{0:Owner:0}\n\t"
+ + "UserInfo{10:Primary:%x} Running\n\t"
+ + "UserInfo{11:Guest:%x}\n\t"
+ + "UserInfo{12:Secondary:0}\n\t"
+ + "UserInfo{13:Managed:%x}\n\t"
+ + "UserInfo{100:Restricted:%x}\n\t",
+ UserUtil.FLAG_PRIMARY,
+ UserUtil.FLAG_GUEST,
+ UserUtil.FLAG_MANAGED_PROFILE,
+ UserUtil.FLAG_RESTRICTED);
+ }
+
+ @Override
+ public int getApiLevel() throws DeviceNotAvailableException {
+ return 22;
+ }
+ };
+ assertEquals(false, mTestDevice.isUserSecondary(0));
+ assertEquals(false, mTestDevice.isUserSecondary(-1));
+ assertEquals(false, mTestDevice.isUserSecondary(10));
+ assertEquals(false, mTestDevice.isUserSecondary(11));
+ assertEquals(true, mTestDevice.isUserSecondary(12));
+ assertEquals(false, mTestDevice.isUserSecondary(13));
+ assertEquals(false, mTestDevice.isUserSecondary(100));
+ }
+
/**
* Unit test for {@link TestDevice#getUserSerialNumber(int)}
*/
diff --git a/tests/src/com/android/tradefed/suite/checker/UserCheckerTest.java b/tests/src/com/android/tradefed/suite/checker/UserCheckerTest.java
new file mode 100644
index 0000000..6c000ce
--- /dev/null
+++ b/tests/src/com/android/tradefed/suite/checker/UserCheckerTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2019 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.tradefed.suite.checker;
+
+import com.android.tradefed.suite.checker.UserChecker.DeviceUserState;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.suite.checker.StatusCheckerResult.CheckStatus;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+/** Unit tests for {@link UserChecker} */
+@RunWith(JUnit4.class)
+public class UserCheckerTest {
+ @Test
+ public void testNoWarningsIsSuccess() throws Exception {
+ UserChecker checker = new UserChecker();
+
+ ITestDevice preDevice =
+ mockDeviceUserState(
+ /* users= */ new Integer[] {0},
+ /* runningUsers= */ new Integer[] {0},
+ /* currentUser= */ 0);
+ assertEquals(CheckStatus.SUCCESS, checker.preExecutionCheck(preDevice).getStatus());
+
+ ITestDevice postDevice =
+ mockDeviceUserState(
+ /* users= */ new Integer[] {0},
+ /* runningUsers= */ new Integer[] {0},
+ /* currentUser= */ 0);
+ assertEquals(CheckStatus.SUCCESS, checker.postExecutionCheck(postDevice).getStatus());
+ }
+
+ @Test
+ /** Returns FAILED in the precessense of errors */
+ public void testAllErrorsIsFailed() throws Exception {
+ UserChecker checker = new UserChecker();
+
+ ITestDevice preDevice =
+ mockDeviceUserState(
+ /* users= */ new Integer[] {0, 10, 11},
+ /* runningUsers= */ new Integer[] {0, 10},
+ /* currentUser= */ 10);
+ assertEquals(CheckStatus.SUCCESS, checker.preExecutionCheck(preDevice).getStatus());
+
+ // User12 created, User11 deleted, User10 stopped, currentUser changed
+ ITestDevice postDevice =
+ mockDeviceUserState(
+ /* users= */ new Integer[] {0, 10, 12},
+ /* runningUsers= */ new Integer[] {0},
+ /* currentUser= */ 0);
+ assertEquals(CheckStatus.FAILED, checker.postExecutionCheck(postDevice).getStatus());
+ }
+
+ @Test
+ public void testSwitchToExistingOrCreateUserType() throws Exception {
+ UserChecker checker = new UserChecker();
+ OptionSetter mOptionSetter = new OptionSetter(checker);
+ mOptionSetter.setOptionValue("user-type", "secondary");
+ ITestDevice device =
+ mockDeviceUserState(
+ /* users= */ new Integer[] {0},
+ /* runningUsers= */ new Integer[] {0},
+ /* currentUser= */ 0);
+
+ when(device.getCurrentUser()).thenReturn(0);
+ mockListUsers(device, new Integer[] {0});
+ when(device.createUserNoThrow(UserChecker.DEFAULT_NAME)).thenReturn(10);
+ when(device.getCurrentUser()).thenReturn(0);
+ mockListUsers(device, new Integer[] {0, 10});
+ when(device.isUserSecondary(10)).thenReturn(true);
+ when(device.switchUser(10)).thenReturn(true);
+
+ StatusCheckerResult result = checker.preExecutionCheck(device);
+ assertEquals(CheckStatus.SUCCESS, result.getStatus());
+ verify(device, times(1)).switchUser(10);
+ }
+
+ @Test
+ public void testSwitchToSecondaryUserCreateNewFail() throws Exception {
+ UserChecker checker = new UserChecker();
+ OptionSetter mOptionSetter = new OptionSetter(checker);
+ mOptionSetter.setOptionValue("user-type", "secondary");
+ ITestDevice device =
+ mockDeviceUserState(
+ /* users= */ new Integer[] {0},
+ /* runningUsers= */ new Integer[] {0},
+ /* currentUser= */ 0);
+
+ when(device.getCurrentUser()).thenReturn(0);
+ mockListUsers(device, new Integer[] {0});
+ when(device.createUserNoThrow(UserChecker.DEFAULT_NAME)).thenReturn(-1);
+
+ StatusCheckerResult result = checker.preExecutionCheck(device);
+ assertEquals(CheckStatus.FAILED, result.getStatus());
+ verify(device, times(1)).createUserNoThrow(UserChecker.DEFAULT_NAME);
+ }
+
+ @Test
+ public void testFindRemovedUsers() throws Exception {
+ DeviceUserState preState =
+ getMockedUserState(
+ /* users= */ new Integer[] {0, 10},
+ /* runningUsers= */ new Integer[] {0, 10},
+ /* currentUser= */ 0);
+ DeviceUserState postState =
+ getMockedUserState(
+ /* users= */ new Integer[] {0},
+ /* runningUsers= */ new Integer[] {0},
+ /* currentUser= */ 0);
+
+ assertArrayEquals(new Integer[] {10}, preState.findRemovedUsers(postState).toArray());
+ }
+
+ @Test
+ public void testFindAddedUsers() throws Exception {
+ DeviceUserState preState =
+ getMockedUserState(
+ /* users= */ new Integer[] {0},
+ /* runningUsers= */ new Integer[] {0},
+ /* currentUser= */ 0);
+ DeviceUserState postState =
+ getMockedUserState(
+ /* users= */ new Integer[] {0, 10},
+ /* runningUsers= */ new Integer[] {0},
+ /* currentUser= */ 0);
+
+ assertArrayEquals(new Integer[] {10}, preState.findAddedUsers(postState).toArray());
+ }
+
+ @Test
+ public void testCurrentUserChanged() throws Exception {
+ DeviceUserState preState =
+ getMockedUserState(
+ /* users= */ new Integer[] {0, 10},
+ /* runningUsers= */ new Integer[] {0, 10},
+ /* currentUser= */ 10);
+ DeviceUserState postState =
+ getMockedUserState(
+ /* users= */ new Integer[] {0, 10},
+ /* runningUsers= */ new Integer[] {0, 10},
+ /* currentUser= */ 0);
+
+ assertEquals(true, preState.currentUserChanged(postState));
+ }
+
+ @Test
+ public void testfindStartedUsers() throws Exception {
+ DeviceUserState preState =
+ getMockedUserState(
+ /* users= */ new Integer[] {0, 10},
+ /* runningUsers= */ new Integer[] {0},
+ /* currentUser= */ 0);
+ DeviceUserState postState =
+ getMockedUserState(
+ /* users= */ new Integer[] {0, 10},
+ /* runningUsers= */ new Integer[] {0, 10},
+ /* currentUser= */ 0);
+
+ assertArrayEquals(new Integer[] {10}, preState.findStartedUsers(postState).toArray());
+ assertArrayEquals(new Integer[] {}, preState.findStoppedUsers(postState).toArray());
+ }
+
+ @Test
+ public void testFindStopedUsers() throws Exception {
+ DeviceUserState preState =
+ getMockedUserState(
+ /* users= */ new Integer[] {0, 10},
+ /* runningUsers= */ new Integer[] {0, 10},
+ /* currentUser= */ 0);
+ DeviceUserState postState =
+ getMockedUserState(
+ /* users= */ new Integer[] {0, 10},
+ /* runningUsers= */ new Integer[] {0},
+ /* currentUser= */ 0);
+
+ assertArrayEquals(new Integer[] {}, preState.findStartedUsers(postState).toArray());
+ assertArrayEquals(new Integer[] {10}, preState.findStoppedUsers(postState).toArray());
+ }
+
+ // TEST HELPERS
+
+ /** Return an instantiated DeviceUserState which was mocked. */
+ private DeviceUserState getMockedUserState(
+ Integer[] userIds, Integer[] runningUsers, int currentUser) throws Exception {
+ ITestDevice device = mockDeviceUserState(userIds, runningUsers, currentUser);
+ return new UserChecker.DeviceUserState(device);
+ }
+
+ /** Return a device with the user state calls mocked. */
+ private ITestDevice mockDeviceUserState(
+ Integer[] userIds, Integer[] runningUsers, int currentUser) throws Exception {
+ HashSet<Integer> runningUsersSet = new HashSet<Integer>(Arrays.asList(runningUsers));
+ ITestDevice device = mock(ITestDevice.class);
+ when(device.getCurrentUser()).thenReturn(currentUser);
+ mockListUsers(device, userIds);
+ for (int userId : userIds) {
+ when(device.isUserRunning(userId)).thenReturn(runningUsersSet.contains(userId));
+ }
+
+ return device;
+ }
+
+ private void mockListUsers(ITestDevice device, Integer[] userIds) throws Exception {
+ when(device.listUsers()).thenReturn(new ArrayList<Integer>(Arrays.asList(userIds)));
+ }
+}
diff --git a/tests/src/com/android/tradefed/targetprep/SwitchUserTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/SwitchUserTargetPreparerTest.java
index 6b55093..70187a2 100644
--- a/tests/src/com/android/tradefed/targetprep/SwitchUserTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/SwitchUserTargetPreparerTest.java
@@ -53,7 +53,7 @@
}
@Test
- public void testSetUpRunAsPrimary_ifAlreadyInPrimary_noUserSwitch()
+ public void testSetUpRunAsPrimary_ifAlreadyInPrimary_switchToPrimary()
throws DeviceNotAvailableException, TargetSetupError, ConfigurationException {
// setup
mockUsers(/* primaryUserId= */ 11, /* currentUserId= */ 11);
@@ -63,11 +63,11 @@
mSwitchUserTargetPreparer.setUp(mMockDevice, /* buildInfo= */ null);
// assert
- verify(mMockDevice, never()).switchUser(anyInt());
+ verify(mMockDevice, times(1)).switchUser(11);
}
@Test
- public void testSetUpRunAsSystem_ifAlreadyInSystem_noUserSwitch()
+ public void testSetUpRunAsSystem_ifAlreadyInSystem_switchToSystem()
throws DeviceNotAvailableException, TargetSetupError, ConfigurationException {
// setup
mockUsers(/* primaryUserId= */ 11, /* currentUserId= */ USER_SYSTEM);
@@ -77,7 +77,7 @@
mSwitchUserTargetPreparer.setUp(mMockDevice, /* buildInfo= */ null);
// assert
- verify(mMockDevice, never()).switchUser(anyInt());
+ verify(mMockDevice, times(1)).switchUser(0);
}
@Test
diff --git a/tests/src/com/android/tradefed/util/UserUtilTest.java b/tests/src/com/android/tradefed/util/UserUtilTest.java
new file mode 100644
index 0000000..ee18375
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/UserUtilTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 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.tradefed.util;
+
+import com.android.tradefed.util.UserUtil.UserType;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.device.ITestDevice;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+/** Unit tests for {@link UserChecker} */
+@RunWith(JUnit4.class)
+public class UserUtilTest {
+ @Test
+ public void testSwitchToUserSystemSuccess() throws Exception {
+ int currentUser = 12;
+
+ ITestDevice device = mock(ITestDevice.class);
+ when(device.switchUser(UserUtil.USER_SYSTEM)).thenReturn(true);
+
+ UserUtil.switchToUserType(device, UserType.SYSTEM);
+ verify(device, times(1)).switchUser(UserUtil.USER_SYSTEM);
+ }
+
+ @Test
+ public void testSwitchToUserSystemFail() throws Exception {
+ int currentUser = 12;
+
+ ITestDevice device = mock(ITestDevice.class);
+ when(device.switchUser(UserUtil.USER_SYSTEM)).thenReturn(false);
+
+ try {
+ UserUtil.switchToUserType(device, UserType.SYSTEM);
+ fail();
+ } catch (UserUtil.UserSwitchFailedException _expected) {
+ }
+ verify(device, times(1)).switchUser(UserUtil.USER_SYSTEM);
+ }
+
+ @Test
+ public void testSwitchToSecondaryUserCurrent() throws Exception {
+ int currentUser = 10;
+
+ ITestDevice device = mock(ITestDevice.class);
+ when(device.getCurrentUser()).thenReturn(currentUser);
+ when(device.isUserSecondary(currentUser)).thenReturn(true);
+
+ UserUtil.switchToUserType(device, UserUtil.UserType.SECONDARY);
+ verify(device, never()).switchUser(currentUser);
+ }
+
+ @Test
+ public void testSwitchToSecondaryUserExists() throws Exception {
+ ITestDevice device = mock(ITestDevice.class);
+ when(device.getCurrentUser()).thenReturn(0);
+ mockListUsers(device, new Integer[] {0, 10});
+ when(device.isUserSecondary(10)).thenReturn(true);
+ when(device.switchUser(10)).thenReturn(true);
+
+ UserUtil.switchToUserType(device, UserUtil.UserType.SECONDARY);
+ verify(device, times(1)).switchUser(10);
+ }
+
+ @Test
+ /** Validate that invalid user types will be skipped as secondaries. */
+ public void testSwitchToSecondaryUserWithInvalid() throws Exception {
+ ITestDevice device = mock(ITestDevice.class);
+ when(device.getCurrentUser()).thenReturn(0);
+ mockListUsers(device, new Integer[] {0, 10, 11, 12});
+ when(device.isUserSecondary(10)).thenReturn(false);
+ when(device.isUserSecondary(11)).thenReturn(false);
+ when(device.isUserSecondary(12)).thenReturn(true);
+ when(device.switchUser(12)).thenReturn(true);
+
+ UserUtil.switchToUserType(device, UserUtil.UserType.SECONDARY);
+ verify(device, times(1)).switchUser(12);
+ }
+
+ @Test
+ public void testSwitchToPrimaryUserNonSystem() throws Exception {
+ ITestDevice device = mock(ITestDevice.class);
+ when(device.getCurrentUser()).thenReturn(0);
+ when(device.getPrimaryUserId()).thenReturn(10);
+ when(device.switchUser(10)).thenReturn(true);
+
+ UserUtil.switchToUserType(device, UserUtil.UserType.PRIMARY);
+ verify(device, times(1)).switchUser(10);
+ }
+
+ // Helpers
+
+ private void mockListUsers(ITestDevice device, Integer[] userIds) throws Exception {
+ when(device.listUsers()).thenReturn(new ArrayList<Integer>(Arrays.asList(userIds)));
+ }
+}