Commit 0195b499 authored by Tobias Sargeant's avatar Tobias Sargeant Committed by Commit Bot

Unfork Minidump uploading code from weblayer.

This has the combined effect of breaking out the core uploading code in
MinidumpUploadCallable into MinidumpUploader, which can be used
separately. Management of file renaming still remains split over
multiple classes.

MinidumpUploadCallable tests are split to reflect the new code
organization.

Bug: 1029724
Test: run_chrome_public_test_apk \
Test: -f 'org.chromium.components.minidump_uploader.*#*'
Test: run_chrome_public_test_apk \
Test: -f 'org.chromium.chrome.browser.crash.*#*'
Test: run_webview_instrumentation_test_apk \
Test: -f 'org.chromium.android_webview.test.services.MinidumpUploadJobTest#*'
Change-Id: I0a8d7f8ad330d0127f7ce8559bd8090da6d44abe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1975918Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Reviewed-by: default avatarMugdha Lakhani <nator@chromium.org>
Commit-Queue: Tobias Sargeant <tobiasjs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#733937}
parent 0fd69f5e
...@@ -38,6 +38,7 @@ android_library("minidump_uploader_java") { ...@@ -38,6 +38,7 @@ android_library("minidump_uploader_java") {
"android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadJob.java", "android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadJob.java",
"android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadJobImpl.java", "android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadJobImpl.java",
"android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadJobService.java", "android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadJobService.java",
"android/java/src/org/chromium/components/minidump_uploader/MinidumpUploader.java",
"android/java/src/org/chromium/components/minidump_uploader/MinidumpUploaderDelegate.java", "android/java/src/org/chromium/components/minidump_uploader/MinidumpUploaderDelegate.java",
"android/java/src/org/chromium/components/minidump_uploader/util/CrashReportingPermissionManager.java", "android/java/src/org/chromium/components/minidump_uploader/util/CrashReportingPermissionManager.java",
"android/java/src/org/chromium/components/minidump_uploader/util/HttpURLConnectionFactory.java", "android/java/src/org/chromium/components/minidump_uploader/util/HttpURLConnectionFactory.java",
...@@ -62,6 +63,7 @@ android_library("minidump_uploader_javatests") { ...@@ -62,6 +63,7 @@ android_library("minidump_uploader_javatests") {
"android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadCallableTest.java", "android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadCallableTest.java",
"android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadJobImplTest.java", "android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadJobImplTest.java",
"android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadTestUtility.java", "android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadTestUtility.java",
"android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploaderTest.java",
"android/javatests/src/org/chromium/components/minidump_uploader/TestMinidumpUploadJobImpl.java", "android/javatests/src/org/chromium/components/minidump_uploader/TestMinidumpUploadJobImpl.java",
"android/javatests/src/org/chromium/components/minidump_uploader/TestMinidumpUploaderDelegate.java", "android/javatests/src/org/chromium/components/minidump_uploader/TestMinidumpUploaderDelegate.java",
] ]
......
...@@ -5,29 +5,17 @@ ...@@ -5,29 +5,17 @@
package org.chromium.components.minidump_uploader; package org.chromium.components.minidump_uploader;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.StreamUtil;
import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager; import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactory;
import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactoryImpl;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.net.HttpURLConnection;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.zip.GZIPOutputStream;
/** /**
* This class tries to upload a minidump to the crash server. * This class tries to upload a minidump to the crash server.
...@@ -42,12 +30,6 @@ public class MinidumpUploadCallable implements Callable<Integer> { ...@@ -42,12 +30,6 @@ public class MinidumpUploadCallable implements Callable<Integer> {
// "crash_dump_week_upload_size" - Deprecated prefs used for limiting crash report uploads over // "crash_dump_week_upload_size" - Deprecated prefs used for limiting crash report uploads over
// cellular network. Last used in M47, removed in M78. // cellular network. Last used in M47, removed in M78.
@VisibleForTesting
protected static final String CRASH_URL_STRING = "https://clients2.google.com/cr/report";
@VisibleForTesting
protected static final String CONTENT_TYPE_TMPL = "multipart/form-data; boundary=%s";
@IntDef({MinidumpUploadStatus.SUCCESS, MinidumpUploadStatus.FAILURE, @IntDef({MinidumpUploadStatus.SUCCESS, MinidumpUploadStatus.FAILURE,
MinidumpUploadStatus.USER_DISABLED, MinidumpUploadStatus.DISABLED_BY_SAMPLING}) MinidumpUploadStatus.USER_DISABLED, MinidumpUploadStatus.DISABLED_BY_SAMPLING})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
...@@ -60,20 +42,19 @@ public class MinidumpUploadCallable implements Callable<Integer> { ...@@ -60,20 +42,19 @@ public class MinidumpUploadCallable implements Callable<Integer> {
private final File mFileToUpload; private final File mFileToUpload;
private final File mLogfile; private final File mLogfile;
private final HttpURLConnectionFactory mHttpURLConnectionFactory;
private final CrashReportingPermissionManager mPermManager; private final CrashReportingPermissionManager mPermManager;
private final MinidumpUploader mMinidumpUploader;
public MinidumpUploadCallable( public MinidumpUploadCallable(
File fileToUpload, File logfile, CrashReportingPermissionManager permissionManager) { File fileToUpload, File logfile, CrashReportingPermissionManager permissionManager) {
this(fileToUpload, logfile, new HttpURLConnectionFactoryImpl(), permissionManager); this(fileToUpload, logfile, new MinidumpUploader(), permissionManager);
} }
public MinidumpUploadCallable(File fileToUpload, File logfile, public MinidumpUploadCallable(File fileToUpload, File logfile,
HttpURLConnectionFactory httpURLConnectionFactory, MinidumpUploader minidumpUploader, CrashReportingPermissionManager permissionManager) {
CrashReportingPermissionManager permissionManager) {
mFileToUpload = fileToUpload; mFileToUpload = fileToUpload;
mLogfile = logfile; mLogfile = logfile;
mHttpURLConnectionFactory = httpURLConnectionFactory; mMinidumpUploader = minidumpUploader;
mPermManager = permissionManager; mPermManager = permissionManager;
} }
...@@ -102,74 +83,9 @@ public class MinidumpUploadCallable implements Callable<Integer> { ...@@ -102,74 +83,9 @@ public class MinidumpUploadCallable implements Callable<Integer> {
} }
} }
HttpURLConnection connection = MinidumpUploader.Result result = mMinidumpUploader.upload(mFileToUpload);
mHttpURLConnectionFactory.createHttpURLConnection(CRASH_URL_STRING); if (result.isSuccess()) {
if (connection == null) { String uploadId = result.message();
return MinidumpUploadStatus.FAILURE;
}
FileInputStream minidumpInputStream = null;
try {
if (!configureConnectionForHttpPost(connection)) {
return MinidumpUploadStatus.FAILURE;
}
minidumpInputStream = new FileInputStream(mFileToUpload);
streamCopy(minidumpInputStream, new GZIPOutputStream(connection.getOutputStream()));
boolean success = handleExecutionResponse(connection);
return success ? MinidumpUploadStatus.SUCCESS : MinidumpUploadStatus.FAILURE;
} catch (IOException | ArrayIndexOutOfBoundsException e) {
// ArrayIndexOutOfBoundsException due to bad GZIPOutputStream implementation on some
// old sony devices.
// For now just log the stack trace.
Log.w(TAG, "Error while uploading " + mFileToUpload.getName(), e);
return MinidumpUploadStatus.FAILURE;
} finally {
connection.disconnect();
if (minidumpInputStream != null) {
StreamUtil.closeQuietly(minidumpInputStream);
}
}
}
/**
* Configures a HttpURLConnection to send a HTTP POST request for uploading the minidump.
*
* This also reads the content-type from the minidump file.
*
* @param connection the HttpURLConnection to configure
* @return true if successful.
* @throws IOException
*/
private boolean configureConnectionForHttpPost(HttpURLConnection connection)
throws IOException {
// Read the boundary which we need for the content type.
String boundary = readBoundary();
if (boundary == null) {
return false;
}
connection.setDoOutput(true);
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Content-Encoding", "gzip");
connection.setRequestProperty("Content-Type", String.format(CONTENT_TYPE_TMPL, boundary));
return true;
}
/**
* Reads the HTTP response and cleans up successful uploads.
*
* @param connection the connection to read the response from
* @return true if the upload was successful, false otherwise.
* @throws IOException
*/
private Boolean handleExecutionResponse(HttpURLConnection connection) throws IOException {
int responseCode = connection.getResponseCode();
if (isSuccessful(responseCode)) {
String responseContent = getResponseContentAsString(connection);
// The crash server returns the crash ID.
String uploadId = responseContent != null ? responseContent : "unknown";
String crashFileName = mFileToUpload.getName(); String crashFileName = mFileToUpload.getName();
Log.i(TAG, "Minidump " + crashFileName + " uploaded successfully, id: " + uploadId); Log.i(TAG, "Minidump " + crashFileName + " uploaded successfully, id: " + uploadId);
...@@ -184,18 +100,25 @@ public class MinidumpUploadCallable implements Callable<Integer> { ...@@ -184,18 +100,25 @@ public class MinidumpUploadCallable implements Callable<Integer> {
} catch (IOException ioe) { } catch (IOException ioe) {
Log.e(TAG, "Fail to write uploaded entry to log file"); Log.e(TAG, "Fail to write uploaded entry to log file");
} }
return true;
} else { return MinidumpUploadStatus.SUCCESS;
}
if (result.isUploadError()) {
// Log the results of the upload. Note that periodic upload failures aren't bad // Log the results of the upload. Note that periodic upload failures aren't bad
// because we will need to throttle uploads in the future anyway. // because we will need to throttle uploads in the future anyway.
String msg = String.format(Locale.US, "Failed to upload %s with code: %d (%s).", String msg = String.format(Locale.US, "Failed to upload %s with code: %d (%s).",
mFileToUpload.getName(), responseCode, connection.getResponseMessage()); mFileToUpload.getName(), result.errorCode(), result.message());
Log.i(TAG, msg); Log.i(TAG, msg);
// TODO(acleung): The return status informs us about why an upload might be // TODO(acleung): The return status informs us about why an upload might be
// rejected. The next logical step is to put the reasons in an UMA histogram. // rejected. The next logical step is to put the reasons in an UMA histogram.
return false; } else {
Log.e(TAG,
"Local error while uploading " + mFileToUpload.getName() + ": "
+ result.message());
} }
return MinidumpUploadStatus.FAILURE;
} }
/** /**
...@@ -228,83 +151,4 @@ public class MinidumpUploadCallable implements Callable<Integer> { ...@@ -228,83 +151,4 @@ public class MinidumpUploadCallable implements Callable<Integer> {
} }
} }
/**
* Get the boundary from the file, we need it for the content-type.
*
* @return the boundary if found, else null.
* @throws IOException
*/
private String readBoundary() throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(mFileToUpload));
String boundary = reader.readLine();
reader.close();
if (boundary == null || boundary.trim().isEmpty()) {
Log.e(TAG, "Ignoring invalid crash dump: '" + mFileToUpload + "'");
return null;
}
boundary = boundary.trim();
if (!boundary.startsWith("--") || boundary.length() < 10) {
Log.e(TAG, "Ignoring invalidly bound crash dump: '" + mFileToUpload + "'");
return null;
}
// Note: The regex allows all alphanumeric characters, as well as dashes.
// This matches the code that generates minidumps boundaries:
// https://chromium.googlesource.com/crashpad/crashpad/+/0c322ecc3f711c34fbf85b2cbe69f38b8dbccf05/util/net/http_multipart_builder.cc#36
if (!boundary.matches("^[a-zA-Z0-9-]*$")) {
Log.e(TAG,
"Ignoring invalidly bound crash dump '" + mFileToUpload
+ "' due to invalid boundary characters: '" + boundary + "'");
return null;
}
boundary = boundary.substring(2); // Remove the initial --
return boundary;
}
/**
* Returns whether the response code indicates a successful HTTP request.
*
* @param responseCode the response code
* @return true if response code indicates success, false otherwise.
*/
private static boolean isSuccessful(int responseCode) {
return responseCode == 200 || responseCode == 201 || responseCode == 202;
}
/**
* Reads the response from |connection| as a String.
*
* @param connection the connection to read the response from.
* @return the content of the response.
* @throws IOException
*/
private static String getResponseContentAsString(HttpURLConnection connection)
throws IOException {
String responseContent = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
streamCopy(connection.getInputStream(), baos);
if (baos.size() > 0) {
responseContent = baos.toString();
}
return responseContent;
}
/**
* Copies all available data from |inStream| to |outStream|. Closes both
* streams when done.
*
* @param inStream the stream to read
* @param outStream the stream to write to
* @throws IOException
*/
private static void streamCopy(InputStream inStream, OutputStream outStream)
throws IOException {
byte[] temp = new byte[4096];
int bytesRead = inStream.read(temp);
while (bytesRead >= 0) {
outStream.write(temp, 0, bytesRead);
bytesRead = inStream.read(temp);
}
inStream.close();
outStream.close();
}
} }
// Copyright 2019 The Chromium Authors. All rights reserved. // Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.weblayer_private; package org.chromium.components.minidump_uploader;
import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactory; import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactory;
import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactoryImpl; import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactoryImpl;
...@@ -25,33 +25,75 @@ import java.util.zip.GZIPOutputStream; ...@@ -25,33 +25,75 @@ import java.util.zip.GZIPOutputStream;
* boundary forms the first line of the file. * boundary forms the first line of the file.
*/ */
public class MinidumpUploader { public class MinidumpUploader {
// TODO(crbug.com/1029724) unfork this class back to //components/minidump_uploader /* package */
private static final String CRASH_URL_STRING = "https://clients2.google.com/cr/report"; static final String CRASH_URL_STRING = "https://clients2.google.com/cr/report";
private static final String CONTENT_TYPE_TMPL = "multipart/form-data; boundary=%s"; /* package */
static final String CONTENT_TYPE_TMPL = "multipart/form-data; boundary=%s";
private final HttpURLConnectionFactory mHttpURLConnectionFactory; private final HttpURLConnectionFactory mHttpURLConnectionFactory;
/* package */ static final class Result { /**
final boolean mSuccess; * The result of an upload attempt.
final int mStatus; *
final String mResult; * An upload attempt may succeed, in which case the result message is the upload ID.
* Alternatively it may fail either as a result of either a local or a remote error.
*/
public static final class Result {
private final int mErrorCode;
private final String mResult;
private Result(boolean success, int status, String result) { private Result(int errorCode, String result) {
mSuccess = success; mErrorCode = errorCode;
mStatus = status;
mResult = result; mResult = result;
} }
/** Returns true if this result represents a succesful upload. */
public boolean isSuccess() {
return mErrorCode == 0;
}
/** Returns true if this result represents a remote error. */
public boolean isUploadError() {
return mErrorCode > 0;
}
/** Returns true if this result represents a local error. */
public boolean isFailure() {
return mErrorCode < 0;
}
/**
* Returns the upload error code.
*
* @return 0 on success
* @return <0 on local error
* @return HTTP status code on remote error
*/
public int errorCode() {
return mErrorCode;
}
/**
* The message associated with this result.
*
* @return the remotely assigned upload id, on success
* @return descriptive error text otherwise.
*/
public String message() {
return mResult;
}
static Result failure(String result) { static Result failure(String result) {
return new Result(false, -1, result); return new Result(-1, result);
} }
static Result failure(int status, String result) { static Result uploadError(int status, String result) {
return new Result(false, status, result); assert status > 0;
return new Result(status, result);
} }
static Result success(String result) { static Result success(String result) {
return new Result(true, 0, result); return new Result(0, result);
} }
} }
...@@ -63,6 +105,16 @@ public class MinidumpUploader { ...@@ -63,6 +105,16 @@ public class MinidumpUploader {
mHttpURLConnectionFactory = httpURLConnectionFactory; mHttpURLConnectionFactory = httpURLConnectionFactory;
} }
/**
* Attempt to upload a single file to the crash server.
*
* The result of the upload attempt is either success (and an associated report ID), or failure.
* Failure may occur locally (the file is invalid or the network connection could not be
* created) or remotely (the crash server rejected the upload with a HTTP error).
*
* @param fileToUpload the file containing a MIME-body with an attached minidump.
* @return the success/failure result of the upload attempt.
*/
public Result upload(File fileToUpload) { public Result upload(File fileToUpload) {
try { try {
if (fileToUpload == null || !fileToUpload.exists()) { if (fileToUpload == null || !fileToUpload.exists()) {
...@@ -87,13 +139,13 @@ public class MinidumpUploader { ...@@ -87,13 +139,13 @@ public class MinidumpUploader {
return Result.success(uploadId); return Result.success(uploadId);
} else { } else {
// Return the remote error code and message. // Return the remote error code and message.
return Result.failure(responseCode, connection.getResponseMessage()); return Result.uploadError(responseCode, connection.getResponseMessage());
} }
} finally { } finally {
connection.disconnect(); connection.disconnect();
} }
} catch (IOException | RuntimeException e) { } catch (IOException | RuntimeException e) {
return Result.failure(e.getMessage()); return Result.failure(e.toString());
} }
} }
...@@ -113,28 +165,28 @@ public class MinidumpUploader { ...@@ -113,28 +165,28 @@ public class MinidumpUploader {
} }
/** /**
* Get the boundary from the file, we need it for the content-type. * Get the MIME boundary from the file, for inclusion in Content-Type header.
* *
* @return the boundary if found, else null. * @return the MIME boundary used in the file.
* @throws IOException * @throws IOException if fileToUpload cannot be read
* @throws RuntimeException if the MIME boundary is missing or malformed.
*/ */
private String readBoundary(File fileToUpload) throws IOException { private String readBoundary(File fileToUpload) throws IOException {
try (FileReader fileReader = new FileReader(fileToUpload); try (FileReader fileReader = new FileReader(fileToUpload);
BufferedReader reader = new BufferedReader(fileReader)) { BufferedReader reader = new BufferedReader(fileReader)) {
String boundary = reader.readLine(); String boundary = reader.readLine();
if (boundary == null || boundary.trim().isEmpty()) { if (boundary == null || boundary.trim().isEmpty()) {
throw new RuntimeException(fileToUpload + " does not have a MIME boundary"); throw new RuntimeException("File does not have a MIME boundary");
} }
boundary = boundary.trim(); boundary = boundary.trim();
if (!boundary.startsWith("--") || boundary.length() < 10) { if (!boundary.startsWith("--") || boundary.length() < 10) {
throw new RuntimeException(fileToUpload + " does not have a MIME boundary"); throw new RuntimeException("File does not have a MIME boundary");
} }
// Note: The regex allows all alphanumeric characters, as well as dashes. // Note: The regex allows all alphanumeric characters, as well as dashes.
// This matches the code that generates minidumps boundaries: // This matches the code that generates minidumps boundaries:
// https://chromium.googlesource.com/crashpad/crashpad/+/0c322ecc3f711c34fbf85b2cbe69f38b8dbccf05/util/net/http_multipart_builder.cc#36 // https://chromium.googlesource.com/crashpad/crashpad/+/0c322ecc3f711c34fbf85b2cbe69f38b8dbccf05/util/net/http_multipart_builder.cc#36
if (!boundary.matches("^[a-zA-Z0-9-]*$")) { if (!boundary.matches("^[a-zA-Z0-9-]*$")) {
throw new RuntimeException( throw new RuntimeException("File has an illegal MIME boundary: " + boundary);
fileToUpload.getName() + " has an illegal MIME boundary: " + boundary);
} }
boundary = boundary.substring(2); // Remove the initial -- boundary = boundary.substring(2); // Remove the initial --
return boundary; return boundary;
......
...@@ -12,173 +12,61 @@ import org.junit.Rule; ...@@ -12,173 +12,61 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.test.BaseJUnit4ClassRunner; import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Feature;
import org.chromium.components.minidump_uploader.CrashTestRule.MockCrashReportingPermissionManager; import org.chromium.components.minidump_uploader.CrashTestRule.MockCrashReportingPermissionManager;
import org.chromium.components.minidump_uploader.MinidumpUploadCallable.MinidumpUploadStatus; import org.chromium.components.minidump_uploader.MinidumpUploadCallable.MinidumpUploadStatus;
import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager; import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactory;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/** /**
* Unittests for {@link MinidumpUploadCallable}. * Unittests for {@link MinidumpUploadCallable}.
*/ */
@RunWith(BaseJUnit4ClassRunner.class) @RunWith(BaseJUnit4ClassRunner.class)
public class MinidumpUploadCallableTest { public class MinidumpUploadCallableTest {
private static final String LOCAL_CRASH_ID = "123_log";
private static final String LOG_FILE_NAME = "chromium_renderer-123_log.dmp224";
@Rule @Rule
public CrashTestRule mTestRule = new CrashTestRule(); public CrashTestRule mTestRule = new CrashTestRule();
private static final String BOUNDARY = "TESTBOUNDARY";
private static final String UPLOAD_CRASH_ID = "IMACRASHID";
private static final String LOCAL_CRASH_ID = "123_log";
private static final String LOG_FILE_NAME = "chromium_renderer-123_log.dmp224";
private File mTestUpload; private File mTestUpload;
private File mUploadLog; private File mUploadLog;
private File mExpectedFileAfterUpload; private File mExpectedFileAfterUpload;
/** private static class MockMinidumpUploader extends MinidumpUploader {
* A HttpURLConnection that performs some basic checks to ensure we are uploading private Result mMockResult;
* 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;
Assert.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.
Assert.assertEquals(mExpectedContentType, mContentTypePropertyValue);
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(ApiCompatibilityUtils.getBytesUtf8(UPLOAD_CRASH_ID));
}
@Override public static MinidumpUploader returnsSuccess() {
public OutputStream getOutputStream() { return new MockMinidumpUploader(Result.success(MinidumpUploaderTest.UPLOAD_CRASH_ID));
return new ByteArrayOutputStream();
}
@Override
public int getResponseCode() {
return 200;
} }
@Override public static MinidumpUploader returnsFailure(String message) {
public String getResponseMessage() { return new MockMinidumpUploader(Result.failure(message));
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) { public static MinidumpUploader returnsUploadError(int status, String message) {
mErrorCode = errorCode; return new MockMinidumpUploader(Result.uploadError(status, message));
} }
@Override private MockMinidumpUploader(Result mockResult) {
public HttpURLConnection createHttpURLConnection(String url) { super(null);
try { mMockResult = mockResult;
return new TestHttpURLConnection(new URL(url)) {
@Override
public int getResponseCode() {
return mErrorCode;
}
};
} catch (IOException e) {
return null;
}
} }
}
private static class FailHttpURLConnectionFactory implements HttpURLConnectionFactory {
@Override @Override
public HttpURLConnection createHttpURLConnection(String url) { public Result upload(File fileToUpload) {
Assert.fail(); return mMockResult;
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 { private void createMinidumpFile() throws Exception {
mTestUpload = new File(mTestRule.getCrashDir(), LOG_FILE_NAME); mTestUpload = new File(mTestRule.getCrashDir(), LOG_FILE_NAME);
CrashTestRule.setUpMinidumpFile(mTestUpload, BOUNDARY); CrashTestRule.setUpMinidumpFile(mTestUpload, MinidumpUploaderTest.BOUNDARY);
} }
private void setForcedUpload() { private void setForcedUpload() {
...@@ -201,6 +89,50 @@ public class MinidumpUploadCallableTest { ...@@ -201,6 +89,50 @@ public class MinidumpUploadCallableTest {
new File(mTestRule.getCrashDir(), mTestUpload.getName().replace(".dmp", ".up")); new File(mTestRule.getCrashDir(), mTestUpload.getName().replace(".dmp", ".up"));
} }
@Test
@SmallTest
@Feature({"Android-AppBase"})
public void testSuccessfulUpload() throws Exception {
final CrashReportingPermissionManager testPermManager =
new MockCrashReportingPermissionManager() {
{ mIsEnabledForTests = true; }
};
MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(
mTestUpload, mUploadLog, MockMinidumpUploader.returnsSuccess(), testPermManager);
Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue());
Assert.assertTrue(mExpectedFileAfterUpload.exists());
assertValidUploadLogEntry();
}
@Test
@SmallTest
@Feature({"Android-AppBase"})
public void testFailedUploadLocalError() throws Exception {
final CrashReportingPermissionManager testPermManager =
new MockCrashReportingPermissionManager() {
{ mIsEnabledForTests = true; }
};
MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(mTestUpload,
mUploadLog, MockMinidumpUploader.returnsFailure("Failed"), testPermManager);
Assert.assertEquals(MinidumpUploadStatus.FAILURE, minidumpUploadCallable.call().intValue());
Assert.assertFalse(mExpectedFileAfterUpload.exists());
}
@Test
@SmallTest
@Feature({"Android-AppBase"})
public void testFailedUploadRemoteError() throws Exception {
final CrashReportingPermissionManager testPermManager =
new MockCrashReportingPermissionManager() {
{ mIsEnabledForTests = true; }
};
MinidumpUploadCallable minidumpUploadCallable =
new MinidumpUploadCallable(mTestUpload, mUploadLog,
MockMinidumpUploader.returnsUploadError(404, "Not Found"), testPermManager);
Assert.assertEquals(MinidumpUploadStatus.FAILURE, minidumpUploadCallable.call().intValue());
Assert.assertFalse(mExpectedFileAfterUpload.exists());
}
@Test @Test
@SmallTest @SmallTest
@Feature({"Android-AppBase"}) @Feature({"Android-AppBase"})
...@@ -215,10 +147,8 @@ public class MinidumpUploadCallableTest { ...@@ -215,10 +147,8 @@ public class MinidumpUploadCallableTest {
} }
}; };
HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(
mTestUpload, mUploadLog, MockMinidumpUploader.returnsSuccess(), testPermManager);
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue()); Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue());
Assert.assertTrue(mExpectedFileAfterUpload.exists()); Assert.assertTrue(mExpectedFileAfterUpload.exists());
assertValidUploadLogEntry(); assertValidUploadLogEntry();
...@@ -238,10 +168,8 @@ public class MinidumpUploadCallableTest { ...@@ -238,10 +168,8 @@ public class MinidumpUploadCallableTest {
} }
}; };
HttpURLConnectionFactory httpURLConnectionFactory = new FailHttpURLConnectionFactory(); MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(
mTestUpload, mUploadLog, MockMinidumpUploader.returnsSuccess(), testPermManager);
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals( Assert.assertEquals(
MinidumpUploadStatus.USER_DISABLED, minidumpUploadCallable.call().intValue()); MinidumpUploadStatus.USER_DISABLED, minidumpUploadCallable.call().intValue());
...@@ -265,10 +193,8 @@ public class MinidumpUploadCallableTest { ...@@ -265,10 +193,8 @@ public class MinidumpUploadCallableTest {
} }
}; };
HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(
mTestUpload, mUploadLog, MockMinidumpUploader.returnsSuccess(), testPermManager);
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals(MinidumpUploadStatus.DISABLED_BY_SAMPLING, Assert.assertEquals(MinidumpUploadStatus.DISABLED_BY_SAMPLING,
minidumpUploadCallable.call().intValue()); minidumpUploadCallable.call().intValue());
...@@ -292,10 +218,8 @@ public class MinidumpUploadCallableTest { ...@@ -292,10 +218,8 @@ public class MinidumpUploadCallableTest {
} }
}; };
HttpURLConnectionFactory httpURLConnectionFactory = new FailHttpURLConnectionFactory(); MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(
mTestUpload, mUploadLog, MockMinidumpUploader.returnsSuccess(), testPermManager);
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals(MinidumpUploadStatus.FAILURE, minidumpUploadCallable.call().intValue()); Assert.assertEquals(MinidumpUploadStatus.FAILURE, minidumpUploadCallable.call().intValue());
Assert.assertFalse(mExpectedFileAfterUpload.exists()); Assert.assertFalse(mExpectedFileAfterUpload.exists());
} }
...@@ -314,10 +238,8 @@ public class MinidumpUploadCallableTest { ...@@ -314,10 +238,8 @@ public class MinidumpUploadCallableTest {
} }
}; };
HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(
mTestUpload, mUploadLog, MockMinidumpUploader.returnsSuccess(), testPermManager);
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue()); Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue());
Assert.assertTrue(mExpectedFileAfterUpload.exists()); Assert.assertTrue(mExpectedFileAfterUpload.exists());
assertValidUploadLogEntry(); assertValidUploadLogEntry();
...@@ -338,10 +260,8 @@ public class MinidumpUploadCallableTest { ...@@ -338,10 +260,8 @@ public class MinidumpUploadCallableTest {
} }
}; };
HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(
mTestUpload, mUploadLog, MockMinidumpUploader.returnsSuccess(), testPermManager);
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue()); Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue());
Assert.assertTrue(mExpectedFileAfterUpload.exists()); Assert.assertTrue(mExpectedFileAfterUpload.exists());
assertValidUploadLogEntry(); assertValidUploadLogEntry();
...@@ -362,10 +282,8 @@ public class MinidumpUploadCallableTest { ...@@ -362,10 +282,8 @@ public class MinidumpUploadCallableTest {
} }
}; };
HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(
mTestUpload, mUploadLog, MockMinidumpUploader.returnsSuccess(), testPermManager);
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue()); Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue());
File expectedSkippedFileAfterUpload = new File( File expectedSkippedFileAfterUpload = new File(
...@@ -389,10 +307,8 @@ public class MinidumpUploadCallableTest { ...@@ -389,10 +307,8 @@ public class MinidumpUploadCallableTest {
} }
}; };
HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(
mTestUpload, mUploadLog, MockMinidumpUploader.returnsSuccess(), testPermManager);
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue()); Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue());
File expectedSkippedFileAfterUpload = new File( File expectedSkippedFileAfterUpload = new File(
...@@ -416,10 +332,8 @@ public class MinidumpUploadCallableTest { ...@@ -416,10 +332,8 @@ public class MinidumpUploadCallableTest {
} }
}; };
HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory(); MinidumpUploadCallable minidumpUploadCallable = new MinidumpUploadCallable(
mTestUpload, mUploadLog, MockMinidumpUploader.returnsSuccess(), testPermManager);
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue()); Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue());
File expectedSkippedFileAfterUpload = new File( File expectedSkippedFileAfterUpload = new File(
...@@ -428,82 +342,6 @@ public class MinidumpUploadCallableTest { ...@@ -428,82 +342,6 @@ public class MinidumpUploadCallableTest {
Assert.assertTrue(mExpectedFileAfterUpload.exists()); Assert.assertTrue(mExpectedFileAfterUpload.exists());
} }
// This is a regression test for http://crbug.com/712420
@Test
@SmallTest
@Feature({"Android-AppBase"})
public void testCallWithInvalidMinidumpBoundary() throws Exception {
// Include an invalid character, '[', in the test string.
CrashTestRule.setUpMinidumpFile(mTestUpload, "--InvalidBoundaryWithSpecialCharacter--[");
CrashReportingPermissionManager testPermManager =
new MockCrashReportingPermissionManager() {
{ mIsEnabledForTests = true; }
};
HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory() {
{ mContentType = ""; }
};
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals(MinidumpUploadStatus.FAILURE, minidumpUploadCallable.call().intValue());
Assert.assertFalse(mExpectedFileAfterUpload.exists());
}
@Test
@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; }
};
CrashTestRule.setUpMinidumpFile(mTestUpload, boundary);
MinidumpUploadCallable minidumpUploadCallable =
new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager);
Assert.assertEquals(MinidumpUploadStatus.SUCCESS, minidumpUploadCallable.call().intValue());
Assert.assertTrue(mExpectedFileAfterUpload.exists());
assertValidUploadLogEntry();
}
@Test
@SmallTest
@Feature({"Android-AppBase"})
public void testReceivingErrorCodes() {
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);
Assert.assertEquals(
MinidumpUploadStatus.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 { private void assertValidUploadLogEntry() throws IOException {
File logfile = new File(mTestRule.getCrashDir(), CrashFileManager.CRASH_DUMP_LOGFILE); File logfile = new File(mTestRule.getCrashDir(), CrashFileManager.CRASH_DUMP_LOGFILE);
BufferedReader input = new BufferedReader(new FileReader(logfile)); BufferedReader input = new BufferedReader(new FileReader(logfile));
...@@ -532,7 +370,7 @@ public class MinidumpUploadCallableTest { ...@@ -532,7 +370,7 @@ public class MinidumpUploadCallableTest {
Assert.assertTrue(time <= now); Assert.assertTrue(time <= now);
Assert.assertTrue(time > now - 60 * 60); Assert.assertTrue(time > now - 60 * 60);
Assert.assertEquals(uploadId, UPLOAD_CRASH_ID); Assert.assertEquals(uploadId, MinidumpUploaderTest.UPLOAD_CRASH_ID);
Assert.assertEquals(localId, LOCAL_CRASH_ID); Assert.assertEquals(localId, LOCAL_CRASH_ID);
} }
} }
...@@ -14,6 +14,8 @@ import org.junit.runner.RunWith; ...@@ -14,6 +14,8 @@ import org.junit.runner.RunWith;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseJUnit4ClassRunner; import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.components.minidump_uploader.CrashTestRule.MockCrashReportingPermissionManager; import org.chromium.components.minidump_uploader.CrashTestRule.MockCrashReportingPermissionManager;
import org.chromium.components.minidump_uploader.MinidumpUploaderTest.TestHttpURLConnection;
import org.chromium.components.minidump_uploader.MinidumpUploaderTest.TestHttpURLConnectionFactory;
import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager; import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactory; import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactory;
...@@ -39,6 +41,31 @@ public class MinidumpUploadJobImplTest { ...@@ -39,6 +41,31 @@ public class MinidumpUploadJobImplTest {
private static final String BOUNDARY = "TESTBOUNDARY"; private static final String BOUNDARY = "TESTBOUNDARY";
/**
* Test to ensure the minidump uploading mechanism allows the expected number of upload retries.
*/
@Test
@MediumTest
public void testRetryCountRespected() throws IOException {
final CrashReportingPermissionManager permManager =
new MockCrashReportingPermissionManager() {
{
mIsInSample = true;
mIsUserPermitted = true;
mIsNetworkAvailable = false; // Will cause us to fail uploads
mIsEnabledForTests = false;
}
};
File firstFile = createMinidumpFileInCrashDir("1_abc.dmp0.try0");
for (int i = 0; i < MinidumpUploadJobImpl.MAX_UPLOAD_TRIES_ALLOWED; ++i) {
MinidumpUploadTestUtility.uploadMinidumpsSync(
new TestMinidumpUploadJobImpl(mTestRule.getExistingCacheDir(), permManager),
i + 1 < MinidumpUploadJobImpl.MAX_UPLOAD_TRIES_ALLOWED);
}
}
/** /**
* Test to ensure the minidump uploading mechanism behaves as expected when we fail to upload * Test to ensure the minidump uploading mechanism behaves as expected when we fail to upload
* minidumps. * minidumps.
...@@ -94,15 +121,15 @@ public class MinidumpUploadJobImplTest { ...@@ -94,15 +121,15 @@ public class MinidumpUploadJobImplTest {
callables.add(new MinidumpUploadCallableCreator() { callables.add(new MinidumpUploadCallableCreator() {
@Override @Override
public MinidumpUploadCallable createCallable(File minidumpFile, File logfile) { public MinidumpUploadCallable createCallable(File minidumpFile, File logfile) {
return new MinidumpUploadCallable( return new MinidumpUploadCallable(minidumpFile, logfile,
minidumpFile, logfile, new FailingHttpUrlConnectionFactory(), permManager); new MinidumpUploader(new FailingHttpUrlConnectionFactory()), permManager);
} }
}); });
callables.add(new MinidumpUploadCallableCreator() { callables.add(new MinidumpUploadCallableCreator() {
@Override @Override
public MinidumpUploadCallable createCallable(File minidumpFile, File logfile) { public MinidumpUploadCallable createCallable(File minidumpFile, File logfile) {
return new MinidumpUploadCallable(minidumpFile, logfile, return new MinidumpUploadCallable(minidumpFile, logfile,
new MinidumpUploadCallableTest.TestHttpURLConnectionFactory(), permManager); new MinidumpUploader(new TestHttpURLConnectionFactory()), permManager);
} }
}); });
MinidumpUploadJob minidumpUploadJob = createCallableListMinidumpUploadJob( MinidumpUploadJob minidumpUploadJob = createCallableListMinidumpUploadJob(
...@@ -328,7 +355,8 @@ public class MinidumpUploadJobImplTest { ...@@ -328,7 +355,8 @@ public class MinidumpUploadJobImplTest {
public MinidumpUploadCallable createMinidumpUploadCallable( public MinidumpUploadCallable createMinidumpUploadCallable(
File minidumpFile, File logfile) { File minidumpFile, File logfile) {
return new MinidumpUploadCallable(minidumpFile, logfile, return new MinidumpUploadCallable(minidumpFile, logfile,
new StallingHttpUrlConnectionFactory(mStopStallingLatch, mSuccessfulUpload), new MinidumpUploader(new StallingHttpUrlConnectionFactory(
mStopStallingLatch, mSuccessfulUpload)),
mDelegate.createCrashReportingPermissionManager()); mDelegate.createCrashReportingPermissionManager());
} }
} }
...@@ -359,7 +387,7 @@ public class MinidumpUploadJobImplTest { ...@@ -359,7 +387,7 @@ public class MinidumpUploadJobImplTest {
@Override @Override
public HttpURLConnection createHttpURLConnection(String url) { public HttpURLConnection createHttpURLConnection(String url) {
try { try {
return new MinidumpUploadCallableTest.TestHttpURLConnection(new URL(url)) { return new TestHttpURLConnection(new URL(url)) {
@Override @Override
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
return new StallingOutputStream(); return new StallingOutputStream();
......
...@@ -13,6 +13,7 @@ import org.chromium.base.ThreadUtils; ...@@ -13,6 +13,7 @@ import org.chromium.base.ThreadUtils;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Utility class for testing the minidump-uploading mechanism. * Utility class for testing the minidump-uploading mechanism.
...@@ -69,17 +70,19 @@ public class MinidumpUploadTestUtility { ...@@ -69,17 +70,19 @@ public class MinidumpUploadTestUtility {
public static void uploadMinidumpsSync( public static void uploadMinidumpsSync(
MinidumpUploadJob minidumpUploadJob, final boolean expectReschedule) { MinidumpUploadJob minidumpUploadJob, final boolean expectReschedule) {
final CountDownLatch uploadsFinishedLatch = new CountDownLatch(1); final CountDownLatch uploadsFinishedLatch = new CountDownLatch(1);
AtomicBoolean wasRescheduled = new AtomicBoolean();
uploadAllMinidumpsOnUiThread( uploadAllMinidumpsOnUiThread(
minidumpUploadJob, new MinidumpUploadJob.UploadsFinishedCallback() { minidumpUploadJob, new MinidumpUploadJob.UploadsFinishedCallback() {
@Override @Override
public void uploadsFinished(boolean reschedule) { public void uploadsFinished(boolean reschedule) {
assertEquals(expectReschedule, reschedule); wasRescheduled.set(reschedule);
uploadsFinishedLatch.countDown(); uploadsFinishedLatch.countDown();
} }
}); });
try { try {
assertTrue(uploadsFinishedLatch.await( assertTrue(uploadsFinishedLatch.await(
scaleTimeout(TIME_OUT_MILLIS), TimeUnit.MILLISECONDS)); scaleTimeout(TIME_OUT_MILLIS), TimeUnit.MILLISECONDS));
assertEquals(expectReschedule, wasRescheduled.get());
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
......
// 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.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Unittests for {@link MinidumpUploadCallable}.
*/
@RunWith(BaseJUnit4ClassRunner.class)
public class MinidumpUploaderTest {
/* package */ static final String BOUNDARY = "TESTBOUNDARY";
/* package */ static final String UPLOAD_CRASH_ID = "IMACRASHID";
@Rule
public CrashTestRule mTestRule = new CrashTestRule();
private File mUploadTestFile;
/**
* A HttpURLConnection that performs some basic checks to ensure we are uploading
* minidumps correctly.
*/
/* package */ static class TestHttpURLConnection extends HttpURLConnection {
static final String DEFAULT_EXPECTED_CONTENT_TYPE =
String.format(MinidumpUploader.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;
Assert.assertEquals(MinidumpUploader.CRASH_URL_STRING, url.toString());
}
@Override
public void disconnect() {
// Check that the "Content-Type" property has been set and the property's value.
Assert.assertEquals(mExpectedContentType, mContentTypePropertyValue);
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(ApiCompatibilityUtils.getBytesUtf8(UPLOAD_CRASH_ID));
}
@Override
public OutputStream getOutputStream() {
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.
*/
/* package */ 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;
}
}
}
/* package */ 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;
}
}
}
/* package */ static class FailHttpURLConnectionFactory implements HttpURLConnectionFactory {
@Override
public HttpURLConnection createHttpURLConnection(String url) {
Assert.fail();
return null;
}
}
@Before
public void setUp() throws IOException {
mUploadTestFile = new File(mTestRule.getCrashDir(), "crashFile");
CrashTestRule.setUpMinidumpFile(mUploadTestFile, MinidumpUploaderTest.BOUNDARY);
}
@After
public void tearDown() throws IOException {
mUploadTestFile.delete();
}
// This is a regression test for http://crbug.com/712420
@Test
@SmallTest
@Feature({"Android-AppBase"})
public void testCallWithInvalidMinidumpBoundary() throws Exception {
// Include an invalid character, '[', in the test string.
final String boundary = "--InvalidBoundaryWithSpecialCharacter--[";
CrashTestRule.setUpMinidumpFile(mUploadTestFile, boundary);
HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory() {
{ mContentType = ""; }
};
MinidumpUploader minidumpUploader = new MinidumpUploader(httpURLConnectionFactory);
MinidumpUploader.Result result = minidumpUploader.upload(mUploadTestFile);
Assert.assertTrue(result.isFailure());
}
@Test
@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(MinidumpUploader.CONTENT_TYPE_TMPL, boundary);
CrashTestRule.setUpMinidumpFile(mUploadTestFile, boundary);
HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory() {
{ mContentType = expectedContentType; }
};
MinidumpUploader minidumpUploader = new MinidumpUploader(httpURLConnectionFactory);
MinidumpUploader.Result result = minidumpUploader.upload(mUploadTestFile);
Assert.assertTrue(result.isSuccess());
}
@Test
@SmallTest
@Feature({"Android-AppBase"})
public void testReceivingErrorCodes() {
final int[] errorCodes = {400, 401, 403, 404, 500};
for (int n = 0; n < errorCodes.length; n++) {
HttpURLConnectionFactory httpURLConnectionFactory =
new ErrorCodeHttpURLConnectionFactory(errorCodes[n]);
MinidumpUploader minidumpUploader = new MinidumpUploader(httpURLConnectionFactory);
MinidumpUploader.Result result = minidumpUploader.upload(mUploadTestFile);
Assert.assertTrue(result.isUploadError());
Assert.assertEquals(result.errorCode(), errorCodes[n]);
}
}
}
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
package org.chromium.components.minidump_uploader; package org.chromium.components.minidump_uploader;
import org.chromium.components.minidump_uploader.MinidumpUploaderTest.TestHttpURLConnectionFactory;
import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager; import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
import java.io.File; import java.io.File;
...@@ -32,7 +33,7 @@ public class TestMinidumpUploadJobImpl extends MinidumpUploadJobImpl { ...@@ -32,7 +33,7 @@ public class TestMinidumpUploadJobImpl extends MinidumpUploadJobImpl {
@Override @Override
public MinidumpUploadCallable createMinidumpUploadCallable(File minidumpFile, File logfile) { public MinidumpUploadCallable createMinidumpUploadCallable(File minidumpFile, File logfile) {
return new MinidumpUploadCallable(minidumpFile, logfile, return new MinidumpUploadCallable(minidumpFile, logfile,
new MinidumpUploadCallableTest.TestHttpURLConnectionFactory(), new MinidumpUploader(new TestHttpURLConnectionFactory()),
mDelegate.createCrashReportingPermissionManager()); mDelegate.createCrashReportingPermissionManager());
} }
} }
...@@ -42,7 +42,6 @@ android_library("java") { ...@@ -42,7 +42,6 @@ android_library("java") {
"org/chromium/weblayer_private/FragmentWindowAndroid.java", "org/chromium/weblayer_private/FragmentWindowAndroid.java",
"org/chromium/weblayer_private/FullscreenCallbackProxy.java", "org/chromium/weblayer_private/FullscreenCallbackProxy.java",
"org/chromium/weblayer_private/LocaleChangedBroadcastReceiver.java", "org/chromium/weblayer_private/LocaleChangedBroadcastReceiver.java",
"org/chromium/weblayer_private/MinidumpUploader.java",
"org/chromium/weblayer_private/NavigationControllerImpl.java", "org/chromium/weblayer_private/NavigationControllerImpl.java",
"org/chromium/weblayer_private/NavigationImpl.java", "org/chromium/weblayer_private/NavigationImpl.java",
"org/chromium/weblayer_private/NewTabCallbackProxy.java", "org/chromium/weblayer_private/NewTabCallbackProxy.java",
......
...@@ -18,6 +18,7 @@ import org.chromium.base.PathUtils; ...@@ -18,6 +18,7 @@ import org.chromium.base.PathUtils;
import org.chromium.base.task.AsyncTask; import org.chromium.base.task.AsyncTask;
import org.chromium.components.crash.browser.ChildProcessCrashObserver; import org.chromium.components.crash.browser.ChildProcessCrashObserver;
import org.chromium.components.minidump_uploader.CrashFileManager; import org.chromium.components.minidump_uploader.CrashFileManager;
import org.chromium.components.minidump_uploader.MinidumpUploader;
import org.chromium.weblayer_private.interfaces.ICrashReporterController; import org.chromium.weblayer_private.interfaces.ICrashReporterController;
import org.chromium.weblayer_private.interfaces.ICrashReporterControllerClient; import org.chromium.weblayer_private.interfaces.ICrashReporterControllerClient;
import org.chromium.weblayer_private.interfaces.StrictModeWorkaround; import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
...@@ -80,16 +81,16 @@ public final class CrashReporterControllerImpl extends ICrashReporterController. ...@@ -80,16 +81,16 @@ public final class CrashReporterControllerImpl extends ICrashReporterController.
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
File minidumpFile = getCrashFileManager().getCrashFileWithLocalId(localId); File minidumpFile = getCrashFileManager().getCrashFileWithLocalId(localId);
MinidumpUploader.Result result = new MinidumpUploader().upload(minidumpFile); MinidumpUploader.Result result = new MinidumpUploader().upload(minidumpFile);
if (result.mSuccess) { if (result.isSuccess()) {
CrashFileManager.markUploadSuccess(minidumpFile); CrashFileManager.markUploadSuccess(minidumpFile);
} else { } else {
CrashFileManager.tryIncrementAttemptNumber(minidumpFile); CrashFileManager.tryIncrementAttemptNumber(minidumpFile);
} }
try { try {
if (result.mSuccess) { if (result.isSuccess()) {
mClient.onCrashUploadSucceeded(localId, result.mResult); mClient.onCrashUploadSucceeded(localId, result.message());
} else { } else {
mClient.onCrashUploadFailed(localId, result.mResult); mClient.onCrashUploadFailed(localId, result.message());
} }
} catch (RemoteException e) { } catch (RemoteException e) {
throw new AndroidRuntimeException(e); throw new AndroidRuntimeException(e);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment