File: BackEnd\TargetBuilder_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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Build.BackEnd;
using Microsoft.Build.BackEnd.SdkResolution;
using Microsoft.Build.Collections;
using Microsoft.Build.Engine.UnitTests.BackEnd;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Shouldly;
using Xunit;
using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService;
using LegacyThreadingData = Microsoft.Build.Execution.LegacyThreadingData;
using NodeLoggingContext = Microsoft.Build.BackEnd.Logging.NodeLoggingContext;
using ProjectLoggingContext = Microsoft.Build.BackEnd.Logging.ProjectLoggingContext;
 
#nullable disable
 
namespace Microsoft.Build.UnitTests.BackEnd
{
    /// <summary>
    /// This is the unit test for the TargetBuilder.  This particular test is confined to just using the
    /// actual TargetBuilder, and uses a mock TaskBuilder on which TargetBuilder depends.
    /// </summary>
    public class TargetBuilder_Tests : IRequestBuilderCallback, IDisposable
    {
        /// <summary>
        /// The component host.
        /// </summary>
        private MockHost _host;
 
        /// <summary>
        /// A mock logger for scenario tests.
        /// </summary>
        private MockLogger _mockLogger;
 
        /// <summary>
        /// The node request id counter
        /// </summary>
        private int _nodeRequestId;
 
#pragma warning disable xUnit1013
 
        /// <summary>
        /// Callback used to receive exceptions from loggers.  Unused here.
        /// </summary>
        /// <param name="e">The exception</param>
        public void LoggingException(Exception e)
        {
        }
 
#pragma warning restore xUnit1013
 
        /// <summary>
        /// Sets up to run tests.  Creates the host object.
        /// </summary>
        public TargetBuilder_Tests()
        {
            _nodeRequestId = 1;
            _host = new MockHost();
            _mockLogger = new MockLogger();
            _host.OnLoggingThreadException += this.LoggingException;
        }
 
        /// <summary>
        /// Executed after all tests are run.
        /// </summary>
        public void Dispose()
        {
            File.Delete("testProject.proj");
            _mockLogger = null;
            _host = null;
        }
 
        /// <summary>
        /// Runs the constructor.
        /// </summary>
        [Fact]
        public void TestConstructor()
        {
            TargetBuilder builder = new TargetBuilder();
        }
 
        /// <summary>
        /// Runs a "simple" build with no dependencies and no outputs.
        /// </summary>
        [Fact]
        public void TestSimpleBuild()
        {
            ProjectInstance project = CreateTestProject();
 
            // The Empty target has no inputs or outputs.
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Empty", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            Assert.True(result.HasResultsForTarget("Empty"));
            Assert.Equal(TargetResultCode.Success, result["Empty"].ResultCode);
            Assert.Empty(result["Empty"].Items);
        }
 
