Skip to content

Fix log accumulation across DynamicData test invocations#7925

Merged
Evangelink merged 3 commits intomainfrom
dev/amauryleve/fix-dynamic-data-log-accumulation
Apr 29, 2026
Merged

Fix log accumulation across DynamicData test invocations#7925
Evangelink merged 3 commits intomainfrom
dev/amauryleve/fix-dynamic-data-log-accumulation

Conversation

@Evangelink
Copy link
Copy Markdown
Member

Summary

Fixes #7846

When running DynamicData tests with multiple invocations, the TestContextImplementation output buffers (_stdOutStringBuilder, _stdErrStringBuilder, _traceStringBuilder) were never cleared between invocations. Since DynamicData tests reuse the same TestContext instance across all invocations, logs accumulated exponentially:

  • Test 1 result: logs from test 1
  • Test 2 result: logs from test 1 + test 2
  • Test 3 result: logs from test 1 + test 2 + test 3
  • ...

This caused >20 GB RAM usage for simple projects and truncated/broken log output in test summaries.

Root Cause

GetOut(), GetErr(), and GetTrace() on TestContextImplementation returned the StringBuilder contents via ToString() but never cleared them. In contrast, GetAndClearDiagnosticMessages() and GetResultFiles() already followed a get-then-clear pattern.

Fix

Replace GetOut(), GetErr(), GetTrace() with GetAndClearOutput(), GetAndClearError(), GetAndClearTrace() that clear the StringBuilder after extracting the value — matching the existing GetAndClearDiagnosticMessages() pattern.

Changes

  • TestContextImplementation.cs — Replace non-clearing getters with GetAndClearOutput/Error/Trace methods
  • TestMethodInfo.cs — Update call site in InvokeAsync finally block
  • UnitTestRunner.cs — Update call sites in assembly init/cleanup
  • TestClassInfo.cs — Update call sites in class init/cleanup
  • TestMethodInfoTests.cs — Add 3 regression tests verifying buffers don't accumulate across invocations
  • TestContextImplementationTests.cs — Update existing thread-safety test to use renamed methods

GetOut(), GetErr(), and GetTrace() on TestContextImplementation returned
accumulated buffer contents but never cleared them. When DynamicData tests
reuse the same TestContext across multiple invocations, each subsequent
result contained all logs from all previous invocations, causing exponential
memory growth (>20 GB for simple projects).

Replace these with GetAndClearOutput(), GetAndClearError(), and
GetAndClearTrace() that clear the StringBuilder after extracting the value,
matching the existing GetAndClearDiagnosticMessages() pattern.

Fixes #7846
Copilot AI review requested due to automatic review settings April 29, 2026 05:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes MSTest adapter log buffer accumulation across multiple DynamicData invocations by switching stdout/stderr/trace retrieval to a get-then-clear pattern, preventing exponential growth in captured output and memory usage.

Changes:

  • Replace GetOut/GetErr/GetTrace with GetAndClearOutput/GetAndClearError/GetAndClearTrace in TestContextImplementation.
  • Update execution pipeline call sites (test method invoke; assembly/class init & cleanup) to use the new draining APIs.
  • Add regression unit tests to ensure output/error/trace don’t accumulate across multiple invocations.
Show a summary per file
File Description
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestContextImplementationTests.cs Updates background-thread write test to use new get-and-clear APIs.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodInfoTests.cs Adds regression tests validating buffers don’t accumulate across repeated InvokeAsync calls.
src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs Introduces get-and-clear methods for stdout/stderr/trace buffers.
src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs Updates assembly init/cleanup log capture to drain buffers.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Updates per-test invocation log capture to drain buffers.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestClassInfo.cs Updates class init/cleanup log capture to drain buffers.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 1

Comment thread src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs Outdated
Copy link
Copy Markdown
Member Author

@Evangelink Evangelink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-04-29
Repository: microsoft/testfx

Key Findings

  1. [Coverage — Minor] WritesFromBackgroundThreadShouldNotThrow in TestContextImplementationTests.cs was updated for Output and Error but not for Trace. GetAndClearTrace() also calls Clear() under concurrency and should be included in the thread-safety test.

  2. [Coverage — Important] No test in TestContextImplementationTests.cs directly verifies the clearing contract of the new GetAndClear* methods. The only test for these methods is the thread-safety check (which only asserts no exception). A dedicated read-then-clear test would document and protect the core invariant introduced by this fix.

  3. [Coverage — Minor] Regression tests for log accumulation are scoped to TestMethodInfo.InvokeAsync. The TestClassInfo (class init/cleanup) and UnitTestRunner (assembly init/cleanup) call sites that were also changed have no equivalent accumulation tests.

