File: ManifestTaskEnvironmentTests.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 Microsoft.Build.Framework;
using Microsoft.Build.Tasks;
using Microsoft.Build.UnitTests;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
 
namespace Microsoft.Build.Tasks.UnitTests
{
    /// <summary>
    /// Tests verifying TaskEnvironment migration compatibility for manifest tasks.
    /// These tests focus on path handling changes from the migration.
    /// </summary>
    public class ManifestTaskEnvironmentTests
    {
        private readonly ITestOutputHelper _output;
 
        public ManifestTaskEnvironmentTests(ITestOutputHelper output) => _output = output;
        // Test 1: Empty ItemSpec - verifies exception handling matches pre-migration behavior
        // GetAbsolutePath throws on empty, but this flows through existing exception handling
        [Fact]
        public void CreateManifestResourceName_EmptyItemSpec_ShouldFail()
        {
            var engine = new MockEngine(_output);
            var task = new CreateCSharpManifestResourceName
            {
                BuildEngine = engine,
                ResourceFiles = new ITaskItem[] { new TaskItem("") },
                RootNamespace = "Test"
            };
 
            // On .NET Framework: returns false with logged error
            // On .NET Core+: throws ArgumentNullException (pre-existing behavior in Path.GetDirectoryName)
#if NETFRAMEWORK
            bool result = task.Execute();
            result.ShouldBeFalse();
#else
            Should.Throw<ArgumentNullException>(() => task.Execute());
#endif
        }
 
        // Test 2: Path with .. segments - critical test for canonicalization
        // GetAbsolutePath does NOT canonicalize, so we wrap with Path.GetFullPath where needed
        [Fact]
        public void CreateManifestResourceName_PathWithDotDot_ShouldResolve()
        {
            using var env = TestEnvironment.Create(_output);
            var folder = env.CreateFolder();
            var subFolder = Path.Combine(folder.Path, "sub");
            Directory.CreateDirectory(subFolder);
            
            var resxPath = Path.Combine(subFolder, "Test.resx");
            File.WriteAllText(resxPath, "<root></root>");
            
            var csPath = Path.Combine(subFolder, "Test.cs");
            File.WriteAllText(csPath, "namespace Test { class Test { } }");
 
            // Use path with .. segments - tests canonicalization
            var pathWithDotDot = Path.Combine(folder.Path, "sub", "..", "sub", "Test.resx");
 
            var task = new CreateCSharpManifestResourceName
            {
                BuildEngine = new MockEngine(_output),
                ResourceFiles = new ITaskItem[] { new TaskItem(pathWithDotDot) },
                RootNamespace = "Test",
                UseDependentUponConvention = true
            };
 
            bool result = task.Execute();
            result.ShouldBeTrue();
        }
 
        // Test 3: Forward slashes - tests path normalization
        [Fact]
        public void CreateManifestResourceName_ForwardSlashes_ShouldWork()
        {
            using var env = TestEnvironment.Create(_output);
            var folder = env.CreateFolder();
            var subFolder = Path.Combine(folder.Path, "Resources");
            Directory.CreateDirectory(subFolder);
            
            var resxPath = Path.Combine(subFolder, "Strings.resx");
            File.WriteAllText(resxPath, "<root></root>");
 
            // Replace backslashes with forward slashes
            var pathWithForwardSlashes = resxPath.Replace('\\', '/');
 
            var task = new CreateCSharpManifestResourceName
            {
                BuildEngine = new MockEngine(_output),
                ResourceFiles = new ITaskItem[] { new TaskItem(pathWithForwardSlashes) },
                RootNamespace = "Test"
            };
 
            bool result = task.Execute();
            result.ShouldBeTrue();
        }
 
        // Test 4: Mixed slashes - tests path normalization handles both
        [Fact]
        public void CreateManifestResourceName_MixedSlashes_ShouldWork()
        {
            using var env = TestEnvironment.Create(_output);
            var folder = env.CreateFolder();
            var subFolder = Path.Combine(folder.Path, "Sub", "Folder");
            Directory.CreateDirectory(subFolder);
            
            var resxPath = Path.Combine(subFolder, "Test.resx");
            File.WriteAllText(resxPath, "<root></root>");
 
            // Mix forward and back slashes
            var mixedPath = folder.Path + "/Sub\\Folder/Test.resx";
 
            var task = new CreateCSharpManifestResourceName
            {
                BuildEngine = new MockEngine(_output),
                ResourceFiles = new ITaskItem[] { new TaskItem(mixedPath) },
                RootNamespace = "Test"
            };
 
            bool result = task.Execute();
            result.ShouldBeTrue();
        }
 
