Skip to content

Commit

Permalink
Add MSBuildFact for tests to lock around MSBuild invocations
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeRobich committed Dec 11, 2020
1 parent e64743f commit d86a78e
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 58 deletions.
75 changes: 44 additions & 31 deletions src/Workspaces/MSBuildWorkspaceLoader.cs
Expand Up @@ -13,7 +13,8 @@ namespace Microsoft.CodeAnalysis.Tools.Workspaces
{
internal static class MSBuildWorkspaceLoader
{
private static readonly SemaphoreSlim s_guard = new SemaphoreSlim(1, 1);
// Used in tests for locking around MSBuild invocations
internal static readonly SemaphoreSlim Guard = new SemaphoreSlim(1, 1);

public static async Task<Workspace?> LoadAsync(
string solutionOrProjectPath,
Expand All @@ -31,45 +32,35 @@ internal static class MSBuildWorkspaceLoader
{ "AlwaysCompileMarkupFilesInSeparateDomain", bool.FalseString },
};

MSBuildWorkspace workspace;
var workspace = MSBuildWorkspace.Create(properties);

await s_guard.WaitAsync();
try
Build.Framework.ILogger? binlog = null;
if (createBinaryLog)
{
workspace = MSBuildWorkspace.Create(properties);

Build.Framework.ILogger? binlog = null;
if (createBinaryLog)
binlog = new Build.Logging.BinaryLogger()
{
binlog = new Build.Logging.BinaryLogger()
{
Parameters = Path.Combine(Environment.CurrentDirectory, "formatDiagnosticLog.binlog"),
Verbosity = Build.Framework.LoggerVerbosity.Diagnostic,
};
}
Parameters = Path.Combine(Environment.CurrentDirectory, "formatDiagnosticLog.binlog"),
Verbosity = Build.Framework.LoggerVerbosity.Diagnostic,
};
}

if (workspaceType == WorkspaceType.Solution)
if (workspaceType == WorkspaceType.Solution)
{
await workspace.OpenSolutionAsync(solutionOrProjectPath, msbuildLogger: binlog, cancellationToken: cancellationToken).ConfigureAwait(false);
}
else
{
try
{
await workspace.OpenSolutionAsync(solutionOrProjectPath, msbuildLogger: binlog, cancellationToken: cancellationToken).ConfigureAwait(false);
await workspace.OpenProjectAsync(solutionOrProjectPath, msbuildLogger: binlog, cancellationToken: cancellationToken).ConfigureAwait(false);
}
else
catch (InvalidOperationException)
{
try
{
await workspace.OpenProjectAsync(solutionOrProjectPath, msbuildLogger: binlog, cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch (InvalidOperationException)
{
logger.LogError(Resources.Could_not_format_0_Format_currently_supports_only_CSharp_and_Visual_Basic_projects, solutionOrProjectPath);
workspace.Dispose();
return null;
}
logger.LogError(Resources.Could_not_format_0_Format_currently_supports_only_CSharp_and_Visual_Basic_projects, solutionOrProjectPath);
workspace.Dispose();
return null;
}
}
finally
{
s_guard.Release();
}

LogWorkspaceDiagnostics(logger, logWorkspaceWarnings, workspace.Diagnostics);

Expand Down Expand Up @@ -100,5 +91,27 @@ static void LogWorkspaceDiagnostics(ILogger logger, bool logWorkspaceWarnings, I
}
}
}

/// <summary>
/// This is for use in tests so that MSBuild is only invoked serially
/// </summary>
internal static async Task<Workspace?> LockedLoadAsync(
string solutionOrProjectPath,
WorkspaceType workspaceType,
bool createBinaryLog,
bool logWorkspaceWarnings,
ILogger logger,
CancellationToken cancellationToken)
{
await Guard.WaitAsync();
try
{
return await LoadAsync(solutionOrProjectPath, workspaceType, createBinaryLog, logWorkspaceWarnings, logger, cancellationToken);
}
finally
{
Guard.Release();
}
}
}
}
2 changes: 1 addition & 1 deletion tests/Analyzers/ThirdPartyAnalyzerFormatterTests.cs
Expand Up @@ -43,7 +43,7 @@ public async Task InitializeAsync()
var workspacePath = Path.Combine(TestProjectsPathHelper.GetProjectsDirectory(), s_analyzerProjectFilePath);

MSBuildRegistrar.RegisterInstance(logger);
var analyzerWorkspace = await MSBuildWorkspaceLoader.LoadAsync(workspacePath, WorkspaceType.Project, createBinaryLog: false, logWorkspaceWarnings: true, logger, CancellationToken.None);
var analyzerWorkspace = await MSBuildWorkspaceLoader.LockedLoadAsync(workspacePath, WorkspaceType.Project, createBinaryLog: false, logWorkspaceWarnings: true, logger, CancellationToken.None);

