File: ProjectExtensionsImportTestBase.cs
Web Access
Project: ..\..\..\src\Tasks.UnitTests\Microsoft.Build.Tasks.UnitTests.csproj (Microsoft.Build.Tasks.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.IO;
using System.Linq;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Shared;
using Shouldly;
using Xunit;
 
#nullable disable
 
namespace Microsoft.Build.UnitTests
{
    public abstract class ProjectExtensionsImportTestBase : IDisposable
    {
        protected readonly string _projectRelativePath = Path.Combine("src", "foo", "foo.csproj");
 
        protected ProjectExtensionsImportTestBase()
        {
            ObjectModelHelpers.DeleteTempProjectDirectory();
        }
 
        protected virtual string BasicProjectImportContents => $@"
            <Project>
                <PropertyGroup>
                <{PropertyNameToSignalImportSucceeded}>true</{PropertyNameToSignalImportSucceeded}>
                </PropertyGroup>
            </Project>";
 
        protected abstract string CustomImportProjectPath { get; }
        protected abstract string ImportProjectPath { get; }
        protected abstract string PropertyNameToEnableImport { get; }
 
        /// <summary>
        /// The name of the property to use in a project that is imported.  This base class will generate a project containing the declaration of the property.
        /// </summary>
        protected abstract string PropertyNameToSignalImportSucceeded { get; }
 
        public void Dispose()
        {
            ObjectModelHelpers.DeleteTempProjectDirectory();
        }
 
        /// <summary>
        /// Ensures that when the MSBuildProjectExtensionsPath does not exist that nothing is imported.
        /// </summary>
        [Fact]
        public void DoesNotImportProjectIfNotExist()
        {
            // ---------------------
            // src\Foo\Foo.csproj
            // ---------------------
 
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath, @"
                <Project DefaultTargets=`Build` ToolsVersion=`msbuilddefaulttoolsversion`>
                    <Import Project=`$(MSBuildBinPath)\Microsoft.Common.props` />
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.CSharp.targets` />
                </Project>
            "));
 
            string projectExtensionsPath = project.GetPropertyValue("MSBuildProjectExtensionsPath");
 
            projectExtensionsPath.ShouldNotBeNullOrWhiteSpace();
            Directory.Exists(projectExtensionsPath).ShouldBeFalse();
            project.GetPropertyValue(PropertyNameToEnableImport).ShouldBe("true");
            project.GetPropertyValue(PropertyNameToSignalImportSucceeded).ShouldBeEmpty();
        }
 
        [Fact]
        public void DoesNotImportProjectIfRestoring()
        {
            ObjectModelHelpers.CreateFileInTempProjectDirectory(ImportProjectPath, BasicProjectImportContents);
 
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath, $@"
                <Project DefaultTargets=`Build` ToolsVersion=`msbuilddefaulttoolsversion`>
                    <PropertyGroup>
                        <{MSBuildConstants.MSBuildIsRestoring}>true</{MSBuildConstants.MSBuildIsRestoring}>
                    </PropertyGroup>
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.Common.props` />
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.CSharp.targets` />
                </Project>
            "));
 
            string projectExtensionsPath = project.GetPropertyValue("MSBuildProjectExtensionsPath");
 
            projectExtensionsPath.ShouldNotBeNullOrWhiteSpace();
            Directory.Exists(projectExtensionsPath).ShouldBeTrue();
            project.GetPropertyValue(PropertyNameToEnableImport).ShouldBe(bool.FalseString, StringCompareShould.IgnoreCase);
            project.GetPropertyValue(PropertyNameToSignalImportSucceeded).ShouldBeEmpty();
        }
 
        [Fact]
        public void ImportsProjectIfRestoringAndExplicitlySet()
        {
            ObjectModelHelpers.CreateFileInTempProjectDirectory(ImportProjectPath, BasicProjectImportContents);
 
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath, $@"
                <Project DefaultTargets=`Build` ToolsVersion=`msbuilddefaulttoolsversion`>
                    <PropertyGroup>
                        <{PropertyNameToEnableImport}>true</{PropertyNameToEnableImport}>
                        <{MSBuildConstants.MSBuildIsRestoring}>true</{MSBuildConstants.MSBuildIsRestoring}>
                    </PropertyGroup>
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.Common.props` />
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.CSharp.targets` />
                </Project>
            "));
 
            string projectExtensionsPath = project.GetPropertyValue("MSBuildProjectExtensionsPath");
 
            projectExtensionsPath.ShouldNotBeNullOrWhiteSpace();
            Directory.Exists(projectExtensionsPath).ShouldBeTrue();
            project.GetPropertyValue(PropertyNameToEnableImport).ShouldBe(bool.TrueString, StringCompareShould.IgnoreCase);
            project.GetPropertyValue(PropertyNameToSignalImportSucceeded).ShouldBe(bool.TrueString, StringCompareShould.IgnoreCase);
        }
 
        /// <summary>
        /// Ensures that even if the MSBuildProjectExtensionsPath exists, the extensions are not imported if the functionality is disabled via the <see cref="PropertyNameToEnableImport"/>.
        /// </summary>
        [Fact]
        public void DoesNotImportProjectWhenDisabled()
        {
            // ---------------------
            // Directory.Build.props
            // ---------------------
            ObjectModelHelpers.CreateFileInTempProjectDirectory(ImportProjectPath, BasicProjectImportContents);
 
            // ---------------------
            // src\Foo\Foo.csproj
            // ---------------------
 
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath, $@"
                <Project DefaultTargets=`Build` ToolsVersion=`msbuilddefaulttoolsversion`>
                    <PropertyGroup>
                        <{PropertyNameToEnableImport}>false</{PropertyNameToEnableImport}>
                    </PropertyGroup>
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.Common.props` />
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.CSharp.targets` />
                </Project>
            "));
 
            string projectExtensionsDirectory = Path.Combine(ObjectModelHelpers.TempProjectDir, Path.GetDirectoryName(ImportProjectPath));
 
            project.GetPropertyValue(PropertyNameToEnableImport).ShouldBe("false");
            project.GetPropertyValue(PropertyNameToSignalImportSucceeded).ShouldBeEmpty();
            Directory.Exists(projectExtensionsDirectory).ShouldBeTrue();
            project.GetPropertyValue("MSBuildProjectExtensionsPath").ShouldBe($@"{projectExtensionsDirectory}{Path.DirectorySeparatorChar}");
        }
 
        /// <summary>
        /// Ensures that if the user set a custom MSBuildProjectExtensionsPath that the import will still succeed.
        /// </summary>
        [Fact]
        public void ImportsProjectIfCustomPath()
        {
            ObjectModelHelpers.CreateFileInTempProjectDirectory(CustomImportProjectPath, BasicProjectImportContents);
 
            // ---------------------
            // src\Foo\Foo.csproj
            // ---------------------
 
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath, $@"
                <Project DefaultTargets=`Build` ToolsVersion=`msbuilddefaulttoolsversion`>
                    <PropertyGroup>
                        <MSBuildProjectExtensionsPath>{Path.GetDirectoryName(CustomImportProjectPath)}</MSBuildProjectExtensionsPath>
                    </PropertyGroup>
                    <Import Project=`$(MSBuildBinPath)\Microsoft.Common.props` />
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.CSharp.targets` />
                </Project>
            "));
 
            project.GetPropertyValue(PropertyNameToEnableImport).ShouldBe("true");
            project.GetPropertyValue(PropertyNameToSignalImportSucceeded).ShouldBe("true");
        }
 
        /// <summary>
        /// Ensures that if the default MSBuildProjectExtensions directory is used, that the projects will be imported.
        /// </summary>
        [Fact]
        public void ImportsProjectIfExists()
        {
            ObjectModelHelpers.CreateFileInTempProjectDirectory(ImportProjectPath, BasicProjectImportContents);
 
            // ---------------------
            // src\Foo\Foo.csproj
            // ---------------------
 
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath, @"
                <Project DefaultTargets=`Build` ToolsVersion=`msbuilddefaulttoolsversion`>
                    <Import Project=`$(MSBuildBinPath)\Microsoft.Common.props` />
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.CSharp.targets` />
                </Project>
            "));
 
            project.GetPropertyValue(PropertyNameToEnableImport).ShouldBe("true");
            project.GetPropertyValue(PropertyNameToSignalImportSucceeded).ShouldBe("true");
        }
 
        /// <summary>
        /// Ensures that an error is logged if MSBuildProjectExtensionsPath is modified after it was set by Microsoft.Common.props.
        /// </summary>
        [Fact]
        public void ErrorIfChangedInBodyOfProject()
        {
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath, @"
                <Project DefaultTargets=`Build` ToolsVersion=`msbuilddefaulttoolsversion`>
                    <Import Project=`$(MSBuildBinPath)\Microsoft.Common.props` />
 
                    <PropertyGroup>
                        <MSBuildProjectExtensionsPath>foo</MSBuildProjectExtensionsPath>
                    </PropertyGroup>
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.CSharp.targets` />
                </Project>
            "));
 
            MockLogger logger = new MockLogger();
 
            project.Build("_CheckForInvalidConfigurationAndPlatform", new[] { logger }).ShouldBeFalse();
 
            logger.Errors.Select(i => i.Code).FirstOrDefault().ShouldBe("MSB3540");
        }
 
        /// <summary>
        /// Ensures that an error is logged if BaseIntermediateOutputPath is modified after it was set by Microsoft.Common.props and
        /// EnableBaseIntermediateOutputPathMismatchWarning is 'true'.
        /// </summary>
        [Fact]
        public void WarningIfBaseIntermediateOutputPathIsChangedInBodyOfProject()
        {
            Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(ObjectModelHelpers.CreateFileInTempProjectDirectory(_projectRelativePath, @"
                <Project DefaultTargets=`Build` ToolsVersion=`msbuilddefaulttoolsversion`>
                    <Import Project=`$(MSBuildBinPath)\Microsoft.Common.props` />
 
                    <PropertyGroup>
                        <EnableBaseIntermediateOutputPathMismatchWarning>true</EnableBaseIntermediateOutputPathMismatchWarning>
                        <BaseIntermediateOutputPath>foo</BaseIntermediateOutputPath>
                    </PropertyGroup>
 
                    <Import Project=`$(MSBuildBinPath)\Microsoft.CSharp.targets` />
                </Project>
            "));
 
            MockLogger logger = new MockLogger();
 
            project.Build("_CheckForInvalidConfigurationAndPlatform", new[] { logger }).ShouldBeTrue();
 
            logger.Warnings.Select(i => i.Code).FirstOrDefault().ShouldBe("MSB3539");
        }
    }
}