File: Collections\List\IEnumerable.NonGeneric.Tests.cs
Web Access
Project: src\src\Compilers\Core\CodeAnalysisTest\Microsoft.CodeAnalysis.UnitTests.csproj (Microsoft.CodeAnalysis.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
// NOTE: This code is derived from an implementation originally in dotnet/runtime:
// https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/tests/System/Collections/IEnumerable.NonGeneric.Tests.cs
//
// See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the
// reference implementation.
 
using System;
using System.Collections;
using System.Collections.Generic;
using Xunit;
 
namespace Microsoft.CodeAnalysis.UnitTests.Collections
{
    /// <summary>
    /// Contains tests that ensure the correctness of any class that implements the nongeneric
    /// IEnumerable interface
    /// </summary>
    public abstract partial class IEnumerable_NonGeneric_Tests : TestBase
    {
        #region IEnumerable Helper Methods
 
        /// <summary>
        /// Creates an instance of an IEnumerable that can be used for testing.
        /// </summary>
        /// <param name="count">The number of unique items that the returned IEnumerable contains.</param>
        /// <returns>An instance of an IEnumerable that can be used for testing.</returns>
        protected abstract IEnumerable NonGenericIEnumerableFactory(int count);
 
        /// <summary>
        /// Modifies the given IEnumerable such that any enumerators for that IEnumerable will be
        /// invalidated.
        /// </summary>
        /// <param name="enumerable">An IEnumerable to modify</param>
        /// <returns>true if the enumerable was successfully modified. Else false.</returns>
        protected delegate bool ModifyEnumerable(IEnumerable enumerable);
 
        /// <summary>
        /// To be implemented in the concrete collections test classes. Returns a set of ModifyEnumerable delegates
        /// that modify the enumerable passed to them.
        /// </summary>
        protected abstract IEnumerable<ModifyEnumerable> GetModifyEnumerables(ModifyOperation operations);
 
        protected virtual ModifyOperation ModifyEnumeratorThrows => ModifyOperation.Add | ModifyOperation.Insert | ModifyOperation.Overwrite | ModifyOperation.Remove | ModifyOperation.Clear;
 
        protected virtual ModifyOperation ModifyEnumeratorAllowed => ModifyOperation.None;
 
        /// <summary>
        /// The Reset method is provided for COM interoperability. It does not necessarily need to be
        /// implemented; instead, the implementer can simply throw a NotSupportedException.
        ///
        /// If Reset is not implemented, this property must return False. The default value is true.
        /// </summary>
        protected virtual bool ResetImplemented => true;
 
        /// <summary>
        /// When calling Current of the enumerator before the first MoveNext, after the end of the collection,
        /// or after modification of the enumeration, the resulting behavior is undefined. Tests are included
        /// to cover two behavioral scenarios:
        ///   - Throwing an InvalidOperationException
        ///   - Returning an undefined value.
        ///
        /// If this property is set to true, the tests ensure that the exception is thrown. The default value is
        /// false.
        /// </summary>
        protected virtual bool Enumerator_Current_UndefinedOperation_Throws => false;
 
        /// <summary>
        /// When calling MoveNext or Reset after modification of the enumeration, the resulting behavior is
        /// undefined. Tests are included to cover two behavioral scenarios:
        ///   - Throwing an InvalidOperationException
        ///   - Execute MoveNext or Reset.
        ///
        /// If this property is set to true, the tests ensure that the exception is thrown. The default value is
        /// true.
        /// </summary>
        protected virtual bool Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException => true;
 
        /// <summary>
        /// When calling MoveNext or Reset after modification of an empty enumeration, the resulting behavior is
        /// undefined. Tests are included to cover two behavioral scenarios:
        ///   - Throwing an InvalidOperationException
        ///   - Execute MoveNext or Reset.
        ///
        /// If this property is set to true, the tests ensure that the exception is thrown. The default value is
        /// <see cref="Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException"/>.
        /// </summary>
        protected virtual bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException;
 
        /// <summary>Whether the enumerator returned from GetEnumerator is a singleton instance when the collection is empty.</summary>
        protected virtual bool Enumerator_Empty_UsesSingletonInstance => false;
 
        /// <summary>
        /// Whether the collection can be serialized.
        /// </summary>
        protected virtual bool SupportsSerialization => true;
 
        /// <summary>
        /// Specifies whether this IEnumerable follows some sort of ordering pattern.
        /// </summary>
        protected virtual EnumerableOrder Order => EnumerableOrder.Sequential;
 
        /// <summary>
        /// An enum to allow specification of the order of the Enumerable. Used in validation for enumerables.
        /// </summary>
        protected enum EnumerableOrder
        {
            Unspecified,
            Sequential
        }
 
        #endregion
 
        #region GetEnumerator()
 
        [Fact]
        public void IEnumerable_NonGeneric_GetEnumerator_EmptyCollection_UsesSingleton()
        {
            IEnumerable enumerable = NonGenericIEnumerableFactory(0);
 
            IEnumerator enumerator1 = enumerable.GetEnumerator();
            try
            {
                IEnumerator enumerator2 = enumerable.GetEnumerator();
                try
                {
                    Assert.Equal(Enumerator_Empty_UsesSingletonInstance, ReferenceEquals(enumerator1, enumerator2));
                }
                finally
                {
                    if (enumerator2 is IDisposable d2) d2.Dispose();
                }
            }
            finally
            {
                if (enumerator1 is IDisposable d1) d1.Dispose();
            }
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_GetEnumerator_NoExceptionsWhileGetting(int count)
        {
            IEnumerable enumerable = NonGenericIEnumerableFactory(count);
            Assert.NotNull(enumerable.GetEnumerator());
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_GetEnumerator_ReturnsUniqueEnumerator(int count)
        {
            //Tests that the enumerators returned by GetEnumerator operate independently of one another
            IEnumerable enumerable = NonGenericIEnumerableFactory(count);
            int iterations = 0;
            foreach (object item in enumerable)
                foreach (object item2 in enumerable)
                    foreach (object item3 in enumerable)
                        iterations++;
            Assert.Equal(count * count * count, iterations);
        }
 
        #endregion
 
        #region Enumerator.MoveNext
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_MoveNext_FromStartToFinish(int count)
        {
            int iterations = 0;
            IEnumerator enumerator = NonGenericIEnumerableFactory(count).GetEnumerator();
            while (enumerator.MoveNext())
                iterations++;
            Assert.Equal(count, iterations);
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_MoveNext_AfterEndOfCollection(int count)
        {
            IEnumerator enumerator = NonGenericIEnumerableFactory(count).GetEnumerator();
            for (int i = 0; i < count; i++)
                enumerator.MoveNext();
            Assert.False(enumerator.MoveNext());
            Assert.False(enumerator.MoveNext());
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_MoveNext_ModifiedBeforeEnumeration_ThrowsInvalidOperationException(int count)
        {
            Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable =>
            {
                IEnumerable enumerable = NonGenericIEnumerableFactory(count);
                IEnumerator enumerator = enumerable.GetEnumerator();
                if (ModifyEnumerable(enumerable))
                {
                    if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException)
                    {
                        Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext());
                    }
                    else
                    {
                        _ = enumerator.MoveNext();
                    }
                }
            });
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_MoveNext_ModifiedDuringEnumeration_ThrowsInvalidOperationException(int count)
        {
            Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable =>
            {
                IEnumerable enumerable = NonGenericIEnumerableFactory(count);
                IEnumerator enumerator = enumerable.GetEnumerator();
 
                for (int i = 0; i < count / 2; i++)
                    enumerator.MoveNext();
 
                if (ModifyEnumerable(enumerable))
                {
                    if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException)
                    {
                        Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext());
                    }
                    else
                    {
                        enumerator.MoveNext();
                    }
                }
            });
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_MoveNext_ModifiedAfterEnumeration_ThrowsInvalidOperationException(int count)
        {
            Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable =>
            {
                IEnumerable enumerable = NonGenericIEnumerableFactory(count);
                IEnumerator enumerator = enumerable.GetEnumerator();
                while (enumerator.MoveNext()) ;
                if (ModifyEnumerable(enumerable))
                {
                    if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException)
                    {
                        Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext());
                    }
                    else
                    {
                        _ = enumerator.MoveNext();
                    }
                }
            });
        }
 
        #endregion
 
        #region Enumerator.Current
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_Current_FromStartToFinish(int count)
        {
            IEnumerator enumerator = NonGenericIEnumerableFactory(count).GetEnumerator();
            object current;
            while (enumerator.MoveNext())
                current = enumerator.Current;
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_Current_ReturnsSameValueOnRepeatedCalls(int count)
        {
            IEnumerator enumerator = NonGenericIEnumerableFactory(count).GetEnumerator();
            while (enumerator.MoveNext())
            {
                object current = enumerator.Current;
                Assert.Equal(current, enumerator.Current);
                Assert.Equal(current, enumerator.Current);
                Assert.Equal(current, enumerator.Current);
            }
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_Current_ReturnsSameObjectsOnDifferentEnumerators(int count)
        {
            // Ensures that the elements returned from enumeration are exactly the same collection of
            // elements returned from a previous enumeration
            IEnumerable enumerable = NonGenericIEnumerableFactory(count);
            HashSet<object> firstValues = new HashSet<object>(count);
            HashSet<object> secondValues = new HashSet<object>(count);
            foreach (object item in enumerable)
                Assert.True(firstValues.Add(item));
            foreach (object item in enumerable)
                Assert.True(secondValues.Add(item));
            Assert.Equal(firstValues.Count, secondValues.Count);
            foreach (object item in firstValues)
                Assert.True(secondValues.Contains(item));
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public virtual void Enumerator_Current_BeforeFirstMoveNext_UndefinedBehavior(int count)
        {
            object current;
            IEnumerable enumerable = NonGenericIEnumerableFactory(count);
            IEnumerator enumerator = enumerable.GetEnumerator();
            if (Enumerator_Current_UndefinedOperation_Throws)
                Assert.Throws<InvalidOperationException>(() => enumerator.Current);
            else
                current = enumerator.Current;
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public virtual void Enumerator_Current_AfterEndOfEnumerable_UndefinedBehavior(int count)
        {
            object current;
            IEnumerable enumerable = NonGenericIEnumerableFactory(count);
            IEnumerator enumerator = enumerable.GetEnumerator();
            while (enumerator.MoveNext()) ;
            if (Enumerator_Current_UndefinedOperation_Throws)
                Assert.Throws<InvalidOperationException>(() => enumerator.Current);
            else
                current = enumerator.Current;
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public virtual void Enumerator_Current_ModifiedDuringEnumeration_UndefinedBehavior(int count)
        {
            Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable =>
            {
                object current;
                IEnumerable enumerable = NonGenericIEnumerableFactory(count);
                IEnumerator enumerator = enumerable.GetEnumerator();
                if (ModifyEnumerable(enumerable))
                {
                    if (Enumerator_Current_UndefinedOperation_Throws)
                        Assert.Throws<InvalidOperationException>(() => enumerator.Current);
                    else
                        current = enumerator.Current;
                }
            });
        }
 
        #endregion
 
        #region Enumerator.Reset
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_Reset_BeforeIteration_Support(int count)
        {
            IEnumerator enumerator = NonGenericIEnumerableFactory(count).GetEnumerator();
            if (ResetImplemented)
                enumerator.Reset();
            else
                Assert.Throws<NotSupportedException>(() => enumerator.Reset());
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_Reset_ModifiedBeforeEnumeration_ThrowsInvalidOperationException(int count)
        {
            Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable =>
            {
                IEnumerable enumerable = NonGenericIEnumerableFactory(count);
                IEnumerator enumerator = enumerable.GetEnumerator();
                if (ModifyEnumerable(enumerable))
                {
                    if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException)
                    {
                        Assert.Throws<InvalidOperationException>(() => enumerator.Reset());
                    }
                    else
                    {
                        enumerator.Reset();
                    }
                }
            });
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_Reset_ModifiedDuringEnumeration_ThrowsInvalidOperationException(int count)
        {
            Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable =>
            {
                IEnumerable enumerable = NonGenericIEnumerableFactory(count);
                IEnumerator enumerator = enumerable.GetEnumerator();
 
                for (int i = 0; i < count / 2; i++)
                    enumerator.MoveNext();
 
                if (ModifyEnumerable(enumerable))
                {
                    if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException)
                    {
                        Assert.Throws<InvalidOperationException>(() => enumerator.Reset());
                    }
                    else
                    {
                        enumerator.Reset();
                    }
                }
            });
        }
 
        [Theory]
        [MemberData(nameof(ValidCollectionSizes))]
        public void IEnumerable_NonGeneric_Enumerator_Reset_ModifiedAfterEnumeration_ThrowsInvalidOperationException(int count)
        {
            Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable =>
            {
                IEnumerable enumerable = NonGenericIEnumerableFactory(count);
                IEnumerator enumerator = enumerable.GetEnumerator();
                while (enumerator.MoveNext()) ;
                if (ModifyEnumerable(enumerable))
                {
                    if (count == 0 ? Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException : Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException)
                    {
                        Assert.Throws<InvalidOperationException>(() => enumerator.Reset());
                    }
                    else
                    {
                        enumerator.Reset();
                    }
                }
            });
        }
 
        #endregion
    }
}