Skip to content

Commit 78eeed5

Browse files
authored
Add tests for the Android and iOS plugin (#1587)
1 parent 5f6cc37 commit 78eeed5

File tree

18 files changed

+795
-258
lines changed

18 files changed

+795
-258
lines changed

.github/workflows/flutter_integration_test.yml renamed to .github/workflows/flutter_test.yml

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
name: flutter integration tests
1+
name: flutter native & integration test
22
on:
3-
# Currently broken, enable after fixing
4-
workflow_dispatch
5-
# push:
6-
# branches:
7-
# - main
8-
# - release/**
9-
# pull_request:
10-
# paths-ignore:
11-
# - 'file/**'
3+
push:
4+
branches:
5+
- main
6+
- release/**
7+
pull_request:
8+
paths-ignore:
9+
- 'file/**'
10+
11+
env:
12+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
1213

1314
jobs:
1415
cancel-previous-workflow:
@@ -73,6 +74,22 @@ jobs:
7374
profile: Nexus 6
7475
script: echo 'Generated AVD snapshot for caching.'
7576

77+
- name: build apk
78+
working-directory: ./flutter/example/android
79+
run: flutter build apk --debug
80+
81+
- name: launch android emulator & run android native test
82+
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b #[email protected]
83+
with:
84+
working-directory: ./flutter/example/android
85+
api-level: 21
86+
force-avd-creation: false
87+
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
88+
disable-animations: true
89+
arch: x86_64
90+
profile: Nexus 6
91+
script: ./gradlew testDebugUnitTest
92+
7693
- name: launch android emulator & run android integration test
7794
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b #[email protected]
7895
with:
@@ -110,10 +127,26 @@ jobs:
110127
- name: flutter pub get
111128
run: flutter pub get
112129

130+
- name: pod install
131+
working-directory: ./flutter/example/ios
132+
run: pod install
133+
113134
- name: launch ios simulator
135+
id: sim
114136
run: |
115137
simulator_id=$(xcrun simctl create sentryPhone com.apple.CoreSimulator.SimDeviceType.iPhone-14 com.apple.CoreSimulator.SimRuntime.iOS-16-2)
138+
echo "SIMULATOR_ID=${simulator_id}" >> "$GITHUB_OUTPUT"
116139
xcrun simctl boot ${simulator_id}
140+
# Disable flutter integration tests because of flaky execution
141+
# - name: run ios integration test
142+
# env:
143+
# SIMULATOR_ID: ${{ steps.sim.outputs.SIMULATOR_ID }}
144+
# run: flutter test -d "$SIMULATOR_ID" integration_test/integration_test.dart --verbose
145+
146+
- name: run ios native test
147+
working-directory: ./flutter/example/ios
148+
env:
149+
SIMULATOR_ID: ${{ steps.sim.outputs.SIMULATOR_ID }}
150+
run: xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug -destination "platform=iOS Simulator,id=$SIMULATOR_ID" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO
151+
117152

118-
- name: run ios integration test
119-
run: flutter test integration_test/integration_test.dart --verbose

flutter/android/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,7 @@ android {
6262
dependencies {
6363
api 'io.sentry:sentry-android:6.28.0'
6464
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
65+
66+
// Required -- JUnit 4 framework
67+
testImplementation "junit:junit:4.13.2"
6568
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package io.sentry.flutter
2+
3+
import io.sentry.SentryLevel
4+
import io.sentry.android.core.BuildConfig
5+
import io.sentry.android.core.SentryAndroidOptions
6+
import io.sentry.protocol.SdkVersion
7+
import java.util.Locale
8+
9+
class SentryFlutter(
10+
private val androidSdk: String,
11+
private val nativeSdk: String
12+
) {
13+
14+
var autoPerformanceTracingEnabled = false
15+
16+
fun updateOptions(options: SentryAndroidOptions, data: Map<String, Any>) {
17+
data.getIfNotNull<String>("dsn") {
18+
options.dsn = it
19+
}
20+
data.getIfNotNull<Boolean>("debug") {
21+
options.isDebug = it
22+
}
23+
data.getIfNotNull<String>("environment") {
24+
options.environment = it
25+
}
26+
data.getIfNotNull<String>("release") {
27+
options.release = it
28+
}
29+
data.getIfNotNull<String>("dist") {
30+
options.dist = it
31+
}
32+
data.getIfNotNull<Boolean>("enableAutoSessionTracking") {
33+
options.isEnableAutoSessionTracking = it
34+
}
35+
data.getIfNotNull<Long>("autoSessionTrackingIntervalMillis") {
36+
options.sessionTrackingIntervalMillis = it
37+
}
38+
data.getIfNotNull<Long>("anrTimeoutIntervalMillis") {
39+
options.anrTimeoutIntervalMillis = it
40+
}
41+
data.getIfNotNull<Boolean>("attachThreads") {
42+
options.isAttachThreads = it
43+
}
44+
data.getIfNotNull<Boolean>("attachStacktrace") {
45+
options.isAttachStacktrace = it
46+
}
47+
data.getIfNotNull<Boolean>("enableAutoNativeBreadcrumbs") {
48+
options.isEnableActivityLifecycleBreadcrumbs = it
49+
options.isEnableAppLifecycleBreadcrumbs = it
50+
options.isEnableSystemEventBreadcrumbs = it
51+
options.isEnableAppComponentBreadcrumbs = it
52+
options.isEnableUserInteractionBreadcrumbs = it
53+
}
54+
data.getIfNotNull<Int>("maxBreadcrumbs") {
55+
options.maxBreadcrumbs = it
56+
}
57+
data.getIfNotNull<Int>("maxCacheItems") {
58+
options.maxCacheItems = it
59+
}
60+
data.getIfNotNull<String>("diagnosticLevel") {
61+
if (options.isDebug) {
62+
val sentryLevel = SentryLevel.valueOf(it.toUpperCase(Locale.ROOT))
63+
options.setDiagnosticLevel(sentryLevel)
64+
}
65+
}
66+
data.getIfNotNull<Boolean>("anrEnabled") {
67+
options.isAnrEnabled = it
68+
}
69+
data.getIfNotNull<Boolean>("sendDefaultPii") {
70+
options.isSendDefaultPii = it
71+
}
72+
data.getIfNotNull<Boolean>("enableNdkScopeSync") {
73+
options.isEnableScopeSync = it
74+
}
75+
data.getIfNotNull<String>("proguardUuid") {
76+
options.proguardUuid = it
77+
}
78+
79+
val nativeCrashHandling = (data["enableNativeCrashHandling"] as? Boolean) ?: true
80+
// nativeCrashHandling has priority over anrEnabled
81+
if (!nativeCrashHandling) {
82+
options.isEnableUncaughtExceptionHandler = false
83+
options.isAnrEnabled = false
84+
// if split symbols are enabled, we need Ndk integration so we can't really offer the option
85+
// to turn it off
86+
// options.isEnableNdk = false
87+
}
88+
89+
data.getIfNotNull<Boolean>("enableAutoPerformanceTracing") { enableAutoPerformanceTracing ->
90+
if (enableAutoPerformanceTracing) {
91+
autoPerformanceTracingEnabled = true
92+
}
93+
}
94+
95+
data.getIfNotNull<Boolean>("sendClientReports") {
96+
options.isSendClientReports = it
97+
}
98+
99+
data.getIfNotNull<Long>("maxAttachmentSize") {
100+
options.maxAttachmentSize = it
101+
}
102+
103+
var sdkVersion = options.sdkVersion
104+
if (sdkVersion == null) {
105+
sdkVersion = SdkVersion(androidSdk, BuildConfig.VERSION_NAME)
106+
} else {
107+
sdkVersion.name = androidSdk
108+
}
109+
110+
options.sdkVersion = sdkVersion
111+
options.sentryClientName = "$androidSdk/${BuildConfig.VERSION_NAME}"
112+
options.nativeSdkName = nativeSdk
113+
114+
data.getIfNotNull<Int>("connectionTimeoutMillis") {
115+
options.connectionTimeoutMillis = it
116+
}
117+
data.getIfNotNull<Int>("readTimeoutMillis") {
118+
options.readTimeoutMillis = it
119+
}
120+
}
121+
}
122+
123+
// Call the `completion` closure if cast to map value with `key` and type `T` is successful.
124+
@Suppress("UNCHECKED_CAST")
125+
private fun <T> Map<String, Any>.getIfNotNull(key: String, callback: (T) -> Unit) {
126+
(get(key) as? T)?.let {
127+
callback(it)
128+
}
129+
}

flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt

Lines changed: 9 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import java.util.UUID
3636
class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
3737
private lateinit var channel: MethodChannel
3838
private lateinit var context: Context
39+
private lateinit var sentryFlutter: SentryFlutter
3940

4041
private var activity: WeakReference<Activity>? = null
4142
private var framesTracker: ActivityFramesTracker? = null
@@ -45,6 +46,11 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
4546
context = flutterPluginBinding.applicationContext
4647
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sentry_flutter")
4748
channel.setMethodCallHandler(this)
49+
50+
sentryFlutter = SentryFlutter(
51+
androidSdk = androidSdk,
52+
nativeSdk = nativeSdk
53+
)
4854
}
4955

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

121127
SentryAndroid.init(context) { options ->
122-
args.getIfNotNull<String>("dsn") { options.dsn = it }
123-
args.getIfNotNull<Boolean>("debug") { options.isDebug = it }
124-
args.getIfNotNull<String>("environment") { options.environment = it }
125-
args.getIfNotNull<String>("release") { options.release = it }
126-
args.getIfNotNull<String>("dist") { options.dist = it }
127-
args.getIfNotNull<Boolean>("enableAutoSessionTracking") {
128-
options.isEnableAutoSessionTracking = it
129-
}
130-
args.getIfNotNull<Long>("autoSessionTrackingIntervalMillis") {
131-
options.sessionTrackingIntervalMillis = it
132-
}
133-
args.getIfNotNull<Long>("anrTimeoutIntervalMillis") {
134-
options.anrTimeoutIntervalMillis = it
135-
}
136-
args.getIfNotNull<Boolean>("attachThreads") { options.isAttachThreads = it }
137-
args.getIfNotNull<Boolean>("attachStacktrace") { options.isAttachStacktrace = it }
138-
args.getIfNotNull<Boolean>("enableAutoNativeBreadcrumbs") {
139-
options.isEnableActivityLifecycleBreadcrumbs = it
140-
options.isEnableAppLifecycleBreadcrumbs = it
141-
options.isEnableSystemEventBreadcrumbs = it
142-
options.isEnableAppComponentBreadcrumbs = it
143-
options.isEnableUserInteractionBreadcrumbs = it
144-
}
145-
args.getIfNotNull<Int>("maxBreadcrumbs") { options.maxBreadcrumbs = it }
146-
args.getIfNotNull<Int>("maxCacheItems") { options.maxCacheItems = it }
147-
args.getIfNotNull<String>("diagnosticLevel") {
148-
if (options.isDebug) {
149-
val sentryLevel = SentryLevel.valueOf(it.toUpperCase(Locale.ROOT))
150-
options.setDiagnosticLevel(sentryLevel)
151-
}
152-
}
153-
args.getIfNotNull<Boolean>("anrEnabled") { options.isAnrEnabled = it }
154-
args.getIfNotNull<Boolean>("sendDefaultPii") { options.isSendDefaultPii = it }
155-
args.getIfNotNull<Boolean>("enableNdkScopeSync") { options.isEnableScopeSync = it }
156-
args.getIfNotNull<String>("proguardUuid") { options.proguardUuid = it }
157-
158-
val nativeCrashHandling = (args["enableNativeCrashHandling"] as? Boolean) ?: true
159-
// nativeCrashHandling has priority over anrEnabled
160-
if (!nativeCrashHandling) {
161-
options.isEnableUncaughtExceptionHandler = false
162-
options.isAnrEnabled = false
163-
// if split symbols are enabled, we need Ndk integration so we can't really offer the option
164-
// to turn it off
165-
// options.isEnableNdk = false
166-
}
167-
168-
args.getIfNotNull<Boolean>("enableAutoPerformanceTracing") { enableAutoPerformanceTracing ->
169-
if (enableAutoPerformanceTracing) {
170-
autoPerformanceTracingEnabled = true
171-
framesTracker = ActivityFramesTracker(LoadClass(), options)
172-
}
173-
}
174-
175-
args.getIfNotNull<Boolean>("sendClientReports") { options.isSendClientReports = it }
128+
sentryFlutter.updateOptions(options, args)
176129

177-
args.getIfNotNull<Long>("maxAttachmentSize") { options.maxAttachmentSize = it }
178-
179-
var sdkVersion = options.sdkVersion
180-
if (sdkVersion == null) {
181-
sdkVersion = SdkVersion(androidSdk, VERSION_NAME)
182-
} else {
183-
sdkVersion.name = androidSdk
130+
if (sentryFlutter.autoPerformanceTracingEnabled) {
131+
framesTracker = ActivityFramesTracker(LoadClass(), options)
184132
}
185133

186-
options.sdkVersion = sdkVersion
187-
options.sentryClientName = "$androidSdk/$VERSION_NAME"
188-
options.nativeSdkName = nativeSdk
189134
options.beforeSend = BeforeSendCallbackImpl(options.sdkVersion)
190-
191-
args.getIfNotNull<Int>("connectionTimeoutMillis") { options.connectionTimeoutMillis = it }
192-
args.getIfNotNull<Int>("readTimeoutMillis") { options.readTimeoutMillis = it }
193-
194-
// missing proxy
195135
}
196136
result.success("")
197137
}
@@ -455,10 +395,3 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
455395
}
456396
}
457397
}
458-
459-
// Call the `completion` closure if cast to map value with `key` and type `T` is successful.
460-
private fun <T> Map<String, Any>.getIfNotNull(key: String, callback: (T) -> Unit) {
461-
(get(key) as? T)?.let {
462-
callback(it)
463-
}
464-
}

0 commit comments

Comments
 (0)