-
Notifications
You must be signed in to change notification settings - Fork 205
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Send minimal error report if cached file is corrupted/empty (#500)
Send minimal error report if cached file is corrupted/empty
- Loading branch information
Showing
14 changed files
with
450 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
...erunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/CorruptedReportScenario.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.bugsnag.android.mazerunner.scenarios | ||
|
||
import android.content.Context | ||
import com.bugsnag.android.Bugsnag | ||
import com.bugsnag.android.Configuration | ||
import java.io.File | ||
|
||
/** | ||
* Verifies that if a report is corrupted, minimal information is still sent to bugsnag. | ||
*/ | ||
internal class CorruptedReportScenario(config: Configuration, | ||
context: Context) : Scenario(config, context) { | ||
|
||
init { | ||
config.setAutoCaptureSessions(false) | ||
val files = File(context.cacheDir, "bugsnag-errors").listFiles() | ||
files.forEach { it.writeText("{\"exceptions\":[{\"stacktrace\":[") } | ||
|
||
val nativeFiles = File(context.cacheDir, "bugsnag-native").listFiles() | ||
nativeFiles.forEach { it.writeText("{\"exceptions\":[{\"stacktrace\":[") } | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
.../mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyReportScenario.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.bugsnag.android.mazerunner.scenarios | ||
|
||
import android.content.Context | ||
import com.bugsnag.android.Bugsnag | ||
import com.bugsnag.android.Configuration | ||
import java.io.File | ||
|
||
/** | ||
* Verifies that if a report is empty, minimal information is still sent to bugsnag. | ||
*/ | ||
internal class EmptyReportScenario(config: Configuration, | ||
context: Context) : Scenario(config, context) { | ||
|
||
init { | ||
config.setAutoCaptureSessions(false) | ||
val files = File(context.cacheDir, "bugsnag-errors").listFiles() | ||
files.forEach { it.writeText("") } | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
...src/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalHandledExceptionScenario.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.bugsnag.android.mazerunner.scenarios | ||
|
||
import android.content.Context | ||
import com.bugsnag.android.Bugsnag | ||
import com.bugsnag.android.Configuration | ||
import java.io.File | ||
|
||
/** | ||
* Sends a handled exception to Bugsnag, which does not include session data. | ||
*/ | ||
internal class MinimalHandledExceptionScenario(config: Configuration, | ||
context: Context) : Scenario(config, context) { | ||
|
||
init { | ||
config.setAutoCaptureSessions(false) | ||
disableAllDelivery(config) | ||
} | ||
|
||
override fun run() { | ||
super.run() | ||
Bugsnag.notify(java.lang.RuntimeException("Whoops")) | ||
} | ||
|
||
} |
22 changes: 22 additions & 0 deletions
22
...c/main/java/com/bugsnag/android/mazerunner/scenarios/MinimalUnhandledExceptionScenario.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.bugsnag.android.mazerunner.scenarios | ||
|
||
import android.content.Context | ||
import com.bugsnag.android.Configuration | ||
import java.io.File | ||
|
||
/** | ||
* Sends an unhandled exception to Bugsnag. | ||
*/ | ||
internal class MinimalUnhandledExceptionScenario(config: Configuration, | ||
context: Context) : Scenario(config, context) { | ||
init { | ||
config.setAutoCaptureSessions(false) | ||
disableAllDelivery(config) | ||
} | ||
|
||
override fun run() { | ||
super.run() | ||
throw java.lang.IllegalStateException("Whoops") | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
Feature: Minimal error information is reported for corrupted/empty files | ||
|
||
Scenario: Minimal error report for a Handled Exception with an empty file | ||
When I run "MinimalHandledExceptionScenario" | ||
And I set environment variable "EVENT_TYPE" to "EmptyReportScenario" | ||
And I relaunch the app | ||
Then I should receive 1 request | ||
And the request is valid for the error reporting API | ||
And the payload field "events.0.exceptions.0.stacktrace" is an array with 0 element | ||
And the exception "errorClass" equals "java.lang.RuntimeException" | ||
And the event "severity" equals "warning" | ||
And the event "unhandled" is false | ||
And the event "incomplete" is true | ||
And the event "severityReason.type" equals "handledException" | ||
|
||
Scenario: Minimal error report for an Unhandled Exception with a corrupted file | ||
When I run "MinimalUnhandledExceptionScenario" | ||
And I set environment variable "EVENT_TYPE" to "CorruptedReportScenario" | ||
And I relaunch the app | ||
Then I should receive 1 request | ||
And the request is valid for the error reporting API | ||
And the payload field "events.0.exceptions.0.stacktrace" is an array with 0 element | ||
And the exception "errorClass" equals "java.lang.IllegalStateException" | ||
And the event "severity" equals "error" | ||
And the event "unhandled" is true | ||
And the event "incomplete" is true | ||
And the event "severityReason.type" equals "unhandledException" |
156 changes: 156 additions & 0 deletions
156
sdk/src/androidTest/java/com/bugsnag/android/ErrorFilenameTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package com.bugsnag.android | ||
|
||
import android.support.test.InstrumentationRegistry | ||
import com.bugsnag.android.ErrorStore.ERROR_REPORT_COMPARATOR | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Assert.assertFalse | ||
import org.junit.Assert.assertNotNull | ||
import org.junit.Assert.assertNull | ||
import org.junit.Assert.assertTrue | ||
import org.junit.Before | ||
import org.junit.Test | ||
import java.io.File | ||
|
||
class SuperCaliFragilisticExpiAlidociousBeanFactoryException: RuntimeException() | ||
|
||
class ErrorFilenameTest { | ||
|
||
private lateinit var errorStore: ErrorStore | ||
private val config = Configuration("api-key") | ||
|
||
/** | ||
* Generates a client and ensures that its errorStore has 0 files persisted | ||
* | ||
* @throws Exception if initialisation failed | ||
*/ | ||
@Before | ||
@Throws(Exception::class) | ||
fun setUp() { | ||
val context = InstrumentationRegistry.getContext() | ||
errorStore = ErrorStore(config, context, null) | ||
} | ||
|
||
@Test | ||
fun testCalculateFilenameUnhandled() { | ||
val err = generateError(true, Severity.ERROR, RuntimeException()) | ||
val filename = errorStore.calculateFilenameForError(err) | ||
assertEquals("e-u-java.lang.RuntimeException", filename) | ||
} | ||
|
||
@Test | ||
fun testCalculateFilenameHandled() { | ||
val err = generateError(false, Severity.INFO, IllegalStateException("Whoops")) | ||
val filename = errorStore.calculateFilenameForError(err) | ||
assertEquals("i-h-java.lang.IllegalStateException", filename) | ||
} | ||
|
||
@Test | ||
fun testCalculateTruncatedFilename() { | ||
val err = generateError(false, Severity.INFO, | ||
SuperCaliFragilisticExpiAlidociousBeanFactoryException()) | ||
val filename = errorStore.calculateFilenameForError(err) | ||
assertEquals("i-h-com.bugsnag.android.SuperCaliFragilistic", filename) | ||
} | ||
|
||
@Test | ||
fun testErrorFromInvalidFilename() { | ||
val invalids = arrayOf( | ||
null, "", "test.txt", "i-h.foo", | ||
"1504255147933_683c6b92-b325-4987-80ad-77086509ca1e.json" | ||
) | ||
invalids.forEach { assertNull(errorStore.generateErrorFromFilename(it)) } | ||
} | ||
|
||
@Test | ||
fun testUnhandledErrorFromFilename() { | ||
val filename = "1504255147933_e-u-java.lang.RuntimeException_" + | ||
"683c6b92-b325-4987-80ad-77086509ca1e.json" | ||
val err = errorStore.generateErrorFromFilename(filename) | ||
assertNotNull(err) | ||
assertTrue(err.handledState.isUnhandled) | ||
assertEquals(Severity.ERROR, err.severity) | ||
assertEquals("java.lang.RuntimeException", err.exceptionName) | ||
} | ||
|
||
@Test | ||
fun testHandledErrorFromFilename() { | ||
val filename = "1504500000000_i-h-java.lang.IllegalStateException_" + | ||
"683c6b92-b325-4987-80ad-77086509ca1e_startupcrash.json" | ||
val err = errorStore.generateErrorFromFilename(filename) | ||
assertNotNull(err) | ||
assertFalse(err.handledState.isUnhandled) | ||
assertEquals(Severity.INFO, err.severity) | ||
assertEquals("java.lang.IllegalStateException", err.exceptionName) | ||
} | ||
|
||
@Test | ||
fun testErrorWithoutClassFromFilename() { | ||
val filename = "1504500000000_i-h-_" + | ||
"683c6b92-b325-4987-80ad-77086509ca1e_startupcrash.json" | ||
val err = errorStore.generateErrorFromFilename(filename) | ||
assertNotNull(err) | ||
assertFalse(err.handledState.isUnhandled) | ||
assertEquals(Severity.INFO, err.severity) | ||
assertEquals("", err.exceptionName) | ||
} | ||
|
||
@Test | ||
fun testIsLaunchCrashReport() { | ||
val valid = | ||
arrayOf("1504255147933_e-u-java.lang.RuntimeException_30b7e350-dcd1-4032-969e-98d30be62bbc_startupcrash.json") | ||
val invalid = arrayOf( | ||
"", | ||
".json", | ||
"abcdeAO.json", | ||
"!@£)(%)(", | ||
"1504255147933.txt", | ||
"1504255147933.json" | ||
) | ||
|
||
for (s in valid) { | ||
assertTrue(errorStore.isLaunchCrashReport(File(s))) | ||
} | ||
for (s in invalid) { | ||
assertFalse(errorStore.isLaunchCrashReport(File(s))) | ||
} | ||
} | ||
|
||
@Test | ||
fun testComparator() { | ||
val first = "1504255147933_e-u-java.lang.RuntimeException_" + | ||
"683c6b92-b325-4987-80ad-77086509ca1e.json" | ||
val second = "1505000000000_i-h-Exception_683c6b92-b325-4987-80ad-77086509ca1e.json" | ||
val startup = "1504500000000_w-h-java.lang.IllegalStateException_683c6b92-b325-" + | ||
"4987-80ad-77086509ca1e_startupcrash.json" | ||
|
||
// handle defaults | ||
assertEquals(0, ERROR_REPORT_COMPARATOR.compare(null, null).toLong()) | ||
assertEquals(-1, ERROR_REPORT_COMPARATOR.compare(File(""), null).toLong()) | ||
assertEquals(1, ERROR_REPORT_COMPARATOR.compare(null, File("")).toLong()) | ||
|
||
// same value should always be 0 | ||
assertEquals(0, ERROR_REPORT_COMPARATOR.compare(File(first), File(first)).toLong()) | ||
assertEquals(0, ERROR_REPORT_COMPARATOR.compare(File(startup), File(startup)).toLong()) | ||
|
||
// first is before second | ||
assertTrue(ERROR_REPORT_COMPARATOR.compare(File(first), File(second)) < 0) | ||
assertTrue(ERROR_REPORT_COMPARATOR.compare(File(second), File(first)) > 0) | ||
|
||
// startup is handled correctly | ||
assertTrue(ERROR_REPORT_COMPARATOR.compare(File(first), File(startup)) < 0) | ||
assertTrue(ERROR_REPORT_COMPARATOR.compare(File(second), File(startup)) > 0) | ||
} | ||
|
||
private fun generateError(unhandled: Boolean, severity: Severity, exc: Throwable): Error { | ||
val currentThread = Thread.currentThread() | ||
val sessionTracker = BugsnagTestUtils.generateSessionTracker() | ||
|
||
val handledState = when { | ||
unhandled -> HandledState.REASON_UNHANDLED_EXCEPTION | ||
else -> HandledState.REASON_HANDLED_EXCEPTION | ||
} | ||
return Error.Builder(config, exc, sessionTracker, currentThread, unhandled) | ||
.severityReasonType(handledState) | ||
.severity(severity).build() | ||
} | ||
} |
Oops, something went wrong.