Index: chrome/android/javatests/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceTest.java |
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceTest.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2a55bbe8fad72e714653c09fce3f6707753947ae |
--- /dev/null |
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceTest.java |
@@ -0,0 +1,395 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.crash; |
+ |
+import android.content.ComponentName; |
+import android.content.Context; |
+import android.content.Intent; |
+import android.os.Handler; |
+import android.os.HandlerThread; |
+import android.test.suitebuilder.annotation.SmallTest; |
+ |
+import org.chromium.base.annotations.SuppressFBWarnings; |
+import org.chromium.base.test.util.AdvancedMockContext; |
+import org.chromium.base.test.util.Feature; |
+import org.chromium.content.browser.test.util.Criteria; |
+import org.chromium.content.browser.test.util.CriteriaHelper; |
+import org.chromium.net.NetworkChangeNotifier; |
+ |
+import java.io.File; |
+import java.io.IOException; |
+import java.util.ArrayList; |
+import java.util.List; |
+import java.util.concurrent.atomic.AtomicInteger; |
+ |
+/** |
+ * Testcase for {@link MinidumpUploadService}. |
+ */ |
+public class MinidumpUploadServiceTest extends CrashTestCase { |
+ |
+ private static final int CHECK_INTERVAL_MS = 250; |
+ private static final int MAX_TIMEOUT_MS = 20000; |
+ private static final String BOUNDARY = "TESTBOUNDARY"; |
+ |
+ @SmallTest |
+ @Feature({"Android-AppBase"}) |
+ public void testFindAndUploadLastCrash() throws IOException { |
+ // Setup prerequisites. |
+ final AtomicInteger numServiceStarts = new AtomicInteger(0); |
+ final File minidumpFile = new File(mCrashDir, "chromium_renderer-123.dmp"); |
+ MinidumpPreparationContext context = new MinidumpPreparationContext( |
+ getInstrumentation().getTargetContext()) { |
+ @Override |
+ public ComponentName startService(Intent intentToCheck) { |
+ String filePath = |
+ intentToCheck.getStringExtra(MinidumpUploadService.FILE_TO_UPLOAD_KEY); |
+ assertEquals("Minidump path should be the absolute path", |
+ minidumpFile.getAbsolutePath(), filePath); |
+ assertEquals("Should only call service once", 1, |
+ numServiceStarts.incrementAndGet()); |
+ assertEquals("Action should be correct", |
+ MinidumpUploadService.ACTION_UPLOAD, intentToCheck.getAction()); |
+ return new ComponentName(getPackageName(), MinidumpUploadService.class.getName()); |
+ } |
+ |
+ }; |
+ MinidumpUploadService service = new TestMinidumpUploadService(context); |
+ setUpMinidumpFile(minidumpFile, BOUNDARY); |
+ |
+ // Run test. |
+ Intent findAndUploadLastCrashIntent = |
+ MinidumpUploadService.createFindAndUploadLastCrashIntent(context); |
+ service.onCreate(); |
+ service.onHandleIntent(findAndUploadLastCrashIntent); |
+ |
+ // Verify. |
+ assertTrue("Minidump file should exist", minidumpFile.isFile()); |
+ assertEquals("Should have called startService() once", 1, numServiceStarts.intValue()); |
+ } |
+ |
+ private static class TestMinidumpUploadService extends MinidumpUploadService { |
+ private TestMinidumpUploadService() {} |
+ private TestMinidumpUploadService(Context context) { |
+ attachBaseContext(context); |
+ } |
+ |
+ private void attachBaseContextLate(Context base) { |
+ super.attachBaseContext(base); |
+ } |
+ } |
+ |
+ @SmallTest |
+ @Feature({"Android-AppBase"}) |
+ public void testFindAndUploadLastCrashNoFile() { |
+ // Setup prerequisites. |
+ MinidumpPreparationContext context = new MinidumpPreparationContext( |
+ getInstrumentation().getTargetContext()) { |
+ @Override |
+ public ComponentName startService(Intent intentToCheck) { |
+ fail("Should not start service"); |
+ return new ComponentName(getPackageName(), MinidumpUploadService.class.getName()); |
+ } |
+ |
+ }; |
+ MinidumpUploadService service = new TestMinidumpUploadService(context); |
+ |
+ // Run test. |
+ Intent findAndUploadLastCrashIntent = |
+ MinidumpUploadService.createFindAndUploadLastCrashIntent(context); |
+ service.onCreate(); |
+ service.onHandleIntent(findAndUploadLastCrashIntent); |
+ |
+ // Verification is done by the test NOT failing with fail(...) in the |
+ // MinidumpPreparationContext startService(...) method. |
+ } |
+ |
+ @SmallTest |
+ @Feature({"Android-AppBase"}) |
+ public void testFindAndUploadAllCrashes() throws IOException { |
+ // Setup prerequisites. |
+ final AtomicInteger numServiceStarts = new AtomicInteger(0); |
+ final File[] minidumpFiles = { |
+ new File(mCrashDir, "chromium_renderer-111.dmp1"), |
+ new File(mCrashDir, "chromium_renderer-222.dmp2"), |
+ new File(mCrashDir, "chromium_renderer-333.dmp3"), |
+ }; |
+ MinidumpPreparationContext context = new MinidumpPreparationContext( |
+ getInstrumentation().getTargetContext()) { |
+ @Override |
+ public ComponentName startService(Intent intentToCheck) { |
+ String filePath = |
+ intentToCheck.getStringExtra(MinidumpUploadService.FILE_TO_UPLOAD_KEY); |
+ // Assuming numServicesStart value corresponds to minidumpFiles index. |
+ assertEquals("Minidump path should be the absolute path", |
+ minidumpFiles[numServiceStarts.intValue()].getAbsolutePath(), filePath); |
+ assertTrue("Should not call service more than number of files", |
+ numServiceStarts.incrementAndGet() <= minidumpFiles.length); |
+ assertEquals("Action should be correct", |
+ MinidumpUploadService.ACTION_UPLOAD, intentToCheck.getAction()); |
+ return new ComponentName(getPackageName(), MinidumpUploadService.class.getName()); |
+ } |
+ |
+ }; |
+ MinidumpUploadService service = new TestMinidumpUploadService(context); |
+ for (File minidumpFile : minidumpFiles) { |
+ setUpMinidumpFile(minidumpFile, BOUNDARY); |
+ } |
+ |
+ // Run test. |
+ Intent findAndUploadAllCrashesIntent = |
+ MinidumpUploadService.createFindAndUploadAllCrashesIntent(context); |
+ service.onCreate(); |
+ service.onHandleIntent(findAndUploadAllCrashesIntent); |
+ |
+ // Verify. |
+ for (File minidumpFile : minidumpFiles) { |
+ assertTrue("Minidump file should exist: " + minidumpFile, minidumpFile.isFile()); |
+ } |
+ assertEquals("Should have called startService() same number of times as there are files", |
+ minidumpFiles.length, numServiceStarts.intValue()); |
+ } |
+ |
+ @SmallTest |
+ @Feature({"Android-AppBase"}) |
+ public void testUploadCrash() throws IOException, InterruptedException { |
+ List<CountedMinidumpUploadCallable> callables = |
+ new ArrayList<CountedMinidumpUploadCallable>(); |
+ callables.add(new CountedMinidumpUploadCallable( |
+ "chromium_renderer-111.dmp1", true, false)); |
+ runUploadCrashTest(callables); |
+ } |
+ |
+ @SmallTest |
+ @Feature({"Android-AppBase"}) |
+ public void testUploadCrashWithThreeFails() throws IOException, InterruptedException { |
+ // Create |MAX_TRIES_ALLOWED| callables. |
+ final List<CountedMinidumpUploadCallable> callables = |
+ new ArrayList<CountedMinidumpUploadCallable>(); |
+ for (int i = 0; i < MinidumpUploadService.MAX_TRIES_ALLOWED; i++) { |
+ callables.add(new CountedMinidumpUploadCallable( |
+ "chromium_renderer-111.dmp1" + (i > 0 ? ".try" + i : "") , false, true)); |
+ } |
+ runUploadCrashTest(callables); |
+ } |
+ |
+ @SmallTest |
+ @Feature({"Android-AppBase"}) |
+ public void testUploadCrashWithOneFailWithNetwork() throws IOException, InterruptedException { |
+ List<CountedMinidumpUploadCallable> callables = |
+ new ArrayList<CountedMinidumpUploadCallable>(); |
+ callables.add(new CountedMinidumpUploadCallable( |
+ "chromium_renderer-111.dmp1", false, true)); |
+ callables.add(new CountedMinidumpUploadCallable( |
+ "chromium_renderer-111.dmp1.try1", true, true)); |
+ runUploadCrashTest(callables); |
+ } |
+ |
+ @SmallTest |
+ @Feature({"Android-AppBase"}) |
+ public void testUploadCrashWithOneFailNoNetwork() throws IOException, InterruptedException { |
+ List<CountedMinidumpUploadCallable> callables = |
+ new ArrayList<CountedMinidumpUploadCallable>(); |
+ callables.add(new CountedMinidumpUploadCallable( |
+ "chromium_renderer-111.dmp1", false, false)); |
+ runUploadCrashTest(callables); |
+ } |
+ |
+ @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") |
+ private void runUploadCrashTest(final List<CountedMinidumpUploadCallable> callables) |
+ throws IOException, InterruptedException { |
+ // Setup prerequisites. |
+ // This version of the service overrides the createMinidumpUploadCallable(...) to be able |
+ // to return fake ones. It also ensures that the service never tries to create a callable |
+ // too many times. |
+ TestMinidumpUploadService service = new TestMinidumpUploadService() { |
+ int mIndex = 0; |
+ boolean mTriggerNetworkChange = false; |
+ |
+ @Override |
+ MinidumpUploadCallable createMinidumpUploadCallable(File minidumpFile, File logfile) { |
+ if (mIndex >= callables.size()) { |
+ fail("Should not create callable number " + mIndex); |
+ } |
+ CountedMinidumpUploadCallable callable = callables.get(mIndex++); |
+ if (callable.mTriggerNetworkChange) { |
+ mTriggerNetworkChange = true; |
+ } |
+ return callable; |
+ } |
+ |
+ @Override |
+ protected void onHandleIntent(Intent intent) { |
+ try { |
+ runTestOnUiThread(new Runnable() { |
+ @Override |
+ public void run() { |
+ // Set up basically a fake. |
+ if (!NetworkChangeNotifier.isInitialized()) { |
+ NetworkChangeNotifier.init(getApplicationContext()); |
+ } |
+ } |
+ }); |
+ } catch (Throwable t) { |
+ t.printStackTrace(); |
+ fail("Failed to set up NetworkChangeNotifier"); |
+ } |
+ |
+ super.onHandleIntent(intent); |
+ |
+ if (mTriggerNetworkChange) { |
+ mTriggerNetworkChange = false; |
+ try { |
+ runTestOnUiThread(new Runnable() { |
+ @Override |
+ public void run() { |
+ NetworkChangeNotifier.setAutoDetectConnectivityState(false); |
+ // Quickly force the state to be connected and back to disconnected. |
+ // An event should be triggered for retry logics. |
+ NetworkChangeNotifier.forceConnectivityState(false); |
+ NetworkChangeNotifier.forceConnectivityState(true); |
+ } |
+ }); |
+ } catch (Throwable t) { |
+ t.printStackTrace(); |
+ fail("Failed to trigger NetworkChangeNotifier"); |
+ } |
+ } |
+ } |
+ }; |
+ // Create a context that supports call to startService(...), where it runs the new service |
+ // calls on a handler thread. We pass in the MinidumpUploadService as an argument so we |
+ // can call it directly without going through the Android framework. |
+ final MinidumpPreparationContext context = new MinidumpPreparationContext( |
+ getInstrumentation().getTargetContext(), service) { |
+ Handler mHandler; |
+ { |
+ HandlerThread handlerThread = |
+ new HandlerThread("MinidumpUploadServiceTest Handler Thread"); |
+ handlerThread.start(); |
+ mHandler = new Handler(handlerThread.getLooper()); |
+ } |
+ |
+ @Override |
+ public ComponentName startService(final Intent intentToCheck) { |
+ assertTrue(MinidumpUploadService.ACTION_UPLOAD.equals(intentToCheck.getAction()) |
+ || MinidumpUploadService.ACTION_FIND_ALL.equals(intentToCheck.getAction())); |
+ // Post to the handler thread to run the retry intent. |
+ mHandler.post(new Runnable() { |
+ @Override |
+ public void run() { |
+ mService.onHandleIntent(intentToCheck); |
+ } |
+ }); |
+ return new ComponentName(getPackageName(), MinidumpUploadService.class.getName()); |
+ } |
+ |
+ }; |
+ // We need the context before we can attach it to the service, so since Context is |
+ // dependent on the service, we do this after context creation. |
+ service.attachBaseContextLate(context); |
+ // Create the file used for uploading. |
+ File minidumpFile = new File(mCrashDir, "chromium_renderer-111.dmp1"); |
+ minidumpFile.createNewFile(); |
+ File logfile = new File(mCacheDir, CrashFileManager.CRASH_DUMP_LOGFILE); |
+ setUpMinidumpFile(minidumpFile, BOUNDARY); |
+ |
+ // Run test. |
+ Intent uploadIntent = |
+ MinidumpUploadService.createUploadIntent(context, minidumpFile, logfile); |
+ service.onCreate(); |
+ service.onHandleIntent(uploadIntent); |
+ |
+ // Verify asynchronously. |
+ assertTrue("All callables should have a call-count of 1", |
+ CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ for (CountedMinidumpUploadCallable callable : callables) { |
+ if (callable.mCalledCount != 1) { |
+ return false; |
+ } |
+ } |
+ return true; |
+ } |
+ }, MAX_TIMEOUT_MS, CHECK_INTERVAL_MS)); |
+ } |
+ |
+ /** |
+ * This tests how we instantiate crash uploading from Main, so it is just a sanity check. |
+ */ |
+ @SmallTest |
+ @Feature({"Android-AppBase"}) |
+ public void testTryUploadAllCrashDumpsHelperMethod() { |
+ // Setup prerequisites. |
+ final String startServiceFlag = "startServiceFlag"; |
+ MinidumpPreparationContext context = new MinidumpPreparationContext( |
+ getInstrumentation().getTargetContext()) { |
+ @Override |
+ public ComponentName startService(Intent intentToCheck) { |
+ assertEquals(MinidumpUploadService.ACTION_FIND_ALL, intentToCheck.getAction()); |
+ setFlag(startServiceFlag); |
+ return new ComponentName(getPackageName(), MinidumpUploadService.class.getName()); |
+ } |
+ }; |
+ |
+ // Run test. |
+ MinidumpUploadService.tryUploadAllCrashDumps(context); |
+ |
+ // Verify. |
+ assertTrue("Should have called startService(...)", context.isFlagSet(startServiceFlag)); |
+ } |
+ |
+ private class MinidumpPreparationContext extends AdvancedMockContext { |
+ /** |
+ * Field used in overridden versions of startService() so we can support retries. |
+ */ |
+ protected MinidumpUploadService mService; |
+ |
+ public MinidumpPreparationContext(Context targetContext) { |
+ this(targetContext, null); |
+ } |
+ |
+ public MinidumpPreparationContext(Context targetContext, MinidumpUploadService service) { |
+ super(targetContext); |
+ mService = service; |
+ } |
+ |
+ @Override |
+ public File getCacheDir() { |
+ return mCacheDir; |
+ } |
+ } |
+ |
+ /** |
+ * A fake callable, that just counts the number of times it is called. |
+ * |
+ * It can be constructed with the wanted return-value of the call()-method. |
+ */ |
+ private static class CountedMinidumpUploadCallable extends MinidumpUploadCallable { |
+ private int mCalledCount; |
+ private final boolean mResult; |
+ private final boolean mTriggerNetworkChange; |
+ |
+ /** |
+ * Creates a fake callable, that just counts the number of times it is called. |
+ * |
+ * @param result the value to return from the call()-method. |
+ * @param networkChange Should trigger a network change after this callable is finished. |
+ * This essentially triggers a retry if result is set to fail. |
+ */ |
+ private CountedMinidumpUploadCallable( |
+ String fileName, boolean result, boolean networkChange) { |
+ super(new File(fileName), null, null, null, null); |
+ this.mResult = result; |
+ this.mTriggerNetworkChange = networkChange; |
+ } |
+ |
+ @Override |
+ public Boolean call() { |
+ ++mCalledCount; |
+ return mResult; |
+ } |
+ } |
+} |