|
1 package org.mozilla.gecko.tests; |
|
2 |
|
3 import org.mozilla.gecko.AppConstants; |
|
4 |
|
5 import android.content.Context; |
|
6 import android.content.Intent; |
|
7 |
|
8 import com.jayway.android.robotium.solo.Condition; |
|
9 |
|
10 import java.io.File; |
|
11 import java.io.FileReader; |
|
12 import java.io.FileWriter; |
|
13 |
|
14 import org.json.JSONObject; |
|
15 |
|
16 /** |
|
17 * Tests the proper operation of the ANR reporter. |
|
18 */ |
|
19 public class testANRReporter extends BaseTest { |
|
20 |
|
21 private static final String ANR_ACTION = "android.intent.action.ANR"; |
|
22 private static final String PING_DIR = "saved-telemetry-pings"; |
|
23 private static final int WAIT_FOR_PING_TIMEOUT = 10000; |
|
24 private static final String ANR_PATH = "/data/anr/traces.txt"; |
|
25 private static final String SAMPLE_ANR |
|
26 = "----- pid 1 at 2014-01-15 18:55:51 -----\n" |
|
27 + "Cmd line: " + AppConstants.ANDROID_PACKAGE_NAME + "\n" |
|
28 + "\n" |
|
29 + "JNI: CheckJNI is off; workarounds are off; pins=0; globals=397\n" |
|
30 + "\n" |
|
31 + "DALVIK THREADS:\n" |
|
32 + "(mutexes: tll=0 tsl=0 tscl=0 ghl=0)\n" |
|
33 + "\n" |
|
34 + "\"main\" prio=5 tid=1 WAIT\n" |
|
35 + " | group=\"main\" sCount=1 dsCount=0 obj=0x41d6bc90 self=0x41d5a3c8\n" |
|
36 + " | sysTid=3485 nice=0 sched=0/0 cgrp=apps handle=1074852180\n" |
|
37 + " | state=S schedstat=( 0 0 0 ) utm=1065 stm=152 core=0\n" |
|
38 + " at java.lang.Object.wait(Native Method)\n" |
|
39 + " - waiting on <0x427ab340> (a org.mozilla.gecko.GeckoEditable$5)\n" |
|
40 + " at java.lang.Object.wait(Object.java:364)\n" |
|
41 + " at org.mozilla.gecko.GeckoEditable$5.run(GeckoEditable.java:746)\n" |
|
42 + " at android.os.Handler.handleCallback(Handler.java:733)\n" |
|
43 + " at android.os.Handler.dispatchMessage(Handler.java:95)\n" |
|
44 + " at android.os.Looper.loop(Looper.java:137)\n" |
|
45 + " at android.app.ActivityThread.main(ActivityThread.java:4998)\n" |
|
46 + " at java.lang.reflect.Method.invokeNative(Native Method)\n" |
|
47 + " at java.lang.reflect.Method.invoke(Method.java:515)\n" |
|
48 + " at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)\n" |
|
49 + " at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)\n" |
|
50 + " at dalvik.system.NativeStart.main(Native Method)\n" |
|
51 + "\n" |
|
52 + "\"Gecko\" prio=5 tid=16 SUSPENDED\n" |
|
53 + " | group=\"main\" sCount=1 dsCount=0 obj=0x426e2b28 self=0x76ae92e8\n" |
|
54 + " | sysTid=3541 nice=0 sched=0/0 cgrp=apps handle=1991153472\n" |
|
55 + " | state=S schedstat=( 0 0 0 ) utm=1118 stm=145 core=0\n" |
|
56 + " #00 pc 00000904 /system/lib/libc.so (__futex_syscall3+4294832136)\n" |
|
57 + " #01 pc 0000eec4 /system/lib/libc.so (__pthread_cond_timedwait_relative+48)\n" |
|
58 + " #02 pc 0000ef24 /system/lib/libc.so (__pthread_cond_timedwait+64)\n" |
|
59 + " #03 pc 000536b7 /system/lib/libdvm.so\n" |
|
60 + " #04 pc 00053c79 /system/lib/libdvm.so (dvmChangeStatus(Thread*, ThreadStatus)+34)\n" |
|
61 + " #05 pc 00049507 /system/lib/libdvm.so\n" |
|
62 + " #06 pc 0004d84b /system/lib/libdvm.so\n" |
|
63 + " #07 pc 0003f1df /dev/ashmem/libxul.so (deleted)\n" |
|
64 + " at org.mozilla.gecko.mozglue.GeckoLoader.nativeRun(Native Method)\n" |
|
65 + " at org.mozilla.gecko.GeckoAppShell.runGecko(GeckoAppShell.java:384)\n" |
|
66 + " at org.mozilla.gecko.GeckoThread.run(GeckoThread.java:177)\n" |
|
67 + "\n" |
|
68 + "----- end 1 -----\n" |
|
69 + "\n" |
|
70 + "\n" |
|
71 + "----- pid 2 at 2013-01-25 13:27:01 -----\n" |
|
72 + "Cmd line: system_server\n" |
|
73 + "\n" |
|
74 + "----- end 2 -----\n"; |
|
75 |
|
76 private boolean mDone; |
|
77 |
|
78 private JSONObject readPingFile(final File pingFile) throws Exception { |
|
79 final long fileSize = pingFile.length(); |
|
80 if (fileSize == 0 || fileSize > Integer.MAX_VALUE) { |
|
81 throw new Exception("Invalid ping file size"); |
|
82 } |
|
83 final char[] buffer = new char[(int) fileSize]; |
|
84 final FileReader reader = new FileReader(pingFile); |
|
85 try { |
|
86 final int readSize = reader.read(buffer); |
|
87 if (readSize == 0 || readSize > buffer.length) { |
|
88 throw new Exception("Invalid number of bytes read"); |
|
89 } |
|
90 } finally { |
|
91 reader.close(); |
|
92 } |
|
93 return new JSONObject(new String(buffer)); |
|
94 } |
|
95 |
|
96 public void testANRReporter() throws Exception { |
|
97 blockForGeckoReady(); |
|
98 |
|
99 // Cannot test ANR reporter if it's disabled. |
|
100 if (!AppConstants.MOZ_ANDROID_ANR_REPORTER) { |
|
101 mAsserter.ok(true, "ANR reporter is disabled", null); |
|
102 return; |
|
103 } |
|
104 |
|
105 // For the ANR reporter to work, we need to provide sample ANR traces to it. |
|
106 // Therefore, we need the ANR file to exist and writable. If not, we don't |
|
107 // have the right permissions to create the file, so we just bail. |
|
108 final File anrFile = new File(ANR_PATH); |
|
109 if (!anrFile.exists()) { |
|
110 mAsserter.ok(true, "ANR file does not exist", null); |
|
111 return; |
|
112 } |
|
113 if (!anrFile.canWrite()) { |
|
114 mAsserter.ok(true, "ANR file is not writable", null); |
|
115 return; |
|
116 } |
|
117 |
|
118 final FileWriter anrWriter = new FileWriter(anrFile); |
|
119 try { |
|
120 anrWriter.write(SAMPLE_ANR); |
|
121 } finally { |
|
122 anrWriter.close(); |
|
123 } |
|
124 |
|
125 // Block the UI thread to simulate an ANR |
|
126 final Runnable uiBlocker = new Runnable() { |
|
127 @Override |
|
128 public synchronized void run() { |
|
129 while (!mDone) { |
|
130 try { |
|
131 wait(); |
|
132 } catch (final InterruptedException e) { |
|
133 } |
|
134 } |
|
135 } |
|
136 }; |
|
137 getActivity().runOnUiThread(uiBlocker); |
|
138 |
|
139 // Make sure our initial ping directory is empty. |
|
140 final File pingDir = new File(mProfile, PING_DIR); |
|
141 final String[] initialFiles = pingDir.list(); |
|
142 mAsserter.ok(initialFiles == null || initialFiles.length == 0, |
|
143 "Ping directory is empty", null); |
|
144 |
|
145 final Intent anrIntent = new Intent(ANR_ACTION); |
|
146 anrIntent.setPackage(AppConstants.ANDROID_PACKAGE_NAME); |
|
147 mAsserter.is(anrIntent.getPackage(), AppConstants.ANDROID_PACKAGE_NAME, |
|
148 "Successfully set package name"); |
|
149 |
|
150 final Context testContext = getInstrumentation().getContext(); |
|
151 mAsserter.isnot(testContext, null, "testContext should not be null"); |
|
152 |
|
153 // Trigger the ANR. |
|
154 mAsserter.info("Triggering ANR", null); |
|
155 testContext.sendBroadcast(anrIntent); |
|
156 |
|
157 // ANR reporter is supposed to ignore duplicate ANRs. |
|
158 // This will be checked later when we look for ping files. |
|
159 mAsserter.info("Triggering second ANR", null); |
|
160 testContext.sendBroadcast(new Intent(anrIntent)); |
|
161 |
|
162 mAsserter.info("Waiting for ping", null); |
|
163 waitForCondition(new Condition() { |
|
164 @Override |
|
165 public boolean isSatisfied() { |
|
166 final File[] newFiles = pingDir.listFiles(); |
|
167 if (newFiles == null || newFiles.length == 0) { |
|
168 // Keep waiting. |
|
169 return false; |
|
170 } |
|
171 // Make sure we have a complete file. We skip assertions and catch all |
|
172 // exceptions here because the condition may not be satisfied now but may |
|
173 // be satisfied later. After the wait is over, we will repeat the same |
|
174 // steps with assertions and exceptions. |
|
175 try { |
|
176 return readPingFile(newFiles[0]).has("slug"); |
|
177 } catch (final Exception e) { |
|
178 return false; |
|
179 } |
|
180 } |
|
181 }, WAIT_FOR_PING_TIMEOUT); |
|
182 |
|
183 mAsserter.ok(pingDir.exists(), "Ping directory exists", null); |
|
184 mAsserter.ok(pingDir.isDirectory(), "Ping directory is a directory", null); |
|
185 |
|
186 final File[] newFiles = pingDir.listFiles(); |
|
187 mAsserter.isnot(newFiles, null, "Ping directory is not empty"); |
|
188 mAsserter.is(newFiles.length, 1, "ANR reporter wrote one ping"); |
|
189 mAsserter.ok(newFiles[0].exists(), "Ping exists", null); |
|
190 mAsserter.ok(newFiles[0].isFile(), "Ping is a file", null); |
|
191 mAsserter.ok(newFiles[0].canRead(), "Ping is readable", null); |
|
192 mAsserter.info("Found ping file", newFiles[0].getPath()); |
|
193 |
|
194 // Check standard properties required by Telemetry server. |
|
195 final JSONObject pingObject = readPingFile(newFiles[0]); |
|
196 mAsserter.ok(pingObject.has("slug"), "Ping has slug property", null); |
|
197 mAsserter.ok(pingObject.has("reason"), "Ping has reason property", null); |
|
198 mAsserter.ok(pingObject.has("payload"), "Ping has payload property", null); |
|
199 |
|
200 final JSONObject pingPayload = pingObject.getJSONObject("payload"); |
|
201 mAsserter.ok(pingPayload.has("ver"), "Payload has ver property", null); |
|
202 mAsserter.ok(pingPayload.has("info"), "Payload has info property", null); |
|
203 mAsserter.ok(pingPayload.has("androidANR"), "Payload has androidANR property", null); |
|
204 |
|
205 final JSONObject pingInfo = pingPayload.getJSONObject("info"); |
|
206 mAsserter.ok(pingInfo.has("reason"), "Info has reason property", null); |
|
207 mAsserter.ok(pingInfo.has("appName"), "Info has appName property", null); |
|
208 mAsserter.ok(pingInfo.has("appUpdateChannel"), "Info has appUpdateChannel property", null); |
|
209 mAsserter.ok(pingInfo.has("appVersion"), "Info has appVersion property", null); |
|
210 mAsserter.ok(pingInfo.has("appBuildID"), "Info has appBuildID property", null); |
|
211 |
|
212 // Do some profile clean up. This is not absolutely necessary because the profile |
|
213 // is blown away after test runs anyways, so we don't check return values here. |
|
214 for (final File ping : newFiles) { |
|
215 ping.delete(); |
|
216 } |
|
217 pingDir.delete(); |
|
218 |
|
219 // Unblock UI thread |
|
220 synchronized (uiBlocker) { |
|
221 mDone = true; |
|
222 uiBlocker.notify(); |
|
223 } |
|
224 |
|
225 // Clear the sample ANR |
|
226 final FileWriter anrClearer = new FileWriter(anrFile); |
|
227 anrClearer.close(); |
|
228 } |
|
229 } |