|
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.UnitTests;
using Microsoft.CodeAnalysis.UnitTests.TestFiles;
using Microsoft.CodeAnalysis.VisualBasic;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.MSBuild.UnitTests
{
public class NetCoreTests : MSBuildWorkspaceTestBase
{
private readonly TempDirectory _nugetCacheDir;
public NetCoreTests()
{
_nugetCacheDir = SolutionDirectory.CreateDirectory(".packages");
}
private void RunDotNet(string arguments)
{
var environmentVariables = new Dictionary<string, string>()
{
["NUGET_PACKAGES"] = _nugetCacheDir.Path
};
var dotNetExeName = "dotnet" + (Path.DirectorySeparatorChar == '/' ? "" : ".exe");
var restoreResult = ProcessUtilities.Run(
dotNetExeName, arguments,
workingDirectory: SolutionDirectory.Path,
additionalEnvironmentVars: environmentVariables);
Assert.True(restoreResult.ExitCode == 0, $"{dotNetExeName} failed with exit code {restoreResult.ExitCode}: {restoreResult.Output}");
}
private void DotNetRestore(string solutionOrProjectFileName)
{
var arguments = $@"msbuild ""{solutionOrProjectFileName}"" /t:restore /bl:{Path.Combine(SolutionDirectory.Path, "restore.binlog")}";
RunDotNet(arguments);
}
private void DotNetBuild(string solutionOrProjectFileName, string configuration = null)
{
var arguments = $@"msbuild ""{solutionOrProjectFileName}"" /bl:{Path.Combine(SolutionDirectory.Path, "build.binlog")}";
if (configuration != null)
{
arguments += $" /p:Configuration={configuration}";
}
RunDotNet(arguments);
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
public async Task TestOpenProject_NetCoreApp()
{
CreateFiles(GetNetCoreAppFiles());
var projectFilePath = GetSolutionFileName("Project.csproj");
var projectDir = Path.GetDirectoryName(projectFilePath);
DotNetRestore("Project.csproj");
using var workspace = CreateMSBuildWorkspace();
var project = await workspace.OpenProjectAsync(projectFilePath);
Assert.Equal(Path.Combine(projectDir, "bin", "Debug", "netcoreapp3.1", "Project.dll"), project.OutputFilePath);
Assert.Equal(Path.Combine(projectDir, "obj", "Debug", "netcoreapp3.1", "Project.dll"), project.CompilationOutputInfo.AssemblyPath);
Assert.Null(project.CompilationOutputInfo.GeneratedFilesOutputDirectory);
// Assert that there is a single project loaded.
Assert.Single(workspace.CurrentSolution.ProjectIds);
// Assert that the project does not have any diagnostics in Program.cs
var document = project.Documents.First(d => d.Name == "Program.cs");
var semanticModel = await document.GetSemanticModelAsync();
var diagnostics = semanticModel.GetDiagnostics();
Assert.Empty(diagnostics);
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
public async Task TestOpenProjectTwice_NetCoreAppAndLibrary()
{
CreateFiles(GetNetCoreAppAndLibraryFiles());
var projectFilePath = GetSolutionFileName(@"Project\Project.csproj");
var libraryFilePath = GetSolutionFileName(@"Library\Library.csproj");
DotNetRestore(@"Project\Project.csproj");
using var workspace = CreateMSBuildWorkspace();
var libraryProject = await workspace.OpenProjectAsync(libraryFilePath);
// Assert that there is a single project loaded.
Assert.Single(workspace.CurrentSolution.ProjectIds);
// Assert that the project does not have any diagnostics in Class1.cs
var document = libraryProject.Documents.First(d => d.Name == "Class1.cs");
var semanticModel = await document.GetSemanticModelAsync();
var diagnostics = semanticModel.GetDiagnostics();
Assert.Empty(diagnostics);
var project = await workspace.OpenProjectAsync(projectFilePath);
// Assert that there are only two projects opened.
Assert.Equal(2, workspace.CurrentSolution.ProjectIds.Count);
// Assert that there is a project reference between Project.csproj and Library.csproj
var projectReference = Assert.Single(project.ProjectReferences);
var projectRefId = projectReference.ProjectId;
Assert.Equal(libraryProject.Id, projectRefId);
Assert.Equal(libraryProject.FilePath, workspace.CurrentSolution.GetProject(projectRefId).FilePath);
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
public async Task TestOpenProjectTwice_NetCoreAppAndTwoLibraries()
{
CreateFiles(GetNetCoreAppAndTwoLibrariesFiles());
var projectFilePath = GetSolutionFileName(@"Project\Project.csproj");
var library1FilePath = GetSolutionFileName(@"Library1\Library1.csproj");
var library2FilePath = GetSolutionFileName(@"Library2\Library2.csproj");
DotNetRestore(@"Project\Project.csproj");
DotNetRestore(@"Library2\Library2.csproj");
// Warning: Found project reference without a matching metadata reference: Library1.csproj
using var workspace = CreateMSBuildWorkspace(throwOnWorkspaceFailed: false);
var project = await workspace.OpenProjectAsync(projectFilePath);
// Assert that there is are two projects loaded (Project.csproj references Library1.csproj).
Assert.Equal(2, workspace.CurrentSolution.ProjectIds.Count);
// Assert that the project does not have any diagnostics in Program.cs
var document = project.Documents.First(d => d.Name == "Program.cs");
var semanticModel = await document.GetSemanticModelAsync();
var diagnostics = semanticModel.GetDiagnostics();
Assert.Empty(diagnostics);
var library2 = await workspace.OpenProjectAsync(library2FilePath);
// Assert that there are now three projects loaded (Library2.csproj also references Library1.csproj)
Assert.Equal(3, workspace.CurrentSolution.ProjectIds.Count);
// Assert that there is a project reference between Project.csproj and Library1.csproj
AssertSingleProjectReference(project, library1FilePath);
// Assert that there is a project reference between Library2.csproj and Library1.csproj
AssertSingleProjectReference(library2, library1FilePath);
static void AssertSingleProjectReference(Project project, string projectRefFilePath)
{
var projectReference = Assert.Single(project.ProjectReferences);
var projectRefId = projectReference.ProjectId;
Assert.Equal(projectRefFilePath, project.Solution.GetProject(projectRefId).FilePath);
}
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
public async Task TestOpenProject_NetCoreMultiTFM()
{
CreateFiles(GetNetCoreMultiTFMFiles());
var projectFilePath = GetSolutionFileName("Project.csproj");
DotNetRestore("Project.csproj");
using var workspace = CreateMSBuildWorkspace();
await workspace.OpenProjectAsync(projectFilePath);
// Assert that three projects have been loaded, one for each TFM.
Assert.Equal(3, workspace.CurrentSolution.ProjectIds.Count);
var projectPaths = new HashSet<string>();
var outputFilePaths = new HashSet<string>();
foreach (var project in workspace.CurrentSolution.Projects)
{
projectPaths.Add(project.FilePath);
outputFilePaths.Add(project.OutputFilePath);
}
// Assert that the three projects share the same file path
Assert.Single(projectPaths);
// Assert that the three projects have different output file paths
Assert.Equal(3, outputFilePaths.Count);
// Assert that none of the projects have any diagnostics in Program.cs
foreach (var project in workspace.CurrentSolution.Projects)
{
var document = project.Documents.First(d => d.Name == "Program.cs");
var semanticModel = await document.GetSemanticModelAsync();
var diagnostics = semanticModel.GetDiagnostics();
Assert.Empty(diagnostics);
}
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
public async Task TestOpenProject_NetCoreMultiTFM_ExtensionWithConditionOnTFM()
{
CreateFiles(GetNetCoreMultiTFMFiles_ExtensionWithConditionOnTFM());
var projectFilePath = GetSolutionFileName("Project.csproj");
DotNetRestore("Project.csproj");
using var workspace = CreateMSBuildWorkspace();
await workspace.OpenProjectAsync(projectFilePath);
// Assert that three projects have been loaded, one for each TFM.
Assert.Equal(3, workspace.CurrentSolution.ProjectIds.Count);
// Assert the TFM is accessible from project extensions.
// The test project extension sets the default namespace based on the TFM.
foreach (var project in workspace.CurrentSolution.Projects)
{
switch (project.Name)
{
case "Project(net6)":
Assert.Equal("Project.NetCore", project.DefaultNamespace);
break;
case "Project(netstandard2.0)":
Assert.Equal("Project.NetStandard", project.DefaultNamespace);
break;
case "Project(net5)":
Assert.Equal("Project.NetFramework", project.DefaultNamespace);
break;
default:
Assert.True(false, $"Unexpected project: {project.Name}");
break;
}
}
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
public async Task TestOpenProject_NetCoreMultiTFM_ProjectReference()
{
CreateFiles(GetNetCoreMultiTFMFiles_ProjectReference());
// Restoring for Project.csproj should also restore Library.csproj
DotNetRestore(@"Project\Project.csproj");
var projectFilePath = GetSolutionFileName(@"Project\Project.csproj");
await AssertNetCoreMultiTFMProject(projectFilePath);
}
private static async Task AssertNetCoreMultiTFMProject(string projectFilePath)
{
using var workspace = CreateMSBuildWorkspace();
await workspace.OpenProjectAsync(projectFilePath);
// Assert that four projects have been loaded, one for each TFM.
Assert.Equal(4, workspace.CurrentSolution.ProjectIds.Count);
var projectPaths = new HashSet<string>();
var outputFilePaths = new HashSet<string>();
foreach (var project in workspace.CurrentSolution.Projects)
{
projectPaths.Add(project.FilePath);
outputFilePaths.Add(project.OutputFilePath);
}
// Assert that there are two project file path among the four projects
Assert.Equal(2, projectPaths.Count);
// Assert that the four projects each have different output file paths
Assert.Equal(4, outputFilePaths.Count);
var expectedNames = new HashSet<string>()
{
"Project(net6)",
"Project(net5)",
"Library(netstandard2",
"Library(net5)"
};
var actualNames = new HashSet<string>();
foreach (var project in workspace.CurrentSolution.Projects)
{
var dotIndex = project.Name.IndexOf('.');
var projectName = dotIndex >= 0
? project.Name[..dotIndex]
: project.Name;
actualNames.Add(projectName);
var fileName = PathUtilities.GetFileName(project.FilePath);
Document document;
switch (fileName)
{
case "Project.csproj":
document = project.Documents.First(d => d.Name == "Program.cs");
break;
case "Library.csproj":
document = project.Documents.First(d => d.Name == "Class1.cs");
break;
default:
Assert.True(false, $"Encountered unexpected project: {project.FilePath}");
return;
}
// Assert that none of the projects have any diagnostics in their primary .cs file.
var semanticModel = await document.GetSemanticModelAsync();
var diagnostics = semanticModel.GetDiagnostics();
Assert.Empty(diagnostics);
}
Assert.True(actualNames.SetEquals(expectedNames), $"Project names differ!{Environment.NewLine}Actual: {{{actualNames.Join(",")}}}{Environment.NewLine}Expected: {{{expectedNames.Join(",")}}}");
// Verify that the projects reference the correct TFMs
var projects = workspace.CurrentSolution.Projects.Where(p => p.FilePath.EndsWith("Project.csproj"));
foreach (var project in projects)
{
var projectReference = Assert.Single(project.ProjectReferences);
var referencedProject = workspace.CurrentSolution.GetProject(projectReference.ProjectId);
if (project.OutputFilePath.Contains("net6"))
{
Assert.Contains("net5", referencedProject.OutputFilePath);
}
else if (project.OutputFilePath.Contains("net5"))
{
Assert.Contains("net5", referencedProject.OutputFilePath);
}
else
{
Assert.True(false, "OutputFilePath with expected TFM not found.");
}
}
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
public async Task TestOpenSolution_NetCoreMultiTFMWithProjectReferenceToFSharp()
{
CreateFiles(GetNetCoreMultiTFMFiles_ProjectReferenceToFSharp());
var solutionFilePath = GetSolutionFileName("Solution.sln");
DotNetRestore("Solution.sln");
using var workspace = CreateMSBuildWorkspace(throwOnWorkspaceFailed: false, skipUnrecognizedProjects: true);
var solution = await workspace.OpenSolutionAsync(solutionFilePath);
var projects = solution.Projects.ToArray();
Assert.Equal(2, projects.Length);
foreach (var project in projects)
{
Assert.StartsWith("csharplib", project.Name);
Assert.Empty(project.ProjectReferences);
Assert.Single(project.AllProjectReferences);
}
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
public async Task TestOpenProject_ReferenceConfigurationSpecificMetadata()
{
var files = GetBaseFiles()
.WithFile(@"Solution.sln", Resources.SolutionFiles.Issue30174_Solution)
.WithFile(@"InspectedLibrary\InspectedLibrary.csproj", Resources.ProjectFiles.CSharp.Issue30174_InspectedLibrary)
.WithFile(@"InspectedLibrary\InspectedClass.cs", Resources.SourceFiles.CSharp.Issue30174_InspectedClass)
.WithFile(@"ReferencedLibrary\ReferencedLibrary.csproj", Resources.ProjectFiles.CSharp.Issue30174_ReferencedLibrary)
.WithFile(@"ReferencedLibrary\SomeMetadataAttribute.cs", Resources.SourceFiles.CSharp.Issue30174_SomeMetadataAttribute);
CreateFiles(files);
DotNetRestore("Solution.sln");
DotNetBuild("Solution.sln", configuration: "Release");
var projectFilePath = GetSolutionFileName(@"InspectedLibrary\InspectedLibrary.csproj");
using var workspace = CreateMSBuildWorkspace(("Configuration", "Release"));
workspace.LoadMetadataForReferencedProjects = true;
var project = await workspace.OpenProjectAsync(projectFilePath);
Assert.Empty(project.ProjectReferences);
Assert.Empty(workspace.Diagnostics);
var compilation = await project.GetCompilationAsync();
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
public async Task TestOpenProject_OverrideTFM()
{
CreateFiles(GetNetCoreAppAndLibraryFiles());
var projectFilePath = GetSolutionFileName(@"Library\Library.csproj");
DotNetRestore(@"Library\Library.csproj");
// Override the TFM properties defined in the file
using var workspace = CreateMSBuildWorkspace(("TargetFramework", ""), ("TargetFrameworks", "net6;net5"));
await workspace.OpenProjectAsync(projectFilePath);
// Assert that two projects have been loaded, one for each TFM.
Assert.Equal(2, workspace.CurrentSolution.ProjectIds.Count);
Assert.Contains(workspace.CurrentSolution.Projects, p => p.Name == "Library(net6)");
Assert.Contains(workspace.CurrentSolution.Projects, p => p.Name == "Library(net5)");
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
[UseCulture("en-EN", "en-EN")]
public async Task TestBuildHostLocale_EN()
{
await AssertInvalidTfmDiagnosticMessageContains("The TargetFramework value 'Invalid' was not recognized. It may be misspelled.");
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
[UseCulture("de-DE", "de-DE")]
public async Task TestBuildHostLocale_DE()
{
await AssertInvalidTfmDiagnosticMessageContains("Der TargetFramework-Wert \"Invalid\" wurde nicht erkannt. Unter Umständen ist die Schreibweise nicht korrekt.");
}
private async Task AssertInvalidTfmDiagnosticMessageContains(string expected)
{
var projectPath = @"Project\Project.csproj";
var files = new FileSet((projectPath, Resources.ProjectFiles.CSharp.InvalidTFM));
CreateFiles(files);
var fullProjectPath = GetSolutionFileName(projectPath);
using var workspace = CreateMSBuildWorkspace(throwOnWorkspaceFailed: false);
var project = await workspace.OpenProjectAsync(fullProjectPath);
var diagnostic = Assert.Single(workspace.Diagnostics);
Assert.Contains(expected, diagnostic.Message);
}
[ConditionalFact(typeof(DotNetSdkMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
[Trait(Traits.Feature, Traits.Features.NetCore)]
public async Task TestOpenProject_VBNetCoreAppWithGlobalImportAndLibrary()
{
CreateFiles(GetVBNetCoreAppWithGlobalImportAndLibraryFiles());
var vbProjectFilePath = GetSolutionFileName(@"VBProject\VBProject.vbproj");
var libraryFilePath = GetSolutionFileName(@"Library\Library.csproj");
DotNetRestore(@"Library\Library.csproj");
DotNetRestore(@"VBProject\VBProject.vbproj");
// Warning:Found project reference without a matching metadata reference: Library.csproj
using var workspace = CreateMSBuildWorkspace(throwOnWorkspaceFailed: false);
var project = await workspace.OpenProjectAsync(vbProjectFilePath);
// Assert that there is are two projects loaded (VBProject.vbproj references Library.csproj).
Assert.Equal(2, workspace.CurrentSolution.ProjectIds.Count);
// Assert that there is a project reference between VBProject.vbproj and Library.csproj
AssertSingleProjectReference(project, libraryFilePath);
// Assert that the project does not have any diagnostics in Program.vb
var document = project.Documents.First(d => d.Name == "Program.vb");
var semanticModel = await document.GetSemanticModelAsync();
var diagnostics = semanticModel.GetDiagnostics();
Assert.Empty(diagnostics.Where(d => d.Severity >= DiagnosticSeverity.Warning));
var compilation = await project.GetCompilationAsync();
var option = compilation.Options as VisualBasicCompilationOptions;
Assert.Contains("LibraryHelperClass = Library.MyHelperClass", option.GlobalImports.Select(i => i.Name));
static void AssertSingleProjectReference(Project project, string projectRefFilePath)
{
var projectReference = Assert.Single(project.ProjectReferences);
var projectRefId = projectReference.ProjectId;
Assert.Equal(projectRefFilePath, project.Solution.GetProject(projectRefId).FilePath);
}
}
[Fact]
public void BuildHostShipsDepsJsonFile()
{
var depsJsonFile = Path.ChangeExtension(BuildHostProcessManager.GetNetCoreBuildHostPath(), "deps.json");
Assert.True(File.Exists(depsJsonFile), $"{depsJsonFile} should exist, or it won't load on some runtimes.");
}
}
}
|