Skip to content

Commit

Permalink
continue with anr message improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalwrench committed Aug 7, 2019
1 parent 73795be commit 5673b0a
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.bugsnag.android

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

class ErrorNameTest {

private lateinit var error: Error

@Before
fun setUp() {
val config = Configuration("api-key")
val exception = RuntimeException("Example message", RuntimeException("Another"))
error = Error.Builder(config, exception, null, Thread.currentThread(), false).build()
}

@Test
fun exceptionTypeConverted() {
assertTrue(error.exception is BugsnagException)
assertTrue(error.exceptions.exception is BugsnagException)
assertTrue(error.exceptions.exception.cause is RuntimeException)
}

@Test
fun defaultExceptionName() {
assertEquals("java.lang.RuntimeException", error.exceptionName)
}

@Test
fun defaultExceptionMessage() {
assertEquals("Example message", error.exceptionMessage)
}

@Test
fun overrideExceptionName() {
error.exceptionName = "Foo"
assertEquals("Foo", error.exceptionName)
}

@Test
fun overrideExceptionMessage() {
error.exceptionMessage = "Some custom message"
assertEquals("Some custom message", error.exceptionMessage)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,6 @@ public void testShouldIgnoreClass() {
assertTrue(error.shouldIgnoreClass());
}

@Test
public void testGetExceptionName() {
assertEquals("java.lang.RuntimeException", error.getExceptionName());
}

@Test
public void testGetExceptionMessage() {
assertEquals("Example message", error.getExceptionMessage());
}

@Test
public void testBasicSerialization() throws JSONException, IOException {
error.setAppData(client.getAppData().getAppData());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public void setUp() throws Exception {

@Test
public void testBasicException() throws JSONException, IOException {
Exceptions exceptions = new Exceptions(config, new RuntimeException("oops"));
RuntimeException oops = new RuntimeException("oops");
Exceptions exceptions = new Exceptions(config, new BugsnagException(oops));
JSONArray exceptionsJson = streamableToJsonArray(exceptions);

assertEquals(1, exceptionsJson.length());
Expand All @@ -45,7 +46,7 @@ public void testBasicException() throws JSONException, IOException {
@Test
public void testCauseException() throws JSONException, IOException {
Throwable ex = new RuntimeException("oops", new Exception("cause"));
Exceptions exceptions = new Exceptions(config, ex);
Exceptions exceptions = new Exceptions(config, new BugsnagException(ex));
JSONArray exceptionsJson = streamableToJsonArray(exceptions);

assertEquals(2, exceptionsJson.length());
Expand All @@ -68,7 +69,7 @@ public void testNamedException() throws JSONException, IOException {
Error error = new Error.Builder(config, "RuntimeException",
"Example message", frames, BugsnagTestUtils.generateSessionTracker(),
Thread.currentThread()).build();
Exceptions exceptions = new Exceptions(config, error.getException());
Exceptions exceptions = new Exceptions(config, new BugsnagException(error.getException()));

JSONObject exceptionJson = streamableToJsonArray(exceptions).getJSONObject(0);
assertEquals("RuntimeException", exceptionJson.get("errorClass"));
Expand All @@ -79,19 +80,4 @@ public void testNamedException() throws JSONException, IOException {
assertEquals("Class.java", stackframeJson.get("file"));
assertEquals(123, stackframeJson.get("lineNumber"));
}

@Test
public void testCustomExceptionSerialization() throws JSONException, IOException {
Exceptions exceptions = new Exceptions(config, new CustomException("Failed serialization"));

JSONObject exceptionJson = streamableToJsonArray(exceptions).getJSONObject(0);
assertEquals("CustomizedException", exceptionJson.get("errorClass"));
assertEquals("Failed serialization", exceptionJson.get("message"));

JSONObject stackframeJson = exceptionJson.getJSONArray("stacktrace").getJSONObject(0);
assertEquals("MyFile.run", stackframeJson.get("method"));
assertEquals("MyFile.java", stackframeJson.get("file"));
assertEquals(408, stackframeJson.get("lineNumber"));
assertEquals(18, stackframeJson.get("offset"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public class BugsnagException extends Throwable {
/**
* The name of the exception (used instead of the exception class)
*/
private final String name;
private String name;
private String message;

private String type;

Expand All @@ -27,11 +28,24 @@ public BugsnagException(@NonNull String name,
@NonNull String message,
@NonNull StackTraceElement[] frames) {
super(message);

super.setStackTrace(frames);
setStackTrace(frames);
this.name = name;
}

BugsnagException(@NonNull Throwable exc) {
super(exc.getMessage());

if (exc instanceof BugsnagException) {
this.message = ((BugsnagException) exc).getMessage();
this.name = ((BugsnagException) exc).getName();
this.type = ((BugsnagException) exc).getType();
} else {
this.name = exc.getClass().getName();
}
setStackTrace(exc.getStackTrace());
initCause(exc.getCause());
}

/**
* @return The name of the exception (used instead of the exception class)
*/
Expand All @@ -40,6 +54,19 @@ public String getName() {
return name;
}

public void setName(@NonNull String name) {
this.name = name;
}

@NonNull
public String getMessage() {
return message != null ? message : super.getMessage();
}

public void setMessage(String message) {
this.message = message;
}

String getType() {
return type;
}
Expand Down
35 changes: 25 additions & 10 deletions bugsnag-android-core/src/main/java/com/bugsnag/android/Error.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,23 @@ public class Error implements JsonStream.Streamable {
private String[] projectPackages;
private final Exceptions exceptions;
private Breadcrumbs breadcrumbs;
private final Throwable exception;
private final BugsnagException exception;
private final HandledState handledState;
private final Session session;
private final ThreadState threadState;
private boolean incomplete = false;

Error(@NonNull Configuration config, @NonNull Throwable exception,
Error(@NonNull Configuration config, @NonNull Throwable exc,
HandledState handledState, @NonNull Severity severity,
Session session, ThreadState threadState) {
this.threadState = threadState;
this.config = config;
this.exception = exception;

if (exc instanceof BugsnagException) {
this.exception = (BugsnagException) exc;
} else {
this.exception = new BugsnagException(exc);
}
this.handledState = handledState;
this.severity = severity;
this.session = session;
Expand Down Expand Up @@ -311,20 +316,30 @@ public void setMetaData(@NonNull MetaData metaData) {
*/
@NonNull
public String getExceptionName() {
if (exception instanceof BugsnagException) {
return ((BugsnagException) exception).getName();
} else {
return exception.getClass().getName();
}
return exception.getName();
}

/**
* Sets the class name from the exception contained in this Error report.
*/
public void setExceptionName(@NonNull String exceptionName) {
exception.setName(exceptionName);
}

/**
* Get the message from the exception contained in this Error report.
*/
@NonNull
public String getExceptionMessage() {
String localizedMessage = exception.getLocalizedMessage();
return localizedMessage != null ? localizedMessage : "";
String msg = exception.getMessage();
return msg != null ? msg : "";
}

/**
* Sets the message from the exception contained in this Error report.
*/
public void setExceptionMessage(@NonNull String exceptionMessage) {
exception.setMessage(exceptionMessage);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
* Unwrap and serialize exception information and any "cause" exceptions.
*/
class Exceptions implements JsonStream.Streamable {
private final Throwable exception;
private final BugsnagException exception;
private String exceptionType;
private String[] projectPackages;

Exceptions(Configuration config, Throwable exception) {
Exceptions(Configuration config, BugsnagException exception) {
this.exception = exception;
exceptionType = Configuration.DEFAULT_EXCEPTION_TYPE;
projectPackages = config.getProjectPackages();
Expand All @@ -39,7 +39,7 @@ public void toStream(@NonNull JsonStream writer) throws IOException {
writer.endArray();
}

Throwable getException() {
BugsnagException getException() {
return exception;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.bugsnag.android

import android.app.ActivityManager
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnitRunner

@RunWith(MockitoJUnitRunner::class)
class AnrDetailsCollectorTest {

private companion object {
private const val PID_EXAMPLE = 5902
}

private val collector = AnrDetailsCollector()
private val stateInfo = ActivityManager.ProcessErrorStateInfo()
private lateinit var error: Error

@Mock
lateinit var am: ActivityManager

@Before
fun setUp() {
stateInfo.pid = PID_EXAMPLE
stateInfo.tag = "com.bugsnag.android.example/.ExampleActivity"
stateInfo.shortMsg = "ANR Input dispatching timed out"
stateInfo.longMsg = "ANR in com.bugsnag.android.example"

error = Error.Builder(
Configuration("f"),
RuntimeException(),
null,
Thread.currentThread(),
true
).build()
}

@Test
fun exceptionReturnsNull() {
Mockito.`when`(am.processesInErrorState).thenThrow(RuntimeException())
assertNull(collector.captureProcessErrorState(am, 0))
}

@Test
fun emptyListReturnsNull() {
Mockito.`when`(am.processesInErrorState).thenReturn(listOf())
assertNull(collector.captureProcessErrorState(am, 0))
}

@Test
fun differentPidReturnsNull() {
Mockito.`when`(am.processesInErrorState).thenReturn(listOf(stateInfo))
val captureProcessErrorState = collector.captureProcessErrorState(am, PID_EXAMPLE)
assertEquals(stateInfo, captureProcessErrorState)
}

@Test
fun samePidReturnsObj() {
val second = ActivityManager.ProcessErrorStateInfo()
Mockito.`when`(am.processesInErrorState).thenReturn(listOf(stateInfo, second))
val captureProcessErrorState = collector.captureProcessErrorState(am, PID_EXAMPLE)
assertEquals(stateInfo, captureProcessErrorState)
}

@Test
fun errMsgAltered() {
collector.mutateError(error, stateInfo)
assertEquals(stateInfo.shortMsg, error.exceptionName)
}

@Test
fun contextAltered() {
collector.mutateError(error, stateInfo)
assertEquals(stateInfo.tag, error.context)
}

@Test
fun anrDetailsAltered() {
collector.mutateError(error, stateInfo)
assertEquals(stateInfo.longMsg, error.exceptionMessage)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import android.content.Context
import android.os.Process
import android.support.annotation.VisibleForTesting

// TODO unit tests

internal class AnrDetailsCollector {

fun collectAnrDetails(ctx: Context): ProcessErrorStateInfo? {
Expand All @@ -23,7 +21,18 @@ internal class AnrDetailsCollector {
*/
@VisibleForTesting
internal fun captureProcessErrorState(am: ActivityManager, pid: Int): ProcessErrorStateInfo? {
val processes = am.processesInErrorState ?: emptyList()
return processes.firstOrNull { it.pid == pid }
return try {
val processes = am.processesInErrorState ?: emptyList()
processes.firstOrNull { it.pid == pid }
} catch (exc: RuntimeException) {
null
}
}

internal fun mutateError(error: Error, anrState: ProcessErrorStateInfo) {
// TODO truncate these values?
error.context = anrState.tag
error.exceptionName = anrState.shortMsg
error.exceptionMessage = anrState.longMsg
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,7 @@ internal class AnrPlugin : BugsnagPlugin {
Logger.warn("Looping in attempt to capture ANR details")
Async.run(this)
} else {
Logger.warn("Captured ANR details: $anrDetails")

// TODO determine how to populate error report
val metaData = error.metaData
metaData.addToTab("ANR", "Message", anrDetails.shortMsg)
metaData.addToTab("ANR", "Component", anrDetails.tag)
metaData.addToTab("ANR", "Details", anrDetails.longMsg)

collector.mutateError(error, anrDetails)
client.notify(error, DeliveryStyle.ASYNC_WITH_CACHE, null)
}
} catch (exc: InterruptedException) {
Expand Down

0 comments on commit 5673b0a

Please sign in to comment.