File: GivenThatWeWantToPublishAHelloWorldProject.cs
Web Access
Project: ..\..\..\test\Microsoft.NET.Publish.Tests\Microsoft.NET.Publish.Tests.csproj (Microsoft.NET.Publish.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.DependencyModel;
 
namespace Microsoft.NET.Publish.Tests
{
    public class GivenThatWeWantToPublishAHelloWorldProject : SdkTest
    {
        private const string PublishRelease = nameof(PublishRelease);
        private const string PackRelease = nameof(PackRelease);
 
        public GivenThatWeWantToPublishAHelloWorldProject(ITestOutputHelper log) : base(log)
        {
        }
 
        [Theory]
        [InlineData(ToolsetInfo.CurrentTargetFramework)]
        public void It_publishes_portable_apps_to_the_publish_folder_and_the_app_should_run(string targetFramework)
        {
            if (!EnvironmentInfo.SupportsTargetFramework(targetFramework))
            {
                return;
            }
 
            var helloWorldAsset = _testAssetsManager
                .CopyTestAsset("HelloWorld", identifier: targetFramework)
                .WithSource()
                .WithTargetFramework(targetFramework);
 
            var publishCommand = new PublishCommand(helloWorldAsset);
            var publishResult = publishCommand.Execute();
 
            publishResult.Should().Pass();
 
            var publishDirectory = publishCommand.GetOutputDirectory(targetFramework);
            var outputDirectory = new BuildCommand(helloWorldAsset).GetOutputDirectory(targetFramework);
 
            var filesPublished = new[] {
                "HelloWorld.dll",
                "HelloWorld.pdb",
                "HelloWorld.deps.json",
                "HelloWorld.runtimeconfig.json"
            };
 
            outputDirectory.Should().HaveFiles(filesPublished);
            publishDirectory.Should().HaveFiles(filesPublished);
 
            new DotnetCommand(Log, Path.Combine(publishDirectory.FullName, "HelloWorld.dll"))
                .Execute()
                .Should()
                .Pass()
                .And
                .HaveStdOutContaining("Hello World!");
        }
 
        [Theory]
        [InlineData("netcoreapp1.1")]
        [InlineData("netcoreapp2.0")]
        [InlineData(ToolsetInfo.CurrentTargetFramework)]
        public void It_publishes_self_contained_apps_to_the_publish_folder_and_the_app_should_run(string targetFramework)
        {
            if (!EnvironmentInfo.SupportsTargetFramework(targetFramework))
            {
                return;
            }
 
            // Some netcoreapp2.0 Linux tests are no longer working on ubuntu 2404
            if (targetFramework == "netcoreapp2.0" && OperatingSystem.IsLinux())
            {
                return;
            }
 
            var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);
 
            var helloWorldAsset = _testAssetsManager
                .CopyTestAsset("HelloWorld", "SelfContained", identifier: targetFramework)
                .WithSource()
                .WithTargetFramework(targetFramework);
 
            var publishCommand = new PublishCommand(helloWorldAsset);
            var publishResult = publishCommand.Execute($"/p:RuntimeIdentifier={rid}", "/p:SelfContained=true", "/p:CopyLocalLockFileAssemblies=true");
 
            publishResult.Should().Pass();
 
            var publishDirectory = publishCommand.GetOutputDirectory(
                targetFramework: targetFramework,
                runtimeIdentifier: rid);
            var outputDirectory = new BuildCommand(helloWorldAsset).GetOutputDirectory(targetFramework, runtimeIdentifier: rid);
            var selfContainedExecutable = $"HelloWorld{Constants.ExeSuffix}";
 
            var filesPublished = new[] {
                selfContainedExecutable,
                "HelloWorld.dll",
                "HelloWorld.pdb",
                "HelloWorld.deps.json",
                "HelloWorld.runtimeconfig.json",
                $"{FileConstants.DynamicLibPrefix}coreclr{FileConstants.DynamicLibSuffix}",
                $"{FileConstants.DynamicLibPrefix}hostfxr{FileConstants.DynamicLibSuffix}",
                $"{FileConstants.DynamicLibPrefix}hostpolicy{FileConstants.DynamicLibSuffix}",
                $"mscorlib.dll",
                $"System.Private.CoreLib.dll",
            };
 
            outputDirectory.Should().HaveFiles(filesPublished);
            publishDirectory.Should().HaveFiles(filesPublished);
 
            var filesNotPublished = new[] {
                $"apphost{Constants.ExeSuffix}"
            };
 
            outputDirectory.Should().NotHaveFiles(filesNotPublished);
            publishDirectory.Should().NotHaveFiles(filesNotPublished);
 
            string selfContainedExecutableFullPath = Path.Combine(publishDirectory.FullName, selfContainedExecutable);
            new RunExeCommand(Log, selfContainedExecutableFullPath)
                .Execute()
                .Should()
                .Pass()
                .And
                .HaveStdOutContaining("Hello World!");
        }
 
        [Fact]
        public void Publish_self_contained_app_with_dot_in_the_name()
        {
            var targetFramework = ToolsetInfo.CurrentTargetFramework;
            var rid = EnvironmentInfo.GetCompatibleRid(targetFramework);
 
            TestProject testProject = new()
            {
                Name = "Hello.World",
                TargetFrameworks = targetFramework,
                RuntimeIdentifier = rid,
                IsExe = true,
            };
 
            testProject.AdditionalProperties["CopyLocalLockFileAssemblies"] = "true";
            testProject.SourceFiles["Program.cs"] = $@"
using System;
public static class Program
{{
    public static void Main()
    {{
        Console.WriteLine(""Hello from a {ToolsetInfo.CurrentTargetFramework}!"");
    }}
}}
";
            var testProjectInstance = _testAssetsManager.CreateTestProject(testProject);
 
            var publishCommand = new PublishCommand(testProjectInstance);
            publishCommand.Execute().Should().Pass();
 
            var publishDirectory = publishCommand.GetOutputDirectory(
                targetFramework: targetFramework,
                runtimeIdentifier: rid);
 
            publishDirectory.Should().HaveFile($"Hello.World{Constants.ExeSuffix}");
        }
 
        [Theory]
        [InlineData($"{ToolsetInfo.LatestWinRuntimeIdentifier}-arm")]
        [InlineData($"{ToolsetInfo.LatestWinRuntimeIdentifier}-arm64")]
        public void Publish_standalone_post_netcoreapp2_arm_app(string runtimeIdentifier)
        {
            // Tests for existence of expected files when publishing an ARM project
            // See https://github.com/dotnet/sdk/issues/1239
 
            var targetFramework = "netcoreapp2.0";
 
            TestProject testProject = new()
            {
                Name = "Hello",
                TargetFrameworks = targetFramework,
                RuntimeIdentifier = runtimeIdentifier,
                IsExe = true,
            };
 
            testProject.AdditionalProperties["CopyLocalLockFileAssemblies"] = "true";
            testProject.SourceFiles["Program.cs"] = @"
using System;
public static class Program
{
    public static void Main()
    {
        Console.WriteLine(""Hello from an arm netcoreapp2.0 app!"");
    }
}
";
            var testProjectInstance = _testAssetsManager.CreateTestProject(testProject, identifier: runtimeIdentifier);
 
            var publishCommand = new PublishCommand(testProjectInstance);
            var publishResult = publishCommand.Execute();
 
            publishResult.Should().Pass();
 
            var publishDirectory = publishCommand.GetOutputDirectory(
                targetFramework: targetFramework,
                runtimeIdentifier: runtimeIdentifier);
            var outputDirectory = publishDirectory.Parent;
 
            // The name of the self contained executable depends on the runtime identifier.
            // For Windows family ARM publishing, it'll always be Hello.exe.
            // We shouldn't use "Constants.ExeSuffix" for the suffix here because that changes
            // depending on the RuntimeInformation
            var selfContainedExecutable = "Hello.exe";
 
            var filesPublished = new[] {
                selfContainedExecutable,
                "Hello.dll",
                "Hello.pdb",
                "Hello.deps.json",
                "Hello.runtimeconfig.json",
                "coreclr.dll",
                "hostfxr.dll",
                "hostpolicy.dll",
                "mscorlib.dll",
                "System.Private.CoreLib.dll",
            };
 
            outputDirectory.Should().HaveFiles(filesPublished);
            publishDirectory.Should().HaveFiles(filesPublished);
        }
 
        [Fact]
        public void Conflicts_are_resolved_when_publishing_a_portable_app()
        {
            Conflicts_are_resolved_when_publishing(selfContained: false, ridSpecific: false);
        }
 
        // This test is for netcoreapp2 and no longer working on ubuntu 2404
        [PlatformSpecificFact(TestPlatforms.Windows | TestPlatforms.OSX)]
        public void Conflicts_are_resolved_when_publishing_a_self_contained_app()
        {
            Conflicts_are_resolved_when_publishing(selfContained: true, ridSpecific: true);
        }
 
        [Fact]
        public void Conflicts_are_resolved_when_publishing_a_rid_specific_shared_framework_app()
        {
            Conflicts_are_resolved_when_publishing(selfContained: false, ridSpecific: true);
        }
 
        void Conflicts_are_resolved_when_publishing(bool selfContained, bool ridSpecific, [CallerMemberName] string callingMethod = "")
        {
            if (selfContained && !ridSpecific)
            {
                throw new ArgumentException("Self-contained apps must be rid specific");
            }
 
            var targetFramework = "netcoreapp2.0";
            if (!EnvironmentInfo.SupportsTargetFramework(targetFramework))
            {
                return;
            }
            var rid = ridSpecific ? EnvironmentInfo.GetCompatibleRid(targetFramework) : null;
 
            TestProject testProject = new()
            {
                Name = selfContained ? "SelfContainedWithConflicts" :
                    (ridSpecific ? "RidSpecificSharedConflicts" : "PortableWithConflicts"),
                TargetFrameworks = targetFramework,
                RuntimeIdentifier = rid,
                IsExe = true,
            };
 
            string outputMessage = $"Hello from {testProject.Name}!";
 
            testProject.AdditionalProperties.Add("RollForward", "LatestMajor");
            testProject.AdditionalProperties["CopyLocalLockFileAssemblies"] = "true";
            testProject.SourceFiles["Program.cs"] = @"
using System;
public static class Program
{
    public static void Main()
    {
        TestConflictResolution();
        Console.WriteLine(""" + outputMessage + @""");
    }
" + ConflictResolutionAssets.ConflictResolutionTestMethod + @"
}
";
            var testProjectInstance = _testAssetsManager.CreateTestProject(testProject, testProject.Name)
                .WithProjectChanges(p =>
                {
 
                    var ns = p.Root.Name.Namespace;
 
                    var itemGroup = new XElement(ns + "ItemGroup");
                    p.Root.Add(itemGroup);
 
                    foreach (var dependency in ConflictResolutionAssets.ConflictResolutionDependencies)
                    {
                        itemGroup.Add(new XElement(ns + "PackageReference",
                            new XAttribute("Include", dependency.Item1),
                            new XAttribute("Version", dependency.Item2)));
                    }
 
                    if (!selfContained && ridSpecific)
                    {
                        var propertyGroup = new XElement(ns + "PropertyGroup");
                        p.Root.Add(propertyGroup);
 
                        propertyGroup.Add(new XElement(ns + "SelfContained",
                            "false"));
                    }
                });
 
            var publishCommand = new PublishCommand(testProjectInstance);
            var publishResult = publishCommand.Execute();
 
            publishResult.Should().Pass();
 
            var publishDirectory = publishCommand.GetOutputDirectory(
                targetFramework: targetFramework,
                runtimeIdentifier: rid ?? string.Empty);
            var outputDirectory = publishDirectory.Parent;
 
            DependencyContext dependencyContext;
            using (var depsJsonFileStream = File.OpenRead(Path.Combine(publishDirectory.FullName, $"{testProject.Name}.deps.json")))
            {
                dependencyContext = new DependencyContextJsonReader().Read(depsJsonFileStream);
            }
 
            dependencyContext.Should()
                .HaveNoDuplicateRuntimeAssemblies(rid ?? "")
                .And
                .HaveNoDuplicateNativeAssets(rid ?? "")
                .And
                .OnlyHavePackagesWithPathProperties();
 
            TestCommand runCommand;
 
            if (selfContained)
            {
                var selfContainedExecutable = testProject.Name + Constants.ExeSuffix;
 
                string selfContainedExecutableFullPath = Path.Combine(publishDirectory.FullName, selfContainedExecutable);
 
                var libPrefix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "" : "lib";
 
                var filesPublished = new[] {
                    selfContainedExecutable,
                    $"{testProject.Name}.dll",
                    $"{testProject.Name}.pdb",
                    $"{testProject.Name}.deps.json",
                    $"{testProject.Name}.runtimeconfig.json",
                    $"{libPrefix}coreclr{FileConstants.DynamicLibSuffix}",
                    $"{libPrefix}hostfxr{FileConstants.DynamicLibSuffix}",
                    $"{libPrefix}hostpolicy{FileConstants.DynamicLibSuffix}",
                    $"mscorlib.dll",
                    $"System.Private.CoreLib.dll",
                };
 
                outputDirectory.Should().HaveFiles(filesPublished);
                publishDirectory.Should().HaveFiles(filesPublished);
 
                dependencyContext.Should()
                    .OnlyHaveRuntimeAssembliesWhichAreInFolder(rid, publishDirectory.FullName)
                    .And
                    .OnlyHaveNativeAssembliesWhichAreInFolder(rid, publishDirectory.FullName, testProject.Name);
 
                runCommand = new RunExeCommand(Log, selfContainedExecutableFullPath);
            }
            else
            {
                var filesPublished = new[] {
                    $"{testProject.Name}.dll",
                    $"{testProject.Name}.pdb",
                    $"{testProject.Name}.deps.json",
                    $"{testProject.Name}.runtimeconfig.json"
                };
 
                outputDirectory.Should().HaveFiles(filesPublished);
                publishDirectory.Should().HaveFiles(filesPublished);
 
                dependencyContext.Should()
                    .OnlyHaveRuntimeAssemblies(rid ?? "", testProject.Name);
 
                runCommand = new DotnetCommand(Log, Path.Combine(publishDirectory.FullName, $"{testProject.Name}.dll"));
            }
 
            runCommand
                    .Execute()
                    .Should()
                    .Pass()
                    .And
                    .HaveStdOutContaining(outputMessage);
 
        }
 
        [Fact]
        public void A_deployment_project_can_reference_the_hello_world_project()
        {
            var helloWorldAsset = _testAssetsManager
                .CopyTestAsset("DeployProjectReferencingSdkProject")
                .WithSource();
 
            var buildCommand = new BuildCommand(helloWorldAsset, Path.Combine("DeployProj", "Deploy.proj"));
 
            buildCommand
                .Execute()
                .Should()
                .Pass();
        }
 
        [Fact]
        public void It_fails_for_unsupported_rid()
        {
            var helloWorldAsset = _testAssetsManager
                .CopyTestAsset("HelloWorld")
                .WithSource();
 
            var publishCommand = new PublishCommand(helloWorldAsset);
            var publishResult = publishCommand.Execute("/p:RuntimeIdentifier=notvalid");
 
            publishResult.Should().Fail();
        }
 
        [Theory]
        [InlineData(true)]
        [InlineData(false)]
        public void It_publishes_on_release_if_PublishRelease_property_set(bool optedOut)
        {
            var helloWorldAsset = _testAssetsManager
               .CopyTestAsset("HelloWorld", $"{optedOut}")
               .WithSource();
 
            File.WriteAllText(Path.Combine(helloWorldAsset.Path, "Directory.Build.props"), "<Project><PropertyGroup><PublishRelease>true</PublishRelease></PropertyGroup></Project>");
 
            new DotnetPublishCommand(Log, helloWorldAsset.TestRoot)
                .WithEnvironmentVariable(EnvironmentVariableNames.DISABLE_PUBLISH_AND_PACK_RELEASE, optedOut.ToString())
                .Execute()
                .Should()
                .Pass();
 
            var expectedAssetPath = Path.Combine(helloWorldAsset.Path, "bin", optedOut ? "Debug" : "Release", ToolsetInfo.CurrentTargetFramework, "HelloWorld.dll");
            Assert.True(File.Exists(expectedAssetPath));
        }
 
        [Fact]
        public void It_respects_CLI_PublishRelease_over_project_PublishRelease_value()
        {
            var helloWorldAsset = _testAssetsManager
                .CopyTestAsset("HelloWorld")
                .WithSource()
                .WithProjectChanges(project =>
                {
                    var ns = project.Root.Name.Namespace;
                    var propertyGroup = project.Root.Elements(ns + "PropertyGroup").First();
                    propertyGroup.Add(new XElement(ns + PublishRelease, "true"));
                });
 
            new DotnetPublishCommand(Log, helloWorldAsset.TestRoot)
                .Execute("-p:PublishRelease=false")
                .Should()
                .Pass();
 
            var expectedAssetPath = Path.Combine(helloWorldAsset.Path, "bin", "Debug", ToolsetInfo.CurrentTargetFramework, "HelloWorld.dll");
            Assert.True(File.Exists(expectedAssetPath));
            var releaseAssetPath = Path.Combine(helloWorldAsset.Path, "bin", "Release", ToolsetInfo.CurrentTargetFramework, "HelloWorld.dll");
            Assert.False(File.Exists(releaseAssetPath)); // build will produce a debug asset, need to make sure this doesn't exist either.
        }
 
        [Fact]
        public void It_publishes_on_release_if_PublishRelease_property_set_in_sln()
        {
 
            var slnDir = _testAssetsManager
               .CopyTestAsset("TestAppWithSlnUsingPublishRelease")
               .WithSource()
               .Path;
 
            new DotnetCommand(Log)
                .WithWorkingDirectory(slnDir)
                .Execute("dotnet", "publish")
                .Should()
                .Pass();
 
            var expectedAssetPath = Path.Combine(slnDir, "App", "bin", "Release", ToolsetInfo.CurrentTargetFramework, "publish", "App.dll");
            Assert.True(File.Exists(expectedAssetPath));
 
        }
 
        [Fact]
        public void It_passes_using_PublishRelease_with_conflicting_capitalization_but_same_values_across_solution_projects()
        {
            var slnDir = _testAssetsManager
               .CopyTestAsset("TestAppWithSlnUsingPublishReleaseConflictingCasing")
               .WithSource()
               .Path;
 
            new DotnetCommand(Log)
                .WithWorkingDirectory(slnDir)
                .Execute("dotnet", "publish")
                .Should()
                .Pass();
 
            var expectedAssetPath = Path.Combine(slnDir, "App", "bin", "Release", ToolsetInfo.CurrentTargetFramework, "publish", "App.dll");
            Assert.True(File.Exists(expectedAssetPath));
        }
 
        [Fact]
        public void It_no_longer_warns_if_PublishRelease_set_on_sln_but_env_var_not_used()
        {
            var slnDir = _testAssetsManager
               .CopyTestAsset("TestAppWithSlnUsingPublishRelease")
               .WithSource()
               .Path;
 
            new DotnetPublishCommand(Log)
                .WithWorkingDirectory(slnDir)
                .Execute()
                .Should()
                .Pass()
                .And
                .NotHaveStdOutContaining("NETSDK1190");
        }
 
        [Fact]
        public void It_publishes_correctly_in_PublishRelease_evaluation_despite_option_forwarded_format()
        {
            var helloWorldAsset = _testAssetsManager
               .CopyTestAsset("HelloWorld", $"PublishesWithProperyFormats")
               .WithSource()
               .WithTargetFramework(ToolsetInfo.CurrentTargetFramework);
 
            new BuildCommand(helloWorldAsset)
           .Execute()
           .Should()
           .Pass();
 
            var publishCommand = new DotnetPublishCommand(Log, helloWorldAsset.TestRoot);
 
            publishCommand
            .Execute("-f", ToolsetInfo.CurrentTargetFramework)
            .Should()
            .Pass().And.NotHaveStdErr();
        }
 
        [Fact]
        public void It_publishes_on_release_if_PublishRelease_property_set_in_csproj()
        {
            var helloWorldAsset = _testAssetsManager
               .CopyTestAsset("HelloWorld")
               .WithSource()
               .WithTargetFramework(ToolsetInfo.CurrentTargetFramework)
               .WithProjectChanges(project =>
               {
                   var ns = project.Root.Name.Namespace;
                   var propertyGroup = project.Root.Elements(ns + "PropertyGroup").First();
                   propertyGroup.Add(new XElement(ns + "PublishRelease", "true"));
               });
 
            new DotnetPublishCommand(Log, helloWorldAsset.TestRoot)
            .Execute()
            .Should()
            .Pass();
 
            var expectedAssetPath = Path.Combine(helloWorldAsset.Path, "bin", "Release", ToolsetInfo.CurrentTargetFramework, "HelloWorld.dll");
            Assert.True(File.Exists(expectedAssetPath));
        }
 
        [Theory]
        [InlineData("-p:Configuration=Debug")]
        [InlineData("-property:Configuration=Debug")]
        [InlineData("--property:Configuration=Debug")]
        [InlineData("/p:Configuration=Debug")]
        [InlineData("-p:_IsPublishing=true;Configuration=Debug")]
        [InlineData("-p:_IsPublishing=true;Configuration=Debug;")]
        [InlineData("/property:Configuration=Debug")]
        public void PublishRelease_does_not_override_Configuration_property_across_formats(string configOpt)
        {
            string tfm = "net7.0";
            var helloWorldAsset = _testAssetsManager
               .CopyTestAsset("HelloWorld", identifier: configOpt)
               .WithSource()
               .WithTargetFramework(tfm)
               .WithProjectChanges(project =>
               {
                   var ns = project.Root.Name.Namespace;
                   var propertyGroup = project.Root.Elements(ns + "PropertyGroup").First();
                   propertyGroup.Add(new XElement(ns + "PublishRelease", "true"));
               });
 
            new DotnetPublishCommand(Log, helloWorldAsset.TestRoot)
                .Execute(configOpt)
                .Should()
                .Pass().And.NotHaveStdErr();
 
            var expectedAssetPath = Path.Combine(helloWorldAsset.Path, "bin", "Debug", tfm, "HelloWorld.dll");
            Assert.True(File.Exists(expectedAssetPath));
            var releaseAssetPath = Path.Combine(helloWorldAsset.Path, "bin", "Release", tfm, "HelloWorld.dll");
            Assert.False(File.Exists(releaseAssetPath)); // build will produce a debug asset, need to make sure this doesn't exist either.
        }
 
        [Theory]
        [InlineData("true")]
        [InlineData("false")]
        public void Debug_Symbols_Implies_Debug_Type(string debugSymbols)
        {
            var testProject = new TestProject()
            {
                IsExe = true,
                TargetFrameworks = "net8.0",
                ProjectSdk = "Microsoft.NET.Sdk"
            };
 
            testProject.RecordProperties("DebugType");
 
            var testAsset = _testAssetsManager.CreateTestProject(testProject);
 
            new DotnetPublishCommand(Log, $"-p:DebugSymbols={debugSymbols}")
                .WithWorkingDirectory(Path.Combine(testAsset.TestRoot, testProject.Name))
                .Execute()
                .Should()
                .Pass();
 
            var properties = testProject.GetPropertyValues(testAsset.TestRoot, targetFramework: "net8.0", configuration: "Release");
            properties["DebugType"].Should().Be(debugSymbols.Equals("true") ? "portable" : "None");
        }
 
        [Theory]
        [InlineData("net7.0")]
        [InlineData("net8.0")]
        public void It_publishes_with_Release_by_default_in_net_8_but_not_net_7(string tfm)
        {
            var testProject = new TestProject()
            {
                IsExe = true,
                TargetFrameworks = tfm
            };
            testProject.RecordProperties("Configuration");
            testProject.RecordProperties("DebugSymbols"); // If Configuration is set too late, it doesn't actually do anything. Check this too.
 
            var testAsset = _testAssetsManager.CreateTestProject(testProject);
 
            new DotnetPublishCommand(Log)
                .WithWorkingDirectory(Path.Combine(testAsset.TestRoot, testProject.Name))
                .Execute()
                .Should()
                .Pass();
 
            var properties = testProject.GetPropertyValues(testAsset.TestRoot, targetFramework: tfm, configuration: tfm == "net7.0" ? "Debug" : "Release");
            var finalConfiguration = properties["Configuration"];
            var finalDebugSymbols = properties["DebugSymbols"];
            Assert.Equal((tfm == "net7.0" ? "Debug" : "Release"), finalConfiguration);
            Assert.Equal((tfm == "net7.0" ? "true" : "false"), finalDebugSymbols);
        }
 
        [Fact]
        public void PublishRelease_interacts_similarly_with_PublishProfile_Configuration()
        {
            var config = "Debug";
            var tfm = ToolsetInfo.CurrentTargetFramework;
            var rid = EnvironmentInfo.GetCompatibleRid(tfm);
 
            var helloWorldAsset = _testAssetsManager
                .CopyTestAsset("HelloWorld")
                .WithSource()
                .WithTargetFramework(ToolsetInfo.CurrentTargetFramework)
                .WithProjectChanges(project =>
                {
                    var ns = project.Root.Name.Namespace;
                    var propertyGroup = project.Root.Elements(ns + "PropertyGroup").First();
                    propertyGroup.Add(new XElement(ns + PublishRelease, "true"));
                });
 
            var publishProfilesDirectory = Path.Combine(helloWorldAsset.Path, "Properties", "PublishProfiles");
            Directory.CreateDirectory(publishProfilesDirectory);
 
            File.WriteAllText(Path.Combine(publishProfilesDirectory, "test.pubxml"), $@"
            <Project>
              <PropertyGroup>
                <RuntimeIdentifier>{rid}</RuntimeIdentifier>
                <Configuration>{config}</Configuration>
              </PropertyGroup>
            </Project>
            ");
 
            var publishCommand = new DotnetPublishCommand(Log, helloWorldAsset.Path);
 
            CommandResult publishOutput = publishCommand
            .Execute("/p:PublishProfile=test");
 
            publishOutput.Should().Pass();
            var releaseAssetPath = Path.Combine(helloWorldAsset.Path, "bin", "Release", ToolsetInfo.CurrentTargetFramework, rid, "HelloWorld.dll");
            Assert.True(File.Exists(releaseAssetPath)); // We ignore Debug configuration and override it
        }
 
        [Fact]
        public void It_allows_unsupported_rid_with_override()
        {
            var helloWorldAsset = _testAssetsManager
                .CopyTestAsset("HelloWorld")
                .WithSource()
                .WithTargetFramework("netcoreapp2.1");
 
            var publishCommand = new PublishCommand(helloWorldAsset);
            var publishResult = publishCommand.Execute("/p:RuntimeIdentifier=notvalid", "/p:EnsureNETCoreAppRuntime=false");
 
            publishResult.Should().Pass();
        }
 
        [Theory]
        [InlineData("netcoreapp2.1")]
        [InlineData(ToolsetInfo.CurrentTargetFramework)]
        public void It_preserves_newest_files_on_publish(string tfm)
        {
            var testProject = new TestProject()
            {
                Name = "PreserveNewestFilesOnPublish",
                TargetFrameworks = tfm,
                IsExe = true
            };
 
            var testAsset = _testAssetsManager.CreateTestProject(testProject, testProject.Name, identifier: tfm);
 
            var publishCommand = new PublishCommand(testAsset);
 
            publishCommand
                .Execute("-v:n")
                .Should()
                .Pass()
                .And
                .HaveStdOutContaining("Copying");
 
            publishCommand
                .Execute("-v:n")
                .Should()
                .Pass()
                .And
                .NotHaveStdOutContaining("Copying");
        }
 
        [Fact]
        public void It_fails_if_nobuild_was_requested_but_build_was_invoked()
        {
            var testProject = new TestProject()
            {
                Name = "InvokeBuildOnPublish",
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                IsExe = true
            };
 
            var testAsset = _testAssetsManager.CreateTestProject(testProject, testProject.Name)
                .WithProjectChanges(project =>
                {
                    project.Root.Add(XElement.Parse(@"<Target Name=""InvokeBuild"" DependsOnTargets=""Build"" BeforeTargets=""Publish"" />"));
                });
 
            new BuildCommand(testAsset)
                .Execute()
                .Should()
                .Pass();
 
            new PublishCommand(testAsset)
                .Execute("/p:NoBuild=true")
                .Should()
                .Fail()
                .And
                .HaveStdOutContaining("NETSDK1085");
        }
 
        [WindowsOnlyFact]
        public void It_contains_no_duplicates_in_resolved_publish_assets_on_windows()
            => It_contains_no_duplicates_in_resolved_publish_assets("windows");
 
        [Theory]
        [InlineData("console")]
        [InlineData("web")]
        public void It_contains_no_duplicates_in_resolved_publish_assets(string type)
        {
            // Use a specific RID to guarantee a consistent set of assets
            var testProject = new TestProject()
            {
                Name = "NoDuplicatesInResolvedPublishAssets",
                TargetFrameworks = "netcoreapp3.0",
                RuntimeIdentifier = "win-x64",
                IsExe = true
            };
 
            switch (type)
            {
                case "windows":
                    testProject.ProjectSdk = "Microsoft.NET.Sdk.WindowsDesktop";
                    testProject.AdditionalProperties.Add("UseWpf", "true");
                    testProject.AdditionalProperties.Add("UseWindowsForms", "true");
                    break;
                case "console":
                    break;
                case "web":
                    testProject.ProjectSdk = "Microsoft.NET.Sdk.Web";
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(type));
            }
 
            testProject.PackageReferences.Add(new TestPackageReference("NewtonSoft.Json", ToolsetInfo.GetNewtonsoftJsonPackageVersion()));
 
            var testAsset = _testAssetsManager.CreateTestProject(testProject, testProject.Name, identifier: type)
                .WithProjectChanges(project =>
                {
                    project.Root.Add(XElement.Parse(@"
<Target Name=""VerifyNoDuplicatesInPublishAssets"" AfterTargets=""Publish"">
    <RemoveDuplicates Inputs=""@(_ResolvedCopyLocalPublishAssets)"">
        <Output TaskParameter=""Filtered"" ItemName=""FilteredAssets""/>
    </RemoveDuplicates>
    <Message Condition=""'@(_ResolvedCopyLocalPublishAssets)' != '@(FilteredAssets)'"" Importance=""High"" Text=""Duplicate items are present in: @(_ResolvedCopyLocalPublishAssets)!"" />
    <ItemGroup>
        <AssetDestinationSubPaths Include=""@(_ResolvedCopyLocalPublishAssets->'%(DestinationSubPath)')"" />
    </ItemGroup>
    <RemoveDuplicates Inputs=""@(AssetDestinationSubPaths)"">
        <Output TaskParameter=""Filtered"" ItemName=""FilteredAssetDestinationSubPaths""/>
    </RemoveDuplicates>
    <Message Condition=""'@(AssetDestinationSubPaths)' != '@(FilteredAssetDestinationSubPaths)'"" Importance=""High"" Text=""Duplicate DestinationSubPaths are present in: @(AssetDestinationSubPaths)!"" />
</Target>"));
                });
 
            new PublishCommand(testAsset)
                .Execute()
                .Should()
                .Pass()
                .And
                .NotHaveStdOutContaining("Duplicate items are present")
                .And
                .NotHaveStdOutContaining("Duplicate filenames are present");
        }
 
        [Theory]
        [InlineData(null, null)]
        [InlineData(false, null)]
        [InlineData(true, null)]
        [InlineData(null, false)]
        [InlineData(null, true)]
        [InlineData(false, false)]
        [InlineData(true, false)]
        [InlineData(false, true)]
        [InlineData(true, true)]
        public void It_publishes_with_a_publish_profile(bool? selfContained, bool? useAppHost)
        {
            var tfm = ToolsetInfo.CurrentTargetFramework;
            var rid = EnvironmentInfo.GetCompatibleRid(tfm);
 
            var testProject = new TestProject()
            {
                Name = "ConsoleWithPublishProfile",
                TargetFrameworks = tfm,
                ProjectSdk = "Microsoft.NET.Sdk;Microsoft.NET.Sdk.Publish",
                IsExe = true,
            };
 
            var identifer = (selfContained == null ? "null" : selfContained.ToString()) + (useAppHost == null ? "null" : useAppHost.ToString());
            var testProjectInstance = _testAssetsManager.CreateTestProject(testProject, identifier: identifer);
 
            var projectDirectory = Path.Combine(testProjectInstance.Path, testProject.Name);
            var publishProfilesDirectory = Path.Combine(projectDirectory, "Properties", "PublishProfiles");
            Directory.CreateDirectory(publishProfilesDirectory);
 
            File.WriteAllText(Path.Combine(publishProfilesDirectory, "test.pubxml"), $@"
<Project>
  <PropertyGroup>
    <RuntimeIdentifier>{rid}</RuntimeIdentifier>
    {(selfContained.HasValue ? $"<SelfContained>{selfContained}</SelfContained>" : "")}
    {((!(selfContained ?? false) && useAppHost.HasValue) ? $"<UseAppHost>{useAppHost}</UseAppHost>" : "")}
  </PropertyGroup>
</Project>
");
 
            var command = new PublishCommand(testProjectInstance);
            command
                .Execute("/p:PublishProfile=test")
                .Should()
                .Pass();
 
            var output = command.GetOutputDirectory(targetFramework: tfm, runtimeIdentifier: rid);
 
            output.Should().HaveFiles(new[] {
                $"{testProject.Name}.dll",
                $"{testProject.Name}.pdb",
                $"{testProject.Name}.deps.json",
                $"{testProject.Name}.runtimeconfig.json",
            });
 
            if (selfContained ?? false)
            {
                output.Should().HaveFiles(new[] {
                    $"{FileConstants.DynamicLibPrefix}hostfxr{FileConstants.DynamicLibSuffix}",
                    $"{FileConstants.DynamicLibPrefix}hostpolicy{FileConstants.DynamicLibSuffix}",
                });
            }
            else
            {
                output.Should().NotHaveFiles(new[] {
                    $"{FileConstants.DynamicLibPrefix}hostfxr{FileConstants.DynamicLibSuffix}",
                    $"{FileConstants.DynamicLibPrefix}hostpolicy{FileConstants.DynamicLibSuffix}",
                });
            }
 
            if ((selfContained ?? false) || (useAppHost ?? true))
            {
                output.Should().HaveFile($"{testProject.Name}{Constants.ExeSuffix}");
            }
            else
            {
                output.Should().NotHaveFile($"{testProject.Name}{Constants.ExeSuffix}");
            }
        }
 
        [Fact]
        public void It_publishes_with_full_path_publish_profile()
        {
            var libProject = new TestProject()
            {
                Name = "LibProjectWithDifferentTFM",
                TargetFrameworks = "netstandard2.0",
            };
 
            var testProject = new TestProject()
            {
                Name = "ExeWithPublishProfile",
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework,
                IsExe = true,
            };
 
            testProject.ReferencedProjects.Add(libProject);
 
            var testProjectInstance = _testAssetsManager.CreateTestProject(testProject)
                .WithProjectChanges(project =>
                {
                    project.Root.Add(XElement.Parse(@"
<ItemDefinitionGroup>
  <ProjectReference>
    <GlobalPropertiesToRemove>%(GlobalPropertiesToRemove);WebPublishProfileFile</GlobalPropertiesToRemove>
  </ProjectReference>
</ItemDefinitionGroup>"));
                });
 
            var projectDirectory = Path.Combine(testProjectInstance.Path, testProject.Name);
            var projectPath = Path.Combine(projectDirectory, $"{testProject.Name}.csproj");
            var publishProfilesDirectory = Path.Combine(projectDirectory, "Properties", "PublishProfiles");
            var publishProfilePath = Path.Combine(publishProfilesDirectory, "test.pubxml");
 
            Directory.CreateDirectory(publishProfilesDirectory);
            File.WriteAllText(publishProfilePath, $@"
<Project>
  <PropertyGroup>
    <TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
  </PropertyGroup>
</Project>
");
 
            var command = new PublishCommand(testProjectInstance);
            command
                .Execute(
                    $"/p:WebPublishProfileFile={publishProfilePath}",
                    $"/p:ProjectToOverrideProjectExtensionsPath={projectPath}"
                )
                .Should()
                .Pass();
        }
 
        [Theory]
        [InlineData("invalidProfile", true)]
        [InlineData("invalidProfile.pubxml", true)]
        [InlineData("..\\Properties\\PublishProfiles\\invalidProfile.pubxml", true)]
        [InlineData("invalidProfile.txt", true)]
        [InlineData("testProfile", false)]
        [InlineData("testProfile.pubxml", false)]
        [InlineData("..\\Properties\\PublishProfiles\\testProfile.pubxml", false)]
        [InlineData("", false)]
        public void It_warns_with_an_invalid_publish_profile_NetSdk(string publishProfile, bool shouldWarn)
        {
            var tfm = ToolsetInfo.CurrentTargetFramework;
 
            var testProject = new TestProject()
            {
                Name = "ConsoleWithPublishProfile",
                TargetFrameworks = tfm,
                ProjectSdk = "Microsoft.NET.Sdk",
                IsExe = true,
            };
 
            var testProjectInstance = _testAssetsManager.CreateTestProject(testProject, identifier: $"PublishProfile{publishProfile.Length}");
 
            var projectDirectory = Path.Combine(testProjectInstance.Path, testProject.Name);
            var publishProfilesDirectory = Path.Combine(projectDirectory, "Properties", "PublishProfiles");
            Directory.CreateDirectory(publishProfilesDirectory);
 
            File.WriteAllText(Path.Combine(publishProfilesDirectory, "testProfile.pubxml"), $@"
<Project>
  <PropertyGroup>
    <msbuildProperty>value</msbuildProperty>
  </PropertyGroup>
</Project>
");
 
            var command = new PublishCommand(testProjectInstance);
            if (shouldWarn)
            {
                command
                    .Execute($"/p:PublishProfile={publishProfile}")
                    .Should()
                    .Pass()
                    .And
                    .HaveStdOutContaining("NETSDK1198");
            }
            else
            {
                command
                    .Execute($"/p:PublishProfile={publishProfile}")
                    .Should()
                    .Pass()
                    .And
                    .NotHaveStdOutContaining("NETSDK1198");
            }
        }
 
        [Theory]
        [InlineData("invalidProfile", true)]
        [InlineData("invalidProfile.pubxml", true)]
        [InlineData("..\\Properties\\PublishProfiles\\invalidProfile.pubxml", true)]
        [InlineData("invalidProfile.txt", true)]
        [InlineData("testProfile", false)]
        [InlineData("testProfile.pubxml", false)]
        [InlineData("..\\Properties\\PublishProfiles\\testProfile.pubxml", false)]
        [InlineData("Default", false)]
        [InlineData("", false)]
        public void It_warns_with_an_invalid_publish_profile_WebSdk(string publishProfile, bool shouldWarn)
        {
            var tfm = ToolsetInfo.CurrentTargetFramework;
 
            var testProject = new TestProject()
            {
                Name = "WebWithPublishProfile",
                TargetFrameworks = tfm,
                ProjectSdk = "Microsoft.NET.Sdk.Web",
                IsExe = true,
            };
 
            var testProjectInstance = _testAssetsManager.CreateTestProject(testProject, identifier: $"PublishProfile{publishProfile.Length}");
 
            var projectDirectory = Path.Combine(testProjectInstance.Path, testProject.Name);
            var publishProfilesDirectory = Path.Combine(projectDirectory, "Properties", "PublishProfiles");
            Directory.CreateDirectory(publishProfilesDirectory);
 
            File.WriteAllText(Path.Combine(publishProfilesDirectory, "testProfile.pubxml"), $@"
<Project>
  <PropertyGroup>
    <msbuildProperty>value</msbuildProperty>
  </PropertyGroup>
</Project>
");
 
            var command = new PublishCommand(testProjectInstance);
            if (shouldWarn)
            {
                command
                    .Execute($"/p:PublishProfile={publishProfile}")
                    .Should()
                    .Pass()
                    .And
                    .HaveStdOutContaining("NETSDK1198");
            }
            else
            {
                command
                    .Execute($"/p:PublishProfile={publishProfile}")
                    .Should()
                    .Pass()
                    .And
                    .NotHaveStdOutContaining("NETSDK1198");
            }
        }
 
        [Theory]
        [InlineData("--p:PublishReadyToRun=true")]
        [InlineData("-p:PublishSingleFile=true")]
        [InlineData("-p:PublishSelfContained=true")]
        public void It_publishes_with_implicit_rid_with_rid_specific_properties(string executeOptionsAndProperties)
        {
            var testProject = new TestProject()
            {
                Name = "PublishImplicitRid",
                TargetFrameworks = ToolsetInfo.CurrentTargetFramework
            };
            testProject.AdditionalProperties.Add("IsPublishable", "false");
            var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: executeOptionsAndProperties);
 
            var publishCommand = new DotnetPublishCommand(Log, Path.Combine(testAsset.TestRoot, testProject.Name));
            publishCommand
               .Execute(executeOptionsAndProperties)
               .Should()
               .Pass()
               .And
               .NotHaveStdErrContaining("NETSDK1191"); // Publish Properties Requiring RID Checks
        }
 
        [Theory]
        [InlineData("AppRelative", "subdirectory", "AppRelative")]
        [InlineData("AppRelative", "subdirectory", null)]
        [InlineData("EnvironmentVariable", null, "EnvironmentVariable")]
        [InlineData("EnvironmentVariable", null, null)]
        [InlineData("AppRelative;EnvironmentVariable", "subdirectory", "AppRelative")]
        [InlineData("AppRelative;EnvironmentVariable", "subdirectory", "EnvironmentVariable")]
        [InlineData(null, "subdirectory", "AppRelative")]
        public void It_configures_dotnet_search_options(string searchLocation, string appRelativeDotNet, string expectedLocation)
        {
            var targetFramework = ToolsetInfo.CurrentTargetFramework;
            var runtimeIdentifier = EnvironmentInfo.GetCompatibleRid(targetFramework);
 
            var testProject = new TestProject()
            {
                Name = "AppHostDotNetSearch",
                TargetFrameworks = targetFramework,
                IsExe = true,
            };
            testProject.SourceFiles["Program.cs"] = $$"""
            using System;
            using System.IO;
            public static class Program
            {
                public static void Main()
                {
                    Console.WriteLine($"Runtime directory: {Path.GetDirectoryName(typeof(object).Assembly.Location)}");
                }
            }
            """;
 
            if (searchLocation != null)
                testProject.AdditionalProperties.Add("AppHostDotNetSearch", searchLocation);
 
            if (appRelativeDotNet != null)
                testProject.AdditionalProperties.Add("AppHostRelativeDotNet", appRelativeDotNet);
 
            // Identifer based on test inputs to create test assets that are unique for each test case
            string assetIdentifier = $"{searchLocation}{appRelativeDotNet}{expectedLocation}";
            var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: assetIdentifier);
 
            var publishCommand = new PublishCommand(testAsset);
            publishCommand.Execute()
                .Should().Pass();
 
            // Published apphost should have .NET search location options changed
            var publishDirectory = publishCommand.GetOutputDirectory(targetFramework: targetFramework).FullName;
            TestCommand runCommand = new RunExeCommand(Log, Path.Combine(publishDirectory, $"{testProject.Name}{Constants.ExeSuffix}"));
 
            string expectedRoot = null;
            switch (expectedLocation)
            {
                case "AppRelative":
                    // Copy the host and runtime to the expected .NET root
                    expectedRoot = Path.Combine(publishDirectory, appRelativeDotNet);
                    CopyDirectory(Path.Combine(TestContext.Current.ToolsetUnderTest.DotNetRoot, "host"), Path.Combine(expectedRoot, "host"));
                    CopyDirectory(Path.Combine(TestContext.Current.ToolsetUnderTest.DotNetRoot, "shared", "Microsoft.NETCore.App"), Path.Combine(expectedRoot, "shared", "Microsoft.NETCore.App"));
                    break;
                case "EnvironmentVariable":
                    // Set DOTNET_ROOT_<arch> environment variable to the expected .NET root
                    expectedRoot = TestContext.Current.ToolsetUnderTest.DotNetRoot;
                    runCommand = runCommand.WithEnvironmentVariable($"DOTNET_ROOT_{RuntimeInformation.OSArchitecture.ToString().ToUpperInvariant()}", expectedRoot);
                    break;
                default:
                    // Should fail - make sure DOTNET_ROOT_<arch> and DOTNET_ROOT are not set
                    runCommand = runCommand.WithEnvironmentVariable($"DOTNET_ROOT", string.Empty);
                    runCommand = runCommand.WithEnvironmentVariable($"DOTNET_ROOT_{RuntimeInformation.OSArchitecture.ToString().ToUpperInvariant()}", string.Empty);
                    break;
            }
 
            var result = runCommand.Execute();
            if (expectedRoot != null)
            {
                expectedRoot = TestPathUtility.ResolveTempPrefixLink(expectedRoot);
                result.Should().Pass()
                    .And.HaveStdOutContaining($"Runtime directory: {expectedRoot}");
            }
            else
            {
                result.Should().Fail();
            }
 
            static void CopyDirectory(string sourceDir, string destinationDir)
            {
                Directory.CreateDirectory(destinationDir);
                foreach (var file in Directory.EnumerateFiles(sourceDir))
                {
                    File.Copy(file, Path.Combine(destinationDir, Path.GetFileName(file)));
                }
 
                foreach (var directory in Directory.EnumerateDirectories(sourceDir))
                {
                    CopyDirectory(directory, Path.Combine(destinationDir, Path.GetFileName(directory)));
                }
            }
        }
 
        [Fact]
        public void It_fails_on_invalid_dotnet_search_options()
        {
            var targetFramework = ToolsetInfo.CurrentTargetFramework;
            var runtimeIdentifier = EnvironmentInfo.GetCompatibleRid(targetFramework);
 
            var testProject = new TestProject()
            {
                Name = "AppHostDotNetSearch",
                TargetFrameworks = targetFramework,
                IsExe = true,
            };
            testProject.AdditionalProperties.Add("AppHostDotNetSearch", "Invalid");
 
            var testAsset = _testAssetsManager.CreateTestProject(testProject);
            var publishCommand = new PublishCommand(testAsset);
            publishCommand.Execute()
                .Should().Fail()
                .And.HaveStdOutContaining("NETSDK1217");
        }
 
        [Fact]
        public void IsPublishableIsRespectedWhenMultitargeting()
        {
            var testProject = new TestProject()
            {
                Name = "PublishMultitarget",
                TargetFrameworks = $"net472;{ToolsetInfo.CurrentTargetFramework}"
            };
            testProject.AdditionalProperties.Add("IsPublishable", "false");
            var testAsset = _testAssetsManager.CreateTestProject(testProject);
 
            var publishCommand = new PublishCommand(testAsset);
            publishCommand
                .Execute()
                .Should()
                .Pass()
                .And
                .NotHaveStdOutContaining("The 'Publish' target is not supported without specifying a target framework.");
        }
    }
}