diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp index ffb38fb2b8..29068edda7 100644 --- a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/cpp/cxx-scenarios-bugsnag.cpp @@ -299,4 +299,10 @@ Java_com_bugsnag_android_mazerunner_scenarios_MultiProcessUnhandledCXXErrorScena abort(); } +JNIEXPORT void JNICALL +Java_com_bugsnag_android_mazerunner_scenarios_CXXCrashLoopScenario_crash(JNIEnv *env, + jobject instance) { + abort(); +} + } diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXCrashLoopScenario.kt b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXCrashLoopScenario.kt new file mode 100644 index 0000000000..841f351fe5 --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/java/com/bugsnag/android/mazerunner/scenarios/CXXCrashLoopScenario.kt @@ -0,0 +1,51 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration +import com.bugsnag.android.OnErrorCallback + +/** + * Triggers a crash loop which Bugsnag allows recovery from. + */ +internal class CXXCrashLoopScenario( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + + init { + config.autoTrackSessions = false + System.loadLibrary("bugsnag-ndk") + System.loadLibrary("cxx-scenarios-bugsnag") + config.addOnError( + OnErrorCallback { event -> + Bugsnag.getLastRunInfo()?.let { + event.addMetadata("LastRunInfo", "crashed", it.crashed) + event.addMetadata("LastRunInfo", "crashedDuringLaunch", it.crashedDuringLaunch) + event.addMetadata( + "LastRunInfo", + "consecutiveLaunchCrashes", + it.consecutiveLaunchCrashes + ) + } + true + } + ) + } + + external fun crash() + + override fun startScenario() { + super.startScenario() + val lastRunInfo = Bugsnag.getLastRunInfo() + + // the last run info allows the scenario to escape from what would otherwise be + // a crash loop, by conditionally entering a 'safe mode'. + if (lastRunInfo?.crashedDuringLaunch == true) { + Bugsnag.notify(IllegalArgumentException("Safe mode enabled")) + } else { + crash() + } + } +} diff --git a/features/full_tests/batch_1/identify_crashes_on_launch.feature b/features/full_tests/batch_1/identify_crashes_on_launch.feature new file mode 100644 index 0000000000..dbca29e87c --- /dev/null +++ b/features/full_tests/batch_1/identify_crashes_on_launch.feature @@ -0,0 +1,17 @@ +Feature: Identifying crashes on launch + + Scenario: Escaping from a crash loop by reading LastRunInfo in a JVM error + When I run "JvmCrashLoopScenario" and relaunch the app + When I run "JvmCrashLoopScenario" + And I wait to receive 2 errors + Then the error is valid for the error reporting API version "4.0" for the "Android Bugsnag Notifier" notifier + And the exception "message" equals "First crash" + And the event "metaData.LastRunInfo.crashed" is null + And the event "metaData.LastRunInfo.crashedDuringLaunch" is null + And the event "metaData.LastRunInfo.consecutiveLaunchCrashes" is null + And I discard the oldest error + Then the error is valid for the error reporting API version "4.0" for the "Android Bugsnag Notifier" notifier + And the exception "message" equals "Safe mode enabled" + And the event "metaData.LastRunInfo.crashed" is true + And the event "metaData.LastRunInfo.crashedDuringLaunch" is true + And the event "metaData.LastRunInfo.consecutiveLaunchCrashes" equals 1