Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MSBuildFact for tests to lock around MSBuild invocations #899

Merged
merged 1 commit into from Feb 16, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
{
}
}