1+ @testable import Sentry
12import XCTest
23
34//swiftlint:disable function_body_length todo
45
56class ProfilingUITests : BaseUITest {
67 override var automaticallyLaunchAndTerminateApp : Bool { false }
7-
8+
89 func testAppLaunchesWithTraceProfiler( ) throws {
910 guard #available( iOS 16 , * ) else {
1011 throw XCTSkip ( " Only run for latest iOS version we test; we've had issues with prior versions in SauceLabs " )
1112 }
1213
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)
1815 }
1916
2017 func testAppLaunchesWithContinuousProfilerV1( ) throws {
2118 guard #available( iOS 16 , * ) else {
2219 throw XCTSkip ( " Only run for latest iOS version we test; we've had issues with prior versions in SauceLabs " )
2320 }
2421
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)
3023 }
3124
3225 func testAppLaunchesWithContinuousProfilerV2TraceLifecycle( ) throws {
3326 guard #available( iOS 16 , * ) else {
3427 throw XCTSkip ( " Only run for latest iOS version we test; we've had issues with prior versions in SauceLabs " )
3528 }
3629
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)
4231 }
4332
4433 func testAppLaunchesWithContinuousProfilerV2ManualLifeCycle( ) throws {
4534 guard #available( iOS 16 , * ) else {
4635 throw XCTSkip ( " Only run for latest iOS version we test; we've had issues with prior versions in SauceLabs " )
4736 }
4837
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)
5439 }
5540
5641 /**
@@ -144,72 +129,104 @@ extension ProfilingUITests {
144129 func stopContinuousProfiler( ) {
145130 app. buttons [ " io.sentry.ios-swift.ui-test.button.stop-continuous-profiler " ] . afterWaitingForExistence ( " Couldn't find button to stop continuous profiler " ) . tap ( )
146131 }
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+
148180 /**
149181 * Performs the various operations for the launch profiler test case:
150182 * - terminates an existing app session
151- * - creates a new one
183+ * - starts a new app session
152184 * - 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
154186 * - asserts the expected outcomes of the config file and launch profiler
155187 */
156188 func launchAndConfigureSubsequentLaunches(
157189 terminatePriorSession: Bool = false ,
158190 shouldProfileThisLaunch: Bool ,
159- continuousProfiling : Bool = false ,
160- v2TraceLifecycle : Bool = false ,
161- v2ManualLifecycle : Bool = false
191+ shouldProfileNextLaunch : Bool ,
192+ profileType : ProfilingType ,
193+ lifecycle : SentryProfileOptions . SentryProfileLifecycle ?
162194 ) throws {
163195 if terminatePriorSession {
164196 app. terminate ( )
165197 app = newAppSession ( )
166198 }
167199
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)
173201
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 ( )
176204
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 ( )
180206
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. " )
192209 } 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. " )
194211 }
195212
196- launchApp ( )
197- goToProfiling ( )
198- XCTAssert ( try checkLaunchProfileMarkerFileExistence ( ) )
199-
200213 guard shouldProfileThisLaunch else {
201214 return
202215 }
203-
204- if continuousProfiling {
205- if !v2TraceLifecycle {
216+
217+ if profileType == . trace {
218+ retrieveLastProfileData ( )
219+ } else {
220+ if profileType == . continuous || ( profileType == . ui && lifecycle == . manual) {
206221 stopContinuousProfiler ( )
207222 }
208223 retrieveFirstProfileChunkData ( )
209- } else {
210- retrieveLastProfileData ( )
211224 }
212-
225+
226+ try assertProfileContents ( )
227+ }
228+
229+ func assertProfileContents( ) throws {
213230 let lastProfile = try marshalJSONDictionaryFromApp ( )
214231 let sampledProfile = try XCTUnwrap ( lastProfile [ " profile " ] as? [ String : Any ] )
215232 let stacks = try XCTUnwrap ( sampledProfile [ " stacks " ] as? [ [ Int ] ] )
@@ -219,7 +236,7 @@ extension ProfilingUITests {
219236 frames [ stackFrame] [ " function " ]
220237 }
221238 } )
222-
239+
223240 // grab the first stack that contained frames from the fixture code that simulates a slow +[load] method
224241 var stackID : Int ?
225242 let stack = try XCTUnwrap ( stackFunctions. enumerated ( ) . first { nextStack in
@@ -238,12 +255,12 @@ extension ProfilingUITests {
238255 XCTFail ( " Didn't find the ID of the stack containing the target function " )
239256 return
240257 }
241-
258+
242259 // ensure that the stack doesn't contain any calls to main functions; this ensures we actually captured pre-main stacks
243260 XCTAssertFalse ( stack. contains ( " main " ) )
244261 XCTAssertFalse ( stack. contains ( " UIApplicationMain " ) )
245262 XCTAssertFalse ( stack. contains ( " -[UIApplication _run] " ) )
246-
263+
247264 // 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)
248265 let samples = try XCTUnwrap ( sampledProfile [ " samples " ] as? [ [ String : Any ] ] )
249266 let sample = try XCTUnwrap ( samples. first { nextSample in
0 commit comments