ProcessErrorsTest.java revision ad8037e3a9b306bae6cdc8927c35946696bf40f6
1/* 2 * Copyright (C) 2008 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 17package com.android.smoketest; 18 19import android.app.ActivityManager; 20import android.app.ActivityManager.ProcessErrorStateInfo; 21import android.content.Context; 22import android.content.ComponentName; 23import android.content.Intent; 24import android.content.pm.PackageManager; 25import android.content.pm.ResolveInfo; 26import android.test.AndroidTestCase; 27import android.util.Log; 28 29import java.util.ArrayList; 30import java.util.Collection; 31import java.util.HashSet; 32import java.util.Iterator; 33import java.util.List; 34import java.util.Set; 35 36/** 37 * This smoke test is designed to quickly sniff for any error conditions 38 * encountered after initial startup. 39 */ 40public class ProcessErrorsTest extends AndroidTestCase { 41 42 private static final String TAG = "ProcessErrorsTest"; 43 44 private final Intent mHomeIntent; 45 46 protected ActivityManager mActivityManager; 47 protected PackageManager mPackageManager; 48 49 public ProcessErrorsTest() { 50 mHomeIntent = new Intent(Intent.ACTION_MAIN); 51 mHomeIntent.addCategory(Intent.CATEGORY_HOME); 52 mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 53 } 54 55 @Override 56 public void setUp() throws Exception { 57 super.setUp(); 58 mActivityManager = (ActivityManager) 59 getContext().getSystemService(Context.ACTIVITY_SERVICE); 60 mPackageManager = getContext().getPackageManager(); 61 } 62 63 public void testSetUpConditions() throws Exception { 64 assertNotNull(mActivityManager); 65 assertNotNull(mPackageManager); 66 } 67 68 public void testNoProcessErrorsAfterBoot() throws Exception { 69 final String reportMsg = checkForProcessErrors(); 70 if (reportMsg != null) { 71 Log.w(TAG, reportMsg); 72 } 73 74 // report a non-empty list back to the test framework 75 assertNull(reportMsg, reportMsg); 76 } 77 78 private String checkForProcessErrors() throws Exception { 79 List<ProcessErrorStateInfo> errList; 80 errList = mActivityManager.getProcessesInErrorState(); 81 82 // note: this contains information about each process that is currently in an error 83 // condition. if the list is empty (null) then "we're good". 84 85 // if the list is non-empty, then it's useful to report the contents of the list 86 final String reportMsg = reportListContents(errList); 87 return reportMsg; 88 } 89 90 /** 91 * A helper function to query the provided {@link PackageManager} for a list of Activities that 92 * can be launched from Launcher. 93 */ 94 static List<ResolveInfo> getLauncherActivities(PackageManager pm) { 95 final Intent launchable = new Intent(Intent.ACTION_MAIN); 96 launchable.addCategory(Intent.CATEGORY_LAUNCHER); 97 final List<ResolveInfo> activities = pm.queryIntentActivities(launchable, 0); 98 return activities; 99 } 100 101 /** 102 * A helper function to create an {@link Intent} to run, given a {@link ResolveInfo} specifying 103 * an activity to be launched. 104 */ 105 static Intent intentForActivity(ResolveInfo app) { 106 // build an Intent to launch the specified app 107 final ComponentName component = new ComponentName(app.activityInfo.packageName, 108 app.activityInfo.name); 109 final Intent intent = new Intent(Intent.ACTION_MAIN); 110 intent.setComponent(component); 111 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 112 intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 113 return intent; 114 } 115 116 /** 117 * A method to run the specified Activity and return a {@link Collection} of the Activities that 118 * were in an error state, as listed by {@link ActivityManager.getProcessesInErrorState()}. 119 * <p /> 120 * The method will launch the app, wait for 7 seconds, check for apps in the error state, send 121 * the Home intent, wait for 2 seconds, and then return. 122 */ 123 public Collection<ProcessError> runOneActivity(ResolveInfo app) { 124 final long appLaunchWait = 7000; 125 final long homeLaunchWait = 2000; 126 127 Log.i(TAG, String.format("Running activity %s/%s", app.activityInfo.packageName, 128 app.activityInfo.name)); 129 130 // We check for any Crash or ANR dialogs that are already up, and we ignore them. This is 131 // so that we don't report crashes that were caused by prior apps (which those particular 132 // tests should have caught and reported already). Otherwise, test failures would cascade 133 // from the initial broken app to many/all of the tests following that app's launch. 134 final Collection<ProcessError> preErrProcs = 135 ProcessError.fromCollection(mActivityManager.getProcessesInErrorState()); 136 137 // launch app, and wait 7 seconds for it to start/settle 138 final Intent intent = intentForActivity(app); 139 getContext().startActivity(intent); 140 try { 141 Thread.sleep(appLaunchWait); 142 } catch (InterruptedException e) { 143 // ignore 144 } 145 146 // See if there are any errors 147 final Collection<ProcessError> errProcs = 148 ProcessError.fromCollection(mActivityManager.getProcessesInErrorState()); 149 // Take the difference between the error processes we see now, and the ones that were 150 // present when we started 151 if (errProcs != null && preErrProcs != null) { 152 errProcs.removeAll(preErrProcs); 153 } 154 155 // Send the "home" intent and wait 2 seconds for us to get there 156 getContext().startActivity(mHomeIntent); 157 try { 158 Thread.sleep(homeLaunchWait); 159 } catch (InterruptedException e) { 160 // ignore 161 } 162 163 return errProcs; 164 } 165 166 /** 167 * A test that runs all Launcher-launchable activities and verifies that no ANRs or crashes 168 * happened while doing so. 169 * <p /> 170 * FIXME: Doesn't detect multiple crashing apps properly, since the crash dialog for the 171 * FIXME: first app doesn't go away. 172 */ 173 public void testRunAllActivities() throws Exception { 174 final Set<ProcessError> errSet = new HashSet<ProcessError>(); 175 176 for (ResolveInfo app : getLauncherActivities(mPackageManager)) { 177 final Collection<ProcessError> errProcs = runOneActivity(app); 178 if (errProcs != null) { 179 errSet.addAll(errProcs); 180 } 181 } 182 183 if (!errSet.isEmpty()) { 184 fail(String.format("Got %d errors: %s", errSet.size(), 185 reportWrappedListContents(errSet))); 186 } 187 } 188 189 String reportWrappedListContents(Collection<ProcessError> errList) { 190 List<ProcessErrorStateInfo> newList = new ArrayList<ProcessErrorStateInfo>(errList.size()); 191 for (ProcessError err : errList) { 192 newList.add(err.info); 193 } 194 return reportListContents(newList); 195 } 196 197 /** 198 * This helper function will dump the actual error reports. 199 * 200 * @param errList The error report containing one or more error records. 201 * @return Returns a string containing all of the errors. 202 */ 203 private String reportListContents(Collection<ProcessErrorStateInfo> errList) { 204 if (errList == null) return null; 205 206 StringBuilder builder = new StringBuilder(); 207 208 Iterator<ProcessErrorStateInfo> iter = errList.iterator(); 209 while (iter.hasNext()) { 210 ProcessErrorStateInfo entry = iter.next(); 211 212 String condition; 213 switch (entry.condition) { 214 case ActivityManager.ProcessErrorStateInfo.CRASHED: 215 condition = "CRASHED"; 216 break; 217 case ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING: 218 condition = "ANR"; 219 break; 220 default: 221 condition = "<unknown>"; 222 break; 223 } 224 225 builder.append("Process error ").append(condition).append(" "); 226 builder.append(" ").append(entry.shortMsg); 227 builder.append(" detected in ").append(entry.processName).append(" ").append(entry.tag); 228 builder.append("\n"); 229 } 230 return builder.toString(); 231 } 232 233 /** 234 * A {@link ProcessErrorStateInfo} wrapper class that hashes how we want (so that equivalent 235 * crashes are considered equal). 236 */ 237 static class ProcessError { 238 public final ProcessErrorStateInfo info; 239 240 public ProcessError(ProcessErrorStateInfo newInfo) { 241 info = newInfo; 242 } 243 244 public static Collection<ProcessError> fromCollection(Collection<ProcessErrorStateInfo> in) 245 { 246 if (in == null) { 247 return null; 248 } 249 250 List<ProcessError> out = new ArrayList<ProcessError>(in.size()); 251 for (ProcessErrorStateInfo info : in) { 252 out.add(new ProcessError(info)); 253 } 254 return out; 255 } 256 257 private boolean strEquals(String a, String b) { 258 if ((a == null) && (b == null)) { 259 return true; 260 } else if ((a == null) || (b == null)) { 261 return false; 262 } else { 263 return a.equals(b); 264 } 265 } 266 267 @Override 268 public boolean equals(Object other) { 269 if (other == null) return false; 270 if (!(other instanceof ProcessError)) return false; 271 ProcessError peOther = (ProcessError) other; 272 273 return (info.condition == peOther.info.condition) 274 && strEquals(info.longMsg, peOther.info.longMsg) 275 && (info.pid == peOther.info.pid) 276 && strEquals(info.processName, peOther.info.processName) 277 && strEquals(info.shortMsg, peOther.info.shortMsg) 278 && strEquals(info.stackTrace, peOther.info.stackTrace) 279 && strEquals(info.tag, peOther.info.tag) 280 && (info.uid == peOther.info.uid); 281 } 282 283 private int hash(Object obj) { 284 if (obj == null) { 285 return 13; 286 } else { 287 return obj.hashCode(); 288 } 289 } 290 291 @Override 292 public int hashCode() { 293 int code = 17; 294 code += info.condition; 295 code *= hash(info.longMsg); 296 code += info.pid; 297 code *= hash(info.processName); 298 code *= hash(info.shortMsg); 299 code *= hash(info.stackTrace); 300 code *= hash(info.tag); 301 code += info.uid; 302 return code; 303 } 304 } 305} 306