| // Copyright 2016 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.components.minidump_uploader; |
| |
| import android.support.test.filters.SmallTest; |
| |
| import org.chromium.base.annotations.SuppressFBWarnings; |
| import org.chromium.base.test.util.Feature; |
| import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager; |
| import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactory; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.HttpURLConnection; |
| import java.net.URL; |
| |
| /** |
| * Unittests for {@link MinidumpUploadCallable}. |
| */ |
| public class MinidumpUploadCallableTest extends CrashTestCase { |
| private static final String BOUNDARY = "TESTBOUNDARY"; |
| private static final String CRASH_ID = "IMACRASHID"; |
| private static final String LOG_FILE_NAME = "chromium_renderer-123_log.dmp224"; |
| private File mTestUpload; |
| private File mUploadLog; |
| private File mExpectedFileAfterUpload; |
| |
| /** |
| * A HttpURLConnection that performs some basic checks to ensure we are uploading |
| * minidumps correctly. |
| */ |
| public static class TestHttpURLConnection extends HttpURLConnection { |
| static final String DEFAULT_EXPECTED_CONTENT_TYPE = |
| String.format(MinidumpUploadCallable.CONTENT_TYPE_TMPL, BOUNDARY); |
| private final String mExpectedContentType; |
| |
| /** |
| * The value of the "Content-Type" property if the property has been set. |
| */ |
| private String mContentTypePropertyValue = ""; |
| |
| public TestHttpURLConnection(URL url) { |
| this(url, DEFAULT_EXPECTED_CONTENT_TYPE); |
| } |
| |
| public TestHttpURLConnection(URL url, String contentType) { |
| super(url); |
| mExpectedContentType = contentType; |
| assertEquals(MinidumpUploadCallable.CRASH_URL_STRING, url.toString()); |
| } |
| |
| @Override |
| public void disconnect() { |
| // Check that the "Content-Type" property has been set and the property's value. |
| assertEquals(mExpectedContentType, mContentTypePropertyValue); |
| } |
| |
| @Override |
| public InputStream getInputStream() { |
| return new ByteArrayInputStream(CRASH_ID.getBytes()); |
| } |
| |
| @Override |
| public OutputStream getOutputStream() throws IOException { |
| return new ByteArrayOutputStream(); |
| } |
| |
| @Override |
| public int getResponseCode() { |
| return 200; |
| } |
| |
| @Override |
| public String getResponseMessage() { |
| return null; |
| } |
| |
| @Override |
| public boolean usingProxy() { |
| return false; |
| } |
| |
| @Override |
| public void connect() { |
| } |
| |
| @Override |
| public void setRequestProperty(String key, String value) { |
| if (key.equals("Content-Type")) { |
| mContentTypePropertyValue = value; |
| } |
| } |
| } |
| |
| /** |
| * A HttpURLConnectionFactory that performs some basic checks to ensure we are uploading |
| * minidumps correctly. |
| */ |
| public static class TestHttpURLConnectionFactory implements HttpURLConnectionFactory { |
| String mContentType; |
| |
| public TestHttpURLConnectionFactory() { |
| mContentType = TestHttpURLConnection.DEFAULT_EXPECTED_CONTENT_TYPE; |
| } |
| |
| @Override |
| public HttpURLConnection createHttpURLConnection(String url) { |
| try { |
| return new TestHttpURLConnection(new URL(url), mContentType); |
| } catch (IOException e) { |
| return null; |
| } |
| } |
| } |
| |
| private static class ErrorCodeHttpUrlConnectionFactory implements HttpURLConnectionFactory { |
| private final int mErrorCode; |
| |
| ErrorCodeHttpUrlConnectionFactory(int errorCode) { |
| mErrorCode = errorCode; |
| } |
| |
| @Override |
| public HttpURLConnection createHttpURLConnection(String url) { |
| try { |
| return new TestHttpURLConnection(new URL(url)) { |
| @Override |
| public int getResponseCode() { |
| return mErrorCode; |
| } |
| }; |
| } catch (IOException e) { |
| return null; |
| } |
| } |
| } |
| |
| private static class FailHttpURLConnectionFactory implements HttpURLConnectionFactory { |
| @Override |
| public HttpURLConnection createHttpURLConnection(String url) { |
| fail(); |
| return null; |
| } |
| } |
| |
| /** |
| * This class calls |getInstrumentation| which cannot be done in a static context. |
| */ |
| private class MockMinidumpUploadCallable extends MinidumpUploadCallable { |
| MockMinidumpUploadCallable( |
| HttpURLConnectionFactory httpURLConnectionFactory, |
| CrashReportingPermissionManager permManager) { |
| super(mTestUpload, mUploadLog, httpURLConnectionFactory, permManager); |
| } |
| } |
| |
| private void createMinidumpFile() throws Exception { |
| mTestUpload = new File(mCrashDir, LOG_FILE_NAME); |
| setUpMinidumpFile(mTestUpload, BOUNDARY); |
| } |
| |
| @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") |
| private void setForcedUpload() throws Exception { |
| File renamed = new File(mCrashDir, mTestUpload.getName().replace(".dmp", ".forced")); |
| mTestUpload.renameTo(renamed); |
| // Update the filename that tests will refer to. |
| mTestUpload = renamed; |
| } |
| |
| @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mUploadLog = new File(mCrashDir, CrashFileManager.CRASH_DUMP_LOGFILE); |
| // Delete all logs from previous runs if possible. |
| mUploadLog.delete(); |
| |
| // Any created files will be cleaned up as part of CrashTestCase::tearDown(). |
| createMinidumpFile(); |
| mExpectedFileAfterUpload = |
| new File(mCrashDir, mTestUpload.getName().replace(".dmp", ".up")); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCallWhenCurrentlyPermitted() throws Exception { |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { |
| mIsInSample = true; |
| mIsUserPermitted = true; |
| mIsNetworkAvailable = true; |
| mIsEnabledForTests = false; |
| } |
| }; |
| |
| HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| assertEquals(MinidumpUploadCallable.UPLOAD_SUCCESS, |
| minidumpUploadCallable.call().intValue()); |
| assertTrue(mExpectedFileAfterUpload.exists()); |
| assertValidUploadLogEntry(); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCallNotPermittedByUser() throws Exception { |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { |
| mIsInSample = true; |
| mIsUserPermitted = false; |
| mIsNetworkAvailable = true; |
| mIsEnabledForTests = false; |
| } |
| }; |
| |
| HttpURLConnectionFactory httpURLConnectionFactory = new FailHttpURLConnectionFactory(); |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| assertEquals(MinidumpUploadCallable.UPLOAD_USER_DISABLED, |
| minidumpUploadCallable.call().intValue()); |
| |
| File expectedSkippedFileAfterUpload = |
| new File(mCrashDir, mTestUpload.getName().replace(".dmp", ".skipped")); |
| assertTrue(expectedSkippedFileAfterUpload.exists()); |
| assertFalse(mExpectedFileAfterUpload.exists()); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCallPermittedButNotInSample() throws Exception { |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { |
| mIsInSample = false; |
| mIsUserPermitted = true; |
| mIsNetworkAvailable = true; |
| mIsEnabledForTests = false; |
| } |
| }; |
| |
| HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| assertEquals(MinidumpUploadCallable.UPLOAD_DISABLED_BY_SAMPLING, |
| minidumpUploadCallable.call().intValue()); |
| |
| File expectedSkippedFileAfterUpload = |
| new File(mCrashDir, mTestUpload.getName().replace(".dmp", ".skipped")); |
| assertTrue(expectedSkippedFileAfterUpload.exists()); |
| assertFalse(mExpectedFileAfterUpload.exists()); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCallPermittedButNotUnderCurrentCircumstances() throws Exception { |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { |
| mIsInSample = true; |
| mIsUserPermitted = true; |
| mIsNetworkAvailable = false; |
| mIsEnabledForTests = false; |
| } |
| }; |
| |
| HttpURLConnectionFactory httpURLConnectionFactory = new FailHttpURLConnectionFactory(); |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| assertEquals(MinidumpUploadCallable.UPLOAD_FAILURE, |
| minidumpUploadCallable.call().intValue()); |
| assertFalse(mExpectedFileAfterUpload.exists()); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCrashUploadEnabledForTestsDespiteConstraints() throws Exception { |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { |
| mIsInSample = true; |
| mIsUserPermitted = false; |
| mIsNetworkAvailable = false; |
| mIsEnabledForTests = true; |
| } |
| }; |
| |
| HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| assertEquals(MinidumpUploadCallable.UPLOAD_SUCCESS, |
| minidumpUploadCallable.call().intValue()); |
| assertTrue(mExpectedFileAfterUpload.exists()); |
| assertValidUploadLogEntry(); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCallWhenCurrentlyPermitted_ForcedUpload() throws Exception { |
| setForcedUpload(); |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { |
| mIsInSample = true; |
| mIsUserPermitted = true; |
| mIsNetworkAvailable = true; |
| mIsEnabledForTests = false; |
| } |
| }; |
| |
| HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| assertEquals( |
| MinidumpUploadCallable.UPLOAD_SUCCESS, minidumpUploadCallable.call().intValue()); |
| assertTrue(mExpectedFileAfterUpload.exists()); |
| assertValidUploadLogEntry(); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCallNotPermittedByUser_ForcedUpload() throws Exception { |
| setForcedUpload(); |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { |
| mIsInSample = true; |
| mIsUserPermitted = false; |
| mIsNetworkAvailable = true; |
| mIsEnabledForTests = false; |
| } |
| }; |
| |
| HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| assertEquals( |
| MinidumpUploadCallable.UPLOAD_SUCCESS, minidumpUploadCallable.call().intValue()); |
| |
| File expectedSkippedFileAfterUpload = |
| new File(mCrashDir, mTestUpload.getName().replace(".forced", ".skipped")); |
| assertFalse(expectedSkippedFileAfterUpload.exists()); |
| assertTrue(mExpectedFileAfterUpload.exists()); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCallPermittedButNotInSample_ForcedUpload() throws Exception { |
| setForcedUpload(); |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { |
| mIsInSample = false; |
| mIsUserPermitted = true; |
| mIsNetworkAvailable = true; |
| mIsEnabledForTests = false; |
| } |
| }; |
| |
| HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| assertEquals( |
| MinidumpUploadCallable.UPLOAD_SUCCESS, minidumpUploadCallable.call().intValue()); |
| |
| File expectedSkippedFileAfterUpload = |
| new File(mCrashDir, mTestUpload.getName().replace(".forced", ".skipped")); |
| assertFalse(expectedSkippedFileAfterUpload.exists()); |
| assertTrue(mExpectedFileAfterUpload.exists()); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCallPermittedButNotUnderCurrentCircumstances_ForcedUpload() throws Exception { |
| setForcedUpload(); |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { |
| mIsInSample = true; |
| mIsUserPermitted = true; |
| mIsNetworkAvailable = false; |
| mIsEnabledForTests = false; |
| } |
| }; |
| |
| HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| assertEquals( |
| MinidumpUploadCallable.UPLOAD_SUCCESS, minidumpUploadCallable.call().intValue()); |
| |
| File expectedSkippedFileAfterUpload = |
| new File(mCrashDir, mTestUpload.getName().replace(".forced", ".skipped")); |
| assertFalse(expectedSkippedFileAfterUpload.exists()); |
| assertTrue(mExpectedFileAfterUpload.exists()); |
| } |
| |
| // This is a regression test for http://crbug.com/712420 |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCallWithInvalidMinidumpBoundary() throws Exception { |
| // Include an invalid character, '[', in the test string. |
| setUpMinidumpFile(mTestUpload, "--InvalidBoundaryWithSpecialCharacter--["); |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { mIsEnabledForTests = true; } |
| }; |
| HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory() { |
| { mContentType = ""; } |
| }; |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| |
| assertEquals( |
| MinidumpUploadCallable.UPLOAD_FAILURE, minidumpUploadCallable.call().intValue()); |
| assertFalse(mExpectedFileAfterUpload.exists()); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testCallWithValidMinidumpBoundary() throws Exception { |
| // Include all valid characters in the test string. |
| final String boundary = "--0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; |
| final String expectedContentType = |
| String.format(MinidumpUploadCallable.CONTENT_TYPE_TMPL, boundary); |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { mIsEnabledForTests = true; } |
| }; |
| HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory() { |
| { mContentType = expectedContentType; } |
| }; |
| |
| setUpMinidumpFile(mTestUpload, boundary); |
| |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| |
| assertEquals( |
| MinidumpUploadCallable.UPLOAD_SUCCESS, minidumpUploadCallable.call().intValue()); |
| assertTrue(mExpectedFileAfterUpload.exists()); |
| assertValidUploadLogEntry(); |
| } |
| |
| @SmallTest |
| @Feature({"Android-AppBase"}) |
| public void testReceivingErrorCodes() throws Exception { |
| CrashReportingPermissionManager testPermManager = |
| new MockCrashReportingPermissionManager() { |
| { |
| mIsInSample = true; |
| mIsUserPermitted = true; |
| mIsNetworkAvailable = true; |
| mIsEnabledForTests = false; |
| } |
| }; |
| |
| final int[] errorCodes = {400, 401, 403, 404, 500}; |
| |
| for (int n = 0; n < errorCodes.length; n++) { |
| HttpURLConnectionFactory httpURLConnectionFactory = |
| new ErrorCodeHttpUrlConnectionFactory(errorCodes[n]); |
| MinidumpUploadCallable minidumpUploadCallable = |
| new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); |
| assertEquals(MinidumpUploadCallable.UPLOAD_FAILURE, |
| minidumpUploadCallable.call().intValue()); |
| // Note that mTestUpload is not renamed on failure - so we can try to upload that file |
| // several times during the same test. |
| } |
| } |
| |
| private void assertValidUploadLogEntry() throws IOException { |
| File logfile = new File(mCrashDir, CrashFileManager.CRASH_DUMP_LOGFILE); |
| BufferedReader input = new BufferedReader(new FileReader(logfile)); |
| String line = null; |
| String lastEntry = null; |
| while ((line = input.readLine()) != null) { |
| lastEntry = line; |
| } |
| input.close(); |
| |
| assertNotNull("We do not have a single entry in uploads.log", lastEntry); |
| int seperator = lastEntry.indexOf(','); |
| |
| long time = Long.parseLong(lastEntry.substring(0, seperator)); |
| long now = System.currentTimeMillis() / 1000; // Timestamp was in seconds. |
| |
| // Sanity check on the time stamp (within an hour). |
| // Chances are the write and the check should have less than 1 second in between. |
| assertTrue(time <= now); |
| assertTrue(time > now - 60 * 60); |
| |
| String id = lastEntry.substring(seperator + 1, lastEntry.length()); |
| assertEquals(id, CRASH_ID); |
| } |
| } |