@@ -19,6 +19,7 @@ namespace GitHub.Runner.Common.Tests.Worker
1919 public sealed class PipelineTemplateEvaluatorWrapperL0
2020 {
2121 private CancellationTokenSource _ecTokenSource ;
22+ private CancellationTokenSource _rootTokenSource ;
2223 private Mock < IExecutionContext > _ec ;
2324 private TestHostContext _hc ;
2425
@@ -65,7 +66,7 @@ public void EvaluateAndCompare_SkipsMismatchRecording_WhenCancellationOccursDuri
6566
6667 var wrapper = new PipelineTemplateEvaluatorWrapper ( _hc , _ec . Object , allowServiceContainerCommand : false ) ;
6768
68- // Call EvaluateAndCompare directly: the new evaluator cancels the token
69+ // Call EvaluateAndCompare directly: the new evaluator cancels the root token
6970 // and returns a different value, forcing hasMismatch = true.
7071 // Because cancellation flipped during the evaluation window, the
7172 // mismatch should be skipped.
@@ -74,7 +75,7 @@ public void EvaluateAndCompare_SkipsMismatchRecording_WhenCancellationOccursDuri
7475 ( ) => "legacy-value" ,
7576 ( ) =>
7677 {
77- _ecTokenSource . Cancel ( ) ;
78+ _rootTokenSource . Cancel ( ) ;
7879 return "different-value" ;
7980 } ,
8081 ( legacy , @new ) => string . Equals ( legacy , @new , StringComparison . Ordinal ) ) ;
@@ -88,6 +89,43 @@ public void EvaluateAndCompare_SkipsMismatchRecording_WhenCancellationOccursDuri
8889 }
8990 }
9091
92+ [ Fact ]
93+ [ Trait ( "Level" , "L0" ) ]
94+ [ Trait ( "Category" , "Worker" ) ]
95+ public void EvaluateAndCompare_SkipsMismatchRecording_WhenRootCancellationOccursBetweenEvaluators ( )
96+ {
97+ // Simulates job-level cancellation firing between legacy and new evaluator runs.
98+ // Root is mocked with a separate CancellationTokenSource to exercise the
99+ // _context.Root?.CancellationToken path (the job-level token).
100+ try
101+ {
102+ Setup ( ) ;
103+ _ec . Object . Global . Variables . Set ( Constants . Runner . Features . CompareWorkflowParser , "true" ) ;
104+
105+ var wrapper = new PipelineTemplateEvaluatorWrapper ( _hc , _ec . Object , allowServiceContainerCommand : false ) ;
106+
107+ // Legacy evaluator cancels the root token (simulating job cancel) and returns a value.
108+ // The new evaluator returns a different value. The mismatch should be skipped.
109+ var result = wrapper . EvaluateAndCompare < string , string > (
110+ "TestRootCancellationSkip" ,
111+ ( ) =>
112+ {
113+ var legacyValue = "legacy-value" ;
114+ _rootTokenSource . Cancel ( ) ;
115+ return legacyValue ;
116+ } ,
117+ ( ) => "different-value" ,
118+ ( legacy , @new ) => string . Equals ( legacy , @new , StringComparison . Ordinal ) ) ;
119+
120+ Assert . Equal ( "legacy-value" , result ) ;
121+ Assert . False ( _ec . Object . Global . HasTemplateEvaluatorMismatch ) ;
122+ }
123+ finally
124+ {
125+ Teardown ( ) ;
126+ }
127+ }
128+
91129 [ Fact ]
92130 [ Trait ( "Level" , "L0" ) ]
93131 [ Trait ( "Category" , "Worker" ) ]
@@ -862,6 +900,8 @@ private void Setup([CallerMemberName] string name = "")
862900 {
863901 _ecTokenSource ? . Dispose ( ) ;
864902 _ecTokenSource = new CancellationTokenSource ( ) ;
903+ _rootTokenSource ? . Dispose ( ) ;
904+ _rootTokenSource = new CancellationTokenSource ( ) ;
865905
866906 _hc = new TestHostContext ( this , name ) ;
867907
@@ -877,6 +917,9 @@ private void Setup([CallerMemberName] string name = "")
877917 WriteDebug = true ,
878918 } ) ;
879919 _ec . Setup ( x => x . CancellationToken ) . Returns ( _ecTokenSource . Token ) ;
920+ var rootEc = new Mock < IExecutionContext > ( ) ;
921+ rootEc . Setup ( x => x . CancellationToken ) . Returns ( _rootTokenSource . Token ) ;
922+ _ec . Setup ( x => x . Root ) . Returns ( rootEc . Object ) ;
880923 _ec . Setup ( x => x . ExpressionValues ) . Returns ( expressionValues ) ;
881924 _ec . Setup ( x => x . ExpressionFunctions ) . Returns ( expressionFunctions ) ;
882925 _ec . Setup ( x => x . Write ( It . IsAny < string > ( ) , It . IsAny < string > ( ) ) ) . Callback ( ( string tag , string message ) => { _hc . GetTrace ( ) . Info ( $ "{ tag } { message } ") ; } ) ;
@@ -885,6 +928,8 @@ private void Setup([CallerMemberName] string name = "")
885928
886929 private void Teardown ( )
887930 {
931+ _ecTokenSource ? . Dispose ( ) ;
932+ _rootTokenSource ? . Dispose ( ) ;
888933 _hc ? . Dispose ( ) ;
889934 }
890935 }
0 commit comments