        // Test 5: AddToWin32Manifest with null ApplicationManifest - tests graceful handling
        [Fact]
        public void AddToWin32Manifest_NullApplicationManifest_HandledGracefully()
        {
            using var env = TestEnvironment.Create(_output);
            var folder = env.CreateFolder();
 
            var task = new AddToWin32Manifest
            {
                BuildEngine = new MockEngine(_output),
                ApplicationManifest = null,
                OutputDirectory = folder.Path,
                SupportedArchitectures = "amd64"
            };
 
            // Null is treated as "no manifest" - should generate new one
            bool result = task.Execute();
            result.ShouldBeTrue();
        }
 
        // Test 6: Batch processing - one error should not abort remaining items
        [Fact]
        public void CreateManifestResourceName_BatchProcessing_ContinuesAfterError()
        {
            using var env = TestEnvironment.Create(_output);
            var folder = env.CreateFolder();
            
            // Create one valid resource
            var validPath = Path.Combine(folder.Path, "Valid.resx");
            File.WriteAllText(validPath, "<root></root>");
            
            // Create another valid resource
            var valid2Path = Path.Combine(folder.Path, "Valid2.resx");
            File.WriteAllText(valid2Path, "<root></root>");
 
            // Invalid: DependentUpon points to non-existent file
            var invalidItem = new TaskItem(validPath);
            invalidItem.SetMetadata("DependentUpon", "NonExistent.cs");
 
            var validItem = new TaskItem(valid2Path);
 
            var task = new CreateCSharpManifestResourceName
            {
                BuildEngine = new MockEngine(_output),
                ResourceFiles = new ITaskItem[] { invalidItem, validItem },
                RootNamespace = "Test"
            };
 
            // Should return false due to error, but should still process both items
            bool result = task.Execute();
            result.ShouldBeFalse();
            // The valid item should still have been processed successfully
            task.ManifestResourceNames[1].ShouldNotBeNull();
            task.ManifestResourceNames[1].ItemSpec.ShouldNotBeNullOrEmpty();
        }
 
        // Test 7: Deeply nested folder - tests path handling with many segments
        [Fact]
        public void CreateManifestResourceName_DeepNesting_ShouldWork()
        {
            using var env = TestEnvironment.Create(_output);
            var folder = env.CreateFolder();
            var deepFolder = Path.Combine(folder.Path, "a", "b", "c", "d", "e");
            Directory.CreateDirectory(deepFolder);
            
            var resxPath = Path.Combine(deepFolder, "Test.resx");
            File.WriteAllText(resxPath, "<root></root>");
 
            var task = new CreateCSharpManifestResourceName
            {
                BuildEngine = new MockEngine(_output),
                ResourceFiles = new ITaskItem[] { new TaskItem(resxPath) },
                RootNamespace = "Test"
            };
 
            bool result = task.Execute();
            result.ShouldBeTrue();
        }
 
        // Test 8: Path with spaces - tests no issues with space handling
        [Fact]
        public void CreateManifestResourceName_PathWithSpaces_ShouldWork()
        {
            using var env = TestEnvironment.Create(_output);
            var folder = env.CreateFolder();
            var spaceFolder = Path.Combine(folder.Path, "My Resources");
            Directory.CreateDirectory(spaceFolder);
            
            var resxPath = Path.Combine(spaceFolder, "My Strings.resx");
            File.WriteAllText(resxPath, "<root></root>");
 
            var task = new CreateCSharpManifestResourceName
            {
                BuildEngine = new MockEngine(_output),
                ResourceFiles = new ITaskItem[] { new TaskItem(resxPath) },
                RootNamespace = "Test"
            };
 
            bool result = task.Execute();
            result.ShouldBeTrue();
        }
    }
}