1
+ @testable import Sentry
1
2
import XCTest
2
3
3
4
//swiftlint:disable function_body_length todo
4
5
5
6
class ProfilingUITests : BaseUITest {
6
7
override var automaticallyLaunchAndTerminateApp : Bool { false }
7
-
8
+
8
9
func testAppLaunchesWithTraceProfiler( ) throws {
9
10
guard #available( iOS 16 , * ) else {
10
11
throw XCTSkip ( " Only run for latest iOS version we test; we've had issues with prior versions in SauceLabs " )
11
12
}
12
13
13
- // by default, launch profiling is not enabled
14
- try launchAndConfigureSubsequentLaunches ( shouldProfileThisLaunch: false )
15
-
16
- // after configuring for launch profiling, check the marker file exists, and that the profile happens
17
- try launchAndConfigureSubsequentLaunches ( terminatePriorSession: true , shouldProfileThisLaunch: true )
14
+ try performTest ( profileType: . trace)
18
15
}
19
16
20
17
func testAppLaunchesWithContinuousProfilerV1( ) throws {
21
18
guard #available( iOS 16 , * ) else {
22
19
throw XCTSkip ( " Only run for latest iOS version we test; we've had issues with prior versions in SauceLabs " )
23
20
}
24
21
25
- // by default, launch profiling is not enabled
26
- try launchAndConfigureSubsequentLaunches ( shouldProfileThisLaunch: false , continuousProfiling: true )
27
-
28
- // after configuring for launch profiling, check the marker file exists, and that the profile happens
29
- try launchAndConfigureSubsequentLaunches ( terminatePriorSession: true , shouldProfileThisLaunch: true , continuousProfiling: true )
22
+ try performTest ( profileType: . continuous)
30
23
}
31
24
32
25
func testAppLaunchesWithContinuousProfilerV2TraceLifecycle( ) throws {
33
26
guard #available( iOS 16 , * ) else {
34
27
throw XCTSkip ( " Only run for latest iOS version we test; we've had issues with prior versions in SauceLabs " )
35
28
}
36
29
37
- // by default, launch profiling is not enabled
38
- try launchAndConfigureSubsequentLaunches ( shouldProfileThisLaunch: false , continuousProfiling: true , v2TraceLifecycle: true )
39
-
40
- // after configuring for launch profiling, check the marker file exists, and that the profile happens
41
- try launchAndConfigureSubsequentLaunches ( terminatePriorSession: true , shouldProfileThisLaunch: true , continuousProfiling: true , v2TraceLifecycle: true )
30
+ try performTest ( profileType: . ui, lifecycle: . trace)
42
31
}
43
32
44
33
func testAppLaunchesWithContinuousProfilerV2ManualLifeCycle( ) throws {
45
34
guard #available( iOS 16 , * ) else {
46
35
throw XCTSkip ( " Only run for latest iOS version we test; we've had issues with prior versions in SauceLabs " )
47
36
}
48
37
49
- // by default, launch profiling is not enabled
50
- try launchAndConfigureSubsequentLaunches ( shouldProfileThisLaunch: false , continuousProfiling: true , v2ManualLifecycle: true )
51
-
52
- // after configuring for launch profiling, check the marker file exists, and that the profile happens
53
- try launchAndConfigureSubsequentLaunches ( terminatePriorSession: true , shouldProfileThisLaunch: true , continuousProfiling: true , v2ManualLifecycle: true )
38
+ try performTest ( profileType: . ui, lifecycle: . manual)
54
39
}
55
40
56
41
/**
@@ -144,72 +129,104 @@ extension ProfilingUITests {
144
129
func stopContinuousProfiler( ) {
145
130
app. buttons [ " io.sentry.ios-swift.ui-test.button.stop-continuous-profiler " ] . afterWaitingForExistence ( " Couldn't find button to stop continuous profiler " ) . tap ( )
146
131
}
147
-
132
+
133
+ enum ProfilingType {
134
+ case trace
135
+ case continuous // aka "continuous beta"
136
+ case ui // aka "continuous v2"
137
+ }
138
+
139
+ func performTest( profileType: ProfilingType , lifecycle: SentryProfileOptions . SentryProfileLifecycle ? = nil ) throws {
140
+ try launchAndConfigureSubsequentLaunches ( shouldProfileThisLaunch: false , shouldProfileNextLaunch: true , profileType: profileType, lifecycle: lifecycle)
141
+ try launchAndConfigureSubsequentLaunches ( terminatePriorSession: true , shouldProfileThisLaunch: true , shouldProfileNextLaunch: false , profileType: profileType, lifecycle: lifecycle)
142
+ }
143
+
144
+ fileprivate func setAppLaunchParameters( _ profileType: ProfilingUITests . ProfilingType , _ lifecycle: SentryProfileOptions . SentryProfileLifecycle ? , _ shouldProfileNextLaunch: Bool ) {
145
+ app. launchArguments. append ( contentsOf: [
146
+ // these help avoid other profiles that'd be taken automatically, that interfere with the checking we do for the assertions later in the tests
147
+ " --disable-swizzling " ,
148
+ " --disable-auto-performance-tracing " ,
149
+ " --disable-uiviewcontroller-tracing " ,
150
+
151
+ // sets a marker function to run in a load command that the launch profile should detect
152
+ " --io.sentry.slow-load-method " ,
153
+
154
+ // override full chunk completion before stoppage introduced in https://github.com/getsentry/sentry-cocoa/pull/4214
155
+ " --io.sentry.continuous-profiler-immediate-stop "
156
+ ] )
157
+
158
+ switch profileType {
159
+ case . ui:
160
+ app. launchEnvironment [ " --io.sentry.profile-session-sample-rate " ] = " 1 "
161
+ switch lifecycle {
162
+ case . none:
163
+ fatalError ( " Misconfigured test case. Must provide a lifecycle for UI profiling. " )
164
+ case . trace:
165
+ break
166
+ case . manual:
167
+ app. launchArguments. append ( " --io.sentry.profile-lifecycle-manual " )
168
+ }
169
+ case . continuous:
170
+ app. launchArguments. append ( " --io.sentry.disable-ui-profiling " )
171
+ case . trace:
172
+ app. launchEnvironment [ " --io.sentry.profilesSampleRate " ] = " 1 "
173
+ }
174
+
175
+ if !shouldProfileNextLaunch {
176
+ app. launchArguments. append ( " --io.sentry.disable-app-start-profiling " )
177
+ }
178
+ }
179
+
148
180
/**
149
181
* Performs the various operations for the launch profiler test case:
150
182
* - terminates an existing app session
151
- * - creates a new one
183
+ * - starts a new app session
152
184
* - sets launch args and env vars to set the appropriate `SentryOption` values for the desired behavior
153
- * - launches the new configured app session
185
+ * - launches the new configured app session, which will optionally start a launch profiler and then call SentrySDK.startWithOptions configured based on the launch args and env vars
154
186
* - asserts the expected outcomes of the config file and launch profiler
155
187
*/
156
188
func launchAndConfigureSubsequentLaunches(
157
189
terminatePriorSession: Bool = false ,
158
190
shouldProfileThisLaunch: Bool ,
159
- continuousProfiling : Bool = false ,
160
- v2TraceLifecycle : Bool = false ,
161
- v2ManualLifecycle : Bool = false
191
+ shouldProfileNextLaunch : Bool ,
192
+ profileType : ProfilingType ,
193
+ lifecycle : SentryProfileOptions . SentryProfileLifecycle ?
162
194
) throws {
163
195
if terminatePriorSession {
164
196
app. terminate ( )
165
197
app = newAppSession ( )
166
198
}
167
199
168
- app. launchArguments. append ( contentsOf: [
169
- // these help avoid other profiles that'd be taken automatically, that interfere with the checking we do for the assertions later in the tests
170
- " --disable-swizzling " ,
171
- " --disable-auto-performance-tracing " ,
172
- " --disable-uiviewcontroller-tracing " ,
200
+ setAppLaunchParameters ( profileType, lifecycle, shouldProfileNextLaunch)
173
201
174
- // sets a marker function to run in a load command that the launch profile should detect
175
- " --io.sentry.slow-load-method " ,
202
+ launchApp ( activateBeforeLaunch : false )
203
+ goToProfiling ( )
176
204
177
- // override full chunk completion before stoppage introduced in https://github.com/getsentry/sentry-cocoa/pull/4214
178
- " --io.sentry.continuous-profiler-immediate-stop "
179
- ] )
205
+ let configFileExists = try checkLaunchProfileMarkerFileExistence ( )
180
206
181
- if continuousProfiling {
182
- if v2TraceLifecycle {
183
- app. launchEnvironment [ " --io.sentry.profile-session-sample-rate " ] = " 1 "
184
- } else if v2ManualLifecycle {
185
- app. launchArguments. append ( contentsOf: [
186
- " --io.sentry.profile-lifecycle-manual "
187
- ] )
188
- app. launchEnvironment [ " --io.sentry.profile-session-sample-rate " ] = " 1 "
189
- } else {
190
- app. launchArguments. append ( " --io.sentry.disable-ui-profiling " )
191
- }
207
+ if shouldProfileNextLaunch {
208
+ XCTAssert ( configFileExists, " A launch profile config file should be present on disk if SentrySDK.startWithOptions configured launch profiling for the next launch. " )
192
209
} else {
193
- app . launchEnvironment [ " --io.sentry.profilesSampleRate " ] = " 1 "
210
+ XCTAssertFalse ( configFileExists , " Launch profile config files should be removed upon starting launch profiles. If SentrySDK.startWithOptions doesn't reconfigure launch profiling, the config file should not be present. " )
194
211
}
195
212
196
- launchApp ( )
197
- goToProfiling ( )
198
- XCTAssert ( try checkLaunchProfileMarkerFileExistence ( ) )
199
-
200
213
guard shouldProfileThisLaunch else {
201
214
return
202
215
}
203
-
204
- if continuousProfiling {
205
- if !v2TraceLifecycle {
216
+
217
+ if profileType == . trace {
218
+ retrieveLastProfileData ( )
219
+ } else {
220
+ if profileType == . continuous || ( profileType == . ui && lifecycle == . manual) {
206
221
stopContinuousProfiler ( )
207
222
}
208
223
retrieveFirstProfileChunkData ( )
209
- } else {
210
- retrieveLastProfileData ( )
211
224
}
212
-
225
+
226
+ try assertProfileContents ( )
227
+ }
228
+
229
+ func assertProfileContents( ) throws {
213
230
let lastProfile = try marshalJSONDictionaryFromApp ( )
214
231
let sampledProfile = try XCTUnwrap ( lastProfile [ " profile " ] as? [ String : Any ] )
215
232
let stacks = try XCTUnwrap ( sampledProfile [ " stacks " ] as? [ [ Int ] ] )
@@ -219,7 +236,7 @@ extension ProfilingUITests {
219
236
frames [ stackFrame] [ " function " ]
220
237
}
221
238
} )
222
-
239
+
223
240
// grab the first stack that contained frames from the fixture code that simulates a slow +[load] method
224
241
var stackID : Int ?
225
242
let stack = try XCTUnwrap ( stackFunctions. enumerated ( ) . first { nextStack in
@@ -238,12 +255,12 @@ extension ProfilingUITests {
238
255
XCTFail ( " Didn't find the ID of the stack containing the target function " )
239
256
return
240
257
}
241
-
258
+
242
259
// ensure that the stack doesn't contain any calls to main functions; this ensures we actually captured pre-main stacks
243
260
XCTAssertFalse ( stack. contains ( " main " ) )
244
261
XCTAssertFalse ( stack. contains ( " UIApplicationMain " ) )
245
262
XCTAssertFalse ( stack. contains ( " -[UIApplication _run] " ) )
246
-
263
+
247
264
// ensure that the stack happened on the main thread; this is a cross-check to make sure we didn't accidentally grab a stack from a different thread that wouldn't have had a call to main() anyways, thereby possibly missing the real stack that may have contained main() calls (but shouldn't for this test)
248
265
let samples = try XCTUnwrap ( sampledProfile [ " samples " ] as? [ [ String : Any ] ] )
249
266
let sample = try XCTUnwrap ( samples. first { nextSample in
0 commit comments