@@ -85,52 +85,69 @@ extension RunnerTests {
8585 }
8686
8787 @ MainActor
88- func testSkippedActivationAfterTargetResetKeepsAnElapsedWindow ( ) {
88+ func testSkippedActivationAfterAnnouncedRelaunchKeepsAnElapsedWindow ( ) {
8989 withStubbedTargetActivation {
9090 mainOwned . needsPostSnapshotInteractionDelay = false
9191 RunnerTargetActivationSpy . state = . runningForeground
92+ mainOwned . app = app
93+ mainOwned . bundleId = " com.example.app "
9294 _ = resetTargetAfterExternalRelaunch ( )
93- firstInteractionReadyUptime = ProcessInfo . processInfo . systemUptime - 1
95+ stampElapsedWindow ( origin : . announcedRelaunch ( bundleId : " com.example.app " ) )
9496
9597 _ = activateTarget ( bundleId : " com.example.app " , reason : " bundle_changed " )
9698
9799 XCTAssertEqual ( RunnerTargetActivationSpy . activationCount , 0 )
98100 XCTAssertLessThan ( measureStabilizationDuration ( ) , firstInteractionAfterActivateDelay / 2 )
99- XCTAssertNil ( firstInteractionReadyUptime )
101+ XCTAssertNil ( firstInteractionWindow )
100102 }
101103 }
102104
103105 @ MainActor
104- func testSkippedActivationAfterTargetResetWaitsOnlyForTheRemainingWindow ( ) {
106+ func testSkippedActivationAfterAnnouncedRelaunchWaitsOnlyForTheRemainingWindow ( ) {
105107 withStubbedTargetActivation {
106108 mainOwned . needsPostSnapshotInteractionDelay = false
107109 RunnerTargetActivationSpy . state = . runningForeground
110+ mainOwned . app = app
111+ mainOwned . bundleId = " com.example.app "
108112 _ = resetTargetAfterExternalRelaunch ( )
109- let resetDeadline = firstInteractionReadyUptime
113+ let resetWindow = firstInteractionWindow
110114 sleepFor ( firstInteractionAfterActivateDelay / 2 )
111115
112116 _ = activateTarget ( bundleId : " com.example.app " , reason : " bundle_changed " )
113117
114- XCTAssertEqual ( firstInteractionReadyUptime , resetDeadline )
115- let waited = measureStabilizationDuration ( )
116- XCTAssertLessThan ( waited , firstInteractionAfterActivateDelay * 0.75 )
117- XCTAssertNil ( firstInteractionReadyUptime )
118+ XCTAssertEqual ( firstInteractionWindow ? . readyUptime , resetWindow ? . readyUptime )
119+ XCTAssertEqual ( firstInteractionWindow ? . origin , . announcedRelaunch ( bundleId : " com.example.app " ) )
120+ XCTAssertLessThan ( measureStabilizationDuration ( ) , firstInteractionAfterActivateDelay * 0.75 )
121+ XCTAssertNil ( firstInteractionWindow )
118122 }
119123 }
120124
121125 @ MainActor
122- func testInvalidationForErrorDiscardsTheWindowSoTheNextSkippedActivationStampsFresh ( ) {
126+ func testAnnouncedRelaunchWithNoBoundTargetCoversWhicheverBundleActivatesNext ( ) {
123127 withStubbedTargetActivation {
124128 mainOwned . needsPostSnapshotInteractionDelay = false
125129 RunnerTargetActivationSpy . state = . runningForeground
126- mainOwned . app = app
127- mainOwned . bundleId = " com.example.app "
128- firstInteractionReadyUptime = ProcessInfo . processInfo . systemUptime - 1
130+ invalidateCachedTarget ( reason : " unit_test_setup " )
131+ _ = resetTargetAfterExternalRelaunch ( )
132+ XCTAssertEqual ( firstInteractionWindow ? . origin , . announcedRelaunch ( bundleId : nil ) )
133+ stampElapsedWindow ( origin : . announcedRelaunch ( bundleId : nil ) )
129134
130- invalidateCachedTarget ( reason : " target_terminated " )
131135 _ = activateTarget ( bundleId : " com.example.app " , reason : " bundle_changed " )
132136
133- XCTAssertEqual ( RunnerTargetActivationSpy . activationCount , 0 )
137+ XCTAssertLessThan ( measureStabilizationDuration ( ) , firstInteractionAfterActivateDelay / 2 )
138+ }
139+ }
140+
141+ @ MainActor
142+ func testSkippedActivationOfAnotherBundleThanTheAnnouncedRelaunchStampsFresh ( ) {
143+ withStubbedTargetActivation {
144+ mainOwned . needsPostSnapshotInteractionDelay = false
145+ RunnerTargetActivationSpy . state = . runningForeground
146+ stampElapsedWindow ( origin : . announcedRelaunch ( bundleId : " com.example.a " ) )
147+
148+ _ = activateTarget ( bundleId : " com.example.b " , reason : " bundle_changed " )
149+
150+ XCTAssertEqual ( firstInteractionWindow ? . origin , . activation )
134151 XCTAssertGreaterThanOrEqual (
135152 measureStabilizationDuration ( ) ,
136153 firstInteractionAfterActivateDelay * 0.8
@@ -139,14 +156,18 @@ extension RunnerTests {
139156 }
140157
141158 @ MainActor
142- func testSkippedActivationWithoutADeadlineStampsTheWindow ( ) {
159+ func testStaleActivationWindowSurvivingTerminateInvalidationStampsFreshOnSkippedActivation ( ) {
143160 withStubbedTargetActivation {
144161 mainOwned . needsPostSnapshotInteractionDelay = false
145162 RunnerTargetActivationSpy . state = . runningForeground
146- firstInteractionReadyUptime = nil
163+ mainOwned . app = app
164+ mainOwned . bundleId = " com.example.app "
165+ stampElapsedWindow ( origin : . activation )
147166
167+ invalidateCachedTarget ( reason : " target_terminated " )
148168 _ = activateTarget ( bundleId : " com.example.app " , reason : " bundle_changed " )
149169
170+ XCTAssertEqual ( RunnerTargetActivationSpy . activationCount , 0 )
150171 XCTAssertGreaterThanOrEqual (
151172 measureStabilizationDuration ( ) ,
152173 firstInteractionAfterActivateDelay * 0.8
@@ -155,15 +176,15 @@ extension RunnerTests {
155176 }
156177
157178 @ MainActor
158- func testPerformedActivationReplacesAnElapsedWindow ( ) {
179+ func testSkippedActivationWithoutAWindowStampsOne ( ) {
159180 withStubbedTargetActivation {
160181 mainOwned . needsPostSnapshotInteractionDelay = false
161- RunnerTargetActivationSpy . state = . runningBackground
162- firstInteractionReadyUptime = ProcessInfo . processInfo . systemUptime - 1
182+ RunnerTargetActivationSpy . state = . runningForeground
183+ firstInteractionWindow = nil
163184
164185 _ = activateTarget ( bundleId : " com.example.app " , reason : " bundle_changed " )
165186
166- XCTAssertEqual ( RunnerTargetActivationSpy . activationCount , 1 )
187+ XCTAssertEqual ( firstInteractionWindow ? . origin , . activation )
167188 XCTAssertGreaterThanOrEqual (
168189 measureStabilizationDuration ( ) ,
169190 firstInteractionAfterActivateDelay * 0.8
@@ -172,55 +193,28 @@ extension RunnerTests {
172193 }
173194
174195 @ MainActor
175- func testSkippedActivationOfAnotherBundleReplacesAnElapsedWindow ( ) {
196+ func testPerformedActivationReplacesAnElapsedAnnouncedWindow ( ) {
176197 withStubbedTargetActivation {
177198 mainOwned . needsPostSnapshotInteractionDelay = false
178- RunnerTargetActivationSpy . state = . runningForeground
179- mainOwned . app = app
180- mainOwned . bundleId = " com.example.previous "
181- firstInteractionReadyUptime = ProcessInfo . processInfo . systemUptime - 1
199+ RunnerTargetActivationSpy . state = . runningBackground
200+ stampElapsedWindow ( origin : . announcedRelaunch ( bundleId : " com.example.app " ) )
182201
183202 _ = activateTarget ( bundleId : " com.example.app " , reason : " bundle_changed " )
184203
204+ XCTAssertEqual ( RunnerTargetActivationSpy . activationCount , 1 )
205+ XCTAssertEqual ( firstInteractionWindow ? . origin , . activation )
185206 XCTAssertGreaterThanOrEqual (
186207 measureStabilizationDuration ( ) ,
187208 firstInteractionAfterActivateDelay * 0.8
188209 )
189210 }
190211 }
191212
192- func testForegroundReacquisitionContinuesOnlyTheSameBoundProcess ( ) {
193- XCTAssertTrue (
194- Self . reacquiresBoundTarget (
195- previousBundleId : nil ,
196- previousProcessIdentifier : nil ,
197- bundleId : " com.example.app " ,
198- processIdentifier : 42
199- )
200- )
201- XCTAssertTrue (
202- Self . reacquiresBoundTarget (
203- previousBundleId : " com.example.app " ,
204- previousProcessIdentifier : 42 ,
205- bundleId : " com.example.app " ,
206- processIdentifier : 42
207- )
208- )
209- XCTAssertFalse (
210- Self . reacquiresBoundTarget (
211- previousBundleId : " com.example.app " ,
212- previousProcessIdentifier : 41 ,
213- bundleId : " com.example.app " ,
214- processIdentifier : 42
215- )
216- )
217- XCTAssertFalse (
218- Self . reacquiresBoundTarget (
219- previousBundleId : " com.example.other " ,
220- previousProcessIdentifier : 42 ,
221- bundleId : " com.example.app " ,
222- processIdentifier : 42
223- )
213+ @ MainActor
214+ private func stampElapsedWindow ( origin : FirstInteractionWindow . Origin ) {
215+ firstInteractionWindow = FirstInteractionWindow (
216+ readyUptime : ProcessInfo . processInfo . systemUptime - 1 ,
217+ origin : origin
224218 )
225219 }
226220#endif
@@ -296,7 +290,7 @@ extension RunnerTests {
296290 mainOwned . bundleId = " com.example.app "
297291 mainOwned . processIdentifier = 42
298292 snapshotXCTestPenaltyWarmupExemption . isPending = true
299- firstInteractionReadyUptime = nil
293+ firstInteractionWindow = nil
300294 penalizeSnapshotXCTestChannel ( bundleId : " com.example.app " , reason : " test " )
301295 XCTAssertTrue ( isSnapshotXCTestChannelPenalized ( bundleId : " com.example.app " ) )
302296
@@ -308,7 +302,7 @@ extension RunnerTests {
308302 XCTAssertNil ( mainOwned . processIdentifier )
309303 XCTAssertFalse ( snapshotXCTestPenaltyWarmupExemption . isPending )
310304 XCTAssertFalse ( isSnapshotXCTestChannelPenalized ( bundleId : " com.example.app " ) )
311- XCTAssertNotNil ( firstInteractionReadyUptime )
305+ XCTAssertEqual ( firstInteractionWindow ? . origin , . announcedRelaunch ( bundleId : " com.example.app " ) )
312306 }
313307
314308 /// The settling window is a deadline measured from the activation, not a pause charged at the
@@ -319,10 +313,13 @@ extension RunnerTests {
319313 mainOwned . needsPostSnapshotInteractionDelay = false
320314
321315 // An activation whose window has already elapsed: the caller spent it getting back to us.
322- firstInteractionReadyUptime = ProcessInfo . processInfo . systemUptime - 1
316+ firstInteractionWindow = FirstInteractionWindow (
317+ readyUptime : ProcessInfo . processInfo . systemUptime - 1 ,
318+ origin : . activation
319+ )
323320 let elapsedAfterSatisfiedWindow = measureStabilizationDuration ( )
324321 XCTAssertLessThan ( elapsedAfterSatisfiedWindow , firstInteractionAfterActivateDelay / 2 )
325- XCTAssertNil ( firstInteractionReadyUptime )
322+ XCTAssertNil ( firstInteractionWindow )
326323
327324 // A fresh activation still gets the whole guard.
328325 beginFirstInteractionStabilization ( )
@@ -331,7 +328,7 @@ extension RunnerTests {
331328 elapsedAfterFreshActivation ,
332329 firstInteractionAfterActivateDelay * 0.8
333330 )
334- XCTAssertNil ( firstInteractionReadyUptime )
331+ XCTAssertNil ( firstInteractionWindow )
335332 }
336333
337334 @ MainActor
0 commit comments