diff options
7 files changed, 343 insertions, 14 deletions
diff --git a/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java b/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java index 5f53a9b..1a2dcb9 100644 --- a/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java +++ b/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java @@ -19,12 +19,21 @@ package com.android.smoketest; import com.android.internal.os.RuntimeInit; import android.app.ActivityManager; +import android.app.ActivityManager.ProcessErrorStateInfo; import android.content.Context; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.test.AndroidTestCase; import android.util.Log; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; /** * This smoke test is designed to quickly sniff for any error conditions @@ -32,53 +41,125 @@ import java.util.List; */ public class ProcessErrorsTest extends AndroidTestCase { - private final String TAG = "ProcessErrorsTest"; + private static final String TAG = "ProcessErrorsTest"; protected ActivityManager mActivityManager; + protected PackageManager mPackageManager; @Override public void setUp() throws Exception { super.setUp(); - mActivityManager = (ActivityManager) + mActivityManager = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE); + mPackageManager = getContext().getPackageManager(); } public void testSetUpConditions() throws Exception { assertNotNull(mActivityManager); + assertNotNull(mPackageManager); } public void testNoProcessErrors() throws Exception { - List<ActivityManager.ProcessErrorStateInfo> errList; + final String reportMsg = checkForProcessErrors(); + if (reportMsg != null) { + Log.w(TAG, reportMsg); + } + + // report a non-empty list back to the test framework + assertNull(reportMsg, reportMsg); + } + + private String checkForProcessErrors() throws Exception { + List<ProcessErrorStateInfo> errList; errList = mActivityManager.getProcessesInErrorState(); // note: this contains information about each process that is currently in an error // condition. if the list is empty (null) then "we're good". // if the list is non-empty, then it's useful to report the contents of the list - // we'll put a copy in the log, and we'll report it back to the framework via the assert. final String reportMsg = reportListContents(errList); - if (reportMsg != null) { - Log.w(TAG, reportMsg); + return reportMsg; + } + + /** + * A test that runs all Launcher-launchable activities and verifies that no ANRs or crashes + * happened while doing so. + * <p /> + * FIXME: Doesn't detect multiple crashing apps properly, since the crash dialog for the + * FIXME: first app doesn't go away. + */ + public void testRunAllActivities() throws Exception { + final Intent home = new Intent(Intent.ACTION_MAIN); + home.addCategory(Intent.CATEGORY_HOME); + home.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + final Intent launchable = new Intent(Intent.ACTION_MAIN); + launchable.addCategory(Intent.CATEGORY_LAUNCHER); + final List<ResolveInfo> activities = mPackageManager.queryIntentActivities(launchable, 0); + final Set<ProcessError> errSet = new HashSet<ProcessError>(); + + for (ResolveInfo info : activities) { + Log.i(TAG, String.format("Got %s/%s", info.activityInfo.packageName, + info.activityInfo.name)); + + // build an Intent to launch the app + final ComponentName component = new ComponentName(info.activityInfo.packageName, + info.activityInfo.name); + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(component); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // launch app, and wait 7 seconds for it to start/settle + getContext().startActivity(intent); + try { + Thread.sleep(7000); + } catch (InterruptedException e) { + // ignore + } + + // See if there are any errors + Collection<ProcessErrorStateInfo> procs = mActivityManager.getProcessesInErrorState(); + if (procs != null) { + errSet.addAll(ProcessError.fromCollection(procs)); + } + + // Send the "home" intent and wait 2 seconds for us to get there + getContext().startActivity(home); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + // ignore + } + } + + if (!errSet.isEmpty()) { + fail(String.format("Got %d errors: %s", errSet.size(), + reportWrappedListContents(errSet))); } - - // report a non-empty list back to the test framework - assertNull(reportMsg, errList); } - + + private String reportWrappedListContents(Collection<ProcessError> errList) { + List<ProcessErrorStateInfo> newList = new ArrayList<ProcessErrorStateInfo>(errList.size()); + for (ProcessError err : errList) { + newList.add(err.info); + } + return reportListContents(newList); + } + /** * This helper function will dump the actual error reports. * * @param errList The error report containing one or more error records. * @return Returns a string containing all of the errors. */ - private String reportListContents(List<ActivityManager.ProcessErrorStateInfo> errList) { + private String reportListContents(Collection<ProcessErrorStateInfo> errList) { if (errList == null) return null; StringBuilder builder = new StringBuilder(); - Iterator<ActivityManager.ProcessErrorStateInfo> iter = errList.iterator(); + Iterator<ProcessErrorStateInfo> iter = errList.iterator(); while (iter.hasNext()) { - ActivityManager.ProcessErrorStateInfo entry = iter.next(); + ProcessErrorStateInfo entry = iter.next(); String condition; switch (entry.condition) { @@ -96,8 +177,77 @@ public class ProcessErrorsTest extends AndroidTestCase { builder.append("Process error ").append(condition).append(" "); builder.append(" ").append(entry.shortMsg); builder.append(" detected in ").append(entry.processName).append(" ").append(entry.tag); + builder.append("\n"); } return builder.toString(); } - + + /** + * A {@link ProcessErrorStateInfo} wrapper class that hashes how we want (so that equivalent + * crashes are considered equal). + */ + private static class ProcessError { + public final ProcessErrorStateInfo info; + + public ProcessError(ProcessErrorStateInfo newInfo) { + info = newInfo; + } + + public static Collection<ProcessError> fromCollection(Collection<ProcessErrorStateInfo> in) + { + List<ProcessError> out = new ArrayList<ProcessError>(in.size()); + for (ProcessErrorStateInfo info : in) { + out.add(new ProcessError(info)); + } + return out; + } + + private boolean strEquals(String a, String b) { + if ((a == null) && (b == null)) { + return true; + } else if ((a == null) || (b == null)) { + return false; + } else { + return a.equals(b); + } + } + + @Override + public boolean equals(Object other) { + if (other == null) return false; + if (!(other instanceof ProcessError)) return false; + ProcessError peOther = (ProcessError) other; + + return (info.condition == peOther.info.condition) + && strEquals(info.longMsg, peOther.info.longMsg) + && (info.pid == peOther.info.pid) + && strEquals(info.processName, peOther.info.processName) + && strEquals(info.shortMsg, peOther.info.shortMsg) + && strEquals(info.stackTrace, peOther.info.stackTrace) + && strEquals(info.tag, peOther.info.tag) + && (info.uid == peOther.info.uid); + } + + private int hash(Object obj) { + if (obj == null) { + return 13; + } else { + return obj.hashCode(); + } + } + + @Override + public int hashCode() { + int code = 17; + code += info.condition; + code *= hash(info.longMsg); + code += info.pid; + code *= hash(info.processName); + code *= hash(info.shortMsg); + code *= hash(info.stackTrace); + code *= hash(info.tag); + code += info.uid; + return code; + } + } } diff --git a/tests/SmokeTestApps/Android.mk b/tests/SmokeTestApps/Android.mk new file mode 100644 index 0000000..3f5f011 --- /dev/null +++ b/tests/SmokeTestApps/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := SmokeTestTriggerApps + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/SmokeTestApps/AndroidManifest.xml b/tests/SmokeTestApps/AndroidManifest.xml new file mode 100644 index 0000000..0f20107 --- /dev/null +++ b/tests/SmokeTestApps/AndroidManifest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.smoketest.triggers"> + + <application android:label="something"> + <activity android:name=".CrashyApp" + android:label="Test Crashy App"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity android:name=".CrashyApp2" + android:label="Test Crashy App2"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity android:name=".UnresponsiveApp" + android:label="Test Unresponsive App"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/SmokeTestApps/README b/tests/SmokeTestApps/README new file mode 100644 index 0000000..04aa366 --- /dev/null +++ b/tests/SmokeTestApps/README @@ -0,0 +1,3 @@ +The apps in this folder are intentionally bad-behaving apps that are intended +to trigger the smoke tests to fail. They are otherwise not useful. + diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java new file mode 100644 index 0000000..c11b0f3 --- /dev/null +++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 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.smoketest.triggers; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; + +public class CrashyApp extends Activity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + TextView tv = new TextView(this); + tv.setText("Hello, Crashy Android"); + setContentView(tv); + } + + @Override + public void onResume() { + ((String) null).length(); + } +} diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp2.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp2.java new file mode 100644 index 0000000..3ef5b2b --- /dev/null +++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp2.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 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.smoketest.triggers; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; + +public class CrashyApp2 extends Activity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + TextView tv = new TextView(this); + tv.setText("Hello, Other Crashy Android"); + setContentView(tv); + } + + + @Override + public void onResume() { + throw new RuntimeException("Two drums and a cymbal fall off a cliff..."); + } +} diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/UnresponsiveApp.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/UnresponsiveApp.java new file mode 100644 index 0000000..1291897 --- /dev/null +++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/UnresponsiveApp.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012 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.smoketest.triggers; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; + +public class UnresponsiveApp extends Activity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + TextView tv = new TextView(this); + tv.setText("Hello, Unresponsive Android"); + setContentView(tv); + } + + @Override + public void onResume() { + // Attempt to provoke the ire of the ActivityManager + while (true) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // ignore + } + } + } +} |