Bug 999071 - Add test for ANR reporter; r=blassey
authorJim Chen <nchen@mozilla.com>
Mon, 28 Apr 2014 22:20:52 -0400
changeset 181054 267b32d6633fda3f372bf59e819103ca982c3c68
parent 181053 77ed9758681b101e3a889a7712a1d083013c9360
child 181055 1b3d03b3493d9c1adb673473f0de6bcaacfa9322
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersblassey
bugs999071
milestone32.0a1
Bug 999071 - Add test for ANR reporter; r=blassey
mobile/android/base/tests/robocop.ini
mobile/android/base/tests/testANRReporter.java
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -5,16 +5,17 @@ skip-if = android_version == "10"
 [testAddonManager]
 # disabled on x86 only; bug 936216
 skip-if = processor == "x86"
 [testAddSearchEngine]
 # disabled on Android 2.3; bug 979552
 skip-if = android_version == "10"
 [testAdobeFlash]
 skip-if = processor == "x86"
+[testANRReporter]
 [testAwesomebar]
 [testAxisLocking]
 # disabled on x86 only; bug 927476
 skip-if = processor == "x86"
 # [testBookmark] # see bug 915350
 [testBookmarksPanel]
 # disabled on 2.3; bug 979615
 skip-if = android_version == "10"
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testANRReporter.java
@@ -0,0 +1,207 @@
+package org.mozilla.gecko.tests;
+
+import org.mozilla.gecko.AppConstants;
+
+import android.content.Context;
+import android.content.Intent;
+
+import com.jayway.android.robotium.solo.Condition;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+
+import org.json.JSONObject;
+
+/**
+ * Tests the proper operation of the ANR reporter.
+ */
+public class testANRReporter extends BaseTest {
+
+    private static final String ANR_ACTION = "android.intent.action.ANR";
+    private static final String PING_DIR = "saved-telemetry-pings";
+    private static final int WAIT_FOR_PING_TIMEOUT = 10000;
+    private static final String ANR_PATH = "/data/anr/traces.txt";
+    private static final String SAMPLE_ANR
+        = "----- pid 1 at 2014-01-15 18:55:51 -----\n"
+        + "Cmd line: " + AppConstants.ANDROID_PACKAGE_NAME + "\n"
+        + "\n"
+        + "JNI: CheckJNI is off; workarounds are off; pins=0; globals=397\n"
+        + "\n"
+        + "DALVIK THREADS:\n"
+        + "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)\n"
+        + "\n"
+        + "\"main\" prio=5 tid=1 WAIT\n"
+        + "  | group=\"main\" sCount=1 dsCount=0 obj=0x41d6bc90 self=0x41d5a3c8\n"
+        + "  | sysTid=3485 nice=0 sched=0/0 cgrp=apps handle=1074852180\n"
+        + "  | state=S schedstat=( 0 0 0 ) utm=1065 stm=152 core=0\n"
+        + "  at java.lang.Object.wait(Native Method)\n"
+        + "  - waiting on <0x427ab340> (a org.mozilla.gecko.GeckoEditable$5)\n"
+        + "  at java.lang.Object.wait(Object.java:364)\n"
+        + "  at org.mozilla.gecko.GeckoEditable$5.run(GeckoEditable.java:746)\n"
+        + "  at android.os.Handler.handleCallback(Handler.java:733)\n"
+        + "  at android.os.Handler.dispatchMessage(Handler.java:95)\n"
+        + "  at android.os.Looper.loop(Looper.java:137)\n"
+        + "  at android.app.ActivityThread.main(ActivityThread.java:4998)\n"
+        + "  at java.lang.reflect.Method.invokeNative(Native Method)\n"
+        + "  at java.lang.reflect.Method.invoke(Method.java:515)\n"
+        + "  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)\n"
+        + "  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)\n"
+        + "  at dalvik.system.NativeStart.main(Native Method)\n"
+        + "\n"
+        + "\"Gecko\" prio=5 tid=16 SUSPENDED\n"
+        + "  | group=\"main\" sCount=1 dsCount=0 obj=0x426e2b28 self=0x76ae92e8\n"
+        + "  | sysTid=3541 nice=0 sched=0/0 cgrp=apps handle=1991153472\n"
+        + "  | state=S schedstat=( 0 0 0 ) utm=1118 stm=145 core=0\n"
+        + "  #00  pc 00000904  /system/lib/libc.so (__futex_syscall3+4294832136)\n"
+        + "  #01  pc 0000eec4  /system/lib/libc.so (__pthread_cond_timedwait_relative+48)\n"
+        + "  #02  pc 0000ef24  /system/lib/libc.so (__pthread_cond_timedwait+64)\n"
+        + "  #03  pc 000536b7  /system/lib/libdvm.so\n"
+        + "  #04  pc 00053c79  /system/lib/libdvm.so (dvmChangeStatus(Thread*, ThreadStatus)+34)\n"
+        + "  #05  pc 00049507  /system/lib/libdvm.so\n"
+        + "  #06  pc 0004d84b  /system/lib/libdvm.so\n"
+        + "  #07  pc 0003f1df  /dev/ashmem/libxul.so (deleted)\n"
+        + "  at org.mozilla.gecko.mozglue.GeckoLoader.nativeRun(Native Method)\n"
+        + "  at org.mozilla.gecko.GeckoAppShell.runGecko(GeckoAppShell.java:384)\n"
+        + "  at org.mozilla.gecko.GeckoThread.run(GeckoThread.java:177)\n"
+        + "\n"
+        + "----- end 1 -----\n"
+        + "\n"
+        + "\n"
+        + "----- pid 2 at 2013-01-25 13:27:01 -----\n"
+        + "Cmd line: system_server\n"
+        + "\n"
+        + "----- end 2 -----\n";
+
+    private boolean mDone;
+
+    public void testANRReporter() throws Exception {
+        blockForGeckoReady();
+
+        // Cannot test ANR reporter if it's disabled.
+        if (!AppConstants.MOZ_ANDROID_ANR_REPORTER) {
+            mAsserter.ok(true, "ANR reporter is disabled", null);
+            return;
+        }
+
+        // For the ANR reporter to work, we need to provide sample ANR traces to it.
+        // Therefore, we need the ANR file to exist and writable. If not, we don't
+        // have the right permissions to create the file, so we just bail.
+        final File anrFile = new File(ANR_PATH);
+        if (!anrFile.exists()) {
+            mAsserter.ok(true, "ANR file does not exist", null);
+            return;
+        }
+        if (!anrFile.canWrite()) {
+            mAsserter.ok(true, "ANR file is not writable", null);
+            return;
+        }
+
+        final FileWriter anrWriter = new FileWriter(anrFile);
+        try {
+            anrWriter.write(SAMPLE_ANR);
+        } finally {
+            anrWriter.close();
+        }
+
+        // Block the UI thread to simulate an ANR
+        final Runnable uiBlocker = new Runnable() {
+            @Override
+            public synchronized void run() {
+                while (!mDone) {
+                    try {
+                        wait();
+                    } catch (final InterruptedException e) {
+                    }
+                }
+            }
+        };
+        getActivity().runOnUiThread(uiBlocker);
+
+        // Make sure our initial ping directory is empty.
+        final File pingDir = new File(mProfile, PING_DIR);
+        final String[] initialFiles = pingDir.list();
+        mAsserter.ok(initialFiles == null || initialFiles.length == 0,
+                     "Ping directory is empty", null);
+
+        final Intent anrIntent = new Intent(ANR_ACTION);
+        anrIntent.setPackage(AppConstants.ANDROID_PACKAGE_NAME);
+        mAsserter.is(anrIntent.getPackage(), AppConstants.ANDROID_PACKAGE_NAME,
+                     "Successfully set package name");
+
+        final Context testContext = getInstrumentation().getContext();
+        mAsserter.isnot(testContext, null, "testContext should not be null");
+
+        // Trigger the ANR.
+        mAsserter.info("Triggering ANR", null);
+        testContext.sendBroadcast(anrIntent);
+
+        // ANR reporter is supposed to ignore duplicate ANRs.
+        // This will be checked later when we look for ping files.
+        mAsserter.info("Triggering second ANR", null);
+        testContext.sendBroadcast(new Intent(anrIntent));
+
+        mAsserter.info("Waiting for ping", null);
+        waitForCondition(new Condition() {
+            @Override
+            public boolean isSatisfied() {
+                final String[] newFiles = pingDir.list();
+                return newFiles != null && newFiles.length > 0;
+            }
+        }, WAIT_FOR_PING_TIMEOUT);
+
+        mAsserter.ok(pingDir.exists(), "Ping directory exists", null);
+        mAsserter.ok(pingDir.isDirectory(), "Ping directory is a directory", null);
+
+        final File[] newFiles = pingDir.listFiles();
+        mAsserter.isnot(newFiles, null, "Ping directory is not empty");
+        mAsserter.is(newFiles.length, 1, "ANR reporter wrote one ping");
+        mAsserter.ok(newFiles[0].exists(), "Ping exists", null);
+        mAsserter.ok(newFiles[0].isFile(), "Ping is a file", null);
+        mAsserter.ok(newFiles[0].canRead(), "Ping is readable", null);
+        mAsserter.info("Found ping file", newFiles[0].getPath());
+
+        final char[] buffer = new char[(int) newFiles[0].length()];
+        final FileReader reader = new FileReader(newFiles[0]);
+        try {
+            mAsserter.ok(reader.read(buffer) > 0, "Read from ping file", null);
+        } finally {
+            reader.close();
+        }
+
+        // Check standard properties required by Telemetry server.
+        final JSONObject pingObject = new JSONObject(new String(buffer));
+        mAsserter.ok(pingObject.has("slug"), "Ping has slug property", null);
+        mAsserter.ok(pingObject.has("reason"), "Ping has reason property", null);
+        mAsserter.ok(pingObject.has("payload"), "Ping has payload property", null);
+
+        final JSONObject pingPayload = pingObject.getJSONObject("payload");
+        mAsserter.ok(pingPayload.has("ver"), "Payload has ver property", null);
+        mAsserter.ok(pingPayload.has("info"), "Payload has info property", null);
+        mAsserter.ok(pingPayload.has("androidANR"), "Payload has androidANR property", null);
+
+        final JSONObject pingInfo = pingPayload.getJSONObject("info");
+        mAsserter.ok(pingInfo.has("reason"), "Info has reason property", null);
+        mAsserter.ok(pingInfo.has("appName"), "Info has appName property", null);
+        mAsserter.ok(pingInfo.has("appUpdateChannel"), "Info has appUpdateChannel property", null);
+        mAsserter.ok(pingInfo.has("appVersion"), "Info has appVersion property", null);
+        mAsserter.ok(pingInfo.has("appBuildID"), "Info has appBuildID property", null);
+
+        // Do some profile clean up. This is not absolutely necessary because the profile
+        // is blown away after test runs anyways, so we don't check return values here.
+        for (final File ping : newFiles) {
+            ping.delete();
+        }
+        pingDir.delete();
+
+        // Unblock UI thread
+        synchronized (uiBlocker) {
+            mDone = true;
+            uiBlocker.notify();
+        }
+
+        // Clear the sample ANR
+        final FileWriter anrClearer = new FileWriter(anrFile);
+        anrClearer.close();
+    }
+}