From e46839223f885c45079c5243778aab229d34d01b Mon Sep 17 00:00:00 2001 From: SmartbearYing Date: Tue, 30 Jan 2024 08:52:48 +0000 Subject: [PATCH] feat(metadata):tests --- .../detekt-baseline.xml | 2 +- .../com/bugsnag/android/ExitInfoCallback.kt | 76 ++++++++++--------- .../mazerunner/bugsnag-dependency.gradle | 1 + .../scenarios/ExitInfoScenario.java | 54 +++++++++++++ .../android/mazerunner/BugsnagConfig.kt | 2 + features/full_tests/exit_information.feature | 15 ++++ 6 files changed, 115 insertions(+), 35 deletions(-) create mode 100644 features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/java/com/bugsnag/android/mazerunner/scenarios/ExitInfoScenario.java create mode 100644 features/full_tests/exit_information.feature diff --git a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml index 29c898e414..4597fca69f 100644 --- a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml +++ b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml @@ -2,7 +2,7 @@ - CyclomaticComplexMethod:ExitInfoCallback.kt$ExitInfoCallback$override fun onSend(event: Event): Boolean + CyclomaticComplexMethod:ExitInfoCallback.kt$ExitInfoCallback$private fun exitReasonOf(exitInfo: ApplicationExitInfo) LongParameterList:TombstoneParser.kt$TombstoneParser$( exitInfo: ApplicationExitInfo, listOpenFds: Boolean, includeLogcat: Boolean, threadConsumer: (BugsnagThread) -> Unit, fileDescriptorConsumer: (Int, String, String) -> Unit, logcatConsumer: (String) -> Unit ) MagicNumber:TraceParser.kt$TraceParser$16 MagicNumber:TraceParser.kt$TraceParser$3 diff --git a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt index d987203bab..807ffd6bfe 100644 --- a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt +++ b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt @@ -4,6 +4,7 @@ import android.app.ActivityManager import android.app.ApplicationExitInfo import android.content.Context import android.os.Build +import android.util.Log import androidx.annotation.RequiresApi @RequiresApi(Build.VERSION_CODES.R) @@ -17,46 +18,19 @@ internal class ExitInfoCallback( override fun onSend(event: Event): Boolean { val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val allExitInfo = am.getHistoricalProcessExitReasons(context.packageName, 0, MAX_EXIT_INFO) + Log.e("Bugsnag", "allExitInfo: $allExitInfo") val sessionIdBytes = event.session?.id?.toByteArray() ?: return true + Log.e("Bugsnag", "sessionIdBytes: $sessionIdBytes") val exitInfo = findExitInfoBySessionId(allExitInfo, sessionIdBytes) ?: findExitInfoByPid(allExitInfo) ?: return true + Log.e("Bugsnag", "exitInfo: $exitInfo") try { - val reason = when (exitInfo.reason) { - ApplicationExitInfo.REASON_UNKNOWN -> "unknown reason (${exitInfo.reason})" - ApplicationExitInfo.REASON_EXIT_SELF -> "exit self" - ApplicationExitInfo.REASON_SIGNALED -> "signaled" - ApplicationExitInfo.REASON_LOW_MEMORY -> "low memory" - ApplicationExitInfo.REASON_CRASH -> "crash" - ApplicationExitInfo.REASON_CRASH_NATIVE -> "crash native" - ApplicationExitInfo.REASON_ANR -> "ANR" - ApplicationExitInfo.REASON_INITIALIZATION_FAILURE -> "initialization failure" - ApplicationExitInfo.REASON_PERMISSION_CHANGE -> "permission change" - ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE -> "excessive resource usage" - ApplicationExitInfo.REASON_USER_REQUESTED -> "user requested" - ApplicationExitInfo.REASON_USER_STOPPED -> "user stopped" - ApplicationExitInfo.REASON_DEPENDENCY_DIED -> "dependency died" - ApplicationExitInfo.REASON_OTHER -> "other" - ApplicationExitInfo.REASON_FREEZER -> "freezer" - ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE -> "package state change" - ApplicationExitInfo.REASON_PACKAGE_UPDATED -> "package updated" - else -> "unknown reason (${exitInfo.reason})" - } - event.addMetadata("App", "exit reason", reason) + val reason = exitReasonOf(exitInfo) + event.addMetadata("app", "exitReason", reason) - val importance = when (exitInfo.importance) { - ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND -> "foreground" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE -> "foreground service" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING -> "top sleeping" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE -> "visible" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE -> "perceptible" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE -> "can not save state" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE -> "service" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED -> "cached" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE -> "gone" - else -> "unknown importance ${exitInfo.importance}" - } - event.addMetadata("App", "process importance", importance) + val importance = importanceDescriptionOf(exitInfo) + event.addMetadata("app", "processImportance", importance) if (exitInfo.reason == ApplicationExitInfo.REASON_CRASH_NATIVE || exitInfo.reason == ApplicationExitInfo.REASON_SIGNALED @@ -71,6 +45,40 @@ internal class ExitInfoCallback( return true } + private fun exitReasonOf(exitInfo: ApplicationExitInfo) = when (exitInfo.reason) { + ApplicationExitInfo.REASON_UNKNOWN -> "unknown reason (${exitInfo.reason})" + ApplicationExitInfo.REASON_EXIT_SELF -> "exit self" + ApplicationExitInfo.REASON_SIGNALED -> "signaled" + ApplicationExitInfo.REASON_LOW_MEMORY -> "low memory" + ApplicationExitInfo.REASON_CRASH -> "crash" + ApplicationExitInfo.REASON_CRASH_NATIVE -> "crash native" + ApplicationExitInfo.REASON_ANR -> "ANR" + ApplicationExitInfo.REASON_INITIALIZATION_FAILURE -> "initialization failure" + ApplicationExitInfo.REASON_PERMISSION_CHANGE -> "permission change" + ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE -> "excessive resource usage" + ApplicationExitInfo.REASON_USER_REQUESTED -> "user requested" + ApplicationExitInfo.REASON_USER_STOPPED -> "user stopped" + ApplicationExitInfo.REASON_DEPENDENCY_DIED -> "dependency died" + ApplicationExitInfo.REASON_OTHER -> "other" + ApplicationExitInfo.REASON_FREEZER -> "freezer" + ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE -> "package state change" + ApplicationExitInfo.REASON_PACKAGE_UPDATED -> "package updated" + else -> "unknown reason (${exitInfo.reason})" + } + + private fun importanceDescriptionOf(exitInfo: ApplicationExitInfo) = when (exitInfo.importance) { + ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND -> "foreground" + ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE -> "foreground service" + ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING -> "top sleeping" + ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE -> "visible" + ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE -> "perceptible" + ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE -> "can not save state" + ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE -> "service" + ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED -> "cached" + ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE -> "gone" + else -> "unknown importance ${exitInfo.importance}" + } + private fun findExitInfoBySessionId( allExitInfo: List, sessionIdBytes: ByteArray diff --git a/features/fixtures/mazerunner/bugsnag-dependency.gradle b/features/fixtures/mazerunner/bugsnag-dependency.gradle index 081d0fc48e..99680b4ddf 100644 --- a/features/fixtures/mazerunner/bugsnag-dependency.gradle +++ b/features/fixtures/mazerunner/bugsnag-dependency.gradle @@ -16,5 +16,6 @@ dependencies { project.logger.lifecycle("Compiling full mazerunner fixture with ANR/NDK scenarios") implementation "com.bugsnag:bugsnag-android:9.9.9" implementation "com.bugsnag:bugsnag-plugin-android-okhttp:9.9.9" + implementation "com.bugsnag:bugsnag-plugin-android-exitinfo:9.9.9" } } diff --git a/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/java/com/bugsnag/android/mazerunner/scenarios/ExitInfoScenario.java b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/java/com/bugsnag/android/mazerunner/scenarios/ExitInfoScenario.java new file mode 100644 index 0000000000..976fc94e9f --- /dev/null +++ b/features/fixtures/mazerunner/cxx-scenarios-bugsnag/src/main/java/com/bugsnag/android/mazerunner/scenarios/ExitInfoScenario.java @@ -0,0 +1,54 @@ +package com.bugsnag.android.mazerunner.scenarios; + +import com.bugsnag.android.Breadcrumb; +import com.bugsnag.android.BreadcrumbType; +import com.bugsnag.android.Bugsnag; +import com.bugsnag.android.Configuration; +import com.bugsnag.android.Event; +import com.bugsnag.android.OnBreadcrumbCallback; +import com.bugsnag.android.OnErrorCallback; +import com.bugsnag.android.Severity; +import com.bugsnag.android.mazerunner.BugsnagConfigKt; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + + +public class ExitInfoScenario extends Scenario { + + public native int crash(int value); + + public ExitInfoScenario(@NonNull Configuration config, + @NonNull Context context, + @Nullable String eventMetadata) { + super(config, context, eventMetadata); + config.setAppType("Overwritten"); + } + + @Override + public void startScenario() { + super.startScenario(); + Bugsnag.startSession(); + + Handler main = new Handler(Looper.getMainLooper()); + main.postDelayed(new Runnable() { + @Override + public void run() { + crash(2726); + } + }, 500); + } + +} diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/BugsnagConfig.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/BugsnagConfig.kt index 63048e09cd..116d236d9c 100644 --- a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/BugsnagConfig.kt +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/BugsnagConfig.kt @@ -1,6 +1,7 @@ package com.bugsnag.android.mazerunner import android.util.Log +import com.bugsnag.android.BugsnagExitInfoPlugin import com.bugsnag.android.Configuration import com.bugsnag.android.Delivery import com.bugsnag.android.DeliveryParams @@ -19,6 +20,7 @@ fun prepareConfig( logFilter: (msg: String) -> Boolean ): Configuration { val config = Configuration(apiKey) + config.addPlugin(BugsnagExitInfoPlugin()) if (notify.isNotEmpty() && sessions.isNotEmpty()) { config.endpoints = EndpointConfiguration(notify, sessions) diff --git a/features/full_tests/exit_information.feature b/features/full_tests/exit_information.feature new file mode 100644 index 0000000000..4b844079ef --- /dev/null +++ b/features/full_tests/exit_information.feature @@ -0,0 +1,15 @@ +Feature: Reporting with other exception handlers installed + + Background: + Given I clear all persistent data + + @skip_below_android_12 + Scenario: Signal raised with overwritten config + When I set the screen orientation to portrait + And I run "ExitInfoScenario" and relaunch the crashed app + And I configure Bugsnag for "ExitInfoScenario" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Android Bugsnag Notifier" notifier + And the error payload field "events.0.metaData.app.processImportance" equals "foreground" + And the error payload field "events.0.metaData.app.exitReason" equals "crash" +