• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package android.host.multiuser;
18 
19 import android.platform.test.annotations.Presubmit;
20 
21 import com.android.tradefed.device.DeviceNotAvailableException;
22 import com.android.tradefed.log.LogUtil.CLog;
23 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
24 
25 import org.junit.Before;
26 import org.junit.Rule;
27 import org.junit.Test;
28 import org.junit.rules.TestRule;
29 import org.junit.runner.Description;
30 import org.junit.runner.RunWith;
31 import org.junit.runners.model.Statement;
32 
33 import java.util.HashSet;
34 import java.util.LinkedHashSet;
35 import java.util.Scanner;
36 import java.util.Set;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39 
40 import static org.junit.Assert.assertTrue;
41 
42 /**
43  * Test verifies that users can be created/switched to without error dialogs shown to the user
44  * Run: atest CreateUsersNoAppCrashesTest
45  */
46 @RunWith(DeviceJUnit4ClassRunner.class)
47 public class CreateUsersNoAppCrashesTest extends BaseMultiUserTest {
48     private int mInitialUserId;
49     private static final long LOGCAT_POLL_INTERVAL_MS = 5000;
50     private static final long USER_SWITCH_COMPLETE_TIMEOUT_MS = 180000;
51 
52     @Rule public AppCrashRetryRule appCrashRetryRule = new AppCrashRetryRule();
53 
54     @Before
setUp()55     public void setUp() throws Exception {
56         CLog.e("setup_CreateUsersNoAppCrashesTest");
57         super.setUp();
58         mInitialUserId = getDevice().getCurrentUser();
59     }
60 
61     @Presubmit
62     @Test
testCanCreateGuestUser()63     public void testCanCreateGuestUser() throws Exception {
64         if (!mSupportsMultiUser) {
65             return;
66         }
67         int userId = getDevice().createUser(
68                 "TestUser_" + System.currentTimeMillis() /* name */,
69                 true /* guest */,
70                 false /* ephemeral */);
71         assertSwitchToNewUser(userId);
72         assertSwitchToUser(userId, mInitialUserId);
73 
74     }
75 
76     @Presubmit
77     @Test
testCanCreateSecondaryUser()78     public void testCanCreateSecondaryUser() throws Exception {
79         if (!mSupportsMultiUser) {
80             return;
81         }
82         int userId = getDevice().createUser(
83                 "TestUser_" + System.currentTimeMillis() /* name */,
84                 false /* guest */,
85                 false /* ephemeral */);
86         assertSwitchToNewUser(userId);
87         assertSwitchToUser(userId, mInitialUserId);
88     }
89 
assertSwitchToNewUser(int toUserId)90     private void assertSwitchToNewUser(int toUserId) throws Exception {
91         final String exitString = "Finished processing BOOT_COMPLETED for u" + toUserId;
92         final Set<String> appErrors = new LinkedHashSet<>();
93         getDevice().executeAdbCommand("logcat", "-c"); // Reset log
94         assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
95         final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
96         assertTrue("Didn't receive BOOT_COMPLETED delivered notification. appErrors="
97                 + appErrors, result);
98         if (!appErrors.isEmpty()) {
99             throw new AppCrashOnBootError(appErrors);
100         }
101     }
102 
assertSwitchToUser(int fromUserId, int toUserId)103     private void assertSwitchToUser(int fromUserId, int toUserId) throws Exception {
104         final String exitString = "Continue user switch oldUser #" + fromUserId + ", newUser #"
105                 + toUserId;
106         final Set<String> appErrors = new LinkedHashSet<>();
107         getDevice().executeAdbCommand("logcat", "-c"); // Reset log
108         assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
109         final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
110         assertTrue("Didn't reach \"Continue user switch\" stage. appErrors=" + appErrors, result);
111         if (!appErrors.isEmpty()) {
112             throw new AppCrashOnBootError(appErrors);
113         }
114     }
115 
waitForUserSwitchComplete(Set<String> appErrors, int targetUserId, String exitString)116     private boolean waitForUserSwitchComplete(Set<String> appErrors, int targetUserId,
117             String exitString) throws DeviceNotAvailableException, InterruptedException {
118         boolean mExitFound = false;
119         long ti = System.currentTimeMillis();
120         while (System.currentTimeMillis() - ti < USER_SWITCH_COMPLETE_TIMEOUT_MS) {
121             String logs = getDevice().executeAdbCommand("logcat", "-v", "brief", "-d",
122                     "ActivityManager:D", "AndroidRuntime:E", "*:S");
123             Scanner in = new Scanner(logs);
124             while (in.hasNextLine()) {
125                 String line = in.nextLine();
126                 if (line.contains("Showing crash dialog for package")) {
127                     appErrors.add(line);
128                 } else if (line.contains(exitString)) {
129                     // Parse all logs in case crashes occur as a result of onUserChange callbacks
130                     mExitFound = true;
131                 } else if (line.contains("FATAL EXCEPTION IN SYSTEM PROCESS")) {
132                     throw new IllegalStateException("System process crashed - " + line);
133                 }
134             }
135             in.close();
136             if (mExitFound) {
137                 if (!appErrors.isEmpty()) {
138                     CLog.w("App crash dialogs found: " + appErrors);
139                 }
140                 return true;
141             }
142             Thread.sleep(LOGCAT_POLL_INTERVAL_MS);
143         }
144         return false;
145     }
146 
147     static class AppCrashOnBootError extends AssertionError {
148         private static final Pattern PACKAGE_NAME_PATTERN = Pattern.compile("package ([^\\s]+)");
149         private Set<String> errorPackages;
150 
AppCrashOnBootError(Set<String> errorLogs)151         AppCrashOnBootError(Set<String> errorLogs) {
152             super("App error dialog(s) are present: " + errorLogs);
153             this.errorPackages = errorLogsToPackageNames(errorLogs);
154         }
155 
errorLogsToPackageNames(Set<String> errorLogs)156         private static Set<String> errorLogsToPackageNames(Set<String> errorLogs) {
157             Set<String> result = new HashSet<>();
158             for (String line : errorLogs) {
159                 Matcher matcher = PACKAGE_NAME_PATTERN.matcher(line);
160                 if (matcher.find()) {
161                     result.add(matcher.group(1));
162                 } else {
163                     throw new IllegalStateException("Unrecognized line " + line);
164                 }
165             }
166             return result;
167         }
168     }
169 
170     /**
171      * Rule that retries the test if it failed due to {@link AppCrashOnBootError}
172      */
173     public static class AppCrashRetryRule implements TestRule {
174 
175         @Override
apply(Statement base, Description description)176         public Statement apply(Statement base, Description description) {
177             return new Statement() {
178                 @Override
179                 public void evaluate() throws Throwable {
180                     Set<String> errors = evaluateAndReturnAppCrashes(base);
181                     if (errors.isEmpty()) {
182                         return;
183                     }
184                     CLog.e("Retrying due to app crashes: " + errors);
185                     // Fail only if same apps are crashing in both runs
186                     errors.retainAll(evaluateAndReturnAppCrashes(base));
187                     assertTrue("App error dialog(s) are present after 2 attempts: " + errors,
188                             errors.isEmpty());
189                 }
190             };
191         }
192 
evaluateAndReturnAppCrashes(Statement base)193         private static Set<String> evaluateAndReturnAppCrashes(Statement base) throws Throwable {
194             try {
195                 base.evaluate();
196             } catch (AppCrashOnBootError e) {
197                 return e.errorPackages;
198             }
199             return new HashSet<>();
200         }
201     }
202 }
203