diff --git a/README.md b/README.md index f2ef148268..1f28e1a750 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Bugsnag exception reporter for Android [![Documentation](https://img.shields.io/badge/documentation-latest-blue.svg)](https://docs.bugsnag.com/platforms/android/) [![Build status](https://travis-ci.org/bugsnag/bugsnag-android.svg?branch=master)](https://travis-ci.org/bugsnag/bugsnag-android) +[![Coverage Status](https://coveralls.io/repos/github/bugsnag/bugsnag-android/badge.svg?branch=master)](https://coveralls.io/github/bugsnag/bugsnag-android?branch=master) [![Method count and size](https://img.shields.io/badge/Methods%20and%20size-core:%20742%20|%20deps:%2032%20|%2090%20KB-e91e63.svg)](http://www.methodscount.com/?lib=com.bugsnag%3Abugsnag-android%3A4.0.0) Get comprehensive [Android crash reports](https://www.bugsnag.com/platforms/android/) to quickly debug errors. diff --git a/build.gradle b/build.gradle index b91348f2dc..671c24fc89 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ buildscript { classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.2' + classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2' } } repositories { diff --git a/features/session_tracking.feature b/features/session_tracking.feature index d140bf4760..c19cba96a5 100644 --- a/features/session_tracking.feature +++ b/features/session_tracking.feature @@ -43,3 +43,9 @@ Scenario: Manual Session sends And the session "user.name" equals "Joe Bloggs" And the session "id" is not null And the session "startedAt" is not null + +Scenario: Set Auto Capture Sessions sends + When I run "SessionSetAutoCaptureScenario" with the defaults + And I wait for 60 seconds + Then I should receive a request + And the request is a valid for the session tracking API diff --git a/mazerunner/src/main/java/com/bugsnag/android/mazerunner/MainActivity.kt b/mazerunner/src/main/java/com/bugsnag/android/mazerunner/MainActivity.kt index dce1bf9a58..42457b6a77 100644 --- a/mazerunner/src/main/java/com/bugsnag/android/mazerunner/MainActivity.kt +++ b/mazerunner/src/main/java/com/bugsnag/android/mazerunner/MainActivity.kt @@ -32,8 +32,10 @@ class MainActivity : Activity() { private fun executeTestCase() { val eventType = intent.getStringExtra("EVENT_TYPE") + val eventMetaData = intent.getStringExtra("EVENT_METADATA") Log.d("Bugsnag", "Received test case, executing " + eventType) val testCase = factory.testCaseForName(eventType, prepareConfig(), this) + testCase.eventMetaData = eventMetaData testCase.run() } diff --git a/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt b/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt index 2518adf652..1a04295df0 100644 --- a/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt +++ b/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt @@ -8,6 +8,8 @@ import com.bugsnag.android.NetworkException abstract internal class Scenario(protected val config: Configuration, protected val context: Context) { + var eventMetaData: String? = null + open fun run() { Bugsnag.init(context, config) Bugsnag.setLoggingEnabled(true) diff --git a/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/SessionSetAutoCaptureScenario.kt b/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/SessionSetAutoCaptureScenario.kt new file mode 100644 index 0000000000..f482d74a81 --- /dev/null +++ b/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/SessionSetAutoCaptureScenario.kt @@ -0,0 +1,21 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import android.content.Intent +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration +import com.bugsnag.android.mazerunner.SecondActivity + +/** + * Sets automatic capture of sessions in Bugsnag and flushes 1 session + */ +internal class SessionSetAutoCaptureScenario(config: Configuration, + context: Context) : Scenario(config, context) { + + override fun run() { + super.run() + Bugsnag.setAutoCaptureSessions(true) + context.startActivity(Intent(context, SecondActivity::class.java)) + } + +} diff --git a/sdk/build.gradle b/sdk/build.gradle index 339ef92d36..cc8e2b6b56 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.library' +apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'maven-publish' apply plugin: 'com.jfrog.bintray' apply plugin: 'com.github.kt3k.coveralls' @@ -149,3 +150,12 @@ bintray { } apply from: "../checkstyle.gradle" + +dexcount { + includeClasses = true + includeClassCount = true + includeFieldCount = true + includeTotalMethodCount = true + orderByMethodCount = true + verbose true +} diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ClientNotifyTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ClientNotifyTest.java new file mode 100644 index 0000000000..29993c1ecf --- /dev/null +++ b/sdk/src/androidTest/java/com/bugsnag/android/ClientNotifyTest.java @@ -0,0 +1,87 @@ +package com.bugsnag.android; + +import static org.junit.Assert.assertEquals; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Map; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ClientNotifyTest { + + private Client client; + private FakeClient apiClient; + + /** + * Generates a configuration and clears sharedPrefs values to begin the test with a clean slate + * @throws Exception if initialisation failed + */ + @Before + public void setUp() throws Exception { + client = BugsnagTestUtils.generateClient(); + apiClient = new FakeClient(); + client.setErrorReportApiClient(apiClient); + } + + @Test + public void testNotifyBlockingDefaultSeverity() { + client.notifyBlocking(new RuntimeException("Testing")); + assertEquals(Severity.WARNING, apiClient.report.getError().getSeverity()); + } + + @Test + public void testNotifyBlockingCallback() { + client.notifyBlocking(new RuntimeException("Testing"), new Callback() { + @Override + public void beforeNotify(Report report) { + report.getError().setUserName("Foo"); + } + }); + Error error = apiClient.report.getError(); + assertEquals(Severity.WARNING, error.getSeverity()); + assertEquals("Foo", error.getUser().getName()); + } + + @Test + public void testNotifyBlockingCustomSeverity() { + client.notifyBlocking(new RuntimeException("Testing"), Severity.INFO); + assertEquals(Severity.INFO, apiClient.report.getError().getSeverity()); + } + + @Test + public void testNotifyBlockingCustomStackTrace() { + StackTraceElement[] stacktrace = { + new StackTraceElement("MyClass", "MyMethod", "MyFile", 5) + }; + + client.notifyBlocking("Name", "Message", stacktrace, new Callback() { + @Override + public void beforeNotify(Report report) { + report.getError().setSeverity(Severity.ERROR); + } + }); + Error error = apiClient.report.getError(); + assertEquals(Severity.ERROR, error.getSeverity()); + assertEquals("Name", error.getExceptionName()); + assertEquals("Message", error.getExceptionMessage()); + } + + static class FakeClient implements ErrorReportApiClient { + Report report; + + @Override + public void postReport(String urlString, + Report report, + Map headers) + throws NetworkException, BadResponseException { + this.report = report; + } + } + +} diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java index 37650ff248..a447bd82b7 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/ReportTest.java @@ -50,4 +50,26 @@ public void testModifyingGroupingHash() throws JSONException, IOException { JSONObject event = events.getJSONObject(0); assertEquals(groupingHash, event.getString("groupingHash")); } + + @Test + public void testModifyReportDetails() throws Exception { + String apiKey = "custom-api-key"; + String notifierName = "React Native"; + String notifierUrl = "https://bugsnag.com/reactnative"; + String notifierVersion = "3.4.5"; + + report.setApiKey(apiKey); + report.setNotifierName(notifierName); + report.setNotifierURL(notifierUrl); + report.setNotifierVersion(notifierVersion); + + JSONObject reportJson = streamableToJson(report); + assertEquals(apiKey, reportJson.getString("apiKey")); + + JSONObject notifier = reportJson.getJSONObject("notifier"); + assertEquals(notifierName, notifier.getString("name")); + assertEquals(notifierVersion, notifier.getString("version")); + assertEquals(notifierUrl, notifier.getString("url")); + } + } diff --git a/sdk/src/androidTest/java/com/bugsnag/android/SessionTrackingPayloadTest.java b/sdk/src/androidTest/java/com/bugsnag/android/SessionTrackingPayloadTest.java index aeb48e9435..b802770480 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/SessionTrackingPayloadTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/SessionTrackingPayloadTest.java @@ -4,7 +4,9 @@ import static com.bugsnag.android.BugsnagTestUtils.generateSessionTracker; import static com.bugsnag.android.BugsnagTestUtils.streamableToJson; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; import android.content.Context; import android.support.test.InstrumentationRegistry; @@ -17,6 +19,7 @@ import org.junit.Test; import java.io.File; +import java.util.Date; import java.util.List; public class SessionTrackingPayloadTest { @@ -27,6 +30,7 @@ public class SessionTrackingPayloadTest { private SessionStore sessionStore; private File storageDir; + private SessionTrackingPayload payload; /** * Configures a session tracking payload and session store, ensuring that 0 files are present @@ -41,13 +45,17 @@ public void setUp() throws Exception { Assert.assertNotNull(sessionStore.storeDirectory); storageDir = new File(sessionStore.storeDirectory); FileUtils.clearFilesInDir(storageDir); - session = generateSession(); - appData = new AppData(context, new Configuration("a"), generateSessionTracker()); - SessionTrackingPayload payload = new SessionTrackingPayload(session, appData); + payload = generatePayloadFromSession(context, generateSession()); rootNode = streamableToJson(payload); } + private SessionTrackingPayload generatePayloadFromSession(Context context, + Session session) throws Exception { + appData = new AppData(context, new Configuration("a"), generateSessionTracker()); + return new SessionTrackingPayload(session, appData); + } + /** * Deletes any files in the session store created during the test * @@ -93,4 +101,17 @@ public void testMultipleSessionFiles() throws Exception { assertEquals(2, sessions.length()); } + @Test + public void testAutoCapturedOverride() throws Exception { + session = new Session("id", new Date(), null, false); + Context context = InstrumentationRegistry.getContext(); + payload = generatePayloadFromSession(context, session); + assertFalse(session.isAutoCaptured()); + session.setAutoCaptured(true); + assertTrue(session.isAutoCaptured()); + + JSONObject rootNode = streamableToJson(payload); + JSONObject sessionNode = rootNode.getJSONArray("sessions").getJSONObject(0); + assertFalse(sessionNode.has("user")); + } }