File: NetCoreTests.cs
Web Access
Project: src\src\Workspaces\MSBuildTest\Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests.csproj (Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests)
// 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.");
        }
    }
}