        /// <summary>
        /// Runs a build with a target which depends on one other target.
        /// </summary>
        [Fact]
        public void TestDependencyBuild()
        {
            ProjectInstance project = CreateTestProject();
 
            // The Baz project depends on the Bar target.  Both should succeed.
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
 
            (string name, TargetBuiltReason reason)[] target = { ("Baz", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
 
            // The result returned from the builder includes only those for the specified targets.
            Assert.True(result.HasResultsForTarget("Baz"));
            Assert.False(result.HasResultsForTarget("Bar"));
            Assert.Equal(TargetResultCode.Success, result["Baz"].ResultCode);
 
            // The results cache should have ALL of the results.
            IResultsCache resultsCache = (IResultsCache)_host.GetComponent(BuildComponentType.ResultsCache);
            Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Bar"));
            Assert.Equal(TargetResultCode.Success, resultsCache.GetResultForRequest(entry.Request)["Bar"].ResultCode);
        }
 
        /// <summary>
        /// Tests a project with a dependency which will be skipped because its up-to-date.
        /// </summary>
        [Fact]
        public void TestDependencyBuildWithSkip()
        {
            ProjectInstance project = CreateTestProject();
 
            // DepSkip depends on Skip (which skips) but should succeed itself.
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("DepSkip", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            Assert.True(result.HasResultsForTarget("DepSkip"));
            Assert.False(result.HasResultsForTarget("Skip"));
            Assert.Equal(TargetResultCode.Success, result["DepSkip"].ResultCode);
 
            IResultsCache resultsCache = (IResultsCache)_host.GetComponent(BuildComponentType.ResultsCache);
            Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("SkipCondition"));
            Assert.Equal(TargetResultCode.Skipped, resultsCache.GetResultForRequest(entry.Request)["SkipCondition"].ResultCode);
        }
 
        /// <summary>
        /// This test is currently ignored because the error tasks aren't implemented yet (due to needing the task builder.)
        /// </summary>
        [Fact]
        public void TestDependencyBuildWithError()
        {
            ProjectInstance project = CreateTestProject();
 
            // The DepError target builds Foo (which succeeds), Skip (which skips) and Error (which fails), and Baz2
            // Baz2 should not run since it came after Error.
            // Error tries to build Foo again as an error (which is already built) and Bar, which produces outputs.
            // DepError builds Baz as an error, which produces outputs
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder);
            taskBuilder.FailTaskNumber = 3; // Succeed on Foo's one task, and Error's first task, and fail the second.
 
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("DepError", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            Assert.True(result.HasResultsForTarget("DepError"));
            Assert.False(result.HasResultsForTarget("Foo"));
            Assert.False(result.HasResultsForTarget("Skip"));
            Assert.False(result.HasResultsForTarget("Error"));
            Assert.False(result.HasResultsForTarget("Baz2"));
            Assert.False(result.HasResultsForTarget("Bar"));
            Assert.False(result.HasResultsForTarget("Baz"));
 
            IResultsCache resultsCache = (IResultsCache)_host.GetComponent(BuildComponentType.ResultsCache);
 
            Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Foo"));
            Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Skip"));
            Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Error"));
            Assert.False(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Baz2"));
            Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Bar"));
            Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Baz"));
            Assert.Equal(TargetResultCode.Failure, resultsCache.GetResultForRequest(entry.Request)["DepError"].ResultCode);
            Assert.Equal(TargetResultCode.Success, resultsCache.GetResultForRequest(entry.Request)["Foo"].ResultCode);
            Assert.Equal(TargetResultCode.Success, resultsCache.GetResultForRequest(entry.Request)["Skip"].ResultCode);
            Assert.Equal(TargetResultCode.Failure, resultsCache.GetResultForRequest(entry.Request)["Error"].ResultCode);
            Assert.Equal(TargetResultCode.Success, resultsCache.GetResultForRequest(entry.Request)["Bar"].ResultCode);
            Assert.Equal(TargetResultCode.Success, resultsCache.GetResultForRequest(entry.Request)["Baz"].ResultCode);
        }
 
        [Fact]
        public void TestLoggingForSkippedTargetInputsAndOutputs()
        {
            string projectContents = @"
<Project>
  <Target Name=""Build"" Inputs=""a.txt;b.txt"" Outputs=""c.txt"">
    <Message Text=""test"" Importance=""High"" />
  </Target>
</Project>";
 
            using (var env = TestEnvironment.Create())
            {
                var files = env.CreateTestProjectWithFiles(projectContents, new[] { "a.txt", "b.txt", "c.txt" });
                var fileA = new FileInfo(files.CreatedFiles[0]);
                var fileB = new FileInfo(files.CreatedFiles[1]);
                var fileC = new FileInfo(files.CreatedFiles[2]);
 
                var now = DateTime.UtcNow;
                fileA.LastWriteTimeUtc = now - TimeSpan.FromSeconds(10);
                fileB.LastWriteTimeUtc = now - TimeSpan.FromSeconds(10);
                fileC.LastWriteTimeUtc = now;
 
                var logger = files.BuildProjectExpectSuccess();
                var logText = logger.FullLog.Replace("\r\n", "\n");
 
                var expected = @"
Skipping target ""Build"" because all output files are up-to-date with respect to the input files.
Input files:
    a.txt
    b.txt
Output files: c.txt
Done building target ""Build"" in project ""build.proj"".".Replace("\r\n", "\n");
 
                logText.ShouldContainWithoutWhitespace(expected);
            }
        }
 
        [Fact]
        public void TestErrorForSkippedTargetInputsAndOutputs()
        {
            string projectContents = @"
<Project>
  <Target Name=""Build"" Inputs=""a.txt;b.txt"" Outputs=""c.txt"">
    <Message Text=""test"" Importance=""High"" />
  </Target>
</Project>";
 
            using (var env = TestEnvironment.Create())
            {
                var buildParameters = new BuildParameters()
                {
                    Question = true,
                };
 
                using (var buildSession = new Helpers.BuildManagerSession(env, buildParameters))
                {
                    var files = env.CreateTestProjectWithFiles(projectContents, new[] { "a.txt", "b.txt", "c.txt" });
                    var fileA = new FileInfo(files.CreatedFiles[0]);
                    var fileB = new FileInfo(files.CreatedFiles[1]);
                    var fileC = new FileInfo(files.CreatedFiles[2]);
 
                    var now = DateTime.UtcNow;
                    fileA.LastWriteTimeUtc = now - TimeSpan.FromSeconds(10);
                    fileB.LastWriteTimeUtc = now + TimeSpan.FromSeconds(10);
                    fileC.LastWriteTimeUtc = now;
 
                    var result = buildSession.BuildProjectFile(files.ProjectFile);
                    result.OverallResult.ShouldBe(BuildResultCode.Failure);
                }
            }
        }
 
        /// <summary>
        /// Ensure that skipped targets only infer outputs once
        /// </summary>
        [Fact]
        public void SkippedTargetsShouldOnlyInferOutputsOnce()
        {
            MockLogger logger = new MockLogger();
 
            string path = FileUtilities.GetTemporaryFile();
 
            Thread.Sleep(100);
 
            string content = String.Format(
@"
<Project ToolsVersion='msbuilddefaulttoolsversion'>
 
  <Target Name='Build' DependsOnTargets='GFA;GFT;DFTA;GAFT'>
        <Message Text='Build: [@(Outs)]' />
  </Target>
 
 
  <Target Name='GFA' Inputs='{0}' Outputs='{0}'>
        <Message Text='GFA' />
        <CreateItem Include='GFA'>
        	<Output TaskParameter='Include' ItemName='Outs' />
        </CreateItem>
  </Target>
  <Target Name='GFT'  Inputs='{0}' Outputs='{0}'>
        <CreateItem Include='GFT'>
            <Output TaskParameter='Include' ItemName='Outs' />
        </CreateItem>
        <Message Text='GFT' />
  </Target>
  <Target Name='DFTA'  Inputs='{0}' Outputs='{0}'>
        <CreateItem Include='DFTA'>
            <Output TaskParameter='Include' ItemName='Outs' />
        </CreateItem>
        <Message Text='DFTA' />
  </Target>
  <Target Name='GAFT'  Inputs='{0}' Outputs='{0}' DependsOnTargets='DFTA'>
        <CreateItem Include='GAFT'>
            <Output TaskParameter='Include' ItemName='Outs' />
        </CreateItem>
        <Message Text='GAFT' />
  </Target>
</Project>
            ",
             path);
            using ProjectFromString projectFromString = new(content);
            Project p = projectFromString.Project;
            p.Build(new string[] { "Build" }, new ILogger[] { logger });
 
            // There should be no duplicates in the list - if there are, then skipped targets are being inferred multiple times
            logger.AssertLogContains("[GFA;GFT;DFTA;GAFT]");
 
            File.Delete(path);
        }
 
        /// <summary>
        /// Test empty before targets
        /// </summary>
        [Fact]
        public void TestLegacyCallTarget()
        {
            string projectBody = @"
<Target Name='Build'>
    <CallTarget Targets='Foo;Goo'/>
</Target>
 
<Target Name='Foo' DependsOnTargets='Foo2'>
    <FooTarget/>
</Target>
 
<Target Name='Goo'>
    <GooTarget/>
</Target>
 
<Target Name='Foo2'>
    <Foo2Target/>
</Target>
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "CallTarget", "Foo2Target", "FooTarget", "GooTarget" });
        }
 
        /// <summary>
        /// BeforeTargets specifies a missing target. Should not warn or error.
        /// </summary>
        [Fact]
        public void TestBeforeTargetsMissing()
        {
            string content = @"
<Project DefaultTargets='t'>
 
    <Target Name='t' BeforeTargets='x'>
        <Message Text='[t]' />
    </Target>
</Project>
                ";
 
            MockLogger log = Helpers.BuildProjectWithNewOMExpectSuccess(content);
 
            log.AssertLogContains("[t]");
            log.AssertLogDoesntContain("MSB4057"); // missing target
            log.AssertNoErrors();
            log.AssertNoWarnings();
        }
 
        /// <summary>
        /// BeforeTargets specifies a missing target. Should not warn or error.
        /// </summary>
        [Fact]
        public void TestBeforeTargetsMissingRunsOthers()
        {
            string content = @"
<Project DefaultTargets='a;c'>
 
    <Target Name='t' BeforeTargets='a;b;c'>
        <Message Text='[t]' />
    </Target>
 
    <Target Name='a'>
        <Message Text='[a]' />
    </Target>
 
    <Target Name='c'>
        <Message Text='[c]' />
    </Target>
 
</Project>
                ";
 
            MockLogger log = Helpers.BuildProjectWithNewOMExpectSuccess(content);
 
            log.AssertLogContains("[t]", "[a]", "[c]");
            log.AssertLogDoesntContain("MSB4057"); // missing target
            log.AssertNoErrors();
            log.AssertNoWarnings();
        }
 
        /// <summary>
        /// AfterTargets specifies a missing target. Should not warn or error.
        /// </summary>
        [Fact]
        public void TestAfterTargetsMissing()
        {
            string content = @"
<Project DefaultTargets='t'>
 
    <Target Name='t' AfterTargets='x'>
        <Message Text='[t]' />
    </Target>
</Project>
                ";
 
            MockLogger log = Helpers.BuildProjectWithNewOMExpectSuccess(content);
 
            log.AssertLogContains("[t]");
            log.AssertLogDoesntContain("MSB4057"); // missing target
            log.AssertNoErrors();
            log.AssertNoWarnings();
        }
 
        /// <summary>
        /// AfterTargets specifies a missing target. Should not warn or error.
        /// </summary>
        [Fact]
        public void TestAfterTargetsMissingRunsOthers()
        {
            string content = @"
<Project DefaultTargets='a;c'>
 
    <Target Name='t' AfterTargets='a;b'>
        <Message Text='[t]' />
    </Target>
 
    <Target Name='t2' AfterTargets='b;c'>
        <Message Text='[t2]' />
    </Target>
 
    <Target Name='a'>
        <Message Text='[a]' />
    </Target>
 
    <Target Name='c'>
        <Message Text='[c]' />
    </Target>
 
</Project>
                ";
 
            MockLogger log = Helpers.BuildProjectWithNewOMExpectSuccess(content);
 
            log.AssertLogContains("[a]", "[t]", "[c]", "[t2]");
            log.AssertLogDoesntContain("MSB4057"); // missing target
            log.AssertNoErrors();
            log.AssertNoWarnings();
        }
 
        /// <summary>
        /// Test empty before targets
        /// </summary>
        [Fact]
        public void TestBeforeTargetsEmpty()
        {
            string projectBody = @"
<Target Name='Build'>
    <BuildTask/>
</Target>
 
<Target Name='Before' BeforeTargets=''>
    <BeforeTask/>
</Target>";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildTask" });
        }
 
