@@ -495,15 +495,20 @@ test('a wedged runner retires the bridge for the session across captures and app
495495 const { snapshot, fallbacks } = runtimeSnapshots ( withHostStates ( provider , 'notRunning' ) ) ;
496496 await snapshot ( ) ;
497497 state . refuseTarget = 'bridge-reader-wedged' ;
498- await snapshot ( ) ;
498+ const wedged = await snapshot ( ) ;
499499 state . process = { pid : 77 , processStartTime : '1700000100.000002' } ;
500- await snapshot ( ) ;
501- await snapshot ( ) ;
500+ const later = await snapshot ( ) ;
501+ const last = await snapshot ( ) ;
502+ for ( const result of [ wedged , later , last ] ) {
503+ expect ( result . warnings ) . toEqual ( [
504+ 'Simulator AX snapshot unavailable (bridge-reader-wedged); used XCTest for this runner session.' ,
505+ ] ) ;
506+ }
502507 expect ( verbs ( sent ) ) . toEqual ( [ 'target' , 'describe' , 'target' ] ) ;
503508 expect ( fallbacks ) . toHaveLength ( 4 ) ;
504509} ) ;
505510
506- test ( 'a never-returning read costs one wedged refusal after the runner bound ' , async ( ) => {
511+ test ( 'a wedged refusal retires the session after a held read ' , async ( ) => {
507512 const state : {
508513 process : RunnerProcess ;
509514 holdNextDescribe ?: Promise < void > ;
@@ -545,3 +550,102 @@ test('a replacement runner retries a generation retired by the previous runner o
545550 expect ( verbs ( sent ) ) . toEqual ( [ 'target' , 'describe' , 'describe' ] ) ;
546551 expect ( presented ) . toHaveLength ( 2 ) ;
547552} ) ;
553+
554+ test ( 'a late presentation failure cannot retire the replacement session generation' , async ( ) => {
555+ const state = { process : { pid : 41 , processStartTime : '1700000000.000001' } } ;
556+ const { provider, sent } = bridgeRunner ( state ) ;
557+ const bridge = createRunnerSnapshotBridge ( { device : IOS_SIMULATOR , provider } ) ;
558+ let sessionId = 'first' ;
559+ let release ! : ( ) => void ;
560+ let entered ! : ( ) => void ;
561+ const blocked = new Promise < void > ( ( resolve ) => {
562+ release = resolve ;
563+ } ) ;
564+ const presenting = new Promise < void > ( ( resolve ) => {
565+ entered = resolve ;
566+ } ) ;
567+ let presentationCount = 0 ;
568+ const route = createSnapshotBridgeRoute (
569+ {
570+ snapshot : {
571+ presentIosAcquisition : async ( result ) => {
572+ if ( ++ presentationCount === 1 ) {
573+ entered ( ) ;
574+ await blocked ;
575+ throw new Error ( 'old presentation failed' ) ;
576+ }
577+ return { ...runnerResult ( ) , producer : result . acquisition . producer } ;
578+ } ,
579+ } ,
580+ appleApplications : { hasLiveRunnerSession : async ( ) => true } ,
581+ } ,
582+ {
583+ source : bridge . source ,
584+ resolveTarget : bridge . resolveTarget ,
585+ isDiscoveryPending : ( ) => false ,
586+ targetFreshness : 'cached' ,
587+ sessionChanged : bridge . resetTargets ,
588+ } ,
589+ async ( ) => ( { foreground : 'background' , sessionId } ) ,
590+ ) ;
591+ const snapshot = ( ) =>
592+ route . capture (
593+ IOS_SIMULATOR ,
594+ { options : { appBundleId : APP , interactiveOnly : true } } ,
595+ new AbortController ( ) . signal ,
596+ async ( ) => runnerResult ( ) ,
597+ ) ;
598+ const old = snapshot ( ) ;
599+ await presenting ;
600+ sessionId = 'second' ;
601+ expect ( ( await snapshot ( ) ) . producer ) . toBe ( 'simulator-ax-bridge' ) ;
602+ release ( ) ;
603+ expect ( ( await old ) . warnings ) . toContain (
604+ 'Simulator AX snapshot unavailable (presentation-invariant); used XCTest for this app generation.' ,
605+ ) ;
606+ expect ( ( await snapshot ( ) ) . producer ) . toBe ( 'simulator-ax-bridge' ) ;
607+ expect ( verbs ( sent ) ) . toEqual ( [ 'target' , 'describe' , 'target' , 'describe' , 'describe' ] ) ;
608+ } ) ;
609+
610+ test ( 'a wedge before the runner id is known does not disable a newly identified session' , async ( ) => {
611+ const state : { process : RunnerProcess ; refuseTarget ?: string } = {
612+ process : { pid : 41 , processStartTime : '1700000000.000001' } ,
613+ refuseTarget : 'bridge-reader-wedged' ,
614+ } ;
615+ const { provider, sent } = bridgeRunner ( state ) ;
616+ const bridge = createRunnerSnapshotBridge ( { device : IOS_SIMULATOR , provider } ) ;
617+ let sessionId : string | undefined = undefined ;
618+ const route = createSnapshotBridgeRoute (
619+ {
620+ snapshot : {
621+ presentIosAcquisition : ( result ) => ( {
622+ ...runnerResult ( ) ,
623+ producer : result . acquisition . producer ,
624+ } ) ,
625+ } ,
626+ appleApplications : { hasLiveRunnerSession : async ( ) => true } ,
627+ } ,
628+ {
629+ source : bridge . source ,
630+ resolveTarget : bridge . resolveTarget ,
631+ isDiscoveryPending : ( ) => false ,
632+ targetFreshness : 'cached' ,
633+ sessionChanged : bridge . resetTargets ,
634+ } ,
635+ async ( ) => ( sessionId ? { foreground : 'background' , sessionId } : 'background' ) ,
636+ ) ;
637+ const snapshot = ( ) =>
638+ route . capture (
639+ IOS_SIMULATOR ,
640+ { options : { appBundleId : APP , interactiveOnly : true } } ,
641+ new AbortController ( ) . signal ,
642+ async ( ) => runnerResult ( ) ,
643+ ) ;
644+ expect ( ( await snapshot ( ) ) . warnings ) . toEqual ( [
645+ 'Simulator AX snapshot unavailable (bridge-reader-wedged); used XCTest for this runner session.' ,
646+ ] ) ;
647+ sessionId = 'new' ;
648+ state . refuseTarget = undefined ;
649+ expect ( ( await snapshot ( ) ) . producer ) . toBe ( 'simulator-ax-bridge' ) ;
650+ expect ( verbs ( sent ) ) . toEqual ( [ 'target' , 'target' , 'describe' ] ) ;
651+ } ) ;
0 commit comments