File: CLR2TaskHost_E2E_Tests.cs
Web Access
Project: ..\..\..\src\Build.UnitTests\Microsoft.Build.Engine.UnitTests.csproj (Microsoft.Build.Engine.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.IO;
using Microsoft.Build.UnitTests;
using Microsoft.Build.UnitTests.Shared;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.Build.Engine.UnitTests;
 
/// <summary>
/// End-to-end tests for the CLR2 task host (MSBuildTaskHost.exe).
/// These tests explicitly force tasks to run out-of-proc in CLR2 via
/// <c>TaskFactory="TaskHostFactory"</c> and <c>Runtime="CLR2"</c>,
/// exercising the CLR2 branch in <c>ResolveNodeLaunchConfiguration</c>.
/// </summary>
public class CLR2TaskHost_E2E_Tests
{
    private readonly ITestOutputHelper _output;
 
    public CLR2TaskHost_E2E_Tests(ITestOutputHelper output)
    {
        _output = output;
    }
 
    /// <summary>
    /// Verifies that the CLR2 task host (MSBuildTaskHost.exe) can be launched and connected to
    /// when a task explicitly requests Runtime="CLR2" with TaskHostFactory.
    ///
    /// Regression test for the apphost changes (PR #13175) that replaced the three-branch
    /// ResolveNodeLaunchConfiguration with a two-branch version, losing the CLR2-specific path:
    ///   1. Empty command-line args (MSBuildTaskHost.Main() takes no arguments)
    ///   2. Handshake with toolsDirectory set to the EXE's directory so the pipe name
    ///      salt matches what the child process computes on startup
    /// Without these, the parent and child compute different pipe name hashes → MSB4216.
    /// </summary>
    [WindowsNet35OnlyFact]
    public void ExplicitCLR2TaskHostFactory_RunsTaskSuccessfully()
    {
        using TestEnvironment env = TestEnvironment.Create(_output);
        TransientTestFolder testFolder = env.CreateFolder(createFolder: true);
 
        string projectContent = """
            <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
              <!-- Force Exec to run out-of-proc in the CLR2 task host (MSBuildTaskHost.exe) -->
              <UsingTask TaskName="Microsoft.Build.Tasks.Exec"
                         AssemblyName="Microsoft.Build.Tasks.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
                         TaskFactory="TaskHostFactory"
                         Runtime="CLR2" />
 
              <Target Name="Build">
                <Exec Command="echo CLR2TaskHostSuccess" />
              </Target>
            </Project>
            """;
 
        string projectPath = Path.Combine(testFolder.Path, "CLR2ExplicitTest.proj");
        File.WriteAllText(projectPath, projectContent);
 
        string testOutput = RunnerUtilities.ExecBootstrapedMSBuild(
            $"\"{projectPath}\" -v:n",
            out bool success,
            outputHelper: _output);
 
 
        // MSB4216 occurs when the parent can't connect to MSBuildTaskHost.exe —
        // either due to handshake salt mismatch (missing toolsDirectory) or wrong process routing.
        testOutput.ShouldNotContain("MSB4216", customMessage: "CLR2 task host connection should succeed with correct handshake salt and empty command-line args");
 
        success.ShouldBeTrue(customMessage: "Task explicitly requesting CLR2 + TaskHostFactory should execute in MSBuildTaskHost.exe");
 
        // Verify the task actually ran by checking for its output.
        testOutput.ShouldContain("CLR2TaskHostSuccess", customMessage: "Exec task output should be visible, confirming it ran in CLR2 task host");
    }
}