        /// <summary>
        /// Test single before targets
        /// </summary>
        [Fact]
        public void TestBeforeTargetsSingle()
        {
            string projectBody = @"
<Target Name='Build' Outputs='$(Test)'>
    <BuildTask/>
</Target>
 
<Target Name='Before' BeforeTargets='Build'>
    <BeforeTask/>
</Target>";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BeforeTask", "BuildTask" });
        }
 
        /// <summary>
        /// Test single before targets on an escaped target
        /// </summary>
        [Fact]
        public void TestBeforeTargetsEscaped()
        {
            string projectBody = @"
<Target Name='Build;Me' Outputs='$(Test)'>
    <BuildTask/>
</Target>
 
<Target Name='Before' BeforeTargets='Build%3bMe'>
    <BeforeTask/>
</Target>";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build;Me", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BeforeTask", "BuildTask" });
        }
 
        /// <summary>
        /// Test single before targets
        /// </summary>
        [Fact]
        public void TestBeforeTargetsSingleWithError()
        {
            string projectBody = @"
<Target Name='Before' BeforeTargets='Build'>
    <BeforeTask/>
</Target>
 
<Target Name='Build'>
    <BuildTask/>
</Target>
";
 
            MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder);
            taskBuilder.FailTaskNumber = 2; // Succeed on BeforeTask, fail on BuildTask
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BeforeTask", "BuildTask" });
        }
 
        /// <summary>
        /// Test single before targets
        /// </summary>
        [Fact]
        public void TestBeforeTargetsSingleWithErrorAndParent()
        {
            string projectBody = @"
<Target Name='Before' BeforeTargets='Build'>
    <BeforeTask/>
</Target>
 
<Target Name='Build'>
    <BuildTask/>
    <OnError ExecuteTargets='ErrorTarget'/>
</Target>
 
<Target Name='ErrorTarget'>
    <Error/>
</Target>
";
 
            MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder);
            taskBuilder.FailTaskNumber = 2; // Succeed on BeforeTask, fail on BuildTask
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BeforeTask", "BuildTask", "Error" });
        }
 
        /// <summary>
        /// Test multiple before targets
        /// </summary>
        [Fact]
        public void TestBeforeTargetsWithTwoReferringToOne()
        {
            string projectBody = @"
<Target Name='Build' Outputs='$(Test)'>
    <BuildTask/>
</Target>
 
<Target Name='Before' BeforeTargets='Build'>
    <BeforeTask/>
</Target>
 
 
<Target Name='Before2' BeforeTargets='Build'>
    <BeforeTask2/>
</Target>
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BeforeTask", "BeforeTask2", "BuildTask" });
        }
 
        /// <summary>
        /// Test multiple before targets
        /// </summary>
        [Fact]
        public void TestBeforeTargetsWithOneReferringToTwo()
        {
            string projectBody = @"
<Target Name='Build' Outputs='$(Test)'>
    <BuildTask/>
</Target>
 
<Target Name='Foo' Outputs='$(Test)'>
    <FooTask/>
</Target>
 
<Target Name='Before' BeforeTargets='Build;Foo'>
    <BeforeTask/>
</Target>
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Foo", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BeforeTask", "FooTask" });
        }
 
        /// <summary>
        /// Test before target on a skipped target
        /// </summary>
        [Fact]
        public void TestBeforeTargetsSkip()
        {
            string projectBody = @"
<Target Name='Build' Condition=""'0'=='1'"">
    <BuildTask/>
</Target>
 
<Target Name='Before' BeforeTargets='Build'>
    <BeforeTask/>
</Target>";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BeforeTask" });
        }
 
        /// <summary>
        /// Test before target on a skipped target
        /// </summary>
        [Fact]
        public void TestBeforeTargetsDependencyOrdering()
        {
            string projectBody = @"
<Target Name='Build' DependsOnTargets='BuildDep'>
    <BuildTask/>
</Target>
 
<Target Name='Before' DependsOnTargets='BeforeDep' BeforeTargets='Build'>
    <BeforeTask/>
</Target>
 
<Target Name='BuildDep'>
    <BuildDepTask/>
</Target>
 
<Target Name='BeforeDep'>
    <BeforeDepTask/>
</Target>
 
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildDepTask", "BeforeDepTask", "BeforeTask", "BuildTask" });
        }
 
        /// <summary>
        /// Test after target on a skipped target
        /// </summary>
        [Fact]
        public void TestAfterTargetsEmpty()
        {
            string projectBody = @"
<Target Name='Build'>
    <BuildTask/>
</Target>
 
<Target Name='After' AfterTargets=''>
    <AfterTask/>
</Target>";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildTask" });
            Assert.False(result.ResultsByTarget["Build"].AfterTargetsHaveFailed);
        }
 
        /// <summary>
        /// Test after target on a skipped target
        /// </summary>
        [Fact]
        public void TestAfterTargetsSkip()
        {
            string projectBody = @"
<Target Name='Build' Condition=""'0'=='1'"">
    <BuildTask/>
</Target>
 
<Target Name='After' AfterTargets='Build'>
    <AfterTask/>
</Target>";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "AfterTask" });
            Assert.False(result.ResultsByTarget["Build"].AfterTargetsHaveFailed);
        }
 
        /// <summary>
        /// Test single before targets
        /// </summary>
        [Fact]
        public void TestAfterTargetsSingleWithError()
        {
            string projectBody = @"
<Target Name='Build'>
    <BuildTask/>
</Target>
 
<Target Name='After' AfterTargets='Build'>
    <AfterTask/>
</Target>";
 
            MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder);
            taskBuilder.FailTaskNumber = 1; // Fail on BuildTask
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildTask" });
            Assert.False(result.ResultsByTarget["Build"].AfterTargetsHaveFailed);
        }
 
        /// <summary>
        /// Test single before targets
        /// </summary>
        [Fact]
        public void TestAfterTargetsSingleWithErrorAndParent()
        {
            string projectBody = @"
<Target Name='After' AfterTargets='Build'>
    <AfterTask/>
</Target>
 
<Target Name='Build'>
    <BuildTask/>
    <OnError ExecuteTargets='ErrorTarget'/>
</Target>
 
<Target Name='ErrorTarget'>
    <Error/>
</Target>
 
<Target Name='ErrorTarget2'>
    <Error2/>
</Target>
 
<Target Name='PostBuild' DependsOnTargets='Build'>
    <OnError ExecuteTargets='ErrorTarget2'/>
</Target>
";
 
            MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder);
            taskBuilder.FailTaskNumber = 2; // Succeed on BuildTask, fail on AfterTask
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("PostBuild", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildTask", "AfterTask", "Error2" });
            Assert.False(result.ResultsByTarget["PostBuild"].AfterTargetsHaveFailed);
        }
 
        /// <summary>
        /// Test after target on a normal target
        /// </summary>
        [Fact]
        public void TestAfterTargetsSingle()
        {
            string projectBody = @"
<Target Name='Build'>
    <BuildTask/>
</Target>
 
<Target Name='After' AfterTargets='Build'>
    <AfterTask/>
</Target>";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildTask", "AfterTask" });
            Assert.False(result.ResultsByTarget["Build"].AfterTargetsHaveFailed);
        }
 
        /// <summary>
        /// Test after target on a target name which needs escaping
        /// </summary>
        [Fact]
        public void TestAfterTargetsEscaped()
        {
            string projectBody = @"
<Target Name='Build;Me'>
    <BuildTask/>
</Target>
 
<Target Name='After' AfterTargets='Build%3bMe'>
    <AfterTask/>
</Target>";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build;Me", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildTask", "AfterTask" });
            Assert.False(result.ResultsByTarget["Build;Me"].AfterTargetsHaveFailed);
        }
 
        /// <summary>
        /// Test after target on a skipped target
        /// </summary>
        [Fact]
        public void TestAfterTargetsWithTwoReferringToOne()
        {
            string projectBody = @"
<Target Name='Build'>
    <BuildTask/>
</Target>
 
<Target Name='After' AfterTargets='Build'>
    <AfterTask/>
</Target>
 
<Target Name='After2' AfterTargets='Build'>
    <AfterTask2/>
</Target>
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildTask", "AfterTask", "AfterTask2" });
            Assert.False(result.ResultsByTarget["Build"].AfterTargetsHaveFailed);
        }
 
        /// <summary>
        /// Test a failing after target
        /// </summary>
        [Fact]
        public void TestAfterTargetsWithFailure()
        {
            string projectBody = @"
<Target Name='Build'>
    <BuildTask/>
</Target>
 
<Target Name='After' AfterTargets='Build'>
    <ErrorTask1/>
</Target>
";
 
            BuildResult result = BuildSimpleProject(projectBody, new (string name, TargetBuiltReason reason)[] { ("Build", TargetBuiltReason.None) }, failTaskNumber: 2 /* Fail on After */);
            result.ResultsByTarget["Build"].ResultCode.ShouldBe(TargetResultCode.Success);
            result.ResultsByTarget["Build"].AfterTargetsHaveFailed.ShouldBe(true);
        }
 
        /// <summary>
        /// Test a transitively failing after target
        /// </summary>
        [Fact]
        public void TestAfterTargetsWithTransitiveFailure()
        {
            string projectBody = @"
<Target Name='Build'>
    <BuildTask/>
</Target>
 
<Target Name='After1' AfterTargets='Build'>
    <BuildTask/>
</Target>
 
<Target Name='After2' AfterTargets='After1'>
    <ErrorTask1/>
</Target>
";
 
            BuildResult result = BuildSimpleProject(projectBody, new (string name, TargetBuiltReason reason)[] { ("Build", TargetBuiltReason.None) }, failTaskNumber: 3 /* Fail on After2 */);
            result.ResultsByTarget["Build"].ResultCode.ShouldBe(TargetResultCode.Success);
            result.ResultsByTarget["Build"].AfterTargetsHaveFailed.ShouldBe(true);
        }
 
        /// <summary>
        /// Test a project that has a cycle in AfterTargets
        /// </summary>
        [Fact]
        public void TestAfterTargetsWithCycleDoesNotHang()
        {
            string projectBody = @"
<Target Name='Build' AfterTargets='After2' />
 
<Target Name='After1' AfterTargets='Build' />
 
<Target Name='After2' AfterTargets='After1' />
";
 
            BuildResult result = BuildSimpleProject(projectBody, new (string name, TargetBuiltReason reason)[] { ("Build", TargetBuiltReason.None) }, failTaskNumber: int.MaxValue /* no task failure needed here */);
            result.ResultsByTarget["Build"].ResultCode.ShouldBe(TargetResultCode.Success);
            result.ResultsByTarget["Build"].AfterTargetsHaveFailed.ShouldBe(false);
        }
 
 
        /// <summary>
        /// Test after target on a skipped target
        /// </summary>
        [Fact]
        public void TestAfterTargetsWithOneReferringToTwo()
        {
            string projectBody = @"
<Target Name='Build'>
    <BuildTask/>
</Target>
 
<Target Name='Foo'>
    <FooTask/>
</Target>
 
<Target Name='After' AfterTargets='Build;Foo'>
    <AfterTask/>
</Target>
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Foo", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "FooTask", "AfterTask" });
        }
 
        /// <summary>
        /// Test after target on a skipped target
        /// </summary>
        [Fact]
        public void TestAfterTargetsWithDependencyOrdering()
        {
            string projectBody = @"
<Target Name='Build' DependsOnTargets='BuildDep'>
    <BuildTask/>
</Target>
 
<Target Name='After' DependsOnTargets='AfterDep' AfterTargets='Build'>
    <AfterTask/>
</Target>
 
<Target Name='BuildDep'>
    <BuildDepTask/>
</Target>
 
<Target Name='AfterDep'>
    <AfterDepTask/>
</Target>
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildDepTask", "BuildTask", "AfterDepTask", "AfterTask" });
        }
 
        /// <summary>
        /// Test a complex ordering with depends, before and after targets
        /// </summary>
        [Fact]
        public void TestComplexOrdering()
        {
            string projectBody = @"
<Target Name='Build' DependsOnTargets='BuildDep'>
    <BuildTask/>
</Target>
 
<Target Name='Before' DependsOnTargets='BeforeDep' BeforeTargets='Build'>
    <BeforeTask/>
</Target>
 
<Target Name='After' DependsOnTargets='AfterDep' AfterTargets='Build'>
    <AfterTask/>
</Target>
 
<Target Name='BuildDep'>
    <BuildDepTask/>
</Target>
 
<Target Name='AfterDep' DependsOnTargets='AfterDepDep'>
    <AfterDepTask/>
</Target>
 
<Target Name='BeforeDep' DependsOnTargets='BeforeDepDep'>
    <BeforeDepTask/>
</Target>
 
<Target Name='BeforeDepDep'>
    <BeforeDepDepTask/>
</Target>
 
<Target Name='AfterDepDep'>
    <AfterDepDepTask/>
</Target>
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildDepTask", "BeforeDepDepTask", "BeforeDepTask", "BeforeTask", "BuildTask", "AfterDepDepTask", "AfterDepTask", "AfterTask" });
        }
 
        /// <summary>
        /// Test a complex ordering with depends, before and after targets
        /// </summary>
        [Fact]
        public void TestComplexOrdering2()
        {
            string projectBody = @"
<Target Name='BuildDep'>
    <BuildDepTask/>
</Target>
 
<Target Name='BeforeDepDep'>
    <BeforeDepDepTask/>
</Target>
 
<Target Name='BeforeBeforeDep' BeforeTargets='BeforeDep'>
    <BeforeBeforeDepTask/>
</Target>
 
<Target Name='AfterBeforeBeforeDep' AfterTargets='BeforeBeforeDep'>
    <AfterBeforeBeforeDepTask/>
</Target>
 
<Target Name='BeforeDep' DependsOnTargets='BeforeDepDep'>
    <BeforeDepTask/>
</Target>
 
<Target Name='Before' DependsOnTargets='BeforeDep' BeforeTargets='Build'>
    <BeforeTask/>
</Target>
 
<Target Name='AfterBeforeDepDep'>
    <AfterBeforeDepDepTask/>
</Target>
 
<Target Name='AfterBeforeDep' DependsOnTargets='AfterBeforeDepDep'>
    <AfterBeforeDepTask/>
</Target>
 
<Target Name='AfterBefore' DependsOnTargets='AfterBeforeDep' AfterTargets='Before'>
    <AfterBeforeTask/>
</Target>
 
<Target Name='Build' DependsOnTargets='BuildDep'>
    <BuildTask/>
</Target>
 
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildDepTask", "BeforeDepDepTask", "BeforeBeforeDepTask", "AfterBeforeBeforeDepTask", "BeforeDepTask", "BeforeTask", "AfterBeforeDepDepTask", "AfterBeforeDepTask", "AfterBeforeTask", "BuildTask" });
        }
 
        /// <summary>
        /// Test a complex ordering with depends, before and after targets
        /// </summary>
        [Fact]
        public void TestBeforeAndAfterWithErrorTargets()
        {
            string projectBody = @"
 
 
<Target Name='Build' >
    <BuildTask/>
    <OnError ExecuteTargets='ErrorTarget'/>
</Target>
 
<Target Name='ErrorTarget'>
    <ErrorTargetTask/>
</Target>
 
<Target Name='BeforeErrorTarget' BeforeTargets='ErrorTarget'>
    <BeforeErrorTargetTask/>
</Target>
 
<Target Name='AfterErrorTarget' AfterTargets='ErrorTarget'>
    <AfterErrorTargetTask/>
</Target>
 
";
 
            MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder);
            taskBuilder.FailTaskNumber = 1; // Fail on BuildTask
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildTask", "BeforeErrorTargetTask", "ErrorTargetTask", "AfterErrorTargetTask" });
            Assert.False(result.ResultsByTarget["Build"].AfterTargetsHaveFailed);
        }
 
        /// <summary>
        /// Test after target on a skipped target
        /// </summary>
        [Fact]
        public void TestBeforeAndAfterOverrides()
        {
            string projectBody = @"
 
<Target Name='BuildDep'>
    <BuildDepTask/>
</Target>
 
<Target Name='Build' DependsOnTargets='BuildDep'>
    <BuildTask/>
</Target>
 
<Target Name='After' AfterTargets='Build'>
    <AfterTask/>
</Target>
 
<Target Name='After' AfterTargets='BuildDep'>
    <AfterTask/>
</Target>
 
<Target Name='Before' BeforeTargets='Build'>
    <BeforeTask/>
</Target>
 
<Target Name='Before' BeforeTargets='BuildDep'>
    <BeforeTask/>
</Target>
 
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BeforeTask", "BuildDepTask", "AfterTask", "BuildTask" });
        }
 
        /// <summary>
        /// Test that if before and after targets skip, the main target still runs (bug 476908)
        /// </summary>
        [Fact]
        public void TestSkippingBeforeAndAfterTargets()
        {
            string projectBody = @"
<Target Name='Build'>
    <BuildTask/>
</Target>
 
<Target Name='Before' BeforeTargets='Build' Condition=""'0'=='1'"">
    <BeforeTask/>
</Target>
 
<Target Name='After' AfterTargets='Build' Condition=""'0'=='1'"">
    <AfterTask/>
</Target>
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
            AssertTaskExecutionOrder(new string[] { "BuildTask" });
        }
 
        /// <summary>
        /// Tests that a circular dependency within a CallTarget call correctly propagates the failure.  Bug 502570.
        /// </summary>
        [Fact]
        public void TestCircularDependencyInCallTarget()
        {
            string projectContents = @"
<Project>
    <Target Name=""t1"">
        <CallTarget Targets=""t3""/>
    </Target>
    <Target Name=""t2"" DependsOnTargets=""t1"">
    </Target>
    <Target Name=""t3"" DependsOnTargets=""t2"">
    </Target>
</Project>
      ";
            using ProjectFromString projectFromString = new(projectContents, null, null);
            Project project = projectFromString.Project;
            bool success = project.Build(_mockLogger);
            Assert.False(success);
        }
 
        /// <summary>
        /// Tests a circular dependency target.
        /// </summary>
        [Fact]
        public void TestCircularDependencyTarget()
        {
            string projectContents = @"
<Project>
    <Target Name=""TargetA"" AfterTargets=""Build"" DependsOnTargets=""TargetB"">
        <Message Text=""TargetA""></Message>
    </Target>
    <Target Name=""TargetB"" DependsOnTargets=""TargetC"">
        <Message Text=""TargetB""></Message>
    </Target>
    <Target Name=""TargetC"" DependsOnTargets=""TargetA"">
        <Message Text=""TargetC""></Message>
    </Target>
</Project>
      ";
            string errorMessage = @"There is a circular dependency in the target dependency graph involving target ""TargetA"". Since ""TargetC"" has ""DependsOn"" dependence on ""TargetA"", the circular is ""TargetA<-TargetC<-TargetB<-TargetA"".";
 
            using ProjectFromString projectFromString = new(projectContents, null, null);
            Project project = projectFromString.Project;
            project.Build(_mockLogger).ShouldBeFalse();
            _mockLogger.ErrorCount.ShouldBe(1);
            _mockLogger.Errors[0].Message.ShouldBe(errorMessage);
        }
 
        /// <summary>
        /// Tests that cancel with no entries after building does not fail.
        /// </summary>
        [Fact]
        public void TestCancelWithNoEntriesAfterBuild()
        {
            string projectBody = @"
<Target Name='Build'>
    <BuildTask/>
</Target>
";
 
            ProjectInstance project = CreateTestProject(projectBody);
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
            using (CancellationTokenSource source = new CancellationTokenSource())
            {
                BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), source.Token).Result;
                AssertTaskExecutionOrder(new string[] { "BuildTask" });
 
                // This simply should not fail.
                source.Cancel();
            }
        }
 
        [Fact]
        public void SkippedTargetWithFailedDependenciesStopsBuild()
        {
            string projectContents = @"
  <Target Name=""Build"" DependsOnTargets=""ProduceError1;ProduceError2"" />
  <Target Name=""ProduceError1"" Condition=""false"" />
  <Target Name=""ProduceError2"">
    <ErrorTask2 />
  </Target>
  <Target Name=""_Error1"" BeforeTargets=""ProduceError1"">
    <ErrorTask1 />
  </Target>
";
 
            var project = CreateTestProject(projectContents, string.Empty, "Build");
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder);
            // Fail the first task
            taskBuilder.FailTaskNumber = 1;
 
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("Build", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target), cache[1]);
 
            var buildResult = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
 
            IResultsCache resultsCache = (IResultsCache)_host.GetComponent(BuildComponentType.ResultsCache);
            Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Build"));
            Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("ProduceError1"));
            Assert.False(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("ProduceError2"));
            Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("_Error1"));
 
            Assert.Equal(TargetResultCode.Failure, resultsCache.GetResultForRequest(entry.Request)["Build"].ResultCode);
            Assert.Equal(TargetResultCode.Skipped, resultsCache.GetResultForRequest(entry.Request)["ProduceError1"].ResultCode);
            Assert.Equal(TargetResultCode.Failure, resultsCache.GetResultForRequest(entry.Request)["_Error1"].ResultCode);
        }
 
        [Fact]
        public void SkipNonexistentTargetsDoesNotExecuteOrCacheTargetResult()
        {
            string projectContents = @"<Target Name=""Build"" />";
 
            var project = CreateTestProject(projectContents, string.Empty, "Build");
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder);
            taskBuilder.FailTaskNumber = 1;
 
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            (string name, TargetBuiltReason reason)[] target = { ("NotFound", TargetBuiltReason.None) };
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, target, BuildRequestDataFlags.SkipNonexistentTargets), cache[1]);
            var buildResult = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, target, CreateStandardLookup(project), CancellationToken.None).Result;
 
            IResultsCache resultsCache = (IResultsCache)_host.GetComponent(BuildComponentType.ResultsCache);
 
            // This should throw because the results cache should not contain the results for the target that was not
            // executed. This is different than when a target is skipped due to condition not met where the result
            // would be in the cache. In this case we can't cache the result because it is only valid for the single
            // request.
            Assert.Throws<InternalErrorException>(() => resultsCache.GetResultForRequest(entry.Request));
        }
 
        #region IRequestBuilderCallback Members
 
        /// <summary>
        /// We have to have this interface, but it won't be used in this test because we aren't doing MSBuild callbacks.
        /// </summary>
        /// <param name="projectFiles">N/A</param>
        /// <param name="properties">N/A</param>
        /// <param name="toolsVersions">N/A</param>
        /// <param name="targets">N/A</param>
        /// <param name="waitForResults">N/A</param>
        /// <param name="skipNonexistentTargets">N/A</param>
        /// <returns>N/A</returns>
        Task<BuildResult[]> IRequestBuilderCallback.BuildProjects(string[] projectFiles, PropertyDictionary<ProjectPropertyInstance>[] properties, string[] toolsVersions, string[] targets, bool waitForResults, bool skipNonexistentTargets)
        {
            throw new NotImplementedException();
        }
 
        /// <summary>
        /// Not implemented
        /// </summary>
        Task IRequestBuilderCallback.BlockOnTargetInProgress(int blockingRequestId, string blockingTarget, BuildResult partialBuildResult)
        {
            throw new NotImplementedException();
        }
 
        /// <summary>
        /// Empty impl
        /// </summary>
        void IRequestBuilderCallback.Yield()
        {
        }
 
        /// <summary>
        /// Empty impl
        /// </summary>
        void IRequestBuilderCallback.Reacquire()
        {
        }
 
        /// <summary>
        /// Empty impl
        /// </summary>
        void IRequestBuilderCallback.EnterMSBuildCallbackState()
        {
        }
 
        /// <summary>
        /// Empty impl
        /// </summary>
        void IRequestBuilderCallback.ExitMSBuildCallbackState()
        {
        }
 
        /// <summary>
        /// Empty impl
        /// </summary>
        int IRequestBuilderCallback.RequestCores(object monitorLockObject, int requestedCores, bool waitForCores)
        {
            return 0;
        }
 
        /// <summary>
        /// Empty impl
        /// </summary>
        void IRequestBuilderCallback.ReleaseCores(int coresToRelease)
        {
        }
 
        #endregion
 
        /// <summary>
        /// Verifies the order in which tasks executed.
        /// </summary>
        private void AssertTaskExecutionOrder(string[] tasks)
        {
            MockTaskBuilder mockBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder);
 
            Assert.Equal(tasks.Length, mockBuilder.ExecutedTasks.Count);
 
            int currentTask = 0;
            foreach (ProjectTaskInstance task in mockBuilder.ExecutedTasks)
            {
                Assert.Equal(task.Name, tasks[currentTask]);
                currentTask++;
            }
        }
 
        /// <summary>
        /// Creates a new build request
        /// </summary>
        private BuildRequest CreateNewBuildRequest(int configurationId, (string name, TargetBuiltReason reason)[] targets, BuildRequestDataFlags flags = BuildRequestDataFlags.None)
        {
            return new BuildRequest(1 /* submissionId */, _nodeRequestId++, configurationId, targets.Select(t => t.name).ToArray(), null, BuildEventContext.Invalid, null, flags);
        }
 
        /// <summary>
        /// Creates a 'Lookup' used to deal with projects.
        /// </summary>
        /// <param name="project">The project for which to create the lookup</param>
        /// <returns>The lookup</returns>
        private Lookup CreateStandardLookup(ProjectInstance project)
        {
            Lookup lookup = new Lookup(new ItemDictionary<ProjectItemInstance>(project.Items), new PropertyDictionary<ProjectPropertyInstance>(project.Properties));
            return lookup;
        }
 
        /// <summary>
        /// Creates a test project.
        /// </summary>
        /// <returns>The project.</returns>
        private ProjectInstance CreateTestProject()
        {
            string projectBodyContents = @"
                    <ItemGroup>
                        <Compile Include='b.cs' />
                        <Compile Include='c.cs' />
                    </ItemGroup>
 
                    <ItemGroup>
                        <Reference Include='System' />
                    </ItemGroup>
 
                    <Target Name='Empty' />
 
                    <Target Name='Skip' Inputs='testProject.proj' Outputs='testProject.proj' />
 
                    <Target Name='SkipCondition' Condition=""'true' == 'false'"" />
 
                    <Target Name='Error' >
                        <ErrorTask1 ContinueOnError='True'/>
                        <ErrorTask2 ContinueOnError='False'/>
                        <ErrorTask3 />
                        <OnError ExecuteTargets='Foo'/>
                        <OnError ExecuteTargets='Bar'/>
                    </Target>
 
                    <Target Name='DepError' DependsOnTargets='Foo;Skip;Error;Baz2'>
                        <OnError ExecuteTargets='Baz'/>
                    </Target>
 
                    <Target Name='Foo' Inputs='foo.cpp' Outputs='foo.o'>
                        <FooTask1/>
                    </Target>
 
                    <Target Name='Bar'>
                        <BarTask1/>
                    </Target>
 
                    <Target Name='Baz' DependsOnTargets='Bar'>
                        <BazTask1/>
                        <BazTask2/>
                    </Target>
 
                    <Target Name='Baz2' DependsOnTargets='Bar;Foo'>
                        <Baz2Task1/>
                        <Baz2Task2/>
                        <Baz2Task3/>
                    </Target>
 
                    <Target Name='DepSkip' DependsOnTargets='SkipCondition'>
                        <DepSkipTask1/>
                        <DepSkipTask2/>
                        <DepSkipTask3/>
                    </Target>
 
                    <Target Name='DepSkip2' DependsOnTargets='Skip' Inputs='testProject.proj' Outputs='testProject.proj'>
                        <DepSkipTask1/>
                        <DepSkipTask2/>
                        <DepSkipTask3/>
                    </Target>
                ";
 
            return CreateTestProject(projectBodyContents);
        }
 
        /// <summary>
        /// Creates a test project.
        /// </summary>
        private ProjectInstance CreateTestProject(string projectBodyContents)
        {
            return CreateTestProject(projectBodyContents, String.Empty, String.Empty);
        }
 
        /// <summary>
        /// Creates a test project.
        /// </summary>
        private ProjectInstance CreateTestProject(string projectBodyContents, string initialTargets, string defaultTargets)
        {
            string projectFileContents = String.Format("<Project ToolsVersion='msbuilddefaulttoolsversion' InitialTargets='{0}' DefaultTargets='{1}'>{2}</Project>", initialTargets, defaultTargets, projectBodyContents);
 
            // retries to deal with occasional locking issues where the file can't be written to initially
            for (int retries = 0; retries < 5; retries++)
            {
                try
                {
                    File.Create("testProject.proj").Dispose();
                    break;
                }
                // If all the retries failed, fail with the actual problem instead of some difficult-to-understand issue later.
                catch (Exception ex) when (retries < 4)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
 
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            BuildRequestConfiguration config = new BuildRequestConfiguration(1, new BuildRequestData("testFile", new Dictionary<string, string>(), "3.5", Array.Empty<string>(), null), "2.0");
            using ProjectFromString projectFromString = new(projectFileContents);
            Project project = projectFromString.Project;
 
            config.Project = project.CreateProjectInstance();
            cache.AddConfiguration(config);
 
            return config.Project;
        }
 
        /// <summary>
        /// Creates a project logging context.
        /// </summary>
        /// <param name="entry">The entry on which to base the logging context.</param>
        /// <returns>The context</returns>
        private ProjectLoggingContext GetProjectLoggingContext(BuildRequestEntry entry)
        {
            return new ProjectLoggingContext(new NodeLoggingContext(_host, 1, false), entry);
        }
 
        /// <summary>
        /// Builds a project using TargetBuilder and returns the result.
        /// </summary>
        /// <param name="projectBody">The project contents.</param>
        /// <param name="targets">The targets to build.</param>
        /// <param name="failTaskNumber">The task ordinal to fail on.</param>
        /// <returns>The result of building the specified project/tasks.</returns>
        private BuildResult BuildSimpleProject(string projectBody, (string name, TargetBuiltReason reason)[] targets, int failTaskNumber)
        {
            ProjectInstance project = CreateTestProject(projectBody);
 
            MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder);
            taskBuilder.FailTaskNumber = failTaskNumber;
 
            TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder);
            IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache);
            BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, targets), cache[1]);
 
            return builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, targets, CreateStandardLookup(project), CancellationToken.None).Result;
        }
 
        /// <summary>
        /// The mock component host object.
        /// </summary>
        private sealed class MockHost : MockLoggingService, IBuildComponentHost, IBuildComponent
        {
            #region IBuildComponentHost Members
 
            /// <summary>
            /// The config cache
            /// </summary>
            private IConfigCache _configCache;
 
            /// <summary>
            /// The logging service
            /// </summary>
            private ILoggingService _loggingService;
 
            /// <summary>
            /// The results cache
            /// </summary>
            private IResultsCache _resultsCache;
 
            /// <summary>
            /// The request builder
            /// </summary>
            private IRequestBuilder _requestBuilder;
 
            /// <summary>
            /// The mock task builder
            /// </summary>
            private ITaskBuilder _taskBuilder;
 
            /// <summary>
            /// The target builder
            /// </summary>
            private ITargetBuilder _targetBuilder;
 
            /// <summary>
            /// The build parameters
            /// </summary>
            private BuildParameters _buildParameters;
 
            /// <summary>
            /// Retrieves the LegacyThreadingData associated with a particular component host
            /// </summary>
            private LegacyThreadingData _legacyThreadingData;
 
            private ISdkResolverService _sdkResolverService;
 
            /// <summary>
            /// Constructor
            /// </summary>
            public MockHost()
            {
                _buildParameters = new BuildParameters();
                _legacyThreadingData = new LegacyThreadingData();
 
                _configCache = new ConfigCache();
                ((IBuildComponent)_configCache).InitializeComponent(this);
 
                _loggingService = this;
 
                _resultsCache = new ResultsCache();
                ((IBuildComponent)_resultsCache).InitializeComponent(this);
 
                _requestBuilder = new RequestBuilder();
                ((IBuildComponent)_requestBuilder).InitializeComponent(this);
 
                _taskBuilder = new MockTaskBuilder();
                ((IBuildComponent)_taskBuilder).InitializeComponent(this);
 
                _targetBuilder = new TargetBuilder();
                ((IBuildComponent)_targetBuilder).InitializeComponent(this);
 
                _sdkResolverService = new MockSdkResolverService();
                ((IBuildComponent)_sdkResolverService).InitializeComponent(this);
            }
 
            /// <summary>
            /// Returns the node logging service.  We don't distinguish here.
            /// </summary>
            /// <returns>The logging service.</returns>
            public ILoggingService LoggingService
            {
                get
                {
                    return _loggingService;
                }
            }
 
            /// <summary>
            /// Retrieves the LegacyThreadingData associated with a particular component host
            /// </summary>
            LegacyThreadingData IBuildComponentHost.LegacyThreadingData
            {
                get
                {
                    return _legacyThreadingData;
                }
            }
 
            /// <summary>
            /// Retrieves the name of the host.
            /// </summary>
            public string Name
            {
                get
                {
                    return "TargetBuilder_Tests.MockHost";
                }
            }
 
            /// <summary>
            /// Returns the build parameters.
            /// </summary>
            public BuildParameters BuildParameters
            {
                get
                {
                    return _buildParameters;
                }
            }
 
            /// <summary>
            /// Constructs and returns a component of the specified type.
            /// </summary>
            /// <param name="type">The type of component to return</param>
            /// <returns>The component</returns>
            public IBuildComponent GetComponent(BuildComponentType type)
            {
                return type switch
                {
                    BuildComponentType.ConfigCache => (IBuildComponent)_configCache,
                    BuildComponentType.LoggingService => (IBuildComponent)_loggingService,
                    BuildComponentType.ResultsCache => (IBuildComponent)_resultsCache,
                    BuildComponentType.RequestBuilder => (IBuildComponent)_requestBuilder,
                    BuildComponentType.TaskBuilder => (IBuildComponent)_taskBuilder,
                    BuildComponentType.TargetBuilder => (IBuildComponent)_targetBuilder,
                    BuildComponentType.SdkResolverService => (IBuildComponent)_sdkResolverService,
                    _ => throw new ArgumentException("Unexpected type " + type),
                };
            }
 
            public TComponent GetComponent<TComponent>(BuildComponentType type) where TComponent : IBuildComponent
                => (TComponent)GetComponent(type);
 
            /// <summary>
            /// Registers a component factory
            /// </summary>
            public void RegisterFactory(BuildComponentType type, BuildComponentFactoryDelegate factory)
            {
            }
 
            #endregion
        }
    }
}