File: GivenAResolvePackageAssetsTask.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.NET.Build.Tasks.UnitTests\Microsoft.NET.Build.Tasks.UnitTests.csproj (Microsoft.NET.Build.Tasks.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Reflection;
using FluentAssertions;
using Microsoft.Build.Framework;
using Xunit;
using static Microsoft.NET.Build.Tasks.ResolvePackageAssets;
 
namespace Microsoft.NET.Build.Tasks.UnitTests
{
    public class GivenAResolvePackageAssetsTask
    {
        [Fact]
        public void ItHashesAllParameters()
        {
            IEnumerable<PropertyInfo> inputProperties;
 
            var task = InitializeTask(out inputProperties);
 
            byte[] oldHash;
            try
            {
                oldHash = task.HashSettings();
            }
            catch (ArgumentNullException)
            {
                Assert.Fail("HashSettings is likely not correctly handling null value of one or more optional task parameters");
 
                throw; // unreachable
            }
 
            foreach (var property in inputProperties)
            {
                switch (property.PropertyType)
                {
                    case var t when t == typeof(bool):
                        property.SetValue(task, true);
                        break;
 
                    case var t when t == typeof(string):
                        property.SetValue(task, property.Name);
                        break;
 
                    case var t when t == typeof(ITaskItem[]):
                        property.SetValue(task, new[] { new MockTaskItem() { ItemSpec = property.Name } });
                        break;
 
                    default:
                        Assert.Fail($"{property.Name} is not a bool or string or ITaskItem[]. Update the test code to handle that.");
                        throw null; // unreachable
                }
 
                byte[] newHash = task.HashSettings();
                newHash.Should().NotBeEquivalentTo(
                    oldHash,
                    because: $"{property.Name} should be included in hash.");
 
                oldHash = newHash;
            }
        }
 
        [Fact]
        public void ItDoesNotHashDesignTimeBuild()
        {
            var task = InitializeTask(out _);
 
            task.DesignTimeBuild = false;
 
            byte[] oldHash = task.HashSettings();
 
            task.DesignTimeBuild = true;
 
            byte[] newHash = task.HashSettings();
 
            newHash.Should().BeEquivalentTo(oldHash,
                because: $"{nameof(task.DesignTimeBuild)} should not be included in hash.");
        }
 
        [Fact]
        public void It_does_not_error_on_duplicate_package_names()
        {
            string projectAssetsJsonPath = Path.GetTempFileName();
            var assetsContent = @"{
  `version`: 3,
  `targets`: {
    `net5.0`: {
      `Humanizer.Core/2.8.25`: {
        `type`: `package`
      },
      `Humanizer.Core/2.8.26`: {
        `type`: `package`
      }
    }
  },
  `project`: {
    `version`: `1.0.0`,
    `frameworks`: {
      `net5.0`: {
        `targetAlias`: `net5.0`
      }
    }
  }
}".Replace('`', '"');
            File.WriteAllText(projectAssetsJsonPath, assetsContent);
 
            var task = InitializeTask(out _);
            task.ProjectAssetsFile = projectAssetsJsonPath;
            task.TargetFramework = "net5.0";
            new CacheWriter(task); // Should not error
        }
 
        private static string AssetsFileWithInvalidLocale(string tfm, string locale) => @"
{
  `version`: 3,
  `targets`: {
    `{tfm}`: {
      `JavaScriptEngineSwitcher.Core/3.3.0`: {
        `type`: `package`,
        `compile`: {
          `lib/netstandard2.0/JavaScriptEngineSwitcher.Core.dll`: {}
        },
        `runtime`: {
          `lib/netstandard2.0/JavaScriptEngineSwitcher.Core.dll`: {}
        },
        `resource`: {
          `lib/netstandard2.0/ru-ru/JavaScriptEngineSwitcher.Core.resources.dll`: {
            `locale`: `{locale}`
          }
        }
      }
    }
  },
  `project`: {
    `version`: `1.0.0`,
    `frameworks`: {
      `{tfm}`: {
        `targetAlias`: `{tfm}`
      }
    }
  }
}".Replace("`", "\"").Replace("{tfm}", tfm).Replace("{locale}", locale);
 
        [InlineData("net7.0", true)]
        [InlineData("net6.0", false)]
        [Theory]
        public void It_warns_on_invalid_culture_codes_of_resources(string tfm, bool shouldHaveWarnings)
        {
            string projectAssetsJsonPath = Path.GetTempFileName();
            var assetsContent = AssetsFileWithInvalidLocale(tfm, "what is this even");
            File.WriteAllText(projectAssetsJsonPath, assetsContent);
            var task = InitializeTask(out _);
            task.ProjectAssetsFile = projectAssetsJsonPath;
            task.TargetFramework = tfm;
            var writer = new CacheWriter(task, new MockPackageResolver());
            writer.WriteToMemoryStream();
            var engine = task.BuildEngine as MockBuildEngine;
 
            var invalidContextWarnings = engine.Warnings.Where(msg => msg.Code == "NETSDK1188");
            invalidContextWarnings.Should().HaveCount(shouldHaveWarnings ? 1 : 0);
 
            var invalidContextMessages = engine.Messages.Where(msg => msg.Code == "NETSDK1188" && msg.Importance == MessageImportance.Low);
            invalidContextMessages.Should().HaveCount(shouldHaveWarnings ? 0 : 1);
 
        }
 
        [InlineData("net7.0", true)]
        [InlineData("net6.0", false)]
        [Theory]
        public void It_warns_on_incorrectly_cased_culture_codes_of_resources(string tfm, bool shouldHaveWarnings)
        {
            string projectAssetsJsonPath = Path.GetTempFileName();
            var assetsContent = AssetsFileWithInvalidLocale(tfm, "ru-ru");
            File.WriteAllText(projectAssetsJsonPath, assetsContent);
            var task = InitializeTask(out _);
            task.ProjectAssetsFile = projectAssetsJsonPath;
            task.TargetFramework = tfm;
            var writer = new CacheWriter(task, new MockPackageResolver());
            writer.WriteToMemoryStream();
            var engine = task.BuildEngine as MockBuildEngine;
 
            var invalidContextWarnings = engine.Warnings.Where(msg => msg.Code == "NETSDK1187");
            invalidContextWarnings.Should().HaveCount(shouldHaveWarnings ? 1 : 0);
 
            var invalidContextMessages = engine.Messages.Where(msg => msg.Code == "NETSDK1187" && msg.Importance == MessageImportance.Low);
            invalidContextMessages.Should().HaveCount(shouldHaveWarnings ? 0 : 1);
        }
 
        private ResolvePackageAssets InitializeTask(out IEnumerable<PropertyInfo> inputProperties)
        {
            inputProperties = typeof(ResolvePackageAssets)
                .GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)
                .Where(p => !p.IsDefined(typeof(OutputAttribute)) &&
                            p.Name != nameof(ResolvePackageAssets.DesignTimeBuild))
                .OrderBy(p => p.Name, StringComparer.Ordinal);
 
            var requiredProperties = inputProperties
                .Where(p => p.IsDefined(typeof(RequiredAttribute)));
 
            var task = new ResolvePackageAssets();
            // Initialize all required properties as a genuine task invocation would. We do this
            // because HashSettings need not defend against required parameters being null.
            foreach (var property in requiredProperties)
            {
                property.PropertyType.Should().Be(
                    typeof(string),
                    because: $"this test hasn't been updated to handle non-string required task parameters like {property.Name}");
 
                property.SetValue(task, "_");
            }
 
            task.BuildEngine = new MockBuildEngine();
 
            return task;
        }
    }
}