Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,9 @@ async Task<TestResult> DoRunAsync()
{
// Assembly initialize and class initialize logs are pre-pended to the first result.
var testContextImpl = testContext as TestContextImplementation;
result.LogOutput = initializationLogs + testContextImpl?.GetOut();
result.LogError = initializationErrorLogs + testContextImpl?.GetErr();
result.DebugTrace = initializationTrace + testContextImpl?.GetTrace();
result.LogOutput = initializationLogs + testContextImpl?.GetAndClearOutput();
result.LogError = initializationErrorLogs + testContextImpl?.GetAndClearError();
result.DebugTrace = initializationTrace + testContextImpl?.GetAndClearTrace();
result.TestContextMessages = initializationTestContextMessages + testContext.GetAndClearDiagnosticMessages();
}

Expand Down Expand Up @@ -680,19 +680,19 @@ async Task<TestResult> DoRunAsync()
Outcome = UnitTestOutcome.Failed,
DisplayName = $"[{ClassType.FullName} ClassCleanup]",
TestFailureException = ex,
LogOutput = testContextImpl?.GetOut(),
LogError = testContextImpl?.GetErr(),
DebugTrace = testContextImpl?.GetTrace(),
LogOutput = testContextImpl?.GetAndClearOutput(),
LogError = testContextImpl?.GetAndClearError(),
DebugTrace = testContextImpl?.GetAndClearTrace(),
TestContextMessages = testContext.GetAndClearDiagnosticMessages(),
};
}

if (results.Length > 0)
{
TestResult lastResult = results[results.Length - 1];
lastResult.LogOutput += testContextImpl?.GetOut();
lastResult.LogError += testContextImpl?.GetErr();
lastResult.DebugTrace += testContextImpl?.GetTrace();
lastResult.LogOutput += testContextImpl?.GetAndClearOutput();
lastResult.LogError += testContextImpl?.GetAndClearError();
lastResult.DebugTrace += testContextImpl?.GetAndClearTrace();
lastResult.TestContextMessages += testContext.GetAndClearDiagnosticMessages();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ public virtual async Task<TestResult> InvokeAsync(object?[]? arguments)
if (result != null)
{
var testContextImpl = TestContext as TestContextImplementation;
result.LogOutput = testContextImpl?.GetOut();
result.LogError = testContextImpl?.GetErr();
result.DebugTrace = testContextImpl?.GetTrace();
result.LogOutput = testContextImpl?.GetAndClearOutput();
result.LogError = testContextImpl?.GetAndClearError();
result.DebugTrace = testContextImpl?.GetAndClearTrace();
result.TestContextMessages = TestContext?.GetAndClearDiagnosticMessages();
result.ResultFiles = TestContext?.GetResultFiles();
result.Duration = watch.Elapsed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,9 @@ private static async Task<TestResult> RunAssemblyInitializeIfNeededAsync(TestMet
finally
{
var testContextImpl = testContext.Context as TestContextImplementation;
result!.LogOutput = testContextImpl?.GetOut();
result.LogError = testContextImpl?.GetErr();
result.DebugTrace = testContextImpl?.GetTrace();
result!.LogOutput = testContextImpl?.GetAndClearOutput();
result.LogError = testContextImpl?.GetAndClearError();
result.DebugTrace = testContextImpl?.GetAndClearTrace();
result.TestContextMessages = testContext.GetAndClearDiagnosticMessages();
}

Expand All @@ -288,19 +288,19 @@ private static async Task<TestResult> RunAssemblyInitializeIfNeededAsync(TestMet
{
Outcome = UnitTestOutcome.Failed,
TestFailureException = ex,
LogOutput = testContextImpl?.GetOut(),
LogError = testContextImpl?.GetErr(),
DebugTrace = testContextImpl?.GetTrace(),
LogOutput = testContextImpl?.GetAndClearOutput(),
LogError = testContextImpl?.GetAndClearError(),
DebugTrace = testContextImpl?.GetAndClearTrace(),
TestContextMessages = testContext.GetAndClearDiagnosticMessages(),
};
}

if (results.Length > 0)
{
TestResult lastResult = results[results.Length - 1];
lastResult.LogOutput += testContextImpl?.GetOut();
lastResult.LogError += testContextImpl?.GetErr();
lastResult.DebugTrace += testContextImpl?.GetTrace();
lastResult.LogOutput += testContextImpl?.GetAndClearOutput();
lastResult.LogError += testContextImpl?.GetAndClearError();
lastResult.DebugTrace += testContextImpl?.GetAndClearTrace();
lastResult.TestContextMessages += testContext.GetAndClearDiagnosticMessages();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ internal void AppendLine(string? value)
internal void Clear()
=> _builder.Clear();

[MethodImpl(MethodImplOptions.Synchronized)]
internal string GetAndClear()
{
string result = _builder.ToString();
_builder.Clear();

return result;
}

[MethodImpl(MethodImplOptions.Synchronized)]
public override string ToString()
=> _builder.ToString();
Expand Down Expand Up @@ -380,12 +389,12 @@ private SynchronizedStringBuilder GetTestContextMessagesStringBuilder()
return _testContextMessageStringBuilder;
}

internal string? GetOut()
=> _stdOutStringBuilder?.ToString();
internal string? GetAndClearOutput()
=> _stdOutStringBuilder?.GetAndClear();

internal string? GetErr()
=> _stdErrStringBuilder?.ToString();
internal string? GetAndClearError()
=> _stdErrStringBuilder?.GetAndClear();

internal string? GetTrace()
=> _traceStringBuilder?.ToString();
internal string? GetAndClearTrace()
=> _traceStringBuilder?.GetAndClear();
}
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,45 @@ public void ResolveArgumentsShouldReturnPopulatedParamsWithAllProvided()
((string[])expectedArguments[1]).SequenceEqual((string[])resolvedArguments[1]!).Should().BeTrue();
}

// Regression tests for https://github.com/microsoft/testfx/issues/7846
// Verify that log output buffers are cleared between invocations to prevent
// exponential memory growth with DynamicData tests.
// NOTE: The TestClassInfo (class init/cleanup) and UnitTestRunner (assembly init/cleanup)
// call sites use the same GetAndClear* methods tested in isolation in
// TestContextImplementationTests.GetAndClear{Output,Error,Trace}_ShouldReturnContentThenClearBuffer.
public async Task InvokeAsync_ShouldNotAccumulateLogOutputAcrossMultipleInvocations()
{
DummyTestClass.TestMethodBody = _ => _testContextImplementation.WriteConsoleOut("invocation_output");

TestResult result1 = await _testMethodInfo.InvokeAsync(null);
TestResult result2 = await _testMethodInfo.InvokeAsync(null);

result1.LogOutput.Should().Be("invocation_output");
result2.LogOutput.Should().Be("invocation_output");
}

public async Task InvokeAsync_ShouldNotAccumulateLogErrorAcrossMultipleInvocations()
{
DummyTestClass.TestMethodBody = _ => _testContextImplementation.WriteConsoleErr("error_output");

TestResult result1 = await _testMethodInfo.InvokeAsync(null);
TestResult result2 = await _testMethodInfo.InvokeAsync(null);

result1.LogError.Should().Be("error_output");
result2.LogError.Should().Be("error_output");
}

public async Task InvokeAsync_ShouldNotAccumulateDebugTraceAcrossMultipleInvocations()
{
DummyTestClass.TestMethodBody = _ => _testContextImplementation.WriteTrace("trace_output");

TestResult result1 = await _testMethodInfo.InvokeAsync(null);
TestResult result2 = await _testMethodInfo.InvokeAsync(null);

result1.DebugTrace.Should().Be("trace_output");
result2.DebugTrace.Should().Be("trace_output");
}
Comment thread
Evangelink marked this conversation as resolved.

#region helper methods

private static async Task RunWithTestablePlatformService(TestablePlatformServiceProvider testablePlatformServiceProvider, Func<Task> action)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,42 @@ public void DisplayMessageShouldForwardToIMessageLogger()
messageLoggerMock.Verify(x => x.SendMessage(TestMessageLevel.Error, "ErrorMessage"), Times.Once);
}

