-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Test MSBuildWorkspace opening dotnet new projects.
#69225
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
Merged
Merged
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
9c99675
Test MSBuildWorkspace project loading against dotnet project templates
JoeRobich 20ba0dd
Update ProjectBuildManager to optionally run MarkupCompilation.
JoeRobich 6eaa4a4
Update OpenProjectsTests to report unnecessarily ignored diagnostics
JoeRobich 4191b8c
Remove unnecessary ignored diagnostics from wpf tests
JoeRobich 62db227
Use DisposableDirectory for test project cleanup.
JoeRobich 7fe7320
Make ignoredDiagnostics optional in OpenProjectsTests
JoeRobich 8e6d5cd
Remove msBuildPath from DotNetSdkLocator
JoeRobich ac074ee
Change how global.json is located to account for Helix layout.
JoeRobich 97f8267
Use system install of dotnet CLI when running in Helix.
JoeRobich 8130b7b
Address PR feedback.
JoeRobich f0edf9c
Added ignore diagnostic to VB test when not run on Windows.
JoeRobich 920dc61
PR Feedback
JoeRobich 29ec1ac
Add links to bugs for ignored diagnostics
JoeRobich 55ffab8
Fix whitespace
JoeRobich 4621051
Do not fail if unable to copy global.json
JoeRobich 42693ec
Remove unnecessary async
JoeRobich 823b5ec
Do not repeatedly try to copy global.json if it doesn't exist
JoeRobich File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
264 changes: 264 additions & 0 deletions
264
src/Workspaces/MSBuildTest/NewlyCreatedProjectsFromDotNetNew.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,264 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.CodeAnalysis.Shared.Extensions; | ||
| using Roslyn.Test.Utilities; | ||
| using Xunit; | ||
| using Xunit.Abstractions; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.MSBuild.UnitTests | ||
| { | ||
| public class NewlyCreatedProjectsFromDotNetNew : MSBuildWorkspaceTestBase | ||
| { | ||
| private static readonly string s_globalJsonPath; | ||
|
|
||
| // The Maui templates require additional dotnet workloads to be installed. | ||
| // Running `dotnet workload restore` will install workloads but may require | ||
| // admin permissions. In addition a restart may be required after workload | ||
| // installation. | ||
| private const bool ExcludeMauiTemplates = true; | ||
|
|
||
| protected ITestOutputHelper TestOutputHelper { get; set; } | ||
|
|
||
| static NewlyCreatedProjectsFromDotNetNew() | ||
| { | ||
| // We'll use the same global.json as we use for our own build. | ||
| s_globalJsonPath = Path.Combine(GetSolutionFolder(), "global.json"); | ||
|
|
||
| static string GetSolutionFolder() | ||
| { | ||
| // Expected assembly path: | ||
| // <solutionFolder>\artifacts\bin\Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests\<Configuration>\<TFM>\Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.dll | ||
| var assemblyLocation = typeof(DotNetSdkMSBuildInstalled).Assembly.Location; | ||
| var solutionFolder = Directory.GetParent(assemblyLocation) | ||
| ?.Parent?.Parent?.Parent?.Parent?.Parent?.FullName; | ||
| Assumes.NotNull(solutionFolder); | ||
| return solutionFolder; | ||
| } | ||
| } | ||
|
|
||
| public NewlyCreatedProjectsFromDotNetNew(ITestOutputHelper output) | ||
| { | ||
| TestOutputHelper = output; | ||
| } | ||
|
|
||
| [ConditionalTheory(typeof(DotNetSdkMSBuildInstalled))] | ||
| [MemberData(nameof(GetCSharpProjectTemplateNames), DisableDiscoveryEnumeration = false)] | ||
| public async Task ValidateCSharpTemplateProjects(string templateName) | ||
| { | ||
| var ignoredDiagnostics = templateName switch | ||
| { | ||
| "blazor" or "blazorwasm" or "blazorwasm-empty" => | ||
| [ | ||
| // The type or namespace name {'csharp_blazor_project'|'App'} could not be found | ||
| // (are you missing a using directive or an assembly reference?) | ||
| // Bug: https://github.com/dotnet/roslyn/issues/72015 | ||
| "CS0246", | ||
| ], | ||
| _ => Array.Empty<string>(), | ||
| }; | ||
|
|
||
| await AssertTemplateProjectLoadsCleanlyAsync(templateName, LanguageNames.CSharp, ignoredDiagnostics); | ||
| } | ||
|
|
||
| [ConditionalTheory(typeof(DotNetSdkMSBuildInstalled))] | ||
| [MemberData(nameof(GetVisualBasicProjectTemplateNames), DisableDiscoveryEnumeration = false)] | ||
| public async Task ValidateVisualBasicTemplateProjects(string templateName) | ||
| { | ||
| var ignoredDiagnostics = !ExecutionConditionUtil.IsWindows | ||
| ? [ | ||
| // Type 'Global.Microsoft.VisualBasic.ApplicationServices.ApplicationBase' is not defined. | ||
| // Bug: https://github.com/dotnet/roslyn/issues/72014 | ||
| "BC30002", | ||
| ] | ||
| : Array.Empty<string>(); | ||
|
|
||
| await AssertTemplateProjectLoadsCleanlyAsync(templateName, LanguageNames.VisualBasic, ignoredDiagnostics); | ||
| } | ||
|
|
||
| public static TheoryData<string> GetCSharpProjectTemplateNames() | ||
| => GetProjectTemplateNames("c#"); | ||
|
|
||
| public static TheoryData<string> GetVisualBasicProjectTemplateNames() | ||
| => GetProjectTemplateNames("vb"); | ||
|
|
||
| public static TheoryData<string> GetProjectTemplateNames(string language) | ||
| { | ||
| // The expected output from the list command is as follows. | ||
|
|
||
| // These templates matched your input: --language='vb', --type='project' | ||
| // | ||
| // Template Name Short Name Language Tags | ||
| // ----------------------------- ------------------- -------- --------------- | ||
| // Class Library classlib C#,F#,VB Common/Library | ||
| // Console App console C#,F#,VB Common/Console | ||
| // ... | ||
|
|
||
| var result = RunDotNet($"new list --type project --language {language}", output: null); | ||
|
|
||
| var lines = result.Output.Split(new[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); | ||
|
|
||
| TheoryData<string> templateNames = []; | ||
| var foundDivider = false; | ||
|
|
||
| foreach (var line in lines) | ||
| { | ||
| if (!foundDivider) | ||
| { | ||
| if (line.StartsWith("----")) | ||
| { | ||
| foundDivider = true; | ||
| } | ||
| continue; | ||
| } | ||
|
|
||
| var columns = line.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries) | ||
| .Select(c => c.Trim()) | ||
| .ToArray(); | ||
| var templateShortName = columns[1].Split(',').First(); | ||
|
|
||
| if (ExcludeMauiTemplates && templateShortName.StartsWith("maui")) | ||
| continue; | ||
|
|
||
| templateNames.Add(templateShortName); | ||
| } | ||
|
|
||
| Assert.True(foundDivider); | ||
|
|
||
| return templateNames; | ||
| } | ||
|
|
||
| private async Task AssertTemplateProjectLoadsCleanlyAsync(string templateName, string languageName, string[]? ignoredDiagnostics = null) | ||
| { | ||
| if (ignoredDiagnostics?.Length > 0) | ||
| { | ||
| TestOutputHelper.WriteLine($"Ignoring compiler diagnostics: \"{string.Join("\", \"", ignoredDiagnostics)}\""); | ||
| } | ||
|
|
||
| var projectDirectory = SolutionDirectory.Path; | ||
| var projectFilePath = GetProjectFilePath(projectDirectory, languageName); | ||
|
|
||
| CreateNewProject(templateName, projectDirectory, languageName, TestOutputHelper); | ||
|
|
||
| await AssertProjectLoadsCleanlyAsync(projectFilePath, ignoredDiagnostics ?? []); | ||
|
|
||
| return; | ||
|
|
||
| static string GetProjectFilePath(string projectDirectory, string languageName) | ||
| { | ||
| var projectName = new DirectoryInfo(projectDirectory).Name; | ||
| var projectExtension = languageName switch | ||
| { | ||
| LanguageNames.CSharp => "csproj", | ||
| LanguageNames.VisualBasic => "vbproj", | ||
| _ => throw new ArgumentOutOfRangeException(nameof(languageName), actualValue: languageName, message: "Only C# and VB.NET projects are supported.") | ||
| }; | ||
| return Path.Combine(projectDirectory, $"{projectName}.{projectExtension}"); | ||
| } | ||
|
|
||
| static void CreateNewProject(string templateName, string outputDirectory, string languageName, ITestOutputHelper output) | ||
| { | ||
| var language = languageName switch | ||
| { | ||
| LanguageNames.CSharp => "C#", | ||
| LanguageNames.VisualBasic => "VB", | ||
| _ => throw new ArgumentOutOfRangeException(nameof(languageName), actualValue: languageName, message: "Only C# and VB.NET projects are supported.") | ||
| }; | ||
|
|
||
| TryCopyGlobalJson(outputDirectory); | ||
|
|
||
| var newResult = RunDotNet($"new \"{templateName}\" -o \"{outputDirectory}\" --language \"{language}\"", output, outputDirectory); | ||
|
|
||
| // Most templates invoke restore as a post-creation action. However, some, like the | ||
| // Maui templates, do not run restore since they require additional workloads to be | ||
| // installed. | ||
| if (newResult.Output.Contains("Restoring")) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| // Attempt a restore and see if we are instructed to install additional workloads. | ||
| var restoreResult = RunDotNet($"restore", output, outputDirectory); | ||
| } | ||
| catch (InvalidOperationException ex) when (ex.Message.Contains("command: dotnet workload restore")) | ||
| { | ||
| throw new InvalidOperationException($"The '{templateName}' template requires additional dotnet workloads to be installed. It should be excluded during template discovery. " + ex.Message); | ||
| } | ||
| } | ||
|
|
||
| static void TryCopyGlobalJson(string outputDirectory) | ||
| { | ||
| var tempGlobalJsonPath = Path.Combine(outputDirectory, "global.json"); | ||
| try | ||
| { | ||
| File.Copy(s_globalJsonPath, tempGlobalJsonPath); | ||
| } | ||
| catch (FileNotFoundException) | ||
| { | ||
| } | ||
JoeRobich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| static async Task AssertProjectLoadsCleanlyAsync(string projectFilePath, string[] ignoredDiagnostics) | ||
| { | ||
| using var workspace = CreateMSBuildWorkspace(); | ||
| var project = await workspace.OpenProjectAsync(projectFilePath, cancellationToken: CancellationToken.None); | ||
|
|
||
| AssertEx.Empty(workspace.Diagnostics, $"The following workspace diagnostics are being reported for the template."); | ||
|
|
||
| var compilation = await project.GetRequiredCompilationAsync(CancellationToken.None); | ||
|
|
||
| // Unnecessary using directives are reported with a severity of Hidden | ||
| var nonHiddenDiagnostics = compilation!.GetDiagnostics() | ||
| .Where(diagnostic => diagnostic.Severity > DiagnosticSeverity.Hidden) | ||
| .ToImmutableArray(); | ||
|
|
||
| // For good test hygiene lets ensure that all ignored diagnostics were actually reported. | ||
| var reportedDiagnosticIds = nonHiddenDiagnostics | ||
| .Select(diagnostic => diagnostic.Id) | ||
| .ToImmutableHashSet(); | ||
| var unnecessaryIgnoreDiagnostics = ignoredDiagnostics | ||
| .Where(id => !reportedDiagnosticIds.Contains(id)); | ||
|
|
||
| AssertEx.Empty(unnecessaryIgnoreDiagnostics, $"The following diagnostics are unnecessarily being ignored for the template."); | ||
|
|
||
| var filteredDiagnostics = nonHiddenDiagnostics | ||
| .Where(diagnostic => !ignoredDiagnostics.Contains(diagnostic.Id)); | ||
|
|
||
| AssertEx.Empty(filteredDiagnostics, $"The following compiler diagnostics are being reported for the template."); | ||
| } | ||
| } | ||
|
|
||
| private static ProcessResult RunDotNet(string arguments, ITestOutputHelper? output, string? workingDirectory = null) | ||
| { | ||
| var dotNetExeName = "dotnet" + (Path.DirectorySeparatorChar == '/' ? "" : ".exe"); | ||
|
|
||
| var result = ProcessUtilities.Run(dotNetExeName, arguments, workingDirectory, additionalEnvironmentVars: [new KeyValuePair<string, string>("DOTNET_CLI_UI_LANGUAGE", "en")]); | ||
|
|
||
| if (result.ExitCode != 0) | ||
| { | ||
| throw new InvalidOperationException(string.Join(Environment.NewLine, | ||
| [ | ||
| $"`dotnet {arguments}` returned a non-zero exit code.", | ||
| "Output:", | ||
| result.Output, | ||
| "Error:", | ||
| result.Errors | ||
| ])); | ||
| } | ||
|
|
||
| output?.WriteLine(result.Output); | ||
|
|
||
| return result; | ||
| } | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.