Recommendations

  • Add a GetAndClearOutput_ShouldReturnContentThenClearBuffer-style test (and equivalents for Error/Trace) in TestContextImplementationTests.cs to directly validate the clearing behavior.
  • Extend WritesFromBackgroundThreadShouldNotThrow to also call GetAndClearTrace() for completeness.
  • Consider adding accumulation regression tests for the class/assembly init-cleanup paths, or add a comment noting they're exercised by acceptance tests.

The existing accumulation tests in TestMethodInfoTests.cs are well-structured and correctly detect the regression: result2.LogOutput.Should().Be("invocation_output") would fail with the old non-clearing code (it would produce "invocation_outputinvocation_output"), so the core fix is properly verified.


Generated by Test Expert Reviewer 🧪

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Copy link
Copy Markdown
Member Author

@Evangelink Evangelink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Expert Code Reviewer
Date: 2026-04-29
Repository: microsoft/testfx

Key Findings

[Threading — Minor/Accepted] GetAndClearOutput/Error/Trace each perform two individually-synchronized calls (ToString() + Clear()). Content written by a background thread in the TOCTOU window is silently discarded. This matches the pre-existing GetAndClearDiagnosticMessages() pattern and is an accepted design trade-off in this codebase. See inline comment.

Positive Observations

  • Root cause addressed correctly: The TestContextImplementation output buffers now clear after each read, consistent with the established GetAndClearDiagnosticMessages pattern. The exponential memory growth scenario is fully closed.
  • Bonus fix in multi-assembly cleanup: In UnitTestRunner.RunAssemblyCleanupAsync, the foreach over multiple TestAssemblyInfo entries previously accumulated logs across iterations (each GetOut() returned all prior logs); the new GetAndClearOutput() correctly scopes each iteration to only its own cleanup logs.
  • Regression tests are valid: The three new public async Task tests in TestMethodInfoTests.cs are properly discoverable by TestFramework.ForTestingMSTest (which discovers all public parameterless methods returning void or Task in TestContainer subclasses — no [TestMethod] attribute required).
  • Exclusive control flow: In both TestClassInfo.DoRunAsync and RunAssemblyCleanupAsync, the failure path (creates a new TestResult and returns) and the success path (lastResult +=) are mutually exclusive, so there is no double-clear or double-append risk.
  • No public API surface changed: All three renamed methods remain internal.

Recommendations

  • Consider adding a brief comment to the three new methods noting the intentional TOCTOU (consistent with GetAndClearDiagnosticMessages), to signal future maintainers that this is deliberate rather than an oversight.

Generated by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

Comment thread src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs Outdated
Address review feedback: ToString() + Clear() as two separate synchronized
calls could lose appended text if another thread writes between them. Add
a single synchronized GetAndClear() method to SynchronizedStringBuilder
that performs both operations under one lock.
- Add focused GetAndClear{Output,Error,Trace} unit tests in
  TestContextImplementationTests that verify the buffer is cleared after
  the first call (returns empty on second call).
- Add GetAndClearTrace() call to WritesFromBackgroundThreadShouldNotThrow
  to complete thread-safety coverage for all three buffer types.
- Consolidate regression test comments: single block comment noting that
  TestClassInfo and UnitTestRunner call sites use the same GetAndClear*
  methods tested in isolation.
Copilot AI review requested due to automatic review settings April 29, 2026 05:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new

@Evangelink Evangelink merged commit 37cd497 into main Apr 29, 2026
28 checks passed
@Evangelink Evangelink deleted the dev/amauryleve/fix-dynamic-data-log-accumulation branch April 29, 2026 09:06
@Evangelink
Copy link
Copy Markdown
Member Author

/backport to rel/4.2

@github-actions
Copy link
Copy Markdown
Contributor

Started backporting to rel/4.2: https://github.com/microsoft/testfx/actions/runs/25104582574

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Log collection is broken, >20 GB RAM usage for simple project

2 participants