@@ -123,7 +123,7 @@ internal async Task<TestResult[]> RunTestMethodAsync()
123123 }
124124
125125 object ? [ ] ? data = _test . ActualData ?? DataSerializationHelper . Deserialize ( _test . SerializedData ) ;
126- TestResult [ ] testResults = await ExecuteTestWithDataSourceAsync ( null , data , actualDataAlreadyHandledDuringDiscovery : true ) . ConfigureAwait ( false ) ;
126+ TestResult [ ] testResults = await ExecuteTestWithDataSourceAsync ( _testContext , null , data , actualDataAlreadyHandledDuringDiscovery : true ) . ConfigureAwait ( false ) ;
127127 results . AddRange ( testResults ) ;
128128 }
129129 else if ( await TryExecuteDataSourceBasedTestsAsync ( results ) . ConfigureAwait ( false ) )
@@ -137,7 +137,7 @@ internal async Task<TestResult[]> RunTestMethodAsync()
137137 else
138138 {
139139 _testContext . SetDisplayName ( _test . DisplayName ) ;
140- TestResult [ ] testResults = await ExecuteTestAsync ( _testMethodInfo ) . ConfigureAwait ( false ) ;
140+ TestResult [ ] testResults = await ExecuteTestAsync ( _testContext , _testMethodInfo ) . ConfigureAwait ( false ) ;
141141
142142 foreach ( TestResult testResult in testResults )
143143 {
@@ -207,6 +207,8 @@ private async Task<bool> TryExecuteDataSourceBasedTestsAsync(List<TestResult> re
207207 private async Task < bool > TryExecuteFoldedDataDrivenTestsAsync ( List < TestResult > results )
208208 {
209209 bool hasTestDataSource = false ;
210+ var outerContext = ( TestContextImplementation ) _testContext . Context ;
211+
210212 foreach ( Attribute attribute in PlatformServiceProvider . Instance . ReflectionOperations . GetCustomAttributesCached ( _testMethodInfo . MethodInfo ) )
211213 {
212214 if ( attribute is not UTF . ITestDataSource testDataSource )
@@ -227,15 +229,24 @@ private async Task<bool> TryExecuteFoldedDataDrivenTestsAsync(List<TestResult> r
227229 foreach ( object ? [ ] data in testDataSource . GetData ( _testMethodInfo . MethodInfo ) )
228230 {
229231 dataSourceHasData = true ;
232+
233+ // Create a fresh TestContextImplementation per iteration so the folded path is
234+ // structurally equivalent to the unfolded path (where each row gets its own
235+ // context). This isolates per-row state (captured output, diagnostic messages,
236+ // result files, outcome, exception, property bag mutations, ...) so a leak in
237+ // any current or future TestContextImplementation field cannot accumulate across
238+ // rows. See https://github.com/microsoft/testfx/issues/7933.
239+ TestContextImplementation iterationContext = outerContext . CloneForDataDrivenIteration ( ) ;
230240 try
231241 {
232- TestResult [ ] testResults = await ExecuteTestWithDataSourceAsync ( testDataSource , data , actualDataAlreadyHandledDuringDiscovery : false ) . ConfigureAwait ( false ) ;
242+ TestResult [ ] testResults = await ExecuteTestWithDataSourceAsync ( iterationContext , testDataSource , data , actualDataAlreadyHandledDuringDiscovery : false ) . ConfigureAwait ( false ) ;
233243
234244 results . AddRange ( testResults ) ;
235245 }
236246 finally
237247 {
238248 _testMethodInfo . SetArguments ( null ) ;
249+ iterationContext . Dispose ( ) ;
239250 }
240251 }
241252
@@ -278,11 +289,23 @@ private async Task ExecuteTestFromDataSourceAttributeAsync(List<TestResult> resu
278289 try
279290 {
280291 int rowIndex = 0 ;
292+ var outerContext = ( TestContextImplementation ) _testContext . Context ;
281293
282294 foreach ( object dataRow in dataRows )
283295 {
284- TestResult [ ] testResults = await ExecuteTestWithDataRowAsync ( dataRow , rowIndex ++ ) . ConfigureAwait ( false ) ;
285- results . AddRange ( testResults ) ;
296+ // Create a fresh TestContextImplementation per row for the same structural
297+ // reason as in TryExecuteFoldedDataDrivenTestsAsync — each row should
298+ // start with no accumulated per-test state.
299+ TestContextImplementation iterationContext = outerContext . CloneForDataDrivenIteration ( ) ;
300+ try
301+ {
302+ TestResult [ ] testResults = await ExecuteTestWithDataRowAsync ( iterationContext , dataRow , rowIndex ++ ) . ConfigureAwait ( false ) ;
303+ results . AddRange ( testResults ) ;
304+ }
305+ finally
306+ {
307+ iterationContext . Dispose ( ) ;
308+ }
286309 }
287310 }
288311 finally
@@ -303,7 +326,7 @@ private async Task ExecuteTestFromDataSourceAttributeAsync(List<TestResult> resu
303326 }
304327 }
305328
306- private async Task < TestResult [ ] > ExecuteTestWithDataSourceAsync ( UTF . ITestDataSource ? testDataSource , object ? [ ] ? data , bool actualDataAlreadyHandledDuringDiscovery )
329+ private async Task < TestResult [ ] > ExecuteTestWithDataSourceAsync ( ITestContext executionContext , UTF . ITestDataSource ? testDataSource , object ? [ ] ? data , bool actualDataAlreadyHandledDuringDiscovery )
307330 {
308331 string ? displayName = StringEx . IsNullOrWhiteSpace ( _test . DisplayName )
309332 ? _test . Name
@@ -353,12 +376,12 @@ private async Task<TestResult[]> ExecuteTestWithDataSourceAsync(UTF.ITestDataSou
353376
354377 var stopwatch = Stopwatch . StartNew ( ) ;
355378 _testMethodInfo . SetArguments ( data ) ;
356- _testContext . SetTestData ( data ) ;
357- _testContext . SetDisplayName ( displayName ) ;
379+ executionContext . SetTestData ( data ) ;
380+ executionContext . SetDisplayName ( displayName ) ;
358381
359382 TestResult [ ] testResults = ignoreFromTestDataRow is not null
360383 ? [ TestResult . CreateIgnoredResult ( ignoreFromTestDataRow ) ]
361- : await ExecuteTestAsync ( _testMethodInfo ) . ConfigureAwait ( false ) ;
384+ : await ExecuteTestAsync ( executionContext , _testMethodInfo ) . ConfigureAwait ( false ) ;
362385
363386 stopwatch . Stop ( ) ;
364387
@@ -375,7 +398,7 @@ private async Task<TestResult[]> ExecuteTestWithDataSourceAsync(UTF.ITestDataSou
375398 return testResults ;
376399 }
377400
378- private async Task < TestResult [ ] > ExecuteTestWithDataRowAsync ( object dataRow , int rowIndex )
401+ private async Task < TestResult [ ] > ExecuteTestWithDataRowAsync ( ITestContext executionContext , object dataRow , int rowIndex )
379402 {
380403 string displayName = string . Format ( CultureInfo . CurrentCulture , Resource . DataDrivenResultDisplayName , _test . DisplayName , rowIndex ) ;
381404 Stopwatch ? stopwatch = null ;
@@ -384,13 +407,13 @@ private async Task<TestResult[]> ExecuteTestWithDataRowAsync(object dataRow, int
384407 try
385408 {
386409 stopwatch = Stopwatch . StartNew ( ) ;
387- _testContext . SetDataRow ( dataRow ) ;
388- testResults = await ExecuteTestAsync ( _testMethodInfo ) . ConfigureAwait ( false ) ;
410+ executionContext . SetDataRow ( dataRow ) ;
411+ testResults = await ExecuteTestAsync ( executionContext , _testMethodInfo ) . ConfigureAwait ( false ) ;
389412 }
390413 finally
391414 {
392415 stopwatch ? . Stop ( ) ;
393- _testContext . SetDataRow ( null ) ;
416+ executionContext . SetDataRow ( null ) ;
394417 }
395418
396419 foreach ( TestResult testResult in testResults )
@@ -402,7 +425,7 @@ private async Task<TestResult[]> ExecuteTestWithDataRowAsync(object dataRow, int
402425 return testResults ;
403426 }
404427
405- private async Task < TestResult [ ] > ExecuteTestAsync ( TestMethodInfo testMethodInfo )
428+ private async Task < TestResult [ ] > ExecuteTestAsync ( ITestContext executionContext , TestMethodInfo testMethodInfo )
406429 {
407430 try
408431 {
@@ -415,9 +438,9 @@ private async Task<TestResult[]> ExecuteTestAsync(TestMethodInfo testMethodInfo)
415438 {
416439 try
417440 {
418- using ( TestContextImplementation . SetCurrentTestContext ( _testContext as TestContext ) )
441+ using ( TestContextImplementation . SetCurrentTestContext ( executionContext as TestContext ) )
419442 {
420- testMethodInfo . TestContext = _testContext ;
443+ testMethodInfo . TestContext = executionContext ;
421444 tcs . SetResult ( await _testMethodInfo . Executor . ExecuteAsync ( testMethodInfo ) . ConfigureAwait ( false ) ) ;
422445 }
423446 }
0 commit comments