@@ -57,31 +57,11 @@ beforeEach(() => {
5757} ) ;
5858
5959test ( 'prepareIosRunner marks a bad restored artifact and rebuilds once after health failure' , async ( ) => {
60- const restoredArtifact = makeRunnerArtifact ( {
61- xctestrunPath : '/tmp/restored.xctestrun' ,
62- cache : 'exact' ,
63- artifact : 'valid' ,
64- } ) ;
65- const rebuiltArtifact = makeRunnerArtifact ( {
66- xctestrunPath : '/tmp/rebuilt.xctestrun' ,
67- cache : 'miss' ,
68- artifact : 'rebuilt' ,
69- buildMs : 123 ,
70- } ) ;
71- const restoredSession = makeRunnerSession ( {
72- port : 8100 ,
73- xctestrunPath : restoredArtifact . xctestrunPath ,
74- xctestrunArtifact : restoredArtifact ,
75- } ) ;
76- const rebuiltSession = makeRunnerSession ( {
77- port : 8101 ,
78- xctestrunPath : rebuiltArtifact . xctestrunPath ,
79- xctestrunArtifact : rebuiltArtifact ,
80- } ) ;
60+ const fixtures = makeBadCacheRecoveryFixtures ( ) ;
8161
8262 mockEnsureRunnerSession
83- . mockResolvedValueOnce ( restoredSession )
84- . mockResolvedValueOnce ( rebuiltSession ) ;
63+ . mockResolvedValueOnce ( fixtures . restoredSession )
64+ . mockResolvedValueOnce ( fixtures . rebuiltSession ) ;
8565 mockExecuteRunnerCommandWithSession
8666 . mockRejectedValueOnce ( new AppError ( 'COMMAND_FAILED' , 'Runner did not accept connection' ) )
8767 . mockResolvedValueOnce ( { uptimeMs : 42 } ) ;
@@ -91,54 +71,9 @@ test('prepareIosRunner marks a bad restored artifact and rebuilds once after hea
9171 buildTimeoutMs : 300_000 ,
9272 } ) ;
9373
94- assert . deepEqual ( result , {
95- runner : { uptimeMs : 42 } ,
96- cache : 'miss' ,
97- artifact : 'rebuilt' ,
98- buildMs : 123 ,
99- connectMs : result . connectMs ,
100- healthCheckMs : result . healthCheckMs ,
101- xctestrunPath : '/tmp/rebuilt.xctestrun' ,
102- recoveryReason : 'Runner did not accept connection' ,
103- } ) ;
104- assert . equal ( result . failureReason , undefined ) ;
105- assert . equal ( result . connectMs >= 0 , true ) ;
106- assert . equal ( result . healthCheckMs >= 0 , true ) ;
107- assert . deepEqual ( mockInvalidateRunnerSession . mock . calls [ 0 ] , [
108- restoredSession ,
109- 'prepare_cached_runner_health_failed' ,
110- ] ) ;
111- assert . deepEqual ( mockMarkRunnerXctestrunArtifactBadForRun . mock . calls [ 0 ] , [
112- restoredArtifact ,
113- 'Runner did not accept connection' ,
114- ] ) ;
115- assert . deepEqual ( mockEnsureRunnerSession . mock . calls [ 1 ] ?. [ 1 ] , {
116- healthTimeoutMs : 90_000 ,
117- buildTimeoutMs : 300_000 ,
118- cleanStaleBundles : true ,
119- forceRunnerXctestrunRebuild : true ,
120- } ) ;
121- assert . equal ( mockExecuteRunnerCommandWithSession . mock . calls . length , 2 ) ;
122- assert . equal ( mockExecuteRunnerCommandWithSession . mock . calls [ 0 ] ?. [ 2 ] . command , 'uptime' ) ;
123- assert . equal ( mockExecuteRunnerCommandWithSession . mock . calls [ 0 ] ?. [ 4 ] , 90_000 ) ;
124- assert . equal ( mockExecuteRunnerCommandWithSession . mock . calls [ 1 ] ?. [ 1 ] , rebuiltSession ) ;
125- assert . ok (
126- mockEmitDiagnostic . mock . calls . some (
127- ( [ event ] ) => event . phase === 'ios_runner_prepare_bad_cache_recovered' ,
128- ) ,
129- ) ;
130- assert . ok (
131- mockEmitDiagnostic . mock . calls . some (
132- ( [ event ] ) =>
133- event . phase === 'apple_runner_prepare' &&
134- event . data ?. cache === 'miss' &&
135- event . data ?. artifact === 'rebuilt' &&
136- event . data ?. xctestrunPath === '/tmp/rebuilt.xctestrun' &&
137- event . data ?. recoveryReason === 'Runner did not accept connection' &&
138- event . data ?. failureReason === undefined &&
139- event . level === 'info' ,
140- ) ,
141- ) ;
74+ assertRecoveredPrepareResult ( result ) ;
75+ assertBadCacheRecoverySideEffects ( fixtures ) ;
76+ assertRecoveredPrepareDiagnostics ( ) ;
14277} ) ;
14378
14479test ( 'prepareIosRunner invalidates rebuilt sessions when bad-cache recovery health fails' , async ( ) => {
@@ -715,6 +650,89 @@ test('mutating commands invalidate the retry session without replaying again', a
715650 } ) ;
716651} ) ;
717652
653+ function makeBadCacheRecoveryFixtures ( ) {
654+ const restoredArtifact = makeRunnerArtifact ( {
655+ xctestrunPath : '/tmp/restored.xctestrun' ,
656+ cache : 'exact' ,
657+ artifact : 'valid' ,
658+ } ) ;
659+ const rebuiltArtifact = makeRunnerArtifact ( {
660+ xctestrunPath : '/tmp/rebuilt.xctestrun' ,
661+ cache : 'miss' ,
662+ artifact : 'rebuilt' ,
663+ buildMs : 123 ,
664+ } ) ;
665+ const restoredSession = makeRunnerSession ( {
666+ port : 8100 ,
667+ xctestrunPath : restoredArtifact . xctestrunPath ,
668+ xctestrunArtifact : restoredArtifact ,
669+ } ) ;
670+ const rebuiltSession = makeRunnerSession ( {
671+ port : 8101 ,
672+ xctestrunPath : rebuiltArtifact . xctestrunPath ,
673+ xctestrunArtifact : rebuiltArtifact ,
674+ } ) ;
675+
676+ return { restoredArtifact, restoredSession, rebuiltSession } ;
677+ }
678+
679+ function assertRecoveredPrepareResult ( result : Awaited < ReturnType < typeof prepareIosRunner > > ) : void {
680+ assert . deepEqual ( result , {
681+ runner : { uptimeMs : 42 } ,
682+ cache : 'miss' ,
683+ artifact : 'rebuilt' ,
684+ buildMs : 123 ,
685+ connectMs : result . connectMs ,
686+ healthCheckMs : result . healthCheckMs ,
687+ xctestrunPath : '/tmp/rebuilt.xctestrun' ,
688+ recoveryReason : 'Runner did not accept connection' ,
689+ } ) ;
690+ assert . equal ( result . failureReason , undefined ) ;
691+ assert . equal ( result . connectMs >= 0 , true ) ;
692+ assert . equal ( result . healthCheckMs >= 0 , true ) ;
693+ }
694+
695+ function assertBadCacheRecoverySideEffects (
696+ fixtures : ReturnType < typeof makeBadCacheRecoveryFixtures > ,
697+ ) : void {
698+ assert . deepEqual ( mockInvalidateRunnerSession . mock . calls [ 0 ] , [
699+ fixtures . restoredSession ,
700+ 'prepare_cached_runner_health_failed' ,
701+ ] ) ;
702+ assert . deepEqual ( mockMarkRunnerXctestrunArtifactBadForRun . mock . calls [ 0 ] , [
703+ fixtures . restoredArtifact ,
704+ 'Runner did not accept connection' ,
705+ ] ) ;
706+ assert . deepEqual ( mockEnsureRunnerSession . mock . calls [ 1 ] ?. [ 1 ] , {
707+ healthTimeoutMs : 90_000 ,
708+ buildTimeoutMs : 300_000 ,
709+ cleanStaleBundles : true ,
710+ forceRunnerXctestrunRebuild : true ,
711+ } ) ;
712+ assert . equal ( mockExecuteRunnerCommandWithSession . mock . calls . length , 2 ) ;
713+ assert . equal ( mockExecuteRunnerCommandWithSession . mock . calls [ 0 ] ?. [ 2 ] . command , 'uptime' ) ;
714+ assert . equal ( mockExecuteRunnerCommandWithSession . mock . calls [ 0 ] ?. [ 4 ] , 90_000 ) ;
715+ assert . equal ( mockExecuteRunnerCommandWithSession . mock . calls [ 1 ] ?. [ 1 ] , fixtures . rebuiltSession ) ;
716+ }
717+
718+ function assertRecoveredPrepareDiagnostics ( ) : void {
719+ assert . ok (
720+ mockEmitDiagnostic . mock . calls . some (
721+ ( [ event ] ) => event . phase === 'ios_runner_prepare_bad_cache_recovered' ,
722+ ) ,
723+ ) ;
724+ const prepareDiagnostic = mockEmitDiagnostic . mock . calls . find (
725+ ( [ event ] ) => event . phase === 'apple_runner_prepare' ,
726+ ) ?. [ 0 ] ;
727+ assert . ok ( prepareDiagnostic ) ;
728+ assert . equal ( prepareDiagnostic . level , 'info' ) ;
729+ assert . equal ( prepareDiagnostic . data ?. cache , 'miss' ) ;
730+ assert . equal ( prepareDiagnostic . data ?. artifact , 'rebuilt' ) ;
731+ assert . equal ( prepareDiagnostic . data ?. xctestrunPath , '/tmp/rebuilt.xctestrun' ) ;
732+ assert . equal ( prepareDiagnostic . data ?. recoveryReason , 'Runner did not accept connection' ) ;
733+ assert . equal ( prepareDiagnostic . data ?. failureReason , undefined ) ;
734+ }
735+
718736function assertDiagnosticDecision ( expected : {
719737 decision : 'skipped' | 'retained' ;
720738 reason : string ;
0 commit comments