File: TaskItem_Tests.cs
Web Access
Project: ..\..\..\src\Utilities.UnitTests\Microsoft.Build.Utilities.UnitTests.csproj (Microsoft.Build.Utilities.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.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.NetCore.Extensions;
 
#pragma warning disable 0219
 
#nullable disable
 
namespace Microsoft.Build.UnitTests
{
    public class TaskItemTests
    {
        // Make sure a TaskItem can be constructed using an ITaskItem
        [Fact]
        public void ConstructWithITaskItem()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = "Monkey.txt";
            from.SetMetadata("Dog", "Bingo");
            from.SetMetadata("Cat", "Morris");
 
            TaskItem to = new TaskItem((ITaskItem)from);
            to.ItemSpec.ShouldBe("Monkey.txt");
            ((string)to).ShouldBe("Monkey.txt");
 
            to.GetMetadata("Dog").ShouldBe("Bingo");
            to.GetMetadata("Cat").ShouldBe("Morris");
 
            // Test that item metadata are case-insensitive.
            to.SetMetadata("CaT", "");
            to.GetMetadata("Cat").ShouldBe("");
 
            // manipulate the item-spec a bit
            to.GetMetadata(FileUtilities.ItemSpecModifiers.Filename).ShouldBe("Monkey");
            to.GetMetadata(FileUtilities.ItemSpecModifiers.Extension).ShouldBe(".txt");
            to.GetMetadata(FileUtilities.ItemSpecModifiers.RelativeDir).ShouldBe(string.Empty);
        }
 
        // Make sure metadata can be cloned from an existing ITaskItem
        [Fact]
        public void CopyMetadataFromITaskItem()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = "Monkey.txt";
            from.SetMetadata("Dog", "Bingo");
            from.SetMetadata("Cat", "Morris");
            from.SetMetadata("Bird", "Big");
 
            TaskItem to = new TaskItem();
            to.ItemSpec = "Bonobo.txt";
            to.SetMetadata("Sponge", "Bob");
            to.SetMetadata("Dog", "Harriet");
            to.SetMetadata("Cat", "Mike");
            from.CopyMetadataTo(to);
 
            to.ItemSpec.ShouldBe("Bonobo.txt");          // ItemSpec is never overwritten
            to.GetMetadata("Sponge").ShouldBe("Bob");   // Metadata not in source are preserved.
            to.GetMetadata("Dog").ShouldBe("Harriet");  // Metadata present on destination are not overwritten.
            to.GetMetadata("Cat").ShouldBe("Mike");
            to.GetMetadata("Bird").ShouldBe("Big");
        }
 
        [Fact]
        public void NullITaskItem()
        {
            Should.Throw<ArgumentNullException>(() =>
            {
                ITaskItem item = null;
                TaskItem taskItem = new TaskItem(item);
 
                // no NullReferenceException
            });
        }
 
        [Fact]
        public void MetadataNamesAndCount()
        {
            TaskItem taskItem = new TaskItem("x");
 
            // Without custom metadata, should return the built in metadata
            taskItem.MetadataNames.Cast<string>().ShouldBeSetEquivalentTo(FileUtilities.ItemSpecModifiers.All);
            taskItem.MetadataCount.ShouldBe(FileUtilities.ItemSpecModifiers.All.Length);
 
            // Now add one
            taskItem.SetMetadata("m", "m1");
 
            taskItem.MetadataNames.Cast<string>().ShouldBeSetEquivalentTo(FileUtilities.ItemSpecModifiers.All.Concat(new[] { "m" }));
            taskItem.MetadataCount.ShouldBe(FileUtilities.ItemSpecModifiers.All.Length + 1);
        }
 
        [Fact]
        public void NullITaskItemCast()
        {
            Should.Throw<ArgumentNullException>(() =>
            {
                TaskItem item = null;
                string result = (string)item;
 
                // no NullReferenceException
            });
        }
        [Fact]
        public void ConstructFromDictionary()
        {
            Hashtable h = new Hashtable();
            h[FileUtilities.ItemSpecModifiers.Filename] = "foo";
            h[FileUtilities.ItemSpecModifiers.Extension] = "bar";
            h["custom"] = "hello";
 
            TaskItem t = new TaskItem("bamboo.baz", h);
 
            // item-spec modifiers were not overridden by dictionary passed to constructor
            t.GetMetadata(FileUtilities.ItemSpecModifiers.Filename).ShouldBe("bamboo");
            t.GetMetadata(FileUtilities.ItemSpecModifiers.Extension).ShouldBe(".baz");
            t.GetMetadata("CUSTOM").ShouldBe("hello");
        }
 
        [Fact]
        public void CannotChangeModifiers()
        {
            Should.Throw<ArgumentException>(() =>
            {
                TaskItem t = new TaskItem("foo");
 
                try
                {
                    t.SetMetadata(FileUtilities.ItemSpecModifiers.FullPath, "bazbaz");
                }
                catch (Exception e)
                {
                    // so I can see the exception message in NUnit's "Standard Out" window
                    Console.WriteLine(e.Message);
                    throw;
                }
            });
        }
 
        [Fact]
        public void CannotRemoveModifiers()
        {
            Should.Throw<ArgumentException>(() =>
            {
                TaskItem t = new TaskItem("foor");
 
                try
                {
                    t.RemoveMetadata(FileUtilities.ItemSpecModifiers.RootDir);
                }
                catch (Exception e)
                {
                    // so I can see the exception message in NUnit's "Standard Out" window
                    Console.WriteLine(e.Message);
                    throw;
                }
            });
        }
        [Fact]
        public void CheckMetadataCount()
        {
            TaskItem t = new TaskItem("foo");
 
            t.MetadataCount.ShouldBe(FileUtilities.ItemSpecModifiers.All.Length);
 
            t.SetMetadata("grog", "RUM");
 
            t.MetadataCount.ShouldBe(FileUtilities.ItemSpecModifiers.All.Length + 1);
        }
 
        [Fact]
        public void NonexistentRequestFullPath()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = "Monkey.txt";
            from.GetMetadata(FileUtilities.ItemSpecModifiers.FullPath).ShouldBe(
                Path.Combine(
                    Directory.GetCurrentDirectory(),
                    "Monkey.txt"));
        }
 
        [Fact]
        public void NonexistentRequestRootDir()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = "Monkey.txt";
            from.GetMetadata(FileUtilities.ItemSpecModifiers.RootDir).ShouldBe(Path.GetPathRoot(from.GetMetadata(FileUtilities.ItemSpecModifiers.FullPath)));
        }
 
        [Fact]
        public void NonexistentRequestFilename()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = "Monkey.txt";
            from.GetMetadata(FileUtilities.ItemSpecModifiers.Filename).ShouldBe("Monkey");
        }
 
        [Fact]
        public void NonexistentRequestExtension()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = "Monkey.txt";
            from.GetMetadata(FileUtilities.ItemSpecModifiers.Extension).ShouldBe(".txt");
        }
 
        [Fact]
        public void NonexistentRequestRelativeDir()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = "Monkey.txt";
            from.GetMetadata(FileUtilities.ItemSpecModifiers.RelativeDir).Length.ShouldBe(0);
        }
 
        [Fact]
        public void NonexistentRequestDirectory()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = NativeMethodsShared.IsWindows ? @"c:\subdir\Monkey.txt" : "/subdir/Monkey.txt";
            from.GetMetadata(FileUtilities.ItemSpecModifiers.Directory).ShouldBe(NativeMethodsShared.IsWindows ? @"subdir\" : "subdir/");
        }
 
        [WindowsOnlyFact("UNC is not implemented except under Windows.")]
        public void NonexistentRequestDirectoryUNC()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = @"\\local\share\subdir\Monkey.txt";
            from.GetMetadata(FileUtilities.ItemSpecModifiers.Directory).ShouldBe(@"subdir\");
        }
 
        [Fact]
        public void NonexistentRequestRecursiveDir()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = "Monkey.txt";
 
            from.GetMetadata(FileUtilities.ItemSpecModifiers.RecursiveDir).Length.ShouldBe(0);
        }
 
        [Fact]
        public void NonexistentRequestIdentity()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = "Monkey.txt";
            from.GetMetadata(FileUtilities.ItemSpecModifiers.Identity).ShouldBe("Monkey.txt");
        }
 
        [Fact]
        public void RequestTimeStamps()
        {
            TaskItem from = new TaskItem();
            from.ItemSpec = FileUtilities.GetTemporaryFile();
 
            from.GetMetadata(FileUtilities.ItemSpecModifiers.ModifiedTime).Length.ShouldBeGreaterThan(0);
 
            from.GetMetadata(FileUtilities.ItemSpecModifiers.CreatedTime).Length.ShouldBeGreaterThan(0);
 
            from.GetMetadata(FileUtilities.ItemSpecModifiers.AccessedTime).Length.ShouldBeGreaterThan(0);
 
            File.Delete(from.ItemSpec);
 
            from.GetMetadata(FileUtilities.ItemSpecModifiers.ModifiedTime).Length.ShouldBe(0);
 
            from.GetMetadata(FileUtilities.ItemSpecModifiers.CreatedTime).Length.ShouldBe(0);
 
            from.GetMetadata(FileUtilities.ItemSpecModifiers.AccessedTime).Length.ShouldBe(0);
        }
 
        /// <summary>
        /// Verify metadata cannot be created with null name
        /// </summary>
        [Fact]
        public void CreateNullNamedMetadata()
        {
            Should.Throw<ArgumentNullException>(() =>
            {
                TaskItem item = new TaskItem("foo");
                item.SetMetadata(null, "x");
            });
        }
        /// <summary>
        /// Verify metadata cannot be created with empty name
        /// </summary>
        [Fact]
        public void CreateEmptyNamedMetadata()
        {
            Should.Throw<ArgumentException>(() =>
            {
                TaskItem item = new TaskItem("foo");
                item.SetMetadata("", "x");
            });
        }
        /// <summary>
        /// Create a TaskItem with a null metadata value -- this is allowed, but
        /// internally converted to the empty string.
        /// </summary>
        [Fact]
        public void CreateTaskItemWithNullMetadata()
        {
            IDictionary<string, string> metadata = new Dictionary<string, string>();
            metadata.Add("m", null);
 
            TaskItem item = new TaskItem("bar", (IDictionary)metadata);
            item.GetMetadata("m").ShouldBe(string.Empty);
        }
 
        /// <summary>
        /// Set metadata value to null value -- this is allowed, but
        /// internally converted to the empty string.
        /// </summary>
        [Fact]
        public void SetNullMetadataValue()
        {
            TaskItem item = new TaskItem("bar");
            item.SetMetadata("m", null);
            item.GetMetadata("m").ShouldBe(string.Empty);
        }
 
        [Fact]
        public void ImplementsIMetadataContainer()
        {
            Dictionary<string, string> metadata = new()
            {
                { "a", "a1" },
                { "b", "b1" },
            };
 
            TaskItem item = new TaskItem("foo");
            IMetadataContainer metadataContainer = (IMetadataContainer)item;
 
            metadataContainer.ImportMetadata(metadata);
 
            var actualMetadata = metadataContainer.EnumerateMetadata().OrderBy(metadata => metadata.Key).ToList();
            var expectedMetadata = metadata.OrderBy(metadata => metadata.Value).ToList();
            Assert.True(actualMetadata.SequenceEqual(expectedMetadata));
        }
 
