File: TaskInvocationAnalysisDataTests.cs
Web Access
Project: ..\..\..\src\BuildCheck.UnitTests\Microsoft.Build.BuildCheck.UnitTests.csproj (Microsoft.Build.BuildCheck.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;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Execution;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.UnitTests;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
using static Microsoft.Build.Experimental.BuildCheck.Infrastructure.BuildCheckManagerProvider;
 
namespace Microsoft.Build.BuildCheck.UnitTests
{
    public class TaskInvocationCheckDataTests : IDisposable
    {
        internal sealed class TestCheck : Check
        {
            #region Check initialization
 
            public static CheckRule SupportedRule = new CheckRule("BC0000", "TestRule", "TestDescription", "TestMessage",
                new CheckConfiguration() { Severity = CheckResultSeverity.Warning });
 
            public override string FriendlyName => "MSBuild.TestCheck";
 
            public override IReadOnlyList<CheckRule> SupportedRules { get; } = [SupportedRule];
 
            public override void Initialize(ConfigurationContext configurationContext)
            { }
 
            public override void RegisterActions(IBuildCheckRegistrationContext registrationContext)
            {
                registrationContext.RegisterTaskInvocationAction(TaskInvocationAction);
            }
 
            #endregion
 
            /// <summary>
            /// Stores all TaskInvocationAnalysisData reported during the build.
            /// </summary>
            public List<TaskInvocationCheckData> CheckData = new();
 
            private void TaskInvocationAction(BuildCheckDataContext<TaskInvocationCheckData> context)
            {
                CheckData.Add(context.Data);
            }
        }
 
        private ITestOutputHelper _output;
 
        private static TestCheck? s_testCheck;
 
        public TaskInvocationCheckDataTests(ITestOutputHelper output)
        {
            _output = output;
 
            BuildCheckManager.s_testFactoriesPerDataSource =
            [
                // BuildCheckDataSource.EventArgs
                [
                    ([TestCheck.SupportedRule.Id], true, () => s_testCheck = new TestCheck()),
                ],
                // BuildCheckDataSource.Execution
                [],
            ];
 
            s_testCheck?.CheckData.Clear();
        }
 
        public void Dispose()
        {
            BuildCheckManager.s_testFactoriesPerDataSource = null;
        }
 
        private void BuildProject(string taskInvocation)
        {
            using (var env = TestEnvironment.Create(_output))
            {
                var testProject = env.CreateTestProjectWithFiles($"<Project><Target Name=\"Build\">{taskInvocation}</Target></Project>");
 
                using (var buildManager = new BuildManager())
                {
                    var request = new BuildRequestData(testProject.ProjectFile, new Dictionary<string, string?>(), MSBuildConstants.CurrentToolsVersion, [], null, BuildRequestDataFlags.None);
                    var parameters = new BuildParameters
                    {
                        Loggers = [new MockLogger(_output)],
                        LogTaskInputs = true,
                        IsBuildCheckEnabled = true,
                        ShutdownInProcNodeOnBuildFinish = true,
                    };
 
                    var result = buildManager.Build(parameters, request);
 
                    result.OverallResult.ShouldBe(BuildResultCode.Success);
                }
 
                foreach (var data in s_testCheck.ShouldNotBeNull().CheckData)
                {
                    data.ProjectFilePath.ShouldBe(testProject.ProjectFile);
                    data.TaskInvocationLocation.Line.ShouldBeGreaterThan(0);
                    data.TaskInvocationLocation.Column.ShouldBeGreaterThan(0);
                }
            }
        }
 
        [Fact]
        public void ReportsSimpleTaskParameters()
        {
            BuildProject("<Message Text='Hello'/>");
 
            s_testCheck!.CheckData.Count.ShouldBe(1);
            var data = s_testCheck.CheckData[0];
            data.TaskName.ShouldBe("Message");
            data.Parameters.Count.ShouldBe(1);
            data.Parameters["Text"].IsOutput.ShouldBe(false);
            data.Parameters["Text"].Value.ShouldBe("Hello");
        }
 
        [Theory]
        [InlineData("<Output TaskParameter='CombinedPaths' ItemName='OutputDirectories' />")]
        [InlineData("<Output TaskParameter='CombinedPaths' PropertyName='OutputDirectories' />")]
        public void ReportsComplexTaskParameters(string outputElement)
        {
            BuildProject($"""
                <ItemGroup>
                  <TestItem Include='item1;item2'/>
                </ItemGroup>
                <CombinePath BasePath='base' Paths='@(TestItem)'>
                    {outputElement}
                </CombinePath>
            """);
 
            s_testCheck!.CheckData.Count.ShouldBe(1);
            var data = s_testCheck.CheckData[0];
            data.TaskName.ShouldBe("CombinePath");
            data.Parameters.Count.ShouldBe(3);
 
            data.Parameters["Paths"].IsOutput.ShouldBe(false);
            data.Parameters["Paths"].Value.ShouldBeAssignableTo(typeof(IList));
            IList listValue = (IList)data.Parameters["Paths"].Value!;
            listValue.Count.ShouldBe(2);
            listValue[0]!.ShouldBeAssignableTo(typeof(ITaskItem));
            listValue[1]!.ShouldBeAssignableTo(typeof(ITaskItem));
            ((ITaskItem)listValue[0]!).ItemSpec.ShouldBe("item1");
            ((ITaskItem)listValue[1]!).ItemSpec.ShouldBe("item2");
            data.Parameters["CombinedPaths"].IsOutput.ShouldBe(true);
            data.Parameters["CombinedPaths"].Value.ShouldNotBeNull();
        }
 
        [Fact]
        public void TaskParameterEnumeratesValues()
        {
            var parameter1 = MakeParameter("string");
            parameter1.EnumerateValues().SequenceEqual(["string"]).ShouldBeTrue();
            parameter1.EnumerateStringValues().SequenceEqual(["string"]).ShouldBeTrue();
 
            var parameter2 = MakeParameter(true);
            parameter2.EnumerateValues().SequenceEqual([true]);
            parameter2.EnumerateStringValues().SequenceEqual(["True"]).ShouldBeTrue();
 
            var item1 = new TaskItem("item1");
            var parameter3 = MakeParameter(item1);
            parameter3.EnumerateValues().SequenceEqual([item1]).ShouldBeTrue();
            parameter3.EnumerateStringValues().SequenceEqual(["item1"]).ShouldBeTrue();
 
            var array1 = new object[] { "string1", "string2" };
            var parameter4 = MakeParameter(array1);
            parameter4.EnumerateValues().SequenceEqual(array1).ShouldBeTrue();
            parameter4.EnumerateStringValues().SequenceEqual(array1).ShouldBeTrue();
 
            var item2 = new TaskItem("item2");
            var array2 = new ITaskItem[] { item1, item2 };
            var parameter5 = MakeParameter(array2);
            parameter5.EnumerateValues().SequenceEqual(array2).ShouldBeTrue();
            parameter5.EnumerateStringValues().SequenceEqual(["item1", "item2"]).ShouldBeTrue();
 
            static TaskInvocationCheckData.TaskParameter MakeParameter(object value)
                => new TaskInvocationCheckData.TaskParameter(value, IsOutput: false);
        }
    }
}