Skip to content

Commit c8f805e

Browse files
Ensure we properly dispose our loggers during MSBuildWorkspace tests (#82942)
xunit enforces that if you try to write to ITestOutputHelper after the end of a test run that the test run is failed. Our ILoggerProvider implementation already handles this -- once it's disposed, it stops sending messages along as a way to not have a failing test (which accidentally runs stuff asynchronously) from killing an entire test run. We however had cases in the MSBuildWorkspace tests were we just forgot to dispose it. Fixes #82939 ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/dotnet/roslyn/pull/82942)
2 parents 8ebf7d5 + a0d9ee7 commit c8f805e

File tree

4 files changed

+43
-5
lines changed

4 files changed

+43
-5
lines changed

src/Workspaces/CoreTestUtilities/Logging/TestOutputLoggerProvider.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ public void Dispose()
5151
}
5252
}
5353

54+
/// <summary>
55+
/// Validates that this provider has not already been disposed, and then disposes it. This is useful in
56+
/// test teardown to catch bugs where the provider was disposed too early and we might lose messages.
57+
/// </summary>
58+
public void ValidateNotAlreadyDisposedAndDispose()
59+
{
60+
if (_testOutputHelper is null)
61+
throw new ObjectDisposedException(nameof(TestOutputLoggerProvider));
62+
63+
Dispose();
64+
}
65+
5466
public void Dispose()
5567
{
5668
_testOutputHelper = null;

src/Workspaces/MSBuild/Core/MSBuild/MSBuildWorkspace.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ private DiagnosticReporter Reporter
9696
internal void AddLoggerProvider(Microsoft.Extensions.Logging.ILoggerProvider loggerProvider)
9797
=> _loader.LoggerFactory.AddProvider(loggerProvider);
9898

99+
protected override void Dispose(bool finalize)
100+
{
101+
// Dispose the LoggerFactory to ensure any logger providers added via AddLoggerProvider are disposed.
102+
_loader.LoggerFactory.Dispose();
103+
base.Dispose(finalize);
104+
}
105+
99106
/// <summary>
100107
/// The MSBuild properties used when interpreting project files.
101108
/// These are the same properties that are passed to msbuild via the /property:&lt;n&gt;=&lt;v&gt; command line argument.

src/Workspaces/MSBuild/Test/MSBuildWorkspaceTestBase.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,31 @@ namespace Microsoft.CodeAnalysis.MSBuild.UnitTests;
2424

2525
public abstract class MSBuildWorkspaceTestBase : WorkspaceTestBase
2626
{
27-
protected readonly ITestOutputHelper TestOutput;
27+
private readonly ITestOutputHelper _testOutputHelper;
28+
private readonly TestOutputLoggerProvider _testOutputLoggerProvider;
2829
protected readonly ILoggerFactory LoggerFactory;
2930

3031
protected MSBuildWorkspaceTestBase(ITestOutputHelper testOutput)
3132
{
32-
TestOutput = testOutput;
33-
LoggerFactory = new LoggerFactory([new TestOutputLoggerProvider(testOutput)]);
33+
_testOutputHelper = testOutput;
34+
_testOutputLoggerProvider = new TestOutputLoggerProvider(testOutput);
35+
LoggerFactory = new LoggerFactory([_testOutputLoggerProvider]);
36+
}
37+
38+
public override void Dispose()
39+
{
40+
// Dispose our LoggingFactory and providers. xunit validates that we don't write anything to ITestOutputHelper after a test is done,
41+
// so we want to ensure our providers are all disposed so a broken test doesn't cause the entire test run to fail -- our implementation of
42+
// TestOutputLoggerProvider stops forwarding messages once it's disposed.
43+
//
44+
// LoggerFactory's handling of lifetime is subtle -- providers passed to the LoggerFactorys' constructor are not owned and we have to dispose them;
45+
// but providers added via AddLoggerProvider are owned and will be disposed by the LoggerFactory. Thus we need to dispose both here.
46+
LoggerFactory.Dispose();
47+
48+
// We'll call ValidateNotAlreadyDisposedAndDispose() as a way to ensure this wasn't disposed prematurely, which would cause us to lose log output.
49+
_testOutputLoggerProvider.ValidateNotAlreadyDisposedAndDispose();
50+
51+
base.Dispose();
3452
}
3553

3654
protected const string MSBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003";
@@ -160,7 +178,7 @@ protected MSBuildWorkspace CreateMSBuildWorkspace(
160178
{
161179
additionalProperties ??= [];
162180
var workspace = MSBuildWorkspace.Create(CreateProperties(additionalProperties));
163-
workspace.AddLoggerProvider(new TestOutputLoggerProvider(TestOutput));
181+
workspace.AddLoggerProvider(new TestOutputLoggerProvider(_testOutputHelper));
164182

165183
if (throwOnWorkspaceFailed)
166184
{

src/Workspaces/MSBuild/Test/NewlyCreatedProjectsFromDotNetNew.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ private async Task AssertTemplateProjectLoadsCleanlyAsync(string templateName, s
147147
{
148148
if (ignoredDiagnostics?.Length > 0)
149149
{
150-
TestOutput.WriteLine($"""
150+
var logger = LoggerFactory.CreateLogger(nameof(AssertTemplateProjectLoadsCleanlyAsync));
151+
logger.LogInformation($"""
151152
Ignoring compiler diagnostics: "{string.Join("\", \"", ignoredDiagnostics)}"
152153
""");
153154
}

0 commit comments

Comments
 (0)