File: AddToWin32Manifest_Tests.cs
Web Access
Project: ..\..\..\src\Tasks.UnitTests\Microsoft.Build.Tasks.UnitTests.csproj (Microsoft.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.
 
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Xml;
using Microsoft.Build.Evaluation;
using Microsoft.Build.UnitTests;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.Build.Tasks.UnitTests
{
    public class AddToWin32Manifest_Tests
    {
        private static string TestAssetsRootPath { get; } = Path.Combine(
            Path.GetDirectoryName(typeof(AddToWin32Manifest_Tests).Assembly.Location) ?? AppContext.BaseDirectory,
            "TestResources",
            "Manifests");
 
        private readonly ITestOutputHelper _testOutput;
 
        public AddToWin32Manifest_Tests(ITestOutputHelper testOutput) => _testOutput = testOutput;
 
        [Theory]
        [InlineData("testManifestWithInvalidSupportedArchs.manifest", false)]
        [InlineData("testManifestWithApplicationDefined.manifest", true)]
        [InlineData("testManifestSavesTheCurrentNodesPositions.manifest", true)]
        [InlineData("testManifestNoPrefixes.manifest", true)]
        [InlineData(null, true)]
        public void ManifestPopulationCheck(string? manifestName, bool expectedResult)
        {
            AddToWin32Manifest task = new AddToWin32Manifest()
            {
                BuildEngine = new MockEngine(_testOutput)
            };
 
            using (TestEnvironment env = TestEnvironment.Create())
            {
                var tempOutput = env.CreateFolder().Path;
                task.OutputDirectory = tempOutput;
                task.SupportedArchitectures = "amd64 arm64";
                if (!string.IsNullOrEmpty(manifestName))
                {
                    task.ApplicationManifest = new TaskItem(Path.Combine(TestAssetsRootPath, manifestName));
                }
 
                var result = task.Execute();
 
                result.ShouldBe(expectedResult);
 
                if (result)
                {
                    string generatedManifest = task.ManifestPath;
                    string expectedManifest = Path.Combine(TestAssetsRootPath, $"{manifestName ?? "default.win32manifest"}_expected");
 
                    XmlDocument expectedDoc = new XmlDocument();
                    XmlDocument actualDoc = new XmlDocument();
 
                    expectedDoc.Load(expectedManifest);
                    actualDoc.Load(generatedManifest);
 
                    expectedDoc.OuterXml.ShouldBe(actualDoc.OuterXml);
                    expectedDoc.InnerXml.ShouldBe(actualDoc.InnerXml);
                }
            }
        }
 
        [SupportedOSPlatform("windows")]
        [WindowsOnlyTheory]
        [InlineData(null, true)]
        [InlineData("buildIn.manifest", true)]
        [InlineData("testManifestWithValidSupportedArchs.manifest", true)]
        public void E2EScenarioTests(string? manifestName, bool expectedResult)
        {
            using (TestEnvironment env = TestEnvironment.Create())
            {
                var outputPath = env.CreateFolder().Path;
                string projectContent = @$"
                <Project DefaultTargets=""Build"">
                    <Import Project=""$(MSBuildBinPath)\Microsoft.Common.props"" />
 
                    <PropertyGroup>
                        <Platform>AnyCPU</Platform>
                        <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
                        <OutputType>Library</OutputType>
                        <PreferNativeArm64>true</PreferNativeArm64>
                        <Prefer32Bit>false</Prefer32Bit>
                        {(!string.IsNullOrEmpty(manifestName) ? $"<ApplicationManifest>{manifestName}</ApplicationManifest>" : "")}
                        <IntermediateOutputPath>{outputPath}</IntermediateOutputPath>
                    </PropertyGroup>
 
                    <Target Name=""Build""/>
                    <Import Project=""$(MSBuildBinPath)\Microsoft.CSharp.targets"" />
 
                </Project>
                ";
 
                var projectFolder = env.CreateFolder();
                var projectFile = env.CreateFile(projectFolder, "test.csproj", projectContent).Path;
 
                // copy application manifest
                if (!string.IsNullOrEmpty(manifestName))
                {
                    File.Copy(Path.Combine(TestAssetsRootPath, manifestName), Path.Combine(projectFolder.Path, manifestName));
                }
 
                Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(projectFile, touchProject: false);
 
                bool result = project.Build(new MockLogger(_testOutput));
                result.ShouldBe(expectedResult);
 
                // #2 - represents the name for native resource (Win 32 resource), #24 - the type (Manifest) 
                byte[]? actualManifestBytes = AssemblyNativeResourceManager.GetResourceFromExecutable(Path.Combine(outputPath, "test.dll"), "#2", "#24");
 
                // check manifest content
                if (actualManifestBytes != null)
                {
                    string expectedManifest = Path.Combine(TestAssetsRootPath, $"{manifestName ?? "default.win32manifest"}_expected");
 
                    XmlDocument expectedDoc = new XmlDocument();
                    XmlDocument actualDoc = new XmlDocument();
 
                    expectedDoc.Load(expectedManifest);
                    using (MemoryStream stream = new MemoryStream(actualManifestBytes))
                    {
                        actualDoc.Load(stream);
                    }
 
                    NormalizeLineEndings(expectedDoc.OuterXml).ShouldBe(NormalizeLineEndings(actualDoc.OuterXml));
                    NormalizeLineEndings(expectedDoc.InnerText).ShouldBe(NormalizeLineEndings(actualDoc.InnerText));
                }
            }
 
            static string NormalizeLineEndings(string input) => input.Replace("\r\n", "\n").Replace("\r", "\n");
        }
 
        [SupportedOSPlatform("windows")]
        internal sealed class AssemblyNativeResourceManager
        {
            public enum LoadLibraryFlags : uint { LOAD_LIBRARY_AS_DATAFILE = 2 };
 
            [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern IntPtr LoadLibrary(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);
 
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);
 
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);
 
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr LockResource(IntPtr hResData);
 
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);
 
            public static byte[]? GetResourceFromExecutable(string assembly, string lpName, string lpType)
            {
                IntPtr hModule = LoadLibrary(assembly, IntPtr.Zero, LoadLibraryFlags.LOAD_LIBRARY_AS_DATAFILE);
                try
                {
                    if (hModule != IntPtr.Zero)
                    {
                        IntPtr hResource = FindResource(hModule, lpName, lpType);
                        if (hResource != IntPtr.Zero)
                        {
                            uint resSize = SizeofResource(hModule, hResource);
                            IntPtr resData = LoadResource(hModule, hResource);
                            if (resData != IntPtr.Zero)
                            {
                                byte[] uiBytes = new byte[resSize];
                                IntPtr ipMemorySource = LockResource(resData);
                                Marshal.Copy(ipMemorySource, uiBytes, 0, (int)resSize);
 
                                return uiBytes;
                            }
                        }
                    }
                }
                finally
                {
                    NativeMethodsShared.FreeLibrary(hModule);
                }
 
                return null;
            }
        }
    }
}