// From this project we can get valid AnalyzerReferences to add to our test project.
_analyzerReferencesProject = analyzerWorkspace.CurrentSolution.Projects.Single();
Expand Down
53 changes: 27 additions & 26 deletions tests/CodeFormatterTests.cs
Expand Up @@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Tools.Tests.Utilities;
using Microsoft.CodeAnalysis.Tools.Tests.XUnit;
using Microsoft.CodeAnalysis.Tools.Utilities;
using Microsoft.Extensions.Logging;
using Xunit;
Expand Down Expand Up @@ -48,7 +49,7 @@ public CodeFormatterTests(ITestOutputHelper output)
_output = output;
}

[Fact]
[MSBuildFact]
public async Task NoFilesFormattedInFormattedProject()
{
await TestFormatWorkspaceAsync(
Expand All @@ -61,7 +62,7 @@ public async Task NoFilesFormattedInFormattedProject()
expectedFileCount: 3);
}

[Fact]
[MSBuildFact]
public async Task NoFilesFormattedInFormattedSolution()
{
await TestFormatWorkspaceAsync(
Expand All @@ -74,7 +75,7 @@ public async Task NoFilesFormattedInFormattedSolution()
expectedFileCount: 3);
}

[Fact]
[MSBuildFact]
public async Task FilesFormattedInUnformattedProject()
{
await TestFormatWorkspaceAsync(
Expand All @@ -87,7 +88,7 @@ public async Task FilesFormattedInUnformattedProject()
expectedFileCount: 6);
}

[Fact]
[MSBuildFact]
public async Task NoFilesFormattedInUnformattedProjectWhenFixingCodeStyle()
{
await TestFormatWorkspaceAsync(
Expand All @@ -102,7 +103,7 @@ public async Task NoFilesFormattedInUnformattedProjectWhenFixingCodeStyle()
expectedFileCount: 6);
}

[Fact]
[MSBuildFact]
public async Task GeneratedFilesFormattedInUnformattedProject()
{
var log = await TestFormatWorkspaceAsync(
Expand All @@ -119,7 +120,7 @@ public async Task GeneratedFilesFormattedInUnformattedProject()
Assert.Contains(logLines, line => line.Contains("NETCoreApp,Version=v3.0.AssemblyAttributes.cs"));
}

[Fact]
[MSBuildFact]
public async Task FilesFormattedInUnformattedSolution()
{
await TestFormatWorkspaceAsync(
Expand All @@ -132,7 +133,7 @@ public async Task FilesFormattedInUnformattedSolution()
expectedFileCount: 6);
}

[Fact]
[MSBuildFact]
public async Task FilesFormattedInUnformattedProjectFolder()
{
// Since the code files are beneath the project folder, files are found and formatted.
Expand All @@ -146,7 +147,7 @@ public async Task FilesFormattedInUnformattedProjectFolder()
expectedFileCount: 6);
}

[Fact]
[MSBuildFact]
public async Task NoFilesFormattedInUnformattedSolutionFolder()
{
// Since the code files are outside the solution folder, no files are found or formatted.
Expand All @@ -160,7 +161,7 @@ public async Task NoFilesFormattedInUnformattedSolutionFolder()
expectedFileCount: 0);
}

[Fact]
[MSBuildFact]
public async Task FSharpProjectsDoNotCreateException()
{
var log = await TestFormatWorkspaceAsync(
Expand All @@ -179,7 +180,7 @@ public async Task FSharpProjectsDoNotCreateException()
Assert.EndsWith(s_fSharpProjectFilePath, match.Groups[1].Value);
}

[Fact]
[MSBuildFact]
public async Task OnlyFormatPathsFromList()
{
// To match a folder pattern it needs to end with a directory separator.
Expand All @@ -195,7 +196,7 @@ public async Task OnlyFormatPathsFromList()
expectedFileCount: 6);
}

[Fact]
[MSBuildFact]
public async Task OnlyFormatFilesFromList()
{
var include = new[] { s_unformattedProgramFilePath };
Expand All @@ -210,7 +211,7 @@ public async Task OnlyFormatFilesFromList()
expectedFileCount: 6);
}

[Fact]
[MSBuildFact]
public async Task NoFilesFormattedWhenNotInList()
{
var include = new[] { Path.Combine(s_unformattedProjectPath, "does_not_exist.cs") };
Expand All @@ -225,7 +226,7 @@ public async Task NoFilesFormattedWhenNotInList()
expectedFileCount: 6);
}

[Fact]
[MSBuildFact]
public async Task OnlyLogFormattedFiles()
{
var include = new[] { s_unformattedProgramFilePath };
Expand All @@ -246,7 +247,7 @@ public async Task OnlyLogFormattedFiles()
Assert.EndsWith("Program.cs", match.Groups[1].Value);
}

[Fact]
[MSBuildFact]
public async Task FormatLocationsLoggedInUnformattedProject()
{
var log = await TestFormatWorkspaceAsync(
Expand Down Expand Up @@ -297,7 +298,7 @@ public async Task FormatLocationsLoggedInUnformattedProject()
}
}

