File: Collections\OMcollections_tests.cs
Web Access
Project: ..\..\..\src\Build.UnitTests\Microsoft.Build.Engine.UnitTests.csproj (Microsoft.Build.Engine.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.BackEnd;
using Microsoft.Build.Collections;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.UnitTests.BackEnd;
using Shouldly;
using Xunit;
using ObjectModel = System.Collections.ObjectModel;
 
#nullable disable
 
namespace Microsoft.Build.UnitTests.OM.Collections
{
    /// <summary>
    /// Tests for several of the collections classes
    /// </summary>
    public class OMcollections_Tests
    {
        /// <summary>
        /// End to end test of PropertyDictionary
        /// </summary>
        [Fact]
        public void BasicPropertyDictionary()
        {
            PropertyDictionary<ProjectPropertyInstance> properties = new PropertyDictionary<ProjectPropertyInstance>();
 
            ProjectPropertyInstance p1 = GetPropertyInstance("p1", "v1");
            ProjectPropertyInstance p2 = GetPropertyInstance("p2", "v2");
            ProjectPropertyInstance p3 = GetPropertyInstance("p1", "v1");
            ProjectPropertyInstance p4 = GetPropertyInstance("p2", "v3");
 
            properties.Set(p1);
            properties.Set(p2);
            properties.Set(p3);
            properties.Set(p1);
            properties.Set(p4);
 
            Assert.Equal(2, properties.Count);
            Assert.Equal("v1", properties["p1"].EvaluatedValue);
            Assert.Equal("v3", properties["p2"].EvaluatedValue);
 
            Assert.True(properties.Remove("p1"));
            Assert.Null(properties["p1"]);
 
            Assert.False(properties.Remove("x"));
 
            properties.Clear();
 
            Assert.Empty(properties);
        }
 
        /// <summary>
        /// Test dictionary serialization with properties
        /// </summary>
        [Fact]
        public void PropertyDictionarySerialization()
        {
            PropertyDictionary<ProjectPropertyInstance> properties = new PropertyDictionary<ProjectPropertyInstance>();
 
            ProjectPropertyInstance p1 = GetPropertyInstance("p1", "v1");
            ProjectPropertyInstance p2 = GetPropertyInstance("p2", "v2");
            ProjectPropertyInstance p3 = GetPropertyInstance("p1", "v1");
            ProjectPropertyInstance p4 = GetPropertyInstance("p2", "v3");
 
            properties.Set(p1);
            properties.Set(p2);
            properties.Set(p3);
            properties.Set(p1);
            properties.Set(p4);
 
            TranslationHelpers.GetWriteTranslator().TranslateDictionary<PropertyDictionary<ProjectPropertyInstance>, ProjectPropertyInstance>(ref properties, ProjectPropertyInstance.FactoryForDeserialization);
            PropertyDictionary<ProjectPropertyInstance> deserializedProperties = null;
            TranslationHelpers.GetReadTranslator().TranslateDictionary<PropertyDictionary<ProjectPropertyInstance>, ProjectPropertyInstance>(ref deserializedProperties, ProjectPropertyInstance.FactoryForDeserialization);
 
            Assert.Equal(properties, deserializedProperties);
        }
 
        /// <summary>
        /// Test dictionary serialization with no properties
        /// </summary>
        [Fact]
        public void PropertyDictionarySerializationEmpty()
        {
            PropertyDictionary<ProjectPropertyInstance> properties = new PropertyDictionary<ProjectPropertyInstance>();
 
            TranslationHelpers.GetWriteTranslator().TranslateDictionary<PropertyDictionary<ProjectPropertyInstance>, ProjectPropertyInstance>(ref properties, ProjectPropertyInstance.FactoryForDeserialization);
            PropertyDictionary<ProjectPropertyInstance> deserializedProperties = null;
            TranslationHelpers.GetReadTranslator().TranslateDictionary<PropertyDictionary<ProjectPropertyInstance>, ProjectPropertyInstance>(ref deserializedProperties, ProjectPropertyInstance.FactoryForDeserialization);
 
            Assert.Equal(properties, deserializedProperties);
        }
 
        /// <summary>
        /// End to end test of ItemDictionary
        /// </summary>
        [Fact]
        public void BasicItemDictionary()
        {
            ItemDictionary<ProjectItemInstance> items = new ItemDictionary<ProjectItemInstance>();
 
            // Clearing empty collection
            items.Clear();
 
            // Enumeration of empty collection
            using (IEnumerator<ProjectItemInstance> enumerator = items.GetEnumerator())
            {
                enumerator.MoveNext().ShouldBeFalse();
                Should.Throw<InvalidOperationException>(() =>
                {
                    object o = ((IEnumerator)enumerator).Current;
                });
                enumerator.Current.ShouldBeNull();
            }
 
            List<ProjectItemInstance> list = new List<ProjectItemInstance>();
            foreach (ProjectItemInstance item in items)
            {
                list.Add(item);
            }
 
            Assert.Empty(list);
 
            // Cause an empty list for type 'x' to be added
            ICollection<ProjectItemInstance> itemList = items["x"];
 
            // Enumerate empty collection, with an empty list in it
            foreach (ProjectItemInstance item in items)
            {
                list.Add(item);
            }
 
            Assert.Empty(list);
 
            // Add and remove some items
            ProjectItemInstance item1 = GetItemInstance("i", "i1");
            Assert.False(items.Remove(item1));
            Assert.Empty(items["j"]);
 
            items.Add(item1);
            Assert.Single(items["i"]);
            Assert.Equal(item1, items["i"].First());
 
            ProjectItemInstance item2 = GetItemInstance("i", "i2");
            items.Add(item2);
            ProjectItemInstance item3 = GetItemInstance("j", "j1");
            items.Add(item3);
 
            // Enumerate to verify contents
            list = new List<ProjectItemInstance>();
            foreach (ProjectItemInstance item in items)
            {
                list.Add(item);
            }
 
            list.Sort(ProjectItemInstanceComparer);
            Assert.Equal(item1, list[0]);
            Assert.Equal(item2, list[1]);
            Assert.Equal(item3, list[2]);
 
            // Direct operations on the enumerator
            using (IEnumerator<ProjectItemInstance> enumerator = items.GetEnumerator())
            {
                Assert.Null(enumerator.Current);
                Assert.True(enumerator.MoveNext());
                Assert.NotNull(enumerator.Current);
                enumerator.Reset();
                Assert.Null(enumerator.Current);
                Assert.True(enumerator.MoveNext());
                Assert.NotNull(enumerator.Current);
            }
        }
 
        /// <summary>
        /// Null backing collection should be like empty collection
        /// </summary>
        [Fact]
        public void ReadOnlyDictionaryNullBackingClone()
        {
            var dictionary = CreateCloneDictionary<string>(null, StringComparer.OrdinalIgnoreCase);
            Assert.Empty(dictionary);
        }
 
        /// <summary>
        /// Null backing collection should be like empty collection
        /// </summary>
        [Fact]
        public void ReadOnlyDictionaryNullBackingWrapper()
        {
            var dictionary = new ObjectModel.ReadOnlyDictionary<string, string>(new Dictionary<string, string>(0));
            Assert.Empty(dictionary);
        }
 
        /// <summary>
        /// Cloning constructor should not see subsequent changes
        /// </summary>
        [Fact]
        public void ReadOnlyDictionaryClone()
        {
            var dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            dictionary.Add("p", "v");
 
            var readOnlyDictionary = CreateCloneDictionary(dictionary, StringComparer.OrdinalIgnoreCase);
            dictionary.Add("p2", "v2");
 
            Assert.Single(readOnlyDictionary);
            Assert.True(readOnlyDictionary.ContainsKey("P"));
            Assert.False(readOnlyDictionary.ContainsKey("p2"));
        }
 
        /// <summary>
        /// Wrapping constructor should be "live"
        /// </summary>
        [Fact]
        public void ReadOnlyDictionaryWrapper()
        {
            var dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            dictionary.Add("p", "v");
 
            var readOnlyDictionary = new ObjectModel.ReadOnlyDictionary<string, string>(dictionary);
            dictionary.Add("p2", "v2");
 
            Assert.Equal(2, dictionary.Count);
            Assert.True(dictionary.ContainsKey("p2"));
        }
 
        /// <summary>
        /// Null backing collection should be an error
        /// </summary>
        [Fact]
        public void ReadOnlyCollectionNullBacking()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                new ReadOnlyCollection<string>(null);
            });
        }
        /// <summary>
        /// Verify non generic enumeration does not recurse
        /// ie., GetEnumerator() does not call itself
        /// </summary>
        [Fact]
        public void ReadOnlyDictionaryNonGenericEnumeration()
        {
            var backing = new Dictionary<string, string>();
            var collection = new ObjectModel.ReadOnlyDictionary<string, string>(backing);
            IEnumerable enumerable = (IEnumerable)collection;
 
            // Does not overflow stack:
            foreach (object o in enumerable)
            {
            }
        }
 
        /// <summary>
        /// Verify that the converting dictionary functions.
        /// </summary>
        [Fact]
        public void ReadOnlyConvertingDictionary()
        {
            Dictionary<string, string> values = new Dictionary<string, string>();
            values["one"] = "1";
            values["two"] = "2";
            values["three"] = "3";
 
            Dictionary<string, int> convertedValues = new Dictionary<string, int>();
            convertedValues["one"] = 1;
            convertedValues["two"] = 2;
            convertedValues["three"] = 3;
 
            ReadOnlyConvertingDictionary<string, string, int> convertingCollection = new ReadOnlyConvertingDictionary<string, string, int>(values, delegate (string x) { return Convert.ToInt32(x); });
            Assert.Equal(3, convertingCollection.Count);
            Assert.True(convertingCollection.IsReadOnly);
 
            foreach (KeyValuePair<string, int> value in convertingCollection)
            {
                Assert.Equal(convertedValues[value.Key], value.Value);
            }
        }
 
        /// <summary>
        /// Verify non generic enumeration does not recurse
        /// ie., GetEnumerator() does not call itself
        /// </summary>
        [Fact]
        public void ReadOnlyCollectionNonGenericEnumeration()
        {
            var backing = new List<string>();
            var collection = new ReadOnlyCollection<string>(backing);
            IEnumerable enumerable = (IEnumerable)collection;
 
            // Does not overflow stack:
            foreach (object o in enumerable)
            {
            }
        }
 
        /// <summary>
        /// Helper to make a ProjectPropertyInstance.
        /// </summary>
        private static ProjectPropertyInstance GetPropertyInstance(string name, string value)
        {
            Project project = new Project();
            ProjectInstance projectInstance = project.CreateProjectInstance();
            ProjectPropertyInstance property = projectInstance.SetProperty(name, value);
 
            return property;
        }
 
        /// <summary>
        /// Helper to make a ProjectItemInstance.
        /// </summary>
        private static ProjectItemInstance GetItemInstance(string itemType, string evaluatedInclude)
        {
            Project project = new Project();
            ProjectInstance projectInstance = project.CreateProjectInstance();
            ProjectItemInstance item = projectInstance.AddItem(itemType, evaluatedInclude);
 
            return item;
        }
 
        /// <summary>
        /// Creates a copy of a dictionary and returns a read-only dictionary around the results.
        /// </summary>
        /// <typeparam name="TValue">The value stored in the dictionary</typeparam>
        /// <param name="dictionary">Dictionary to clone.</param>
        private static ObjectModel.ReadOnlyDictionary<string, TValue> CreateCloneDictionary<TValue>(IDictionary<string, TValue> dictionary, StringComparer strComparer)
        {
            Dictionary<string, TValue> clone;
            if (dictionary == null)
            {
                clone = new Dictionary<string, TValue>(0);
            }
            else
            {
                clone = new Dictionary<string, TValue>(dictionary, strComparer);
            }
 
            return new ObjectModel.ReadOnlyDictionary<string, TValue>(clone);
        }
 
        /// <summary>
        /// Simple comparer for ProjectItemInstances. Ought to compare metadata etc.
        /// </summary>
        private int ProjectItemInstanceComparer(ProjectItemInstance one, ProjectItemInstance two)
        {
            return String.Compare(one.EvaluatedInclude, two.EvaluatedInclude);
        }
    }
}