public void GetAndClearOutput_ShouldReturnContentThenClearBuffer()
{
_testContextImplementation = CreateTestContextImplementation();
_testContextImplementation.WriteConsoleOut("hello");

string? first = _testContextImplementation.GetAndClearOutput();
string? second = _testContextImplementation.GetAndClearOutput();

first.Should().Be("hello");
second.Should().BeEmpty();
}

public void GetAndClearError_ShouldReturnContentThenClearBuffer()
{
_testContextImplementation = CreateTestContextImplementation();
_testContextImplementation.WriteConsoleErr("hello");

string? first = _testContextImplementation.GetAndClearError();
string? second = _testContextImplementation.GetAndClearError();

first.Should().Be("hello");
second.Should().BeEmpty();
}

public void GetAndClearTrace_ShouldReturnContentThenClearBuffer()
{
_testContextImplementation = CreateTestContextImplementation();
_testContextImplementation.WriteTrace("hello");

string? first = _testContextImplementation.GetAndClearTrace();
string? second = _testContextImplementation.GetAndClearTrace();

first.Should().Be("hello");
second.Should().BeEmpty();
}

public void WritesFromBackgroundThreadShouldNotThrow()
{
TestContextImplementation testContextImplementation = CreateTestContextImplementation(new Mock<IMessageLogger>().Object);
Expand All @@ -360,8 +396,9 @@ public void WritesFromBackgroundThreadShouldNotThrow()
});

t.Start();
_ = testContextImplementation.GetOut();
_ = testContextImplementation.GetErr();
_ = testContextImplementation.GetAndClearOutput();
_ = testContextImplementation.GetAndClearError();
_ = testContextImplementation.GetAndClearTrace();
t.Join();
}
}
Comment thread
Evangelink marked this conversation as resolved.
Comment thread
Evangelink marked this conversation as resolved.
Loading