[Fact]
[MSBuildFact]
public async Task FormatLocationsNotLoggedInFormattedProject()
{
var log = await TestFormatWorkspaceAsync(
Expand All @@ -315,7 +316,7 @@ public async Task FormatLocationsNotLoggedInFormattedProject()
Assert.Empty(formatLocations);
}

[Fact]
[MSBuildFact]
public async Task LogFilesThatDontMatchExclude()
{
var include = new[] { s_unformattedProgramFilePath };
Expand All @@ -336,7 +337,7 @@ public async Task LogFilesThatDontMatchExclude()
Assert.EndsWith("Program.cs", match.Groups[1].Value);
}

[Fact]
[MSBuildFact]
public async Task IgnoreFileWhenListedInExcludeList()
{
var include = new[] { s_unformattedProgramFilePath };
Expand All @@ -351,7 +352,7 @@ public async Task IgnoreFileWhenListedInExcludeList()
expectedFileCount: 6);
}

[Fact]
[MSBuildFact]
public async Task IgnoreFileWhenContainingFolderListedInExcludeList()
{
var include = new[] { s_unformattedProgramFilePath };
Expand All @@ -367,7 +368,7 @@ public async Task IgnoreFileWhenContainingFolderListedInExcludeList()
expectedFileCount: 6);
}

[Fact]
[MSBuildFact]
public async Task IgnoreAllFileWhenExcludingAllFiles()
{
var include = new[] { s_unformattedProgramFilePath };
Expand All @@ -383,7 +384,7 @@ public async Task IgnoreAllFileWhenExcludingAllFiles()
expectedFileCount: 6);
}

[Fact]
[MSBuildFact]
public async Task NoFilesFormattedInGeneratedProject_WhenNotIncludingGeneratedCode()
{
await TestFormatWorkspaceAsync(
Expand All @@ -396,7 +397,7 @@ public async Task NoFilesFormattedInGeneratedProject_WhenNotIncludingGeneratedCo
expectedFileCount: 3);
}

[Fact]
[MSBuildFact]
public async Task FilesFormattedInGeneratedProject_WhenIncludingGeneratedCode()
{
await TestFormatWorkspaceAsync(
Expand All @@ -409,7 +410,7 @@ public async Task FilesFormattedInGeneratedProject_WhenIncludingGeneratedCode()
expectedFileCount: 3);
}

[Fact]
[MSBuildFact]
public async Task NoFilesFormattedInCodeStyleSolution_WhenNotFixingCodeStyle()
{
var restoreExitCode = await NuGetHelper.PerformRestore(s_codeStyleSolutionFilePath, _output);
Expand All @@ -426,7 +427,7 @@ public async Task NoFilesFormattedInCodeStyleSolution_WhenNotFixingCodeStyle()
fixCategory: FixCategory.Whitespace | FixCategory.CodeStyle);
}

[Fact]
[MSBuildFact]
public async Task NoFilesFormattedInCodeStyleSolution_WhenFixingCodeStyleErrors()
{
var restoreExitCode = await NuGetHelper.PerformRestore(s_codeStyleSolutionFilePath, _output);
Expand All @@ -444,7 +445,7 @@ public async Task NoFilesFormattedInCodeStyleSolution_WhenFixingCodeStyleErrors(
codeStyleSeverity: DiagnosticSeverity.Error);
}

[Fact]
[MSBuildFact]
public async Task FilesFormattedInCodeStyleSolution_WhenFixingCodeStyleWarnings()
{
var restoreExitCode = await NuGetHelper.PerformRestore(s_codeStyleSolutionFilePath, _output);
Expand All @@ -462,7 +463,7 @@ public async Task FilesFormattedInCodeStyleSolution_WhenFixingCodeStyleWarnings(
codeStyleSeverity: DiagnosticSeverity.Warning);
}

[Fact]
[MSBuildFact]
public async Task NoFilesFormattedInAnalyzersSolution_WhenNotFixingAnalyzers()
{
var restoreExitCode = await NuGetHelper.PerformRestore(s_analyzersSolutionFilePath, _output);
Expand All @@ -479,7 +480,7 @@ public async Task NoFilesFormattedInAnalyzersSolution_WhenNotFixingAnalyzers()
fixCategory: FixCategory.Whitespace);
}

[Fact]
[MSBuildFact]
public async Task FilesFormattedInAnalyzersSolution_WhenFixingAnalyzerErrors()
{
var restoreExitCode = await NuGetHelper.PerformRestore(s_analyzersSolutionFilePath, _output);
Expand Down
16 changes: 16 additions & 0 deletions tests/XUnit/MSBuildFactAttribute.cs
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using Xunit;
using Xunit.Sdk;

#nullable enable

namespace Microsoft.CodeAnalysis.Tools.Tests.XUnit
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("Microsoft.CodeAnalysis.Tools.Tests.XUnit.MSBuildFactDiscoverer", "dotnet-format.UnitTests")]
public sealed class MSBuildFactAttribute : FactAttribute
{
}
}

0 comments on commit d86a78e

Please sign in to comment.