Skip to content

Commit

Permalink
Add tests for the Android and iOS plugin (#1587)
Browse files Browse the repository at this point in the history
  • Loading branch information
denrase authored Sep 4, 2023
1 parent 5f6cc37 commit 78eeed5
Show file tree
Hide file tree
Showing 18 changed files with 795 additions and 258 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
name: flutter integration tests
name: flutter native & integration test
on:
# Currently broken, enable after fixing
workflow_dispatch
# push:
# branches:
# - main
# - release/**
# pull_request:
# paths-ignore:
# - 'file/**'
push:
branches:
- main
- release/**
pull_request:
paths-ignore:
- 'file/**'

env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}

jobs:
cancel-previous-workflow:
Expand Down Expand Up @@ -73,6 +74,22 @@ jobs:
profile: Nexus 6
script: echo 'Generated AVD snapshot for caching.'

- name: build apk
working-directory: ./flutter/example/android
run: flutter build apk --debug

- name: launch android emulator & run android native test
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b #[email protected]
with:
working-directory: ./flutter/example/android
api-level: 21
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
arch: x86_64
profile: Nexus 6
script: ./gradlew testDebugUnitTest

- name: launch android emulator & run android integration test
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b #[email protected]
with:
Expand Down Expand Up @@ -110,10 +127,26 @@ jobs:
- name: flutter pub get
run: flutter pub get

- name: pod install
working-directory: ./flutter/example/ios
run: pod install

- name: launch ios simulator
id: sim
run: |
simulator_id=$(xcrun simctl create sentryPhone com.apple.CoreSimulator.SimDeviceType.iPhone-14 com.apple.CoreSimulator.SimRuntime.iOS-16-2)
echo "SIMULATOR_ID=${simulator_id}" >> "$GITHUB_OUTPUT"
xcrun simctl boot ${simulator_id}
# Disable flutter integration tests because of flaky execution
# - name: run ios integration test
# env:
# SIMULATOR_ID: ${{ steps.sim.outputs.SIMULATOR_ID }}
# run: flutter test -d "$SIMULATOR_ID" integration_test/integration_test.dart --verbose

- name: run ios native test
working-directory: ./flutter/example/ios
env:
SIMULATOR_ID: ${{ steps.sim.outputs.SIMULATOR_ID }}
run: xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug -destination "platform=iOS Simulator,id=$SIMULATOR_ID" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO


- name: run ios integration test
run: flutter test integration_test/integration_test.dart --verbose
3 changes: 3 additions & 0 deletions flutter/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,7 @@ android {
dependencies {
api 'io.sentry:sentry-android:6.28.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

// Required -- JUnit 4 framework
testImplementation "junit:junit:4.13.2"
}
129 changes: 129 additions & 0 deletions flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package io.sentry.flutter

import io.sentry.SentryLevel
import io.sentry.android.core.BuildConfig
import io.sentry.android.core.SentryAndroidOptions
import io.sentry.protocol.SdkVersion
import java.util.Locale

class SentryFlutter(
private val androidSdk: String,
private val nativeSdk: String
) {

var autoPerformanceTracingEnabled = false

fun updateOptions(options: SentryAndroidOptions, data: Map<String, Any>) {
data.getIfNotNull<String>("dsn") {
options.dsn = it
}
data.getIfNotNull<Boolean>("debug") {
options.isDebug = it
}
data.getIfNotNull<String>("environment") {
options.environment = it
}
data.getIfNotNull<String>("release") {
options.release = it
}
data.getIfNotNull<String>("dist") {
options.dist = it
}
data.getIfNotNull<Boolean>("enableAutoSessionTracking") {
options.isEnableAutoSessionTracking = it
}
data.getIfNotNull<Long>("autoSessionTrackingIntervalMillis") {
options.sessionTrackingIntervalMillis = it
}
data.getIfNotNull<Long>("anrTimeoutIntervalMillis") {
options.anrTimeoutIntervalMillis = it
}
data.getIfNotNull<Boolean>("attachThreads") {
options.isAttachThreads = it
}
data.getIfNotNull<Boolean>("attachStacktrace") {
options.isAttachStacktrace = it
}
data.getIfNotNull<Boolean>("enableAutoNativeBreadcrumbs") {
options.isEnableActivityLifecycleBreadcrumbs = it
options.isEnableAppLifecycleBreadcrumbs = it
options.isEnableSystemEventBreadcrumbs = it
options.isEnableAppComponentBreadcrumbs = it
options.isEnableUserInteractionBreadcrumbs = it
}
data.getIfNotNull<Int>("maxBreadcrumbs") {
options.maxBreadcrumbs = it
}
data.getIfNotNull<Int>("maxCacheItems") {
options.maxCacheItems = it
}
data.getIfNotNull<String>("diagnosticLevel") {
if (options.isDebug) {
val sentryLevel = SentryLevel.valueOf(it.toUpperCase(Locale.ROOT))
options.setDiagnosticLevel(sentryLevel)
}
}
data.getIfNotNull<Boolean>("anrEnabled") {
options.isAnrEnabled = it
}
data.getIfNotNull<Boolean>("sendDefaultPii") {
options.isSendDefaultPii = it
}
data.getIfNotNull<Boolean>("enableNdkScopeSync") {
options.isEnableScopeSync = it
}
data.getIfNotNull<String>("proguardUuid") {
options.proguardUuid = it
}

val nativeCrashHandling = (data["enableNativeCrashHandling"] as? Boolean) ?: true
// nativeCrashHandling has priority over anrEnabled
if (!nativeCrashHandling) {
options.isEnableUncaughtExceptionHandler = false
options.isAnrEnabled = false
// if split symbols are enabled, we need Ndk integration so we can't really offer the option
// to turn it off
// options.isEnableNdk = false
}

data.getIfNotNull<Boolean>("enableAutoPerformanceTracing") { enableAutoPerformanceTracing ->
if (enableAutoPerformanceTracing) {
autoPerformanceTracingEnabled = true
}
}

data.getIfNotNull<Boolean>("sendClientReports") {
options.isSendClientReports = it
}

data.getIfNotNull<Long>("maxAttachmentSize") {
options.maxAttachmentSize = it
}

var sdkVersion = options.sdkVersion
if (sdkVersion == null) {
sdkVersion = SdkVersion(androidSdk, BuildConfig.VERSION_NAME)
} else {
sdkVersion.name = androidSdk
}

options.sdkVersion = sdkVersion
options.sentryClientName = "$androidSdk/${BuildConfig.VERSION_NAME}"
options.nativeSdkName = nativeSdk

data.getIfNotNull<Int>("connectionTimeoutMillis") {
options.connectionTimeoutMillis = it
}
data.getIfNotNull<Int>("readTimeoutMillis") {
options.readTimeoutMillis = it
}
}
}

// Call the `completion` closure if cast to map value with `key` and type `T` is successful.
@Suppress("UNCHECKED_CAST")
private fun <T> Map<String, Any>.getIfNotNull(key: String, callback: (T) -> Unit) {
(get(key) as? T)?.let {
callback(it)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import java.util.UUID
class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
private lateinit var channel: MethodChannel
private lateinit var context: Context
private lateinit var sentryFlutter: SentryFlutter

private var activity: WeakReference<Activity>? = null
private var framesTracker: ActivityFramesTracker? = null
Expand All @@ -45,6 +46,11 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sentry_flutter")
channel.setMethodCallHandler(this)

sentryFlutter = SentryFlutter(
androidSdk = androidSdk,
nativeSdk = nativeSdk
)
}

override fun onMethodCall(call: MethodCall, result: Result) {
Expand Down Expand Up @@ -119,79 +125,13 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}

SentryAndroid.init(context) { options ->
args.getIfNotNull<String>("dsn") { options.dsn = it }
args.getIfNotNull<Boolean>("debug") { options.isDebug = it }
args.getIfNotNull<String>("environment") { options.environment = it }
args.getIfNotNull<String>("release") { options.release = it }
args.getIfNotNull<String>("dist") { options.dist = it }
args.getIfNotNull<Boolean>("enableAutoSessionTracking") {
options.isEnableAutoSessionTracking = it
}
args.getIfNotNull<Long>("autoSessionTrackingIntervalMillis") {
options.sessionTrackingIntervalMillis = it
}
args.getIfNotNull<Long>("anrTimeoutIntervalMillis") {
options.anrTimeoutIntervalMillis = it
}
args.getIfNotNull<Boolean>("attachThreads") { options.isAttachThreads = it }
args.getIfNotNull<Boolean>("attachStacktrace") { options.isAttachStacktrace = it }
args.getIfNotNull<Boolean>("enableAutoNativeBreadcrumbs") {
options.isEnableActivityLifecycleBreadcrumbs = it
options.isEnableAppLifecycleBreadcrumbs = it
options.isEnableSystemEventBreadcrumbs = it
options.isEnableAppComponentBreadcrumbs = it
options.isEnableUserInteractionBreadcrumbs = it
}
args.getIfNotNull<Int>("maxBreadcrumbs") { options.maxBreadcrumbs = it }
args.getIfNotNull<Int>("maxCacheItems") { options.maxCacheItems = it }
args.getIfNotNull<String>("diagnosticLevel") {
if (options.isDebug) {
val sentryLevel = SentryLevel.valueOf(it.toUpperCase(Locale.ROOT))
options.setDiagnosticLevel(sentryLevel)
}
}
args.getIfNotNull<Boolean>("anrEnabled") { options.isAnrEnabled = it }
args.getIfNotNull<Boolean>("sendDefaultPii") { options.isSendDefaultPii = it }
args.getIfNotNull<Boolean>("enableNdkScopeSync") { options.isEnableScopeSync = it }
args.getIfNotNull<String>("proguardUuid") { options.proguardUuid = it }

val nativeCrashHandling = (args["enableNativeCrashHandling"] as? Boolean) ?: true
// nativeCrashHandling has priority over anrEnabled
if (!nativeCrashHandling) {
options.isEnableUncaughtExceptionHandler = false
options.isAnrEnabled = false
// if split symbols are enabled, we need Ndk integration so we can't really offer the option
// to turn it off
// options.isEnableNdk = false
}

args.getIfNotNull<Boolean>("enableAutoPerformanceTracing") { enableAutoPerformanceTracing ->
if (enableAutoPerformanceTracing) {
autoPerformanceTracingEnabled = true
framesTracker = ActivityFramesTracker(LoadClass(), options)
}
}

args.getIfNotNull<Boolean>("sendClientReports") { options.isSendClientReports = it }
sentryFlutter.updateOptions(options, args)

args.getIfNotNull<Long>("maxAttachmentSize") { options.maxAttachmentSize = it }

var sdkVersion = options.sdkVersion
if (sdkVersion == null) {
sdkVersion = SdkVersion(androidSdk, VERSION_NAME)
} else {
sdkVersion.name = androidSdk
if (sentryFlutter.autoPerformanceTracingEnabled) {
framesTracker = ActivityFramesTracker(LoadClass(), options)
}

options.sdkVersion = sdkVersion
options.sentryClientName = "$androidSdk/$VERSION_NAME"
options.nativeSdkName = nativeSdk
options.beforeSend = BeforeSendCallbackImpl(options.sdkVersion)

args.getIfNotNull<Int>("connectionTimeoutMillis") { options.connectionTimeoutMillis = it }
args.getIfNotNull<Int>("readTimeoutMillis") { options.readTimeoutMillis = it }

// missing proxy
}
result.success("")
}
Expand Down Expand Up @@ -455,10 +395,3 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}
}
}

// Call the `completion` closure if cast to map value with `key` and type `T` is successful.
private fun <T> Map<String, Any>.getIfNotNull(key: String, callback: (T) -> Unit) {
(get(key) as? T)?.let {
callback(it)
}
}
Loading

0 comments on commit 78eeed5

Please sign in to comment.