diff --git a/.buildkite/pipeline.full.yml b/.buildkite/pipeline.full.yml index 668b6a9bdd..02fe640f07 100644 --- a/.buildkite/pipeline.full.yml +++ b/.buildkite/pipeline.full.yml @@ -11,6 +11,8 @@ steps: timeout_in_minutes: 5 agents: queue: macos-14 + env: + JAVA_VERSION: 17 command: 'make example-app' - label: ':android: Build debug fixture APK' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 966414a05a..0000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: instrumentation_tests - -on: [ push, pull_request ] - -env: - GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false -Dorg.gradle.parallel=true" - ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL: 60 - -jobs: - android: - runs-on: macos-latest - - strategy: - fail-fast: true - matrix: - api-level: - - 29 - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - uses: gradle/wrapper-validation-action@v1 - - - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: 11 - - - name: Gradle cache - uses: gradle/gradle-build-action@v2 - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - - name: Run Tests - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - script: ./gradlew connectedCheck -x :bugsnag-benchmarks:connectedCheck - env: - API_LEVEL: ${{ matrix.api-level }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cecf42f66..9e501cbd60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 6.5.0 (2024-05-15) + +### Enhancements + +* `Configuration.maxBreadcrumbs` is now obeyed by `bugsnag-plugin-android-ndk`, so native events will include the correct number of breadcrumbs + [#2020](https://github.com/bugsnag/bugsnag-android/pull/2020) +* `bugsnag-plugin-android-ndk` will no longer create an `ArrayList` copy of metadata keys when leaving breadcrumbs + [#2022](https://github.com/bugsnag/bugsnag-android/pull/2022) + ## 6.4.0 (2024-04-15) ### Enhancements diff --git a/Makefile b/Makefile index 42696fe2cf..0e5bba7b8f 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ fixture-debug: notifier example-app: @./gradlew assembleRelease publishToMavenLocal -x check # Build Android example app - @./examples/sdk-app-example/gradlew clean assembleRelease + @cd ./examples/sdk-app-example/ && ./gradlew clean assembleRelease bump: ifneq ($(shell git diff --staged),) diff --git a/bugsnag-android-core/api/bugsnag-android-core.api b/bugsnag-android-core/api/bugsnag-android-core.api index 10b89831f3..ecca487084 100644 --- a/bugsnag-android-core/api/bugsnag-android-core.api +++ b/bugsnag-android-core/api/bugsnag-android-core.api @@ -687,9 +687,10 @@ public final class com/bugsnag/android/StateEvent$Install : com/bugsnag/android/ public final field buildUuid Ljava/lang/String; public final field consecutiveLaunchCrashes I public final field lastRunInfoPath Ljava/lang/String; + public final field maxBreadcrumbs I public final field releaseStage Ljava/lang/String; public final field sendThreads Lcom/bugsnag/android/ThreadSendPolicy; - public fun (Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILcom/bugsnag/android/ThreadSendPolicy;)V + public fun (Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILcom/bugsnag/android/ThreadSendPolicy;I)V } public final class com/bugsnag/android/StateEvent$NotifyHandled : com/bugsnag/android/StateEvent { diff --git a/bugsnag-android-core/build.gradle b/bugsnag-android-core/build.gradle deleted file mode 100644 index e1d57b95f7..0000000000 --- a/bugsnag-android-core/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - id("bugsnag-build-plugin") -} - -bugsnagBuildOptions { - usesNdk = true -} - -apply plugin: "com.android.library" -apply plugin: "org.jetbrains.dokka" - -dokkaHtml.configure { - dokkaSourceSets { - named("main") { - noAndroidSdkLink.set(false) - perPackageOption { - matchingRegex.set("com\\.bugsnag\\.android\\..*") - suppress.set(true) - } - } - } -} - -apply from: "../gradle/kotlin.gradle" - -// pick up dsl-json by adding to the default sourcesets -android { - sourceSets { - main { - java.srcDirs += ["dsl-json/library/src/main/java"] - } - test { - java.srcDirs += ["dsl-json/library/src/test/java"] - } - } -} - -apiValidation.ignoredPackages += ["com.bugsnag.android.repackaged.dslplatform.json"] - diff --git a/bugsnag-android-core/build.gradle.kts b/bugsnag-android-core/build.gradle.kts new file mode 100644 index 0000000000..d6774d451a --- /dev/null +++ b/bugsnag-android-core/build.gradle.kts @@ -0,0 +1,41 @@ +import kotlinx.validation.ApiValidationExtension +import org.jetbrains.dokka.gradle.DokkaTask + +plugins { + id("bugsnag-build-plugin") +} + +bugsnagBuildOptions { + usesNdk = true + + // pick up dsl-json by adding to the default sourcesets + android { + sourceSets { + named("main") { + java.srcDirs("dsl-json/library/src/main/java") + } + named("test") { + java.srcDirs("dsl-json/library/src/test/java") + } + } + } +} + +apply(plugin = "com.android.library") +apply(plugin = "org.jetbrains.dokka") + +tasks.getByName("dokkaHtml") { + dokkaSourceSets { + named("main") { + noAndroidSdkLink.set(false) + perPackageOption { + matchingRegex.set("com\\.bugsnag\\.android\\..*") + suppress.set(true) + } + } + } +} + +plugins.withId("org.jetbrains.kotlinx.binary-compatibility-validator") { + project.extensions.getByType(ApiValidationExtension::class.java).ignoredPackages.add("com.bugsnag.android.repackaged.dslplatform.json") +} diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml index 1e44fd786f..a5e99e257b 100644 --- a/bugsnag-android-core/detekt-baseline.xml +++ b/bugsnag-android-core/detekt-baseline.xml @@ -18,7 +18,7 @@ LongParameterList:EventInternal.kt$EventInternal$( apiKey: String, logger: Logger, breadcrumbs: MutableList<Breadcrumb> = mutableListOf(), discardClasses: Set<Pattern> = setOf(), errors: MutableList<Error> = mutableListOf(), metadata: Metadata = Metadata(), featureFlags: FeatureFlags = FeatureFlags(), originalError: Throwable? = null, projectPackages: Collection<String> = setOf(), severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION), threads: MutableList<Thread> = mutableListOf(), user: User = User(), redactionKeys: Set<Pattern>? = null ) LongParameterList:EventStorageModule.kt$EventStorageModule$( contextModule: ContextModule, configModule: ConfigModule, dataCollectionModule: DataCollectionModule, bgTaskService: BackgroundTaskService, trackerModule: TrackerModule, systemServiceModule: SystemServiceModule, notifier: Notifier, callbackState: CallbackState ) LongParameterList:NativeStackframe.kt$NativeStackframe$( /** * The name of the method that was being executed */ var method: String?, /** * The location of the source file */ var file: String?, /** * The line number within the source file this stackframe refers to */ var lineNumber: Number?, /** * The address of the instruction where the event occurred. */ var frameAddress: Long?, /** * The address of the function where the event occurred. */ var symbolAddress: Long?, /** * The address of the library where the event occurred. */ var loadAddress: Long?, /** * Whether this frame identifies the program counter */ var isPC: Boolean?, /** * The type of the error */ var type: ErrorType? = null, /** * Identifies the exact build this frame originates from. */ var codeIdentifier: String? = null, ) - LongParameterList:StateEvent.kt$StateEvent.Install$( @JvmField val apiKey: String, @JvmField val autoDetectNdkCrashes: Boolean, @JvmField val appVersion: String?, @JvmField val buildUuid: String?, @JvmField val releaseStage: String?, @JvmField val lastRunInfoPath: String, @JvmField val consecutiveLaunchCrashes: Int, @JvmField val sendThreads: ThreadSendPolicy ) + LongParameterList:StateEvent.kt$StateEvent.Install$( @JvmField val apiKey: String, @JvmField val autoDetectNdkCrashes: Boolean, @JvmField val appVersion: String?, @JvmField val buildUuid: String?, @JvmField val releaseStage: String?, @JvmField val lastRunInfoPath: String, @JvmField val consecutiveLaunchCrashes: Int, @JvmField val sendThreads: ThreadSendPolicy, @JvmField val maxBreadcrumbs: Int ) LongParameterList:ThreadState.kt$ThreadState$( allThreads: List<JavaThread>, currentThread: JavaThread, exc: Throwable?, isUnhandled: Boolean, maxThreadCount: Int, threadCollectionTimeLimitMillis: Long, projectPackages: Collection<String>, logger: Logger ) MagicNumber:DefaultDelivery.kt$DefaultDelivery$299 MagicNumber:DefaultDelivery.kt$DefaultDelivery$429 diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/MemoryTrimTest.java b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/MemoryTrimTest.java index 8d8799b65d..838f1de2a5 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/MemoryTrimTest.java +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/MemoryTrimTest.java @@ -2,6 +2,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,6 +36,7 @@ public class MemoryTrimTest { @Test public void onLowMemoryEvent() { when(context.getApplicationContext()).thenReturn(context); + doNothing().when(context).registerComponentCallbacks(any()); Client client = new Client(context, BugsnagTestUtils.generateConfiguration()); // block until observer is registered diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt index cd6ab1fe5d..b099c7edc3 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt @@ -22,7 +22,8 @@ internal class ClientObservable : BaseObservable() { conf.releaseStage, lastRunInfoPath, consecutiveLaunchCrashes, - conf.sendThreads + conf.sendThreads, + conf.maxBreadcrumbs ) } } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt index bb1172c769..6eb471d22d 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt @@ -208,7 +208,7 @@ internal class EventStore( } private fun handleEventFlushFailure(exc: Exception, eventFile: File) { - delegate?.onErrorIOFailure(exc, eventFile, "Crash Report Deserialization") + logger.e(exc.message ?: "Failed to send event", exc) deleteStoredFiles(setOf(eventFile)) } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt index bea14c68a8..4cc446d679 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt @@ -7,7 +7,7 @@ import java.io.IOException */ class Notifier @JvmOverloads constructor( var name: String = "Android Bugsnag Notifier", - var version: String = "6.4.0", + var version: String = "6.5.0", var url: String = "https://bugsnag.com" ) : JsonStream.Streamable { diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Session.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Session.java index 408d6fc0f7..5081ad22e7 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Session.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Session.java @@ -28,11 +28,11 @@ public final class Session implements JsonStream.Streamable, UserAware { private App app; private Device device; - private final AtomicBoolean autoCaptured = new AtomicBoolean(false); + private volatile boolean autoCaptured = false; private final AtomicInteger unhandledCount = new AtomicInteger(); private final AtomicInteger handledCount = new AtomicInteger(); private final AtomicBoolean tracked = new AtomicBoolean(false); - final AtomicBoolean isPaused = new AtomicBoolean(false); + private final AtomicBoolean isPaused = new AtomicBoolean(false); private String apiKey; @@ -41,7 +41,7 @@ static Session copySession(Session session) { session.unhandledCount.get(), session.handledCount.get(), session.notifier, session.logger, session.getApiKey()); copy.tracked.set(session.tracked.get()); - copy.autoCaptured.set(session.isAutoCaptured()); + copy.autoCaptured = session.isAutoCaptured(); return copy; } @@ -68,7 +68,7 @@ static Session copySession(Session session) { this.id = id; this.startedAt = new Date(startedAt.getTime()); this.user = user; - this.autoCaptured.set(autoCaptured); + this.autoCaptured = autoCaptured; this.apiKey = apiKey; } @@ -198,16 +198,28 @@ Session incrementUnhandledAndCopy() { return copySession(this); } - AtomicBoolean isTracked() { - return tracked; + boolean markTracked() { + return tracked.compareAndSet(false, true); + } + + boolean markResumed() { + return isPaused.compareAndSet(true, false); + } + + void markPaused() { + isPaused.set(true); + } + + boolean isPaused() { + return isPaused.get(); } boolean isAutoCaptured() { - return autoCaptured.get(); + return autoCaptured; } void setAutoCaptured(boolean autoCaptured) { - this.autoCaptured.set(autoCaptured); + this.autoCaptured = autoCaptured; } /** diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java index e9e15b2a2e..9fc2747f5b 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java @@ -120,7 +120,7 @@ void pauseSession() { Session session = currentSession; if (session != null) { - session.isPaused.set(true); + session.markPaused(); updateState(StateEvent.PauseSession.INSTANCE); } } @@ -133,7 +133,7 @@ boolean resumeSession() { session = startSession(false); resumed = false; } else { - resumed = session.isPaused.compareAndSet(true, false); + resumed = session.markResumed(); } if (session != null) { @@ -191,7 +191,7 @@ private boolean trackSessionIfNeeded(final Session session) { session.setDevice(client.getDeviceDataCollector().generateDevice()); boolean deliverSession = callbackState.runOnSessionTasks(session, logger); - if (deliverSession && session.isTracked().compareAndSet(false, true)) { + if (deliverSession && session.markTracked()) { currentSession = session; notifySessionStartObserver(session); flushInMemorySession(session); @@ -205,7 +205,7 @@ private boolean trackSessionIfNeeded(final Session session) { Session getCurrentSession() { Session session = currentSession; - if (session != null && !session.isPaused.get()) { + if (session != null && !session.isPaused()) { return session; } return null; diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt index 729c9b9fb7..e16671559f 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt @@ -10,7 +10,8 @@ sealed class StateEvent { // JvmField allows direct field access optimizations @JvmField val releaseStage: String?, @JvmField val lastRunInfoPath: String, @JvmField val consecutiveLaunchCrashes: Int, - @JvmField val sendThreads: ThreadSendPolicy + @JvmField val sendThreads: ThreadSendPolicy, + @JvmField val maxBreadcrumbs: Int ) : StateEvent() object DeliverPending : StateEvent() diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTest.kt index 73f80449c0..ae7c0f6fcd 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTest.kt @@ -141,7 +141,10 @@ class SessionTest { assertEquals(startedAt, copy.startedAt) assertEquals(getUser(), copy.getUser()) // make a shallow copy assertEquals(isAutoCaptured, copy.isAutoCaptured) - assertEquals(isTracked.get(), copy.isTracked.get()) + assertEquals(markTracked(), copy.markTracked()) + assertEquals(markResumed(), copy.markResumed()) + assertEquals(isPaused, copy.isPaused) + assertEquals(markPaused(), copy.markPaused()) assertEquals(session.unhandledCount, copy.unhandledCount) assertEquals(session.handledCount, copy.handledCount) } diff --git a/bugsnag-android/build.gradle b/bugsnag-android/build.gradle deleted file mode 100644 index 3b6d9059b7..0000000000 --- a/bugsnag-android/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -plugins { - id("bugsnag-build-plugin") -} - -bugsnagBuildOptions { - compilesCode = false -} - -apply plugin: "com.android.library" - -dependencies { - api(project(":bugsnag-android-core")) - api(project(":bugsnag-plugin-android-anr")) - api(project(":bugsnag-plugin-android-ndk")) -} diff --git a/bugsnag-android/build.gradle.kts b/bugsnag-android/build.gradle.kts new file mode 100644 index 0000000000..bd5c5a6162 --- /dev/null +++ b/bugsnag-android/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("bugsnag-build-plugin") + id("com.android.library") +} + +bugsnagBuildOptions { + compilesCode = false +} + +dependencies { + add("api", project(":bugsnag-android-core")) + add("api", project(":bugsnag-plugin-android-anr")) + add("api", project(":bugsnag-plugin-android-ndk")) +} diff --git a/bugsnag-plugin-android-anr/build.gradle b/bugsnag-plugin-android-anr/build.gradle deleted file mode 100644 index 169d3dd223..0000000000 --- a/bugsnag-plugin-android-anr/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -plugins { - id("bugsnag-build-plugin") -} - -bugsnagBuildOptions { - usesNdk = true -} - -apply plugin: "com.android.library" - -dependencies { - api(project(":bugsnag-android-core")) -} - -apply from: "../gradle/kotlin.gradle" diff --git a/bugsnag-plugin-android-anr/build.gradle.kts b/bugsnag-plugin-android-anr/build.gradle.kts new file mode 100644 index 0000000000..9f778ce9f9 --- /dev/null +++ b/bugsnag-plugin-android-anr/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("bugsnag-build-plugin") +} + +bugsnagBuildOptions { + usesNdk = true +} + +apply(plugin = "com.android.library") + +dependencies { + add("api", project(":bugsnag-android-core")) +} diff --git a/bugsnag-plugin-android-exitinfo/build.gradle b/bugsnag-plugin-android-exitinfo/build.gradle deleted file mode 100644 index 835102de1c..0000000000 --- a/bugsnag-plugin-android-exitinfo/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - id("bugsnag-build-plugin") - id "com.google.protobuf" version "0.9.4" -} - -apply plugin: "com.android.library" - -dependencies { - api(project(":bugsnag-android-core")) -} - -apply from: "../gradle/kotlin.gradle" - -dependencies { - implementation 'com.google.protobuf:protobuf-javalite:3.24.2' -} - -android.libraryVariants.configureEach { variant -> - variant.processJavaResourcesProvider.configure { - exclude('**/*.proto') - } -} - -protobuf { - protoc { - artifact = 'com.google.protobuf:protoc:3.24.2' - } - generateProtoTasks { - all().configureEach { task -> - task.builtins { - java { - option "lite" - } - } - } - } -} - -apiValidation.ignoredPackages += ["com.bugsnag.android.repackaged.server.os"] diff --git a/bugsnag-plugin-android-exitinfo/build.gradle.kts b/bugsnag-plugin-android-exitinfo/build.gradle.kts new file mode 100644 index 0000000000..6a5fdc1b5a --- /dev/null +++ b/bugsnag-plugin-android-exitinfo/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + id("bugsnag-build-plugin") + id("com.android.library") + id("com.google.protobuf") version "0.9.4" +} + +dependencies { + api(project(":bugsnag-android-core")) + implementation("com.google.protobuf:protobuf-javalite:3.24.2") +} + +android.libraryVariants.configureEach { + processJavaResourcesProvider { + exclude("**/*.proto") + } +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.24.2" + } + generateProtoTasks { + all().configureEach { + builtins { + create("java") { + option("lite") + } + } + } + } +} + +apiValidation.ignoredPackages += "com.bugsnag.android.repackaged.server.os" diff --git a/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api b/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api index ab910c524c..cdc5a7496c 100644 --- a/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api +++ b/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api @@ -21,7 +21,7 @@ public final class com/bugsnag/android/ndk/NativeBridge : com/bugsnag/android/in public final fun getCurrentNativeApiCallUsage ()Ljava/util/Map; public final fun getSignalUnwindStackFunction ()J public final fun initCallbackCounts (Ljava/util/Map;)V - public final fun install (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZIZI)V + public final fun install (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZIZII)V public final fun notifyAddCallback (Ljava/lang/String;)V public final fun notifyRemoveCallback (Ljava/lang/String;)V public fun onStateChange (Lcom/bugsnag/android/StateEvent;)V diff --git a/bugsnag-plugin-android-ndk/build.gradle b/bugsnag-plugin-android-ndk/build.gradle.kts similarity index 60% rename from bugsnag-plugin-android-ndk/build.gradle rename to bugsnag-plugin-android-ndk/build.gradle.kts index 47dcec3f26..1006109c80 100644 --- a/bugsnag-plugin-android-ndk/build.gradle +++ b/bugsnag-plugin-android-ndk/build.gradle.kts @@ -7,21 +7,19 @@ bugsnagBuildOptions { publishesPrefab = "bugsnag-ndk" } -apply plugin: "com.android.library" +apply(plugin = "com.android.library") dependencies { - api(project(":bugsnag-android-core")) + add("api", project(":bugsnag-android-core")) } -apply from: "../gradle/kotlin.gradle" - afterEvaluate { - tasks.named("prefabReleasePackage") { + tasks.getByName("prefabReleasePackage") { doLast { project.fileTree("build/intermediates/prefab_package/") { include("**/abi.json") }.forEach { file -> - file.text = file.text.replace("c++_static", "none") + file.writeText(file.readText().replace("c++_static", "none")) } } } diff --git a/bugsnag-plugin-android-ndk/detekt-baseline.xml b/bugsnag-plugin-android-ndk/detekt-baseline.xml index dbc3020513..57df3f72f2 100644 --- a/bugsnag-plugin-android-ndk/detekt-baseline.xml +++ b/bugsnag-plugin-android-ndk/detekt-baseline.xml @@ -4,7 +4,7 @@ CyclomaticComplexMethod:NativeBridge.kt$NativeBridge$override fun onStateChange(event: StateEvent) LongMethod:EventOnDiskTests.kt$EventOnDiskTests$@Test fun testEvent() - LongParameterList:NativeBridge.kt$NativeBridge$( apiKey: String, reportingDirectory: String, lastRunInfoPath: String, consecutiveLaunchCrashes: Int, autoDetectNdkCrashes: Boolean, apiLevel: Int, is32bit: Boolean, threadSendPolicy: Int ) + LongParameterList:NativeBridge.kt$NativeBridge$( apiKey: String, reportingDirectory: String, lastRunInfoPath: String, consecutiveLaunchCrashes: Int, autoDetectNdkCrashes: Boolean, apiLevel: Int, is32bit: Boolean, threadSendPolicy: Int, maxBreadcrumbs: Int, ) NestedBlockDepth:NativeBridge.kt$NativeBridge$private fun deliverPendingReports() TooManyFunctions:NativeBridge.kt$NativeBridge : StateObserver UseCheckOrError:ResourceUtils.kt$throw IllegalStateException("Failed to read JSON from $resourceName") diff --git a/bugsnag-plugin-android-ndk/proguard-rules.pro b/bugsnag-plugin-android-ndk/proguard-rules.pro index 3fa50bcdb9..eb3469de29 100644 --- a/bugsnag-plugin-android-ndk/proguard-rules.pro +++ b/bugsnag-plugin-android-ndk/proguard-rules.pro @@ -1,6 +1,7 @@ -keepattributes LineNumberTable,SourceFile -keep class com.bugsnag.android.ndk.OpaqueValue { java.lang.String getJson(); + static java.lang.Object makeSafe(java.lang.Object); } -keep class com.bugsnag.android.ndk.NativeBridge { *; } -keep class com.bugsnag.android.NdkPlugin { *; } diff --git a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt index 3ff0b0e87e..33115f355c 100644 --- a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt +++ b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt @@ -1,6 +1,7 @@ package com.bugsnag.android.ndk import android.os.Build +import com.bugsnag.android.BreadcrumbType import com.bugsnag.android.NativeInterface import com.bugsnag.android.StateEvent import com.bugsnag.android.StateEvent.AddBreadcrumb @@ -51,7 +52,8 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse autoDetectNdkCrashes: Boolean, apiLevel: Int, is32bit: Boolean, - threadSendPolicy: Int + threadSendPolicy: Int, + maxBreadcrumbs: Int, ) external fun startedSession( @@ -62,7 +64,15 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse ) external fun deliverReportAtPath(filePath: String) - external fun addBreadcrumb(name: String, type: String, timestamp: String, metadata: Any) + fun addBreadcrumb(name: String, type: String, timestamp: String, metadata: Any) { + val breadcrumbType = BreadcrumbType.values() + .find { it.toString() == type } + ?: BreadcrumbType.MANUAL + + addBreadcrumb(name, breadcrumbType.toNativeValue(), timestamp, metadata) + } + + private external fun addBreadcrumb(name: String, type: Int, timestamp: String, metadata: Any) external fun addMetadataString(tab: String, key: String, value: String) external fun addMetadataDouble(tab: String, key: String, value: Double) external fun addMetadataBoolean(tab: String, key: String, value: Boolean) @@ -106,12 +116,14 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse event.section, event.key ?: "" ) + is AddBreadcrumb -> addBreadcrumb( event.message, - event.type.toString(), + event.type.toNativeValue(), event.timestamp, - makeSafeMetadata(event.metadata) + event.metadata ) + NotifyHandled -> addHandledEvent() NotifyUnhandled -> addUnhandledEvent() PauseSession -> pausedSession() @@ -121,11 +133,13 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse event.handledCount, event.unhandledCount ) + is UpdateContext -> updateContext(event.context ?: "") is UpdateInForeground -> updateInForeground( event.inForeground, event.contextActivity ?: "" ) + is StateEvent.UpdateLastRunInfo -> updateLastRunInfo(event.consecutiveLaunchCrashes) is StateEvent.UpdateIsLaunching -> { updateIsLaunching(event.isLaunching) @@ -135,32 +149,29 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse bgTaskService.submitTask(TaskType.DEFAULT, this::refreshSymbolTable) } } + is UpdateOrientation -> updateOrientation(event.orientation ?: "") is UpdateUser -> { updateUserId(event.user.id ?: "") updateUserName(event.user.name ?: "") updateUserEmail(event.user.email ?: "") } + is StateEvent.UpdateMemoryTrimEvent -> updateLowMemory( event.isLowMemory, event.memoryTrimLevelDescription ) + is StateEvent.AddFeatureFlag -> addFeatureFlag( event.name, event.variant ) + is StateEvent.ClearFeatureFlag -> clearFeatureFlag(event.name) is StateEvent.ClearFeatureFlags -> clearFeatureFlags() } } - private fun makeSafeMetadata(metadata: Map): Map { - if (metadata.isEmpty()) return metadata - return object : Map by metadata { - override fun get(key: String): Any? = OpaqueValue.makeSafe(metadata[key]) - } - } - private fun isInvalidMessage(msg: Any?): Boolean { if (msg == null || msg !is StateEvent) { return true @@ -209,7 +220,8 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse arg.autoDetectNdkCrashes, Build.VERSION.SDK_INT, is32bit, - arg.sendThreads.ordinal + arg.sendThreads.ordinal, + arg.maxBreadcrumbs ) installed.set(true) } @@ -227,4 +239,21 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse } } } + + /** + * Convert a [BreadcrumbType] to the value expected by the [addBreadcrumb] implementation. This + * is implemented as an exhaustive when so that any changes to the `enum` are picked up and/or + * don't directly impact the implementation. + */ + @Suppress("MagicNumber") // introducing consts would reduce readability + private fun BreadcrumbType.toNativeValue(): Int = when (this) { + BreadcrumbType.ERROR -> 0 + BreadcrumbType.LOG -> 1 + BreadcrumbType.MANUAL -> 2 + BreadcrumbType.NAVIGATION -> 3 + BreadcrumbType.PROCESS -> 4 + BreadcrumbType.REQUEST -> 5 + BreadcrumbType.STATE -> 6 + BreadcrumbType.USER -> 7 + } } diff --git a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt index 426b5baf83..c4383c1655 100644 --- a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt +++ b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/OpaqueValue.kt @@ -7,11 +7,12 @@ import java.io.StringWriter * Marker class for values that are `BSG_METADATA_OPAQUE_VALUE` in the C layer */ internal class OpaqueValue(val json: String) { - companion object { + internal companion object { private const val MAX_NDK_STRING_LENGTH = 64 private const val US_ASCII_MAX_CODEPOINT = 127 + private const val INITIAL_BUFFER_SIZE = 256 - private fun isStringNDKSupported(value: String): Boolean { + fun isStringNDKSupported(value: String): Boolean { // anything over 63 characters is definitely not supported if (value.length >= MAX_NDK_STRING_LENGTH) return false @@ -26,9 +27,9 @@ internal class OpaqueValue(val json: String) { return value.toByteArray().size < MAX_NDK_STRING_LENGTH } - private fun encode(value: Any): String { - val writer = StringWriter() - writer.use { JsonStream(it).value(value, true) } + fun encode(value: Any): String { + val writer = StringWriter(INITIAL_BUFFER_SIZE) + writer.use { JsonStream(it).value(value, false) } return writer.toString() } @@ -37,6 +38,7 @@ internal class OpaqueValue(val json: String) { * is both a compatible type and fits into the available space. This method can return * any one of: `Boolean`, `Number`, `String`, `OpaqueValue` or `null`. */ + @JvmStatic fun makeSafe(value: Any?): Any? = when { value is Boolean -> value value is Number -> value diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c index e2fbd419b4..8b06c9b17d 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c @@ -150,7 +150,7 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_install( JNIEnv *env, jobject _this, jstring _api_key, jstring _event_path, jstring _last_run_info_path, jint consecutive_launch_crashes, jboolean auto_detect_ndk_crashes, jint _api_level, jboolean is32bit, - jint send_threads) { + jint send_threads, jint max_breadcrumbs) { if (!bsg_jni_cache_init(env)) { BUGSNAG_LOG("Could not init JNI jni_cache."); @@ -165,6 +165,14 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_install( bugsnag_env->send_threads = send_threads; bugsnag_env->handling_crash = ATOMIC_VAR_INIT(false); + bugsnag_env->next_event.max_crumb_count = max_breadcrumbs; + bugsnag_env->next_event.breadcrumbs = + calloc(max_breadcrumbs, sizeof(bugsnag_breadcrumb)); + + if (bugsnag_env->next_event.breadcrumbs == NULL) { + goto error; + } + // copy event path to env struct const char *event_path = bsg_safe_get_string_utf_chars(env, _event_path); if (event_path == NULL) { @@ -387,7 +395,7 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_pausedSession( } JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb( - JNIEnv *env, jobject _this, jstring name_, jstring crumb_type, + JNIEnv *env, jobject _this, jstring name_, jint crumb_type, jstring timestamp_, jobject metadata) { if (!bsg_jni_cache->initialized) { @@ -395,28 +403,41 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb( return; } const char *name = bsg_safe_get_string_utf_chars(env, name_); - const char *type = bsg_safe_get_string_utf_chars(env, crumb_type); const char *timestamp = bsg_safe_get_string_utf_chars(env, timestamp_); - if (name != NULL && type != NULL && timestamp != NULL) { + if (name != NULL && timestamp != NULL) { bugsnag_breadcrumb *crumb = calloc(1, sizeof(bugsnag_breadcrumb)); bsg_strncpy(crumb->name, name, sizeof(crumb->name)); bsg_strncpy(crumb->timestamp, timestamp, sizeof(crumb->timestamp)); - if (strcmp(type, "user") == 0) { - crumb->type = BSG_CRUMB_USER; - } else if (strcmp(type, "error") == 0) { + + // the values of crumb_type are defined in + // NativeBridge.BreadcrumbType.toNativeValue() + switch (crumb_type) { + case 0: crumb->type = BSG_CRUMB_ERROR; - } else if (strcmp(type, "log") == 0) { + break; + case 1: crumb->type = BSG_CRUMB_LOG; - } else if (strcmp(type, "navigation") == 0) { + break; + case 2: + crumb->type = BSG_CRUMB_MANUAL; + break; + case 3: crumb->type = BSG_CRUMB_NAVIGATION; - } else if (strcmp(type, "request") == 0) { + break; + case 4: + crumb->type = BSG_CRUMB_PROCESS; + break; + case 5: crumb->type = BSG_CRUMB_REQUEST; - } else if (strcmp(type, "state") == 0) { + break; + case 6: crumb->type = BSG_CRUMB_STATE; - } else if (strcmp(type, "process") == 0) { - crumb->type = BSG_CRUMB_PROCESS; - } else { + break; + case 7: + crumb->type = BSG_CRUMB_USER; + break; + default: crumb->type = BSG_CRUMB_MANUAL; } @@ -428,7 +449,6 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb( free(crumb); } bsg_safe_release_string_utf_chars(env, name_, name); - bsg_safe_release_string_utf_chars(env, crumb_type, type); bsg_safe_release_string_utf_chars(env, timestamp_, timestamp); } diff --git a/bugsnag-plugin-android-ndk/src/main/jni/event.c b/bugsnag-plugin-android-ndk/src/main/jni/event.c index 5ef89aea8b..e76618690b 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/event.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/event.c @@ -353,13 +353,13 @@ void bugsnag_event_set_user(void *event_ptr, const char *id, const char *email, void bsg_event_add_breadcrumb(bugsnag_event *event, bugsnag_breadcrumb *crumb) { int crumb_index; - if (event->crumb_count < BUGSNAG_CRUMBS_MAX) { + if (event->crumb_count < event->max_crumb_count) { crumb_index = event->crumb_count; event->crumb_count++; } else { crumb_index = event->crumb_first_index; event->crumb_first_index = - (event->crumb_first_index + 1) % BUGSNAG_CRUMBS_MAX; + (event->crumb_first_index + 1) % event->max_crumb_count; } bsg_free_opaque_metadata(&(event->breadcrumbs[crumb_index].metadata)); diff --git a/bugsnag-plugin-android-ndk/src/main/jni/event.h b/bugsnag-plugin-android-ndk/src/main/jni/event.h index c129743439..d92324f394 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/event.h +++ b/bugsnag-plugin-android-ndk/src/main/jni/event.h @@ -12,12 +12,6 @@ */ #define BUGSNAG_METADATA_MAX 128 #endif -#ifndef BUGSNAG_CRUMBS_MAX -/** - * Max number of breadcrumbs in an event. Configures a default if not defined. - */ -#define BUGSNAG_CRUMBS_MAX 50 -#endif #ifndef BUGSNAG_DEFAULT_EX_TYPE /** * Type assigned to exceptions. Configures a default if not defined. @@ -34,7 +28,7 @@ /** * Version of the bugsnag_event struct. Serialized to report header. */ -#define BUGSNAG_EVENT_VERSION 13 +#define BUGSNAG_EVENT_VERSION 14 #ifdef __cplusplus extern "C" { @@ -229,7 +223,9 @@ typedef struct { // Breadcrumbs are a ring; the first index moves as the // structure is filled and replaced. int crumb_first_index; - bugsnag_breadcrumb breadcrumbs[BUGSNAG_CRUMBS_MAX]; + // the maximum number of breadcrumbs that can be placed in the buffer + int max_crumb_count; + bugsnag_breadcrumb *breadcrumbs; char context[64]; bugsnag_severity severity; diff --git a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c index 21b60ec282..b5909e885d 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c @@ -218,6 +218,8 @@ bool bsg_jni_cache_init(JNIEnv *env) { CACHE_CLASS(OpaqueValue, "com/bugsnag/android/ndk/OpaqueValue"); CACHE_METHOD(OpaqueValue, OpaqueValue_getJson, "getJson", "()Ljava/lang/String;"); + CACHE_STATIC_METHOD(OpaqueValue, OpaqueValue_makeSafe, "makeSafe", + "(Ljava/lang/Object;)Ljava/lang/Object;"); CACHE_ENUM_CONSTANT(ErrorType_C, "com/bugsnag/android/ErrorType", "C"); diff --git a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h index 9b3200379a..44253a390c 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h +++ b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h @@ -89,6 +89,7 @@ typedef struct { jclass OpaqueValue; jmethodID OpaqueValue_getJson; + jmethodID OpaqueValue_makeSafe; jobject ErrorType_C; } bsg_jni_cache_t; diff --git a/bugsnag-plugin-android-ndk/src/main/jni/metadata.c b/bugsnag-plugin-android-ndk/src/main/jni/metadata.c index 818e0f7866..f466322c9a 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/metadata.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/metadata.c @@ -343,24 +343,29 @@ static void populate_metadata_value(JNIEnv *env, bugsnag_metadata *dst, return; } - if (bsg_safe_is_instance_of(env, _value, bsg_jni_cache->number)) { + jobject safe_value = bsg_safe_call_static_object_method( + env, bsg_jni_cache->OpaqueValue, bsg_jni_cache->OpaqueValue_makeSafe, + _value); + + if (bsg_safe_is_instance_of(env, safe_value, bsg_jni_cache->number)) { // add a double metadata value double value = bsg_safe_call_double_method( - env, _value, bsg_jni_cache->number_double_value); + env, safe_value, bsg_jni_cache->number_double_value); bsg_add_metadata_value_double(dst, section, name, value); - } else if (bsg_safe_is_instance_of(env, _value, bsg_jni_cache->Boolean)) { + } else if (bsg_safe_is_instance_of(env, safe_value, bsg_jni_cache->Boolean)) { // add a boolean metadata value bool value = bsg_safe_call_boolean_method( - env, _value, bsg_jni_cache->Boolean_booleanValue); + env, safe_value, bsg_jni_cache->Boolean_booleanValue); bsg_add_metadata_value_bool(dst, section, name, value); - } else if (bsg_safe_is_instance_of(env, _value, bsg_jni_cache->String)) { - const char *value = bsg_safe_get_string_utf_chars(env, _value); + } else if (bsg_safe_is_instance_of(env, safe_value, bsg_jni_cache->String)) { + const char *value = bsg_safe_get_string_utf_chars(env, safe_value); if (value != NULL) { bsg_add_metadata_value_str(dst, section, name, value); } - } else if (bsg_safe_is_instance_of(env, _value, bsg_jni_cache->OpaqueValue)) { + } else if (bsg_safe_is_instance_of(env, safe_value, + bsg_jni_cache->OpaqueValue)) { jstring _json = bsg_safe_call_object_method( - env, _value, bsg_jni_cache->OpaqueValue_getJson); + env, safe_value, bsg_jni_cache->OpaqueValue_getJson); const char *json = bsg_safe_get_string_utf_chars(env, _json); if (json != NULL) { @@ -513,8 +518,8 @@ void bsg_populate_metadata(JNIEnv *env, bugsnag_metadata *dst, void bsg_populate_crumb_metadata(JNIEnv *env, bugsnag_breadcrumb *crumb, jobject metadata) { - jobject keyset = NULL; - jobject keylist = NULL; + jobject entryset = NULL; + jobject entries = NULL; if (metadata == NULL) { goto exit; @@ -526,43 +531,48 @@ void bsg_populate_crumb_metadata(JNIEnv *env, bugsnag_breadcrumb *crumb, // get size of metadata map jint map_size = bsg_safe_call_int_method(env, metadata, bsg_jni_cache->Map_size); - if (map_size == -1) { + if (map_size <= 0) { goto exit; } // create a list of metadata keys - keyset = - bsg_safe_call_object_method(env, metadata, bsg_jni_cache->Map_keySet); - if (keyset == NULL) { + entryset = + bsg_safe_call_object_method(env, metadata, bsg_jni_cache->Map_entrySet); + if (entryset == NULL) { goto exit; } - keylist = bsg_safe_new_object(env, bsg_jni_cache->ArrayList, - bsg_jni_cache->ArrayList_constructor_collection, - keyset); - if (keylist == NULL) { + entries = + bsg_safe_call_object_method(env, entryset, bsg_jni_cache->Set_iterator); + if (entries == NULL) { goto exit; } - for (int i = 0; i < map_size; i++) { - jstring _key = bsg_safe_call_object_method( - env, keylist, bsg_jni_cache->ArrayList_get, (jint)i); - jobject _value = bsg_safe_call_object_method(env, metadata, - bsg_jni_cache->Map_get, _key); - - if (_key != NULL && _value != NULL) { - const char *key = bsg_safe_get_string_utf_chars(env, _key); - if (key != NULL) { - populate_metadata_value(env, &crumb->metadata, "metaData", key, _value); - bsg_safe_release_string_utf_chars(env, _key, key); + while (bsg_safe_call_boolean_method(env, entries, + bsg_jni_cache->Iterator_hasNext)) { + (*env)->PushLocalFrame(env, 3); + { + jobject entry = bsg_safe_call_object_method(env, entries, + bsg_jni_cache->Iterator_next); + jstring _key = bsg_safe_call_object_method( + env, entry, bsg_jni_cache->MapEntry_getKey); + jobject _value = bsg_safe_call_object_method( + env, entry, bsg_jni_cache->MapEntry_getValue); + + if (_key != NULL && _value != NULL) { + const char *key = bsg_safe_get_string_utf_chars(env, _key); + if (key != NULL) { + populate_metadata_value(env, &crumb->metadata, "metaData", key, + _value); + bsg_safe_release_string_utf_chars(env, _key, key); + } } } - bsg_safe_delete_local_ref(env, _key); - bsg_safe_delete_local_ref(env, _value); + (*env)->PopLocalFrame(env, NULL); } exit: - bsg_safe_delete_local_ref(env, keyset); - bsg_safe_delete_local_ref(env, keylist); + bsg_safe_delete_local_ref(env, entries); + bsg_safe_delete_local_ref(env, entryset); } void bsg_populate_event(JNIEnv *env, bugsnag_event *event) { diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_reader.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_reader.c index 729ac9b30f..021de3e18e 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_reader.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_reader.c @@ -2,6 +2,7 @@ #include "../../event.h" #include "../string.h" +#include "utils/logger.h" #include #include @@ -9,7 +10,7 @@ #include #include -const int BSG_MIGRATOR_CURRENT_VERSION = 13; +const int BSG_MIGRATOR_CURRENT_VERSION = 14; void bsg_read_feature_flags(int fd, bool expect_verification, bsg_feature_flag **out_feature_flags, @@ -21,6 +22,8 @@ void bsg_read_opaque_breadcrumb_metadata(int fd, bugsnag_breadcrumb *breadcrumbs, int crumb_count); +bool bsg_read_breadcrumbs(int fd, bugsnag_event *event); + bsg_report_header *bsg_report_header_read(int fd) { bsg_report_header *header = calloc(1, sizeof(bsg_report_header)); ssize_t len = read(fd, header, sizeof(bsg_report_header)); @@ -55,10 +58,13 @@ bugsnag_event *bsg_read_event(char *filepath) { ssize_t len = read(fd, event, event_size); if (len != event_size) { - free(event); - return NULL; + goto error; } + // read the breadcrumbs + if (!bsg_read_breadcrumbs(fd, event)) { + goto error; + } // read the feature flags, if possible bsg_read_feature_flags(fd, true, &event->feature_flags, &event->feature_flag_count); @@ -67,6 +73,31 @@ bugsnag_event *bsg_read_event(char *filepath) { event->crumb_count); return event; +error: + free(event); + return NULL; +} + +bool bsg_read_breadcrumbs(int fd, bugsnag_event *event) { + bugsnag_breadcrumb *breadcrumbs = + calloc(event->max_crumb_count, sizeof(bugsnag_breadcrumb)); + if (breadcrumbs == NULL) { + goto error; + } + const size_t bytes_to_read = + event->max_crumb_count * sizeof(bugsnag_breadcrumb); + const ssize_t read_count = read(fd, breadcrumbs, bytes_to_read); + if (read_count != bytes_to_read) { + goto error; + } + + event->breadcrumbs = breadcrumbs; + return true; +error: + event->breadcrumbs = NULL; + event->crumb_count = 0; + event->crumb_first_index = 0; + return false; } static char *read_string(int fd) { diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c index b81987e78a..df787e479d 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c @@ -8,11 +8,13 @@ #include "buffered_writer.h" #include "utils/seqlock.h" +bool bsg_write_breadcrumbs(bugsnag_event *event, bsg_buffered_writer *writer); bool bsg_write_feature_flags(bugsnag_event *event, bsg_buffered_writer *writer); bool bsg_write_opaque_metadata(bugsnag_event *event, bsg_buffered_writer *writer); +bool bsg_write_breadcrumbs(bugsnag_event *event, bsg_buffered_writer *writer); bool bsg_report_header_write(bsg_report_header *header, int fd) { ssize_t len = write(fd, header, sizeof(bsg_report_header)); @@ -30,7 +32,9 @@ bool bsg_event_write(bsg_environment *env) { bsg_report_header_write(&env->report_header, writer.fd) && // add cached event info writer.write(&writer, &env->next_event, sizeof(bugsnag_event)) && - // append feature flags after event structure + // append the breadcrumbs after the event structure + bsg_write_breadcrumbs(&env->next_event, &writer) && + // append feature flags bsg_write_feature_flags(&env->next_event, &writer) && // append opaque metadata after the feature flags bsg_write_opaque_metadata(&env->next_event, &writer); @@ -85,6 +89,11 @@ static bool write_feature_flag(bsg_buffered_writer *writer, return true; } +bool bsg_write_breadcrumbs(bugsnag_event *event, bsg_buffered_writer *writer) { + return writer->write(writer, event->breadcrumbs, + sizeof(bugsnag_breadcrumb) * event->max_crumb_count); +} + extern bsg_seqlock_t bsg_feature_flag_lock; bool bsg_write_feature_flags(bugsnag_event *event, diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/json_writer.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/json_writer.c index 390439c56d..ad3c356a5b 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/json_writer.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/json_writer.c @@ -365,7 +365,7 @@ void bsg_serialize_breadcrumbs(const bugsnag_event *event, JSON_Array *crumbs) { bsg_crumb_type_string(breadcrumb.type)); bsg_serialize_breadcrumb_metadata(breadcrumb.metadata, crumb); current_index++; - if (current_index == BUGSNAG_CRUMBS_MAX) { + if (current_index == event->max_crumb_count) { current_index = 0; } } diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/main.c b/bugsnag-plugin-android-ndk/src/test/cpp/main.c index 76c03c88f8..68624d369a 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/main.c +++ b/bugsnag-plugin-android-ndk/src/test/cpp/main.c @@ -191,6 +191,8 @@ JNIEXPORT jstring JNICALL Java_com_bugsnag_android_ndk_BreadcrumbStateSerializationTest_run(JNIEnv *env, jobject thiz) { bugsnag_event *event = calloc(1, sizeof(bugsnag_event)); + event->max_crumb_count = 50; + event->breadcrumbs = calloc(event->max_crumb_count, sizeof(bugsnag_breadcrumb)); loadBreadcrumbsTestCase(event); JSON_Value *eventVal = json_value_init_array(); JSON_Array *eventAry = json_value_get_array(eventVal); diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/migrations/EventOnDiskTests.cpp b/bugsnag-plugin-android-ndk/src/test/cpp/migrations/EventOnDiskTests.cpp index 8ba09f69c2..18a06b4acf 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/migrations/EventOnDiskTests.cpp +++ b/bugsnag-plugin-android-ndk/src/test/cpp/migrations/EventOnDiskTests.cpp @@ -41,7 +41,9 @@ static void *create_full_event() { event->app.version_code = 8139512718; // breadcrumbs - auto max = 50; + event->max_crumb_count = 50; + event->breadcrumbs = new bugsnag_breadcrumb[event->max_crumb_count]; + auto max = event->max_crumb_count; event->crumb_first_index = 2; // test the circular buffer logic char name[30]; for (int i = event->crumb_first_index; i < max; i++) { @@ -132,7 +134,7 @@ static void *create_full_event() { static const char *write_event(JNIEnv *env, jstring temp_file, void *(event_generator)()) { auto event_ctx = (bsg_environment *)calloc(1, sizeof(bsg_environment)); - event_ctx->report_header.version = 13; + event_ctx->report_header.version = BUGSNAG_EVENT_VERSION; const char *path = (*env).GetStringUTFChars(temp_file, nullptr); sprintf(event_ctx->next_event_path, "%s", path); @@ -185,6 +187,7 @@ Java_com_bugsnag_android_ndk_migrations_EventOnDiskTests_generateAndStoreEvent( free(parsed_event->feature_flags[i].name); free(parsed_event->feature_flags[i].variant); } + free(parsed_event->breadcrumbs); free(parsed_event->feature_flags); free(parsed_event); diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c b/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c index 7ddf7da67f..5f5a9588c8 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c +++ b/bugsnag-plugin-android-ndk/src/test/cpp/test_breadcrumbs.c @@ -20,6 +20,9 @@ bugsnag_breadcrumb *init_breadcrumb(const char *name, const char *message, bugsn TEST test_add_breadcrumb(void) { bugsnag_event *event = calloc(1, sizeof(bugsnag_event)); + event->max_crumb_count = 50; + event->breadcrumbs = + calloc(event->max_crumb_count, sizeof(bugsnag_breadcrumb)); bugsnag_breadcrumb *crumb = init_breadcrumb("stroll", "this is a drill.", BSG_CRUMB_USER); bsg_event_add_breadcrumb(event, crumb); ASSERT_EQ(1, event->crumb_count); @@ -39,6 +42,7 @@ TEST test_add_breadcrumb(void) { ASSERT(strcmp("message", event->breadcrumbs[1].metadata.values[0].name) == 0); ASSERT(strcmp("this is not a drill.", event->breadcrumbs[1].metadata.values[0].char_value) == 0); + free(event->breadcrumbs); free(event); free(crumb2); PASS(); @@ -46,6 +50,9 @@ TEST test_add_breadcrumb(void) { TEST test_add_breadcrumbs_over_max(void) { bugsnag_event *event = calloc(1, sizeof(bugsnag_event)); + event->max_crumb_count = 50; + event->breadcrumbs = + calloc(event->max_crumb_count, sizeof(bugsnag_breadcrumb)); int breadcrumb_count = 64; for (int i=0; i < breadcrumb_count; i++) { @@ -59,7 +66,7 @@ TEST test_add_breadcrumbs_over_max(void) { } // assertions assume that the crumb count is always 50 - ASSERT_EQ(BUGSNAG_CRUMBS_MAX, event->crumb_count); + ASSERT_EQ(event->max_crumb_count, event->crumb_count); ASSERT_EQ(14, event->crumb_first_index); ASSERT_STR_EQ("crumb: 50", event->breadcrumbs[0].name); @@ -71,6 +78,7 @@ TEST test_add_breadcrumbs_over_max(void) { ASSERT_STR_EQ("crumb: 14", event->breadcrumbs[14].name); ASSERT_STR_EQ("crumb: 15", event->breadcrumbs[15].name); ASSERT_STR_EQ("crumb: 16", event->breadcrumbs[16].name); + free(event->breadcrumbs); free(event); PASS(); } diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/test_serializer.c b/bugsnag-plugin-android-ndk/src/test/cpp/test_serializer.c index 69da848637..682b8b2e71 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/test_serializer.c +++ b/bugsnag-plugin-android-ndk/src/test/cpp/test_serializer.c @@ -108,12 +108,12 @@ void loadBreadcrumbsTestCase(bugsnag_event *event) { bugsnag_breadcrumb *crumb = calloc(1, sizeof(bugsnag_breadcrumb)); memset(crumb, 0, sizeof(bugsnag_breadcrumb)); event->crumb_count = 4; - event->crumb_first_index = BUGSNAG_CRUMBS_MAX - 2; + event->crumb_first_index = event->max_crumb_count - 2; // ensure that serialization loop is covered by test // first breadcrumb - crumb = &event->breadcrumbs[BUGSNAG_CRUMBS_MAX - 2]; + crumb = &event->breadcrumbs[event->max_crumb_count - 2]; crumb->type = BSG_CRUMB_USER; strcpy(crumb->name, "Jane"); strcpy(crumb->timestamp, "2018-10-08T12:07:09Z"); @@ -127,7 +127,7 @@ void loadBreadcrumbsTestCase(bugsnag_event *event) { strcpy(data->values[0].char_value, "Foo"); // second breadcrumb - crumb = &event->breadcrumbs[BUGSNAG_CRUMBS_MAX - 1]; + crumb = &event->breadcrumbs[event->max_crumb_count - 1]; crumb->type = BSG_CRUMB_MANUAL; strcpy(crumb->name, "Something went wrong"); strcpy(crumb->timestamp, "2018-10-08T12:07:11Z"); diff --git a/bugsnag-plugin-android-ndk/src/test/cpp/test_utils_serialize.c b/bugsnag-plugin-android-ndk/src/test/cpp/test_utils_serialize.c index 15953ecd34..cd1835580d 100644 --- a/bugsnag-plugin-android-ndk/src/test/cpp/test_utils_serialize.c +++ b/bugsnag-plugin-android-ndk/src/test/cpp/test_utils_serialize.c @@ -87,6 +87,9 @@ void generate_basic_report(bugsnag_event *event) { bugsnag_event *bsg_generate_event(void) { bugsnag_event *report = calloc(1, sizeof(bugsnag_event)); + report->max_crumb_count = 50; + report->breadcrumbs = + calloc(report->max_crumb_count, sizeof(bugsnag_breadcrumb)); strcpy(report->grouping_hash, "foo-hash"); strcpy(report->api_key, "5d1e5fbd39a74caa1200142706a90b20"); strcpy(report->context, "SomeActivity"); @@ -177,6 +180,7 @@ TEST test_report_to_file(void) { strcpy(env->report_header.os_build, "macOS Sierra"); strcpy(env->next_event_path, SERIALIZE_TEST_FILE); ASSERT(bsg_serialize_event_to_file(env)); + free(report->breadcrumbs); free(report); free(env); PASS(); @@ -193,6 +197,7 @@ TEST test_report_with_feature_flags_to_file(void) { strcpy(env->report_header.os_build, "macOS Sierra"); strcpy(env->next_event_path, SERIALIZE_TEST_FILE); ASSERT(bsg_serialize_event_to_file(env)); + free(report->breadcrumbs); free(report); free(env); PASS(); @@ -212,6 +217,7 @@ TEST test_file_to_report(void) { ASSERT(report != NULL); ASSERT(strcmp("SIGBUS", report->error.errorClass) == 0); ASSERT(strcmp("POSIX is serious about oncoming traffic", report->error.errorMessage) == 0); + free(generated_report->breadcrumbs); free(generated_report); free(env); free(report); @@ -234,6 +240,7 @@ TEST test_report_with_feature_flags_from_file(void) { ASSERT_EQ(2, event->feature_flag_count); + free(report->breadcrumbs); free(report); free(env); free(event); @@ -259,6 +266,7 @@ TEST test_report_with_opaque_metadata_from_file(void) { ASSERT_EQ(BSG_METADATA_OPAQUE_VALUE, bugsnag_event_has_metadata(event, "opaque", "map")); ASSERT_EQ(BSG_METADATA_OPAQUE_VALUE, bugsnag_event_has_metadata(event, "opaque", "list")); + free(report->breadcrumbs); free(report); free(env); free(event); @@ -271,6 +279,7 @@ JSON_Value *bsg_generate_json(void) { char *json = bsg_serialize_event_to_json_string(event); JSON_Value *root_value = json_parse_string(json); free(json); + free(event->breadcrumbs); free(event); return root_value; } diff --git a/bugsnag-plugin-android-okhttp/build.gradle b/bugsnag-plugin-android-okhttp/build.gradle deleted file mode 100644 index 0cee87d116..0000000000 --- a/bugsnag-plugin-android-okhttp/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id("bugsnag-build-plugin") -} - -apply plugin: "com.android.library" - -dependencies { - api(project(":bugsnag-android-core")) -} - -apply from: "../gradle/kotlin.gradle" - -dependencies { - compileOnly("com.squareup.okhttp3:okhttp:4.9.1") { - exclude group: "org.jetbrains.kotlin" - } - - testImplementation("com.squareup.okhttp3:mockwebserver:4.9.1") { - exclude group: "org.jetbrains.kotlin" - } -} diff --git a/bugsnag-plugin-android-okhttp/build.gradle.kts b/bugsnag-plugin-android-okhttp/build.gradle.kts new file mode 100644 index 0000000000..9bfba99091 --- /dev/null +++ b/bugsnag-plugin-android-okhttp/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("bugsnag-build-plugin") + id("com.android.library") +} + +dependencies { + add("api", project(":bugsnag-android-core")) + + add("compileOnly", "com.squareup.okhttp3:okhttp:4.9.1") { + exclude(group = "org.jetbrains.kotlin") + } + + add("testImplementation", "com.squareup.okhttp3:mockwebserver:4.9.1") { + exclude(group = "org.jetbrains.kotlin") + } +} diff --git a/bugsnag-plugin-react-native/build.gradle b/bugsnag-plugin-react-native/build.gradle deleted file mode 100644 index 345adbf3ae..0000000000 --- a/bugsnag-plugin-react-native/build.gradle +++ /dev/null @@ -1,9 +0,0 @@ -plugins { - id("bugsnag-build-plugin") -} - -apply plugin: "com.android.library" - -dependencies { - api(project(":bugsnag-android-core")) -} diff --git a/bugsnag-plugin-react-native/build.gradle.kts b/bugsnag-plugin-react-native/build.gradle.kts new file mode 100644 index 0000000000..da00b5fbe3 --- /dev/null +++ b/bugsnag-plugin-react-native/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("bugsnag-build-plugin") + id("com.android.library") +} + +dependencies { + add("api", project(":bugsnag-android-core")) +} diff --git a/build.gradle b/build.gradle deleted file mode 100644 index c6dcc94a09..0000000000 --- a/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -import com.bugsnag.android.Versions - -buildscript { - repositories { - google() - mavenCentral() - maven { url "https://plugins.gradle.org/m2/" } - } - dependencies { - classpath "com.android.tools.build:gradle:${Versions.androidGradlePlugin}" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" - classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:${Versions.detektPlugin}" - classpath "org.jetbrains.dokka:dokka-gradle-plugin:${Versions.dokkaPlugin}" - classpath "org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktlintPlugin}" - classpath "androidx.benchmark:benchmark-gradle-plugin:${Versions.benchmarkPlugin}" - } -} - -plugins { - id "com.github.hierynomus.license" version "0.16.1" - id "org.jetbrains.kotlinx.binary-compatibility-validator" version "0.13.1" apply false -} - -allprojects { - repositories { - google() - mavenCentral() - } - - gradle.projectsEvaluated { - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:all" << "-Werror" - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000..07f756315d --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,34 @@ +buildscript { + repositories { + google() + mavenCentral() + maven(url = "https://plugins.gradle.org/m2/") + } + + dependencies { + classpath("com.android.tools.build:gradle:7.0.4") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10") + classpath("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.1") + classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.0") + classpath("org.jlleitschuh.gradle:ktlint-gradle:10.2.0") + classpath("androidx.benchmark:benchmark-gradle-plugin:1.1.1") + } +} + +plugins { + id("com.github.hierynomus.license") version "0.16.1" + id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.13.1" apply false +} + +allprojects { + repositories { + google() + mavenCentral() + } + + gradle.projectsEvaluated { + tasks.withType { + options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror")) + } + } +} \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index f34c558161..901628b472 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -20,4 +20,5 @@ repositories { dependencies { compileOnly(gradleApi()) implementation("com.android.tools.build:gradle:7.0.2") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10") } diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt index dc7571d57d..3f0d29e096 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPlugin.kt @@ -6,6 +6,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.dependencies +import org.jetbrains.kotlin.gradle.dsl.KotlinCompile import java.io.File /** @@ -39,7 +40,7 @@ class BugsnagBuildPlugin : Plugin { // load 3rd party gradle plugins project.applyPlugins(bugsnag) - val android = project.extensions.getByType(BaseExtension::class.java) + val android = project.extensions.getByType(LibraryExtension::class.java) android.apply { configureDefaults() configureAndroidLint(project) @@ -50,11 +51,13 @@ class BugsnagBuildPlugin : Plugin { configureNdk(project) bugsnag.publishesPrefab?.let { prefabModuleName -> - (android as? LibraryExtension)?.run { - configurePrefabPublishing(prefabModuleName) - } + configurePrefabPublishing(prefabModuleName) } } + + bugsnag.androidConfiguration.forEach { config -> + config(android) + } } // add 3rd party dependencies to the project @@ -65,8 +68,10 @@ class BugsnagBuildPlugin : Plugin { project.apply(from = project.file("../gradle/license-check.gradle")) if (bugsnag.compilesCode) { + project.configureKotlinOptions() + project.configureCheckstyle() + project.apply(from = project.file("../gradle/detekt.gradle")) - project.apply(from = project.file("../gradle/checkstyle.gradle")) } } @@ -175,6 +180,21 @@ class BugsnagBuildPlugin : Plugin { } } + private fun Project.configureKotlinOptions() { + tasks.withType(KotlinCompile::class.java).configureEach { + kotlinOptions { + allWarningsAsErrors = true + apiVersion = Versions.kotlinLang + languageVersion = Versions.kotlinLang + freeCompilerArgs += listOf( + "-Xno-call-assertions", + "-Xno-receiver-assertions", + "-Xno-param-assertions" + ) + } + } + } + /** * Configures Android project defaults such as minSdkVersion. */ @@ -197,6 +217,7 @@ class BugsnagBuildPlugin : Plugin { plugins.apply("com.github.hierynomus.license") if (bugsnag.compilesCode) { + plugins.apply("checkstyle") plugins.apply("kotlin-android") plugins.apply("io.gitlab.arturbosch.detekt") plugins.apply("org.jlleitschuh.gradle.ktlint") diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPluginExtension.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPluginExtension.kt index c1eabb8def..d2637be468 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPluginExtension.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/BugsnagBuildPluginExtension.kt @@ -1,5 +1,6 @@ package com.bugsnag.android +import com.android.build.api.dsl.LibraryExtension import org.gradle.api.model.ObjectFactory /** @@ -9,6 +10,8 @@ import org.gradle.api.model.ObjectFactory */ open class BugsnagBuildPluginExtension(@Suppress("UNUSED_PARAMETER") objects: ObjectFactory) { + internal val androidConfiguration = ArrayList Unit>() + /** * Whether this project compiles code or not. If this is set to false then unnecessary * plugins are not applied, which speeds up the build. By default this is enabled. @@ -25,4 +28,7 @@ open class BugsnagBuildPluginExtension(@Suppress("UNUSED_PARAMETER") objects: Ob */ open var publishesPrefab: String? = null + fun android(config: LibraryExtension.() -> Unit) { + androidConfiguration.add(config) + } } diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/Checkstyle.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/Checkstyle.kt new file mode 100644 index 0000000000..989a9ae6ad --- /dev/null +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/Checkstyle.kt @@ -0,0 +1,23 @@ +package com.bugsnag.android + +import org.gradle.api.Project +import org.gradle.api.plugins.quality.Checkstyle +import org.gradle.api.plugins.quality.CheckstyleExtension +import org.gradle.kotlin.dsl.get + +fun Project.configureCheckstyle() { + extensions.getByType(CheckstyleExtension::class.java).apply { + toolVersion = "8.18" + } + + val checkstyle = tasks.register("checkstyle", Checkstyle::class.java) { + configFile = rootProject.file("config/checkstyle/checkstyle.xml") + source = fileTree("src/") { + include("**/*.java") + exclude("**/external/**/*.java") + } + classpath = files() + } + + tasks["check"].dependsOn(checkstyle) +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt index 6e672819bb..ae6b8ecc45 100644 --- a/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt +++ b/buildSrc/src/main/kotlin/com/bugsnag/android/Versions.kt @@ -12,6 +12,7 @@ object Versions { val ndk = "23.1.7779620" val java = JavaVersion.VERSION_1_8 val kotlin = "1.5.10" + val kotlinLang = "1.5" // plugins val androidGradlePlugin = "7.0.4" diff --git a/examples/sdk-app-example/app/build.gradle b/examples/sdk-app-example/app/build.gradle index d03a5b6c74..28a225a86b 100644 --- a/examples/sdk-app-example/app/build.gradle +++ b/examples/sdk-app-example/app/build.gradle @@ -2,13 +2,13 @@ apply plugin: "com.android.application" apply plugin: "kotlin-android" android { - compileSdk 31 + compileSdk 34 ndkVersion "21.4.7075529" defaultConfig { applicationId "com.example.bugsnag.android" minSdkVersion 16 - targetSdkVersion 31 + targetSdkVersion 34 versionCode 1 versionName "1.0" } @@ -38,15 +38,16 @@ android { buildFeatures.prefab = true packagingOptions.jniLibs.pickFirsts += ["**/libbugsnag-ndk.so"] + namespace 'com.example.bugsnag.android' } dependencies { - implementation "com.bugsnag:bugsnag-android:6.4.0" - implementation "com.bugsnag:bugsnag-plugin-android-okhttp:6.4.0" + implementation "com.bugsnag:bugsnag-android:6.5.0" + implementation "com.bugsnag:bugsnag-plugin-android-okhttp:6.5.0" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "androidx.appcompat:appcompat:1.4.0" - implementation "com.google.android.material:material:1.4.0" - implementation "com.squareup.okhttp3:okhttp:4.10.0" + implementation "androidx.appcompat:appcompat:1.6.1" + implementation "com.google.android.material:material:1.11.0" + implementation "com.squareup.okhttp3:okhttp:4.12.0" } apply plugin: "com.bugsnag.android.gradle" diff --git a/examples/sdk-app-example/app/proguard-rules.pro b/examples/sdk-app-example/app/proguard-rules.pro index f1b424510d..2f9dc5a47e 100644 --- a/examples/sdk-app-example/app/proguard-rules.pro +++ b/examples/sdk-app-example/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/examples/sdk-app-example/app/src/main/AndroidManifest.xml b/examples/sdk-app-example/app/src/main/AndroidManifest.xml index 9e3bd6979b..5017380f20 100644 --- a/examples/sdk-app-example/app/src/main/AndroidManifest.xml +++ b/examples/sdk-app-example/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> diff --git a/examples/sdk-app-example/build.gradle b/examples/sdk-app-example/build.gradle index bd8394d830..bf66353fdf 100644 --- a/examples/sdk-app-example/build.gradle +++ b/examples/sdk-app-example/build.gradle @@ -6,9 +6,9 @@ buildscript { mavenLocal() } dependencies { - classpath "com.android.tools.build:gradle:7.4.2" + classpath "com.android.tools.build:gradle:8.3.2" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "com.bugsnag:bugsnag-android-gradle-plugin:7.1.0" + classpath "com.bugsnag:bugsnag-android-gradle-plugin:8.+" } } diff --git a/examples/sdk-app-example/gradle.properties b/examples/sdk-app-example/gradle.properties index b7aa9d48f5..fa41b5bf20 100644 --- a/examples/sdk-app-example/gradle.properties +++ b/examples/sdk-app-example/gradle.properties @@ -2,3 +2,6 @@ android.useAndroidX=true org.gradle.jvmargs=-Xmx4096m org.gradle.parallel=true kotlin.code.style=official +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/examples/sdk-app-example/gradle/wrapper/gradle-wrapper.properties b/examples/sdk-app-example/gradle/wrapper/gradle-wrapper.properties index 068cdb2dc2..e411586a54 100644 --- a/examples/sdk-app-example/gradle/wrapper/gradle-wrapper.properties +++ b/examples/sdk-app-example/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/features/fixtures/mazerunner/app/build.gradle b/features/fixtures/mazerunner/app/build.gradle index 79eab90a7a..aab4b30864 100644 --- a/features/fixtures/mazerunner/app/build.gradle +++ b/features/fixtures/mazerunner/app/build.gradle @@ -8,13 +8,15 @@ android { ndkVersion parent.ext.ndkVersion defaultConfig { - minSdkVersion 16 + minSdkVersion 17 targetSdkVersion 31 versionCode 34 versionName "1.1.14" manifestPlaceholders = [ // omit any of the following placeholders to use the default values bugsnagApiKey: "abc12312312312312312312312312312", + bugsnagPerformanceApiKey: System.getenv("BUGSNAG_PERFORMANCE_API_KEY") + ?: "abc12312312312312312312312312312", bugsnagAppType: "test", bugsnagAppVersion: "7.5.3", bugsnagAutoDetectErrors: true, @@ -90,8 +92,9 @@ dependencies { } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "androidx.annotation:annotation:1.2.0" + implementation "com.bugsnag:bugsnag-android-performance:1.2.2" } apply from: "../bugsnag-dependency.gradle" apply from: "../../../../gradle/detekt.gradle" -apply from: "../../../../gradle/checkstyle.gradle" +apply from: "../gradle/checkstyle.gradle" diff --git a/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml b/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml index 1e4571c14e..d0439114c9 100644 --- a/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml +++ b/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ /> + diff --git a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt index ad586c4f5d..f07e7fd46c 100644 --- a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt +++ b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt @@ -3,6 +3,9 @@ package com.bugsnag.android.mazerunner import android.app.Application import android.os.Build import android.os.StrictMode +import com.bugsnag.android.performance.BugsnagPerformance +import com.bugsnag.android.performance.PerformanceConfiguration +import com.bugsnag.android.performance.internal.InternalDebug class MazerunnerApp : Application() { @@ -11,6 +14,8 @@ class MazerunnerApp : Application() { triggerStartupAnrIfRequired() setupNonSdkUsageStrictMode() triggerManualSessionIfRequired() + InternalDebug.spanBatchSizeSendTriggerPoint = 1 + BugsnagPerformance.start(PerformanceConfiguration.load(this)) } /** diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/build.gradle b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/build.gradle index 341504a053..3d2486d0c1 100644 --- a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/build.gradle +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/build.gradle @@ -46,4 +46,4 @@ dependencies { } apply from: "../../../../gradle/detekt.gradle" -apply from: "../../../../gradle/checkstyle.gradle" +apply from: "../gradle/checkstyle.gradle" diff --git a/features/fixtures/mazerunner/cxx-scenarios/build.gradle b/features/fixtures/mazerunner/cxx-scenarios/build.gradle index 9e5c2da16b..ca3600068b 100644 --- a/features/fixtures/mazerunner/cxx-scenarios/build.gradle +++ b/features/fixtures/mazerunner/cxx-scenarios/build.gradle @@ -43,4 +43,4 @@ dependencies { apply from: "../bugsnag-dependency.gradle" apply from: "../../../../gradle/detekt.gradle" -apply from: "../../../../gradle/checkstyle.gradle" +apply from: "../gradle/checkstyle.gradle" diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp index c6fe958d2e..e188aa0864 100644 --- a/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/cpp/cxx-scenarios.cpp @@ -274,5 +274,10 @@ Java_com_bugsnag_android_mazerunner_scenarios_UnhandledNdkAutoNotifyFalseScenari abort(); } +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXMaxBreadcrumbCrashScenario_activate(JNIEnv *env, + jobject thiz) { + abort(); } +} \ No newline at end of file diff --git a/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXMaxBreadcrumbCrashScenario.kt b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXMaxBreadcrumbCrashScenario.kt new file mode 100644 index 0000000000..9dffb9ea54 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXMaxBreadcrumbCrashScenario.kt @@ -0,0 +1,28 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration + +private const val MAX_BREADCRUMB_COUNT = 500 + +class CXXMaxBreadcrumbCrashScenario( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + init { + config.maxBreadcrumbs = MAX_BREADCRUMB_COUNT + } + + external fun activate() + + override fun startScenario() { + super.startScenario() + repeat(config.maxBreadcrumbs) { index -> + Bugsnag.leaveBreadcrumb("this is breadcrumb $index") + } + + activate() + } +} diff --git a/gradle/checkstyle.gradle b/features/fixtures/mazerunner/gradle/checkstyle.gradle similarity index 100% rename from gradle/checkstyle.gradle rename to features/fixtures/mazerunner/gradle/checkstyle.gradle diff --git a/features/fixtures/mazerunner/jvm-scenarios/build.gradle b/features/fixtures/mazerunner/jvm-scenarios/build.gradle index 411e5d1294..c14c318d52 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/build.gradle +++ b/features/fixtures/mazerunner/jvm-scenarios/build.gradle @@ -10,7 +10,11 @@ android { compileSdkVersion 31 defaultConfig { - minSdkVersion 14 + minSdkVersion 17 + manifestPlaceholders = [ + bugsnagPerformanceApiKey: System.getenv("BUGSNAG_PERFORMANCE_API_KEY") + ?: "abc12312312312312312312312312312", + ] } buildTypes { @@ -40,6 +44,7 @@ dependencies { project.logger.lifecycle("Using OkHttp 4 dependency in test fixture") implementation "com.squareup.okhttp3:okhttp:4.9.1" } + implementation "com.bugsnag:bugsnag-android-performance:1.2.2" } private boolean useLegacyOkHttp() { @@ -48,4 +53,4 @@ private boolean useLegacyOkHttp() { apply from: "../bugsnag-dependency.gradle" apply from: "../../../../gradle/detekt.gradle" -apply from: "../../../../gradle/checkstyle.gradle" +apply from: "../gradle/checkstyle.gradle" diff --git a/features/fixtures/mazerunner/jvm-scenarios/detekt-baseline.xml b/features/fixtures/mazerunner/jvm-scenarios/detekt-baseline.xml index 1fcda340f9..d18bb02cbd 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/detekt-baseline.xml +++ b/features/fixtures/mazerunner/jvm-scenarios/detekt-baseline.xml @@ -24,6 +24,8 @@ MagicNumber:StartupCrashFlushScenario.kt$StartupCrashFlushScenario$6000 MagicNumber:TestHarnessHooks.kt$<no name provided>$500 MagicNumber:TrimmedStacktraceScenario.kt$TrimmedStacktraceScenario$100000 + MagicNumber:UnhandledExceptionEventDetailChangeScenario.kt$UnhandledExceptionEventDetailChangeScenario$123 + MagicNumber:UnhandledExceptionEventDetailChangeScenario.kt$UnhandledExceptionEventDetailChangeScenario$123456 ThrowingExceptionsWithoutMessageOrCause:AnrHelper.kt$<no name provided>$IllegalStateException() ThrowingExceptionsWithoutMessageOrCause:BugsnagInitScenario.kt$BugsnagInitScenario$RuntimeException() ThrowingExceptionsWithoutMessageOrCause:CustomPluginNotifierDescriptionScenario.kt$CustomPluginNotifierDescriptionScenario$RuntimeException() diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/AnrHelper.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/AnrHelper.kt index 71e5af0b66..f60934082c 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/AnrHelper.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/AnrHelper.kt @@ -1,6 +1,7 @@ package com.bugsnag.android.mazerunner import android.os.Handler +import android.os.Looper val mutex = Any() @@ -21,7 +22,7 @@ fun createDeadlock() { } ).start() - Handler().postDelayed( + Handler(Looper.getMainLooper()).postDelayed( object : java.lang.Runnable { override fun run() { synchronized(mutex) { throw java.lang.IllegalStateException() } diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationFromManifestScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationFromManifestScenario.kt index 72c04300b0..5162172192 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationFromManifestScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationFromManifestScenario.kt @@ -30,8 +30,7 @@ internal class LoadConfigurationFromManifestScenario( true } ) - - reportBugsnagStartupDuration { Bugsnag.start(this.context, testConfig) } + measureBugsnagStartupDuration(this.context, testConfig) } override fun startScenario() { diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationKotlinScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationKotlinScenario.kt index 46700a5620..73dd62feef 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationKotlinScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/LoadConfigurationKotlinScenario.kt @@ -46,7 +46,7 @@ internal class LoadConfigurationKotlinScenario( } ) - reportBugsnagStartupDuration { Bugsnag.start(this.context, testConfig) } + measureBugsnagStartupDuration(this.context, testConfig) } override fun startScenario() { diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/MultiThreadedStartupScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/MultiThreadedStartupScenario.kt index c29d85fd33..aea3d67d10 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/MultiThreadedStartupScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/MultiThreadedStartupScenario.kt @@ -14,7 +14,7 @@ class MultiThreadedStartupScenario( override fun startScenario() { val startThread = thread(name = "AsyncStart") { - reportBugsnagStartupDuration { Bugsnag.start(context, config) } + measureBugsnagStartupDuration(context, config) } thread(name = "leaveBreadcrumb") { @@ -24,7 +24,7 @@ class MultiThreadedStartupScenario( Bugsnag.leaveBreadcrumb("I'm leaving a breadcrumb on another thread") Bugsnag.notify(Exception("Scenario complete")) } catch (e: Exception) { - reportBugsnagStartupDuration { Bugsnag.start(context, config) } + measureBugsnagStartupDuration(context, config) Bugsnag.notify(e) } } diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt index 232c5ef09d..eddb4e51f8 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/Scenario.kt @@ -14,12 +14,14 @@ import android.os.HandlerThread import android.os.Looper import android.util.Log import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Client import com.bugsnag.android.Configuration import com.bugsnag.android.mazerunner.BugsnagIntentParams import com.bugsnag.android.mazerunner.MazerunnerHttpClient import com.bugsnag.android.mazerunner.log import com.bugsnag.android.mazerunner.multiprocess.MultiProcessService import com.bugsnag.android.mazerunner.multiprocess.findCurrentProcessName +import com.bugsnag.android.performance.measureSpan import java.io.File import kotlin.system.measureNanoTime @@ -77,13 +79,19 @@ abstract class Scenario( ) { startup() } } + fun measureBugsnagStartupDuration(context: Context, config: Configuration): Client { + return measureSpan("Bugsnag Startup") { + Bugsnag.start(context, config) + } + } + /** * Initializes Bugsnag. It is possible to override this method if the scenario requires * it - e.g., if the config needs to be loaded from the manifest. */ open fun startBugsnag(startBugsnagOnly: Boolean) { this.startBugsnagOnly = startBugsnagOnly - reportBugsnagStartupDuration { Bugsnag.start(context, config) } + measureBugsnagStartupDuration(context, config) } /** diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/SharedPrefMigrationScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/SharedPrefMigrationScenario.kt index 8f66d59c08..cbc9bb9375 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/SharedPrefMigrationScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/SharedPrefMigrationScenario.kt @@ -5,6 +5,7 @@ import android.content.Context import com.bugsnag.android.Bugsnag import com.bugsnag.android.Configuration import com.bugsnag.android.mazerunner.BugsnagIntentParams +import java.io.File /** * User/device information is migrated from the legacy [SharedPreferences] location @@ -21,6 +22,8 @@ internal class SharedPrefMigrationScenario( override fun startBugsnag(startBugsnagOnly: Boolean) { persistLegacyPrefs() + // make sure there is no "leftover" device-id file to interfere with the test + File(context.filesDir, "device-id").delete() super.startBugsnag(startBugsnagOnly) } diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartupCrashFlushScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartupCrashFlushScenario.kt index 531890eb0f..faf9f675bc 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartupCrashFlushScenario.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartupCrashFlushScenario.kt @@ -2,6 +2,7 @@ package com.bugsnag.android.mazerunner.scenarios import android.content.Context import android.os.Handler +import android.os.Looper import com.bugsnag.android.Configuration import com.bugsnag.android.mazerunner.disableAllDelivery @@ -35,7 +36,7 @@ internal class StartupCrashFlushScenario( override fun startScenario() { super.startScenario() if ("CrashOfflineWithDelay" == eventMetadata) { - Handler().postDelayed( + Handler(Looper.getMainLooper()).postDelayed( Runnable { throw RuntimeException("Regular crash") }, diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/UnhandledExceptionEventDetailChangeScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/UnhandledExceptionEventDetailChangeScenario.kt new file mode 100644 index 0000000000..fdacbf4c3e --- /dev/null +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/UnhandledExceptionEventDetailChangeScenario.kt @@ -0,0 +1,86 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.BreadcrumbType +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration +import com.bugsnag.android.OnErrorCallback +import com.bugsnag.android.Severity + +/** + * Sends an unhandled exception to Bugsnag where the event details are changed in a callback + */ +internal class UnhandledExceptionEventDetailChangeScenario( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + + init { + + config.addOnError( + OnErrorCallback { event -> + event.apiKey = "0000111122223333aaaabbbbcccc9999" + event.severity = Severity.ERROR + event.groupingHash = "groupingHash1" + event.context = "new-context" + event.setUser("abc", "joe@test.com", "Joe") + + event.clearMetadata("custom_data1") + event.clearMetadata("custom_data2", "data") + event.addMetadata("custom_data2", "test_data", "this is test") + + event.clearFeatureFlag("test1") + event.addFeatureFlag("beta", "b") + event.addFeatureFlag("gamma") + + event.isUnhandled = false + event.app.binaryArch = "x86" + event.app.id = "12345" + event.app.releaseStage = "custom" + event.app.version = "1.2.3" + event.app.buildUuid = "12345678" + event.app.type = "android_custom" + event.app.versionCode = 123 + event.app.duration = 123456 + event.app.durationInForeground = 123456 + event.app.inForeground = false + event.app.isLaunching = false + + event.device.id = "12345" + event.device.jailbroken = true + event.device.locale = "en-UK" + event.device.totalMemory = 123456 + event.device.runtimeVersions = mutableMapOf("androidApiLevel" to "30") + event.device.freeDisk = 123456 + event.device.freeMemory = 123456 + event.device.orientation = "portrait" + + event.breadcrumbs.removeLast() + event.breadcrumbs.first().type = BreadcrumbType.ERROR + event.breadcrumbs.first().message = "new breadcrumb message" + event.breadcrumbs[1].type = BreadcrumbType.ERROR + event.breadcrumbs[1].message = "Second breadcrumb message" + event.breadcrumbs.first().metadata = mapOf("foo" to "data") + true + } + ) + } + + override fun startScenario() { + super.startScenario() + Bugsnag.leaveBreadcrumb("Hello1") + Bugsnag.leaveBreadcrumb("Hello2") + Bugsnag.leaveBreadcrumb("Hello3") + + Bugsnag.addMetadata("custom_data1", "data", "hello") + Bugsnag.addMetadata("custom_data2", "data", "hello") + Bugsnag.addMetadata("custom_data3", "test data", "divert all available power to the crash reporter") + + Bugsnag.addFeatureFlag("test1") + Bugsnag.addFeatureFlag("test2") + + Bugsnag.notify(RuntimeException("UnhandledExceptionEventDetailChangeScenario")) + throw NullPointerException("something broke") + } +} diff --git a/features/fixtures/minimalapp/build.gradle b/features/fixtures/minimalapp/build.gradle index e294e05703..e9c0e57f3d 100644 --- a/features/fixtures/minimalapp/build.gradle +++ b/features/fixtures/minimalapp/build.gradle @@ -13,7 +13,7 @@ buildscript { classpath "com.bugsnag:bugsnag-android-gradle-plugin:7.1.0" } // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + // in the individual module build.gradle.kts files } } diff --git a/features/full_tests/error_callback_alters_fields.feature b/features/full_tests/error_callback_alters_fields.feature new file mode 100644 index 0000000000..2748be9376 --- /dev/null +++ b/features/full_tests/error_callback_alters_fields.feature @@ -0,0 +1,59 @@ +Feature: When the api key is altered in an Event the JSON payload reflects this + + Background: + Given I clear all persistent data + + Scenario: Unhandled exception with altered event details + When I run "UnhandledExceptionEventDetailChangeScenario" and relaunch the crashed app + And I configure Bugsnag for "UnhandledExceptionApiKeyChangeScenario" + And I wait to receive an error + And the error payload field "events" is an array with 1 elements + And the exception "message" equals "UnhandledExceptionEventDetailChangeScenario" + And the error payload field "apiKey" equals "0000111122223333aaaabbbbcccc9999" + And the error "Bugsnag-Api-Key" header equals "0000111122223333aaaabbbbcccc9999" + And the event "severity" equals "error" + And the event "context" equals "new-context" + And the event "groupingHash" equals "groupingHash1" + And the event "user.id" equals "abc" + And the event "user.email" equals "joe@test.com" + And the event "user.name" equals "Joe" + And the event "metaData.custom_data2.test_data" equals "this is test" + And the event "metaData.custom_data1" is null + And the event "metaData.custom_data2.data" is null + And the event "metaData.custom_data3.test data" equals "divert all available power to the crash reporter" + And event 0 contains the feature flag "beta" with variant "b" + And event 0 does not contain the feature flag "alpha" + And event 0 contains the feature flag "gamma" with no variant + And event 0 does not contain the feature flag "test1" + And event 0 contains the feature flag "test2" with no variant + + # app fields + And the event "unhandled" is false + And the event "app.binaryArch" equals "x86" + And the event "app.id" equals "12345" + And the event "app.releaseStage" equals "custom" + And the event "app.version" equals "1.2.3" + And the event "app.buildUUID" equals "12345678" + And the event "app.type" equals "android_custom" + And the event "app.versionCode" equals 123 + And the event "app.duration" equals 123456 + And the event "app.durationInForeground" equals 123456 + And the event "app.inForeground" is false + And the event "app.isLaunching" is false + + # device fields + And the event "device.id" equals "12345" + And the event "device.jailbroken" is true + And the event "device.locale" equals "en-UK" + And the event "device.totalMemory" equals 123456 + And the event "device.runtimeVersions.androidApiLevel" equals "30" + And the event "device.freeDisk" equals 123456 + And the event "device.freeMemory" equals 123456 + And the event "device.orientation" equals "portrait" + + # breadcrumbs fields + And the event "breadcrumbs.0.type" equals "error" + And the event "breadcrumbs.0.name" equals "new breadcrumb message" + And the event "breadcrumbs.0.metaData.foo" equals "data" + And the event "breadcrumbs.1.type" equals "error" + And the event "breadcrumbs.1.name" equals "Second breadcrumb message" \ No newline at end of file diff --git a/features/full_tests/native_breadcrumbs.feature b/features/full_tests/native_breadcrumbs.feature index 14388f3a3d..3775e0cfe1 100644 --- a/features/full_tests/native_breadcrumbs.feature +++ b/features/full_tests/native_breadcrumbs.feature @@ -23,3 +23,11 @@ Feature: Native Breadcrumbs API And the event "severity" equals "warning" And the event has a "process" breadcrumb named "Rerun field analysis" And the event "unhandled" is false + + Scenario: Leaving the maximum number of native breadcrumbs + When I run "CXXMaxBreadcrumbCrashScenario" and relaunch the crashed app + And I configure Bugsnag for "CXXMaxBreadcrumbCrashScenario" + And I wait to receive an error + And the error payload contains a completed handled native report + And the event "unhandled" is true + And the event has 500 breadcrumbs \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 73dfdc3f4e..f27b1d23a3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ org.gradle.jvmargs=-Xmx4096m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects org.gradle.parallel=true -VERSION_NAME=6.4.0 +VERSION_NAME=6.5.0 GROUP=com.bugsnag POM_SCM_URL=https://github.com/bugsnag/bugsnag-android POM_SCM_CONNECTION=scm:git@github.com:bugsnag/bugsnag-android.git diff --git a/gradle/kotlin.gradle b/gradle/kotlin.gradle deleted file mode 100644 index 2d3be612fd..0000000000 --- a/gradle/kotlin.gradle +++ /dev/null @@ -1,13 +0,0 @@ -android { - kotlinOptions { - allWarningsAsErrors = true - jvmTarget = "1.8" - apiVersion = "1.5" - languageVersion = "1.5" - freeCompilerArgs += [ - '-Xno-call-assertions', - '-Xno-receiver-assertions', - '-Xno-param-assertions' - ] - } -} diff --git a/settings.gradle b/settings.gradle.kts similarity index 90% rename from settings.gradle rename to settings.gradle.kts index a561e56bf8..a093eafc48 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id "com.gradle.enterprise" version "3.5" + id("com.gradle.enterprise") version "3.5" } gradleEnterprise {