1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/tests/testANRReporter.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,229 @@ 1.4 +package org.mozilla.gecko.tests; 1.5 + 1.6 +import org.mozilla.gecko.AppConstants; 1.7 + 1.8 +import android.content.Context; 1.9 +import android.content.Intent; 1.10 + 1.11 +import com.jayway.android.robotium.solo.Condition; 1.12 + 1.13 +import java.io.File; 1.14 +import java.io.FileReader; 1.15 +import java.io.FileWriter; 1.16 + 1.17 +import org.json.JSONObject; 1.18 + 1.19 +/** 1.20 + * Tests the proper operation of the ANR reporter. 1.21 + */ 1.22 +public class testANRReporter extends BaseTest { 1.23 + 1.24 + private static final String ANR_ACTION = "android.intent.action.ANR"; 1.25 + private static final String PING_DIR = "saved-telemetry-pings"; 1.26 + private static final int WAIT_FOR_PING_TIMEOUT = 10000; 1.27 + private static final String ANR_PATH = "/data/anr/traces.txt"; 1.28 + private static final String SAMPLE_ANR 1.29 + = "----- pid 1 at 2014-01-15 18:55:51 -----\n" 1.30 + + "Cmd line: " + AppConstants.ANDROID_PACKAGE_NAME + "\n" 1.31 + + "\n" 1.32 + + "JNI: CheckJNI is off; workarounds are off; pins=0; globals=397\n" 1.33 + + "\n" 1.34 + + "DALVIK THREADS:\n" 1.35 + + "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)\n" 1.36 + + "\n" 1.37 + + "\"main\" prio=5 tid=1 WAIT\n" 1.38 + + " | group=\"main\" sCount=1 dsCount=0 obj=0x41d6bc90 self=0x41d5a3c8\n" 1.39 + + " | sysTid=3485 nice=0 sched=0/0 cgrp=apps handle=1074852180\n" 1.40 + + " | state=S schedstat=( 0 0 0 ) utm=1065 stm=152 core=0\n" 1.41 + + " at java.lang.Object.wait(Native Method)\n" 1.42 + + " - waiting on <0x427ab340> (a org.mozilla.gecko.GeckoEditable$5)\n" 1.43 + + " at java.lang.Object.wait(Object.java:364)\n" 1.44 + + " at org.mozilla.gecko.GeckoEditable$5.run(GeckoEditable.java:746)\n" 1.45 + + " at android.os.Handler.handleCallback(Handler.java:733)\n" 1.46 + + " at android.os.Handler.dispatchMessage(Handler.java:95)\n" 1.47 + + " at android.os.Looper.loop(Looper.java:137)\n" 1.48 + + " at android.app.ActivityThread.main(ActivityThread.java:4998)\n" 1.49 + + " at java.lang.reflect.Method.invokeNative(Native Method)\n" 1.50 + + " at java.lang.reflect.Method.invoke(Method.java:515)\n" 1.51 + + " at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)\n" 1.52 + + " at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)\n" 1.53 + + " at dalvik.system.NativeStart.main(Native Method)\n" 1.54 + + "\n" 1.55 + + "\"Gecko\" prio=5 tid=16 SUSPENDED\n" 1.56 + + " | group=\"main\" sCount=1 dsCount=0 obj=0x426e2b28 self=0x76ae92e8\n" 1.57 + + " | sysTid=3541 nice=0 sched=0/0 cgrp=apps handle=1991153472\n" 1.58 + + " | state=S schedstat=( 0 0 0 ) utm=1118 stm=145 core=0\n" 1.59 + + " #00 pc 00000904 /system/lib/libc.so (__futex_syscall3+4294832136)\n" 1.60 + + " #01 pc 0000eec4 /system/lib/libc.so (__pthread_cond_timedwait_relative+48)\n" 1.61 + + " #02 pc 0000ef24 /system/lib/libc.so (__pthread_cond_timedwait+64)\n" 1.62 + + " #03 pc 000536b7 /system/lib/libdvm.so\n" 1.63 + + " #04 pc 00053c79 /system/lib/libdvm.so (dvmChangeStatus(Thread*, ThreadStatus)+34)\n" 1.64 + + " #05 pc 00049507 /system/lib/libdvm.so\n" 1.65 + + " #06 pc 0004d84b /system/lib/libdvm.so\n" 1.66 + + " #07 pc 0003f1df /dev/ashmem/libxul.so (deleted)\n" 1.67 + + " at org.mozilla.gecko.mozglue.GeckoLoader.nativeRun(Native Method)\n" 1.68 + + " at org.mozilla.gecko.GeckoAppShell.runGecko(GeckoAppShell.java:384)\n" 1.69 + + " at org.mozilla.gecko.GeckoThread.run(GeckoThread.java:177)\n" 1.70 + + "\n" 1.71 + + "----- end 1 -----\n" 1.72 + + "\n" 1.73 + + "\n" 1.74 + + "----- pid 2 at 2013-01-25 13:27:01 -----\n" 1.75 + + "Cmd line: system_server\n" 1.76 + + "\n" 1.77 + + "----- end 2 -----\n"; 1.78 + 1.79 + private boolean mDone; 1.80 + 1.81 + private JSONObject readPingFile(final File pingFile) throws Exception { 1.82 + final long fileSize = pingFile.length(); 1.83 + if (fileSize == 0 || fileSize > Integer.MAX_VALUE) { 1.84 + throw new Exception("Invalid ping file size"); 1.85 + } 1.86 + final char[] buffer = new char[(int) fileSize]; 1.87 + final FileReader reader = new FileReader(pingFile); 1.88 + try { 1.89 + final int readSize = reader.read(buffer); 1.90 + if (readSize == 0 || readSize > buffer.length) { 1.91 + throw new Exception("Invalid number of bytes read"); 1.92 + } 1.93 + } finally { 1.94 + reader.close(); 1.95 + } 1.96 + return new JSONObject(new String(buffer)); 1.97 + } 1.98 + 1.99 + public void testANRReporter() throws Exception { 1.100 + blockForGeckoReady(); 1.101 + 1.102 + // Cannot test ANR reporter if it's disabled. 1.103 + if (!AppConstants.MOZ_ANDROID_ANR_REPORTER) { 1.104 + mAsserter.ok(true, "ANR reporter is disabled", null); 1.105 + return; 1.106 + } 1.107 + 1.108 + // For the ANR reporter to work, we need to provide sample ANR traces to it. 1.109 + // Therefore, we need the ANR file to exist and writable. If not, we don't 1.110 + // have the right permissions to create the file, so we just bail. 1.111 + final File anrFile = new File(ANR_PATH); 1.112 + if (!anrFile.exists()) { 1.113 + mAsserter.ok(true, "ANR file does not exist", null); 1.114 + return; 1.115 + } 1.116 + if (!anrFile.canWrite()) { 1.117 + mAsserter.ok(true, "ANR file is not writable", null); 1.118 + return; 1.119 + } 1.120 + 1.121 + final FileWriter anrWriter = new FileWriter(anrFile); 1.122 + try { 1.123 + anrWriter.write(SAMPLE_ANR); 1.124 + } finally { 1.125 + anrWriter.close(); 1.126 + } 1.127 + 1.128 + // Block the UI thread to simulate an ANR 1.129 + final Runnable uiBlocker = new Runnable() { 1.130 + @Override 1.131 + public synchronized void run() { 1.132 + while (!mDone) { 1.133 + try { 1.134 + wait(); 1.135 + } catch (final InterruptedException e) { 1.136 + } 1.137 + } 1.138 + } 1.139 + }; 1.140 + getActivity().runOnUiThread(uiBlocker); 1.141 + 1.142 + // Make sure our initial ping directory is empty. 1.143 + final File pingDir = new File(mProfile, PING_DIR); 1.144 + final String[] initialFiles = pingDir.list(); 1.145 + mAsserter.ok(initialFiles == null || initialFiles.length == 0, 1.146 + "Ping directory is empty", null); 1.147 + 1.148 + final Intent anrIntent = new Intent(ANR_ACTION); 1.149 + anrIntent.setPackage(AppConstants.ANDROID_PACKAGE_NAME); 1.150 + mAsserter.is(anrIntent.getPackage(), AppConstants.ANDROID_PACKAGE_NAME, 1.151 + "Successfully set package name"); 1.152 + 1.153 + final Context testContext = getInstrumentation().getContext(); 1.154 + mAsserter.isnot(testContext, null, "testContext should not be null"); 1.155 + 1.156 + // Trigger the ANR. 1.157 + mAsserter.info("Triggering ANR", null); 1.158 + testContext.sendBroadcast(anrIntent); 1.159 + 1.160 + // ANR reporter is supposed to ignore duplicate ANRs. 1.161 + // This will be checked later when we look for ping files. 1.162 + mAsserter.info("Triggering second ANR", null); 1.163 + testContext.sendBroadcast(new Intent(anrIntent)); 1.164 + 1.165 + mAsserter.info("Waiting for ping", null); 1.166 + waitForCondition(new Condition() { 1.167 + @Override 1.168 + public boolean isSatisfied() { 1.169 + final File[] newFiles = pingDir.listFiles(); 1.170 + if (newFiles == null || newFiles.length == 0) { 1.171 + // Keep waiting. 1.172 + return false; 1.173 + } 1.174 + // Make sure we have a complete file. We skip assertions and catch all 1.175 + // exceptions here because the condition may not be satisfied now but may 1.176 + // be satisfied later. After the wait is over, we will repeat the same 1.177 + // steps with assertions and exceptions. 1.178 + try { 1.179 + return readPingFile(newFiles[0]).has("slug"); 1.180 + } catch (final Exception e) { 1.181 + return false; 1.182 + } 1.183 + } 1.184 + }, WAIT_FOR_PING_TIMEOUT); 1.185 + 1.186 + mAsserter.ok(pingDir.exists(), "Ping directory exists", null); 1.187 + mAsserter.ok(pingDir.isDirectory(), "Ping directory is a directory", null); 1.188 + 1.189 + final File[] newFiles = pingDir.listFiles(); 1.190 + mAsserter.isnot(newFiles, null, "Ping directory is not empty"); 1.191 + mAsserter.is(newFiles.length, 1, "ANR reporter wrote one ping"); 1.192 + mAsserter.ok(newFiles[0].exists(), "Ping exists", null); 1.193 + mAsserter.ok(newFiles[0].isFile(), "Ping is a file", null); 1.194 + mAsserter.ok(newFiles[0].canRead(), "Ping is readable", null); 1.195 + mAsserter.info("Found ping file", newFiles[0].getPath()); 1.196 + 1.197 + // Check standard properties required by Telemetry server. 1.198 + final JSONObject pingObject = readPingFile(newFiles[0]); 1.199 + mAsserter.ok(pingObject.has("slug"), "Ping has slug property", null); 1.200 + mAsserter.ok(pingObject.has("reason"), "Ping has reason property", null); 1.201 + mAsserter.ok(pingObject.has("payload"), "Ping has payload property", null); 1.202 + 1.203 + final JSONObject pingPayload = pingObject.getJSONObject("payload"); 1.204 + mAsserter.ok(pingPayload.has("ver"), "Payload has ver property", null); 1.205 + mAsserter.ok(pingPayload.has("info"), "Payload has info property", null); 1.206 + mAsserter.ok(pingPayload.has("androidANR"), "Payload has androidANR property", null); 1.207 + 1.208 + final JSONObject pingInfo = pingPayload.getJSONObject("info"); 1.209 + mAsserter.ok(pingInfo.has("reason"), "Info has reason property", null); 1.210 + mAsserter.ok(pingInfo.has("appName"), "Info has appName property", null); 1.211 + mAsserter.ok(pingInfo.has("appUpdateChannel"), "Info has appUpdateChannel property", null); 1.212 + mAsserter.ok(pingInfo.has("appVersion"), "Info has appVersion property", null); 1.213 + mAsserter.ok(pingInfo.has("appBuildID"), "Info has appBuildID property", null); 1.214 + 1.215 + // Do some profile clean up. This is not absolutely necessary because the profile 1.216 + // is blown away after test runs anyways, so we don't check return values here. 1.217 + for (final File ping : newFiles) { 1.218 + ping.delete(); 1.219 + } 1.220 + pingDir.delete(); 1.221 + 1.222 + // Unblock UI thread 1.223 + synchronized (uiBlocker) { 1.224 + mDone = true; 1.225 + uiBlocker.notify(); 1.226 + } 1.227 + 1.228 + // Clear the sample ANR 1.229 + final FileWriter anrClearer = new FileWriter(anrFile); 1.230 + anrClearer.close(); 1.231 + } 1.232 +}