#if FEATURE_APPDOMAIN
        /// <summary>
        /// Test that task items can be successfully constructed based on a task item from another appdomain.
        /// </summary>
        [Fact]
        public void RemoteTaskItem()
        {
            AppDomain appDomain = null;
            try
            {
                appDomain = AppDomain.CreateDomain(
                                "generateResourceAppDomain",
                                null,
                                AppDomain.CurrentDomain.SetupInformation);
 
                object obj = appDomain.CreateInstanceFromAndUnwrap(
                       typeof(TaskItemCreator).Module.FullyQualifiedName,
                       typeof(TaskItemCreator).FullName);
 
                TaskItemCreator creator = (TaskItemCreator)obj;
 
                IDictionary<string, string> metadata = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                metadata.Add("c", "C");
                metadata.Add("d", "D");
 
                creator.Run(new[] { "a", "b" }, metadata);
 
                ITaskItem[] itemsInThisAppDomain = new ITaskItem[creator.CreatedTaskItems.Length];
 
                for (int i = 0; i < creator.CreatedTaskItems.Length; i++)
                {
                    itemsInThisAppDomain[i] = new TaskItem(creator.CreatedTaskItems[i]);
 
                    itemsInThisAppDomain[i].ItemSpec.ShouldBe(creator.CreatedTaskItems[i].ItemSpec);
                    itemsInThisAppDomain[i].MetadataCount.ShouldBe(creator.CreatedTaskItems[i].MetadataCount + 1);
 
                    Dictionary<string, string> creatorMetadata = new Dictionary<string, string>(creator.CreatedTaskItems[i].MetadataCount);
                    foreach (string metadatum in creator.CreatedTaskItems[i].MetadataNames)
                    {
                        creatorMetadata[metadatum] = creator.CreatedTaskItems[i].GetMetadata(metadatum);
                    }
 
                    Dictionary<string, string> metadataInThisAppDomain = new Dictionary<string, string>(itemsInThisAppDomain[i].MetadataCount);
                    foreach (string metadatum in itemsInThisAppDomain[i].MetadataNames)
                    {
                        if (!string.Equals("OriginalItemSpec", metadatum))
                        {
                            metadataInThisAppDomain[metadatum] = itemsInThisAppDomain[i].GetMetadata(metadatum);
                        }
                    }
 
                    metadataInThisAppDomain.ShouldBe(creatorMetadata, ignoreOrder: true);
                }
            }
            finally
            {
                if (appDomain != null)
                {
                    AppDomain.Unload(appDomain);
                }
            }
        }
 
        /// <summary>
        /// Miniature class to be remoted to another appdomain that just creates some TaskItems and makes them available for returning.
        /// </summary>
        private sealed class TaskItemCreator
#if FEATURE_APPDOMAIN
                : MarshalByRefObject
#endif
        {
            /// <summary>
            /// Task items that will be consumed by the other appdomain
            /// </summary>
            public ITaskItem[] CreatedTaskItems
            {
                get;
                private set;
            }
 
            /// <summary>
            /// Creates task items
            /// </summary>
            public void Run(string[] includes, IDictionary<string, string> metadataToAdd)
            {
                ErrorUtilities.VerifyThrowArgumentNull(includes, nameof(includes));
 
                CreatedTaskItems = new TaskItem[includes.Length];
 
                for (int i = 0; i < includes.Length; i++)
                {
                    CreatedTaskItems[i] = new TaskItem(includes[i], (IDictionary)metadataToAdd);
                }
            }
        }
#endif
    }
}