Tests for nullable reference types in Span apis (dotnet/corefx#38625)
authorAnirudh Agnihotry <anirudhagnihotry098@gmail.com>
Wed, 26 Jun 2019 18:54:06 +0000 (11:54 -0700)
committerAhson Khan <ahkha@microsoft.com>
Wed, 26 Jun 2019 18:54:06 +0000 (11:54 -0700)
* Some more null index checks

* adding test for ReadonlySpan overload and refactoring all overloads into a single test

* FIxing the tests with ben change and rebasing

Commit migrated from https://github.com/dotnet/corefx/commit/96a713d3434af8d443f87874e915f5d36c47a564

14 files changed:
src/libraries/System.Memory/tests/ReadOnlySpan/Contains.T.cs
src/libraries/System.Memory/tests/ReadOnlySpan/IndexOfAny.T.cs
src/libraries/System.Memory/tests/ReadOnlySpan/IndexOfSequence.T.cs
src/libraries/System.Memory/tests/ReadOnlySpan/LastIndexOfAny.T.cs
src/libraries/System.Memory/tests/ReadOnlySpan/LastIndexOfSequence.T.cs
src/libraries/System.Memory/tests/ReadOnlySpan/SequenceEqual.T.cs
src/libraries/System.Memory/tests/Span/Contains.T.cs [new file with mode: 0644]
src/libraries/System.Memory/tests/Span/IndexOfAny.T.cs
src/libraries/System.Memory/tests/Span/IndexOfSequence.T.cs
src/libraries/System.Memory/tests/Span/LastIndexOfAny.T.cs
src/libraries/System.Memory/tests/Span/LastIndexOfSequence.T.cs
src/libraries/System.Memory/tests/Span/SequenceEqual.T.cs
src/libraries/System.Memory/tests/System.Memory.Tests.csproj
src/libraries/System.Memory/tests/TestHelpers.cs

index e48ca72..0a2b924 100644 (file)
@@ -186,5 +186,13 @@ namespace System.SpanTests
                 Assert.True(found);
             }
         }
+
+        [Theory]
+        [MemberData(nameof(TestHelpers.ContainsNullData), MemberType = typeof(TestHelpers))]
+        public static void ContainsNull_String(string[] spanInput, bool expected)
+        {
+            ReadOnlySpan<string> theStrings = spanInput;
+            Assert.Equal(expected, theStrings.Contains(null));
+        }
     }
 }
index 4451253..d32501c 100644 (file)
@@ -963,5 +963,27 @@ namespace System.SpanTests
                 Assert.Equal(-1, index);
             }
         }
+
+        [Theory]
+        [MemberData(nameof(TestHelpers.IndexOfAnyNullSequenceData), MemberType = typeof(TestHelpers))]
+        public static void IndexOfAnyNullSequence_String(string[] spanInput, string[] searchInput, int expected)
+        {
+            ReadOnlySpan<string> theStrings = spanInput;
+            Assert.Equal(expected, theStrings.IndexOfAny(searchInput));
+            Assert.Equal(expected, theStrings.IndexOfAny((ReadOnlySpan<string>)searchInput));
+
+            if (searchInput != null)
+            {
+                if (searchInput.Length >= 3)
+                {
+                    Assert.Equal(expected, theStrings.IndexOfAny(searchInput[0], searchInput[1], searchInput[2]));
+                }
+
+                if (searchInput.Length >= 2)
+                {
+                    Assert.Equal(expected, theStrings.IndexOfAny(searchInput[0], searchInput[1]));
+                }
+            }
+        }
     }
 }
index 796a0f6..cb0fd3a 100644 (file)
@@ -238,6 +238,7 @@ namespace System.SpanTests
         {
             ReadOnlySpan<string> theStrings = spanInput;
             Assert.Equal(expected, theStrings.IndexOf(searchInput));
+            Assert.Equal(expected, theStrings.IndexOf((ReadOnlySpan<string>)searchInput));
         }
     }
 }
index 32d5464..861382a 100644 (file)
@@ -933,5 +933,27 @@ namespace System.SpanTests
                 Assert.Equal(-1, index);
             }
         }
+
+        [Theory]
+        [MemberData(nameof(TestHelpers.LastIndexOfAnyNullSequenceData), MemberType = typeof(TestHelpers))]
+        public static void LastIndexOfAnyNullSequence_String(string[] spanInput, string[] searchInput, int expected)
+        {
+            ReadOnlySpan<string> theStrings = spanInput;
+            Assert.Equal(expected, theStrings.LastIndexOfAny(searchInput));
+            Assert.Equal(expected, theStrings.LastIndexOfAny((ReadOnlySpan<string>)searchInput));
+
+            if (searchInput != null)
+            {
+                if (searchInput.Length >= 3)
+                {
+                    Assert.Equal(expected, theStrings.LastIndexOfAny(searchInput[0], searchInput[1], searchInput[2]));
+                }
+
+                if (searchInput.Length >= 2)
+                {
+                    Assert.Equal(expected, theStrings.LastIndexOfAny(searchInput[0], searchInput[1]));
+                }
+            }
+        }
     }
 }
index 4885b0b..667d2b7 100644 (file)
@@ -248,6 +248,7 @@ namespace System.SpanTests
         {
             ReadOnlySpan<string> theStrings = spanInput;
             Assert.Equal(expected, theStrings.LastIndexOf(searchInput));
+            Assert.Equal(expected, theStrings.LastIndexOf((ReadOnlySpan<string>)searchInput));
         }
     }
 }
index 4557186..4d56723 100644 (file)
@@ -130,5 +130,14 @@ namespace System.SpanTests
                 Assert.True(b);
             }
         }
+
+        [Theory]
+        [MemberData(nameof(TestHelpers.SequenceEqualsNullData), MemberType = typeof(TestHelpers))]
+        public static void SequenceEqualsNullData_String(string[] firstInput, string[] secondInput, bool expected)
+        {
+            ReadOnlySpan<string> theStrings = firstInput;
+            Assert.Equal(expected, theStrings.SequenceEqual(secondInput));
+            Assert.Equal(expected, theStrings.SequenceEqual((ReadOnlySpan<string>)secondInput));
+        }
     }
 }
diff --git a/src/libraries/System.Memory/tests/Span/Contains.T.cs b/src/libraries/System.Memory/tests/Span/Contains.T.cs
new file mode 100644 (file)
index 0000000..ba8f5f9
--- /dev/null
@@ -0,0 +1,198 @@
+// 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.
+
+using Xunit;
+
+namespace System.SpanTests
+{
+    // Adapted from IndexOf.T.cs
+    public static partial class SpanTests // .Contains<T>
+    {
+        [Fact]
+        public static void ZeroLengthContains()
+        {
+            Span<int> span = new Span<int>(Array.Empty<int>());
+
+            bool found = span.Contains(0);
+            Assert.False(found);
+        }
+
+        [Fact]
+        public static void TestContains()
+        {
+            for (int length = 0; length < 32; length++)
+            {
+                int[] a = new int[length];
+                for (int i = 0; i < length; i++)
+                {
+                    a[i] = 10 * (i + 1);
+                }
+                Span<int> span = new Span<int>(a);
+
+                for (int targetIndex = 0; targetIndex < length; targetIndex++)
+                {
+                    int target = a[targetIndex];
+                    bool found = span.Contains(target);
+                    Assert.True(found);
+                }
+            }
+        }
+
+        [Fact]
+        public static void TestMultipleContains()
+        {
+            for (int length = 2; length < 32; length++)
+            {
+                int[] a = new int[length];
+                for (int i = 0; i < length; i++)
+                {
+                    a[i] = 10 * (i + 1);
+                }
+
+                a[length - 1] = 5555;
+                a[length - 2] = 5555;
+
+                Span<int> span = new Span<int>(a);
+                bool found = span.Contains(5555);
+                Assert.True(found);
+            }
+        }
+
+        [Fact]
+        public static void OnNoMatchForContainsMakeSureEveryElementIsCompared()
+        {
+            for (int length = 0; length < 100; length++)
+            {
+                TIntLog log = new TIntLog();
+
+                TInt[] a = new TInt[length];
+                for (int i = 0; i < length; i++)
+                {
+                    a[i] = new TInt(10 * (i + 1), log);
+                }
+                Span<TInt> span = new Span<TInt>(a);
+                bool found = span.Contains(new TInt(9999, log));
+                Assert.False(found);
+
+                // Since we asked for a non-existent value, make sure each element of the array was compared once.
+                // (Strictly speaking, it would not be illegal for IndexOf to compare an element more than once but
+                // that would be a non-optimal implementation and a red flag. So we'll stick with the stricter test.)
+                Assert.Equal(a.Length, log.Count);
+                foreach (TInt elem in a)
+                {
+                    int numCompares = log.CountCompares(elem.Value, 9999);
+                    Assert.True(numCompares == 1, $"Expected {numCompares} == 1 for element {elem.Value}.");
+                }
+            }
+        }
+
+        [Fact]
+        public static void MakeSureNoChecksForContainsGoOutOfRange()
+        {
+            const int GuardValue = 77777;
+            const int GuardLength = 50;
+
+            void checkForOutOfRangeAccess(int x, int y)
+            {
+                if (x == GuardValue || y == GuardValue)
+                    throw new Exception("Detected out of range access in IndexOf()");
+            }
+
+            for (int length = 0; length < 100; length++)
+            {
+                TInt[] a = new TInt[GuardLength + length + GuardLength];
+                for (int i = 0; i < a.Length; i++)
+                {
+                    a[i] = new TInt(GuardValue, checkForOutOfRangeAccess);
+                }
+
+                for (int i = 0; i < length; i++)
+                {
+                    a[GuardLength + i] = new TInt(10 * (i + 1), checkForOutOfRangeAccess);
+                }
+
+                Span<TInt> span = new Span<TInt>(a, GuardLength, length);
+                bool found = span.Contains(new TInt(9999, checkForOutOfRangeAccess));
+                Assert.False(found);
+            }
+        }
+
+        [Fact]
+        public static void ZeroLengthContains_String()
+        {
+            Span<string> span = new Span<string>(Array.Empty<string>());
+            bool found = span.Contains("a");
+            Assert.False(found);
+        }
+
+        [Fact]
+        public static void TestMatchContains_String()
+        {
+            for (int length = 0; length < 32; length++)
+            {
+                string[] a = new string[length];
+                for (int i = 0; i < length; i++)
+                {
+                    a[i] = (10 * (i + 1)).ToString();
+                }
+                Span<string> span = new Span<string>(a);
+
+                for (int targetIndex = 0; targetIndex < length; targetIndex++)
+                {
+                    string target = a[targetIndex];
+                    bool found = span.Contains(target);
+                    Assert.True(found);
+                }
+            }
+        }
+
+        [Fact]
+        public static void TestNoMatchContains_String()
+        {
+            var rnd = new Random(42);
+            for (int length = 0; length <= byte.MaxValue; length++)
+            {
+                string[] a = new string[length];
+                string target = (rnd.Next(0, 256)).ToString();
+                for (int i = 0; i < length; i++)
+                {
+                    string val = (i + 1).ToString();
+                    a[i] = val == target ? (target + 1) : val;
+                }
+                Span<string> span = new Span<string>(a);
+
+                bool found = span.Contains(target);
+                Assert.False(found);
+            }
+        }
+
+        [Fact]
+        public static void TestMultipleMatchContains_String()
+        {
+            for (int length = 2; length < 32; length++)
+            {
+                string[] a = new string[length];
+                for (int i = 0; i < length; i++)
+                {
+                    a[i] = (10 * (i + 1)).ToString();
+                }
+
+                a[length - 1] = "5555";
+                a[length - 2] = "5555";
+
+                Span<string> span = new Span<string>(a);
+                bool found = span.Contains("5555");
+                Assert.True(found);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(TestHelpers.ContainsNullData), MemberType = typeof(TestHelpers))]
+        public static void ContainsNull_String(string[] spanInput, bool expected)
+        {
+            Span<string> theStrings = spanInput;
+            Assert.Equal(expected, theStrings.Contains(null));
+        }
+    }
+}
index cc42791..9e4e5f0 100644 (file)
@@ -963,5 +963,27 @@ namespace System.SpanTests
                 Assert.Equal(-1, index);
             }
         }
+
+        [Theory]
+        [MemberData(nameof(TestHelpers.IndexOfAnyNullSequenceData), MemberType = typeof(TestHelpers))]
+        public static void IndexOfAnyNullSequence_String(string[] spanInput, string[] searchInput, int expected)
+        {
+            Span<string> theStrings = spanInput;
+            Assert.Equal(expected, theStrings.IndexOfAny(searchInput));
+            Assert.Equal(expected, theStrings.IndexOfAny((ReadOnlySpan<string>)searchInput));
+
+            if (searchInput != null)
+            {
+                if (searchInput.Length >= 3)
+                {
+                    Assert.Equal(expected, theStrings.IndexOfAny(searchInput[0], searchInput[1], searchInput[2]));
+                }
+
+                if (searchInput.Length >= 2)
+                {
+                    Assert.Equal(expected, theStrings.IndexOfAny(searchInput[0], searchInput[1]));
+                }
+            }
+        }
     }
 }
index 697d5ce..5fdb3db 100644 (file)
@@ -238,6 +238,7 @@ namespace System.SpanTests
         {
             Span<string> theStrings = spanInput;
             Assert.Equal(expected, theStrings.IndexOf(searchInput));
+            Assert.Equal(expected, theStrings.IndexOf((ReadOnlySpan<string>)searchInput));
         }
     }
 }
index 5fb04db..590b805 100644 (file)
@@ -930,5 +930,27 @@ namespace System.SpanTests
                 Assert.Equal(-1, index);
             }
         }
+
+        [Theory]
+        [MemberData(nameof(TestHelpers.LastIndexOfAnyNullSequenceData), MemberType = typeof(TestHelpers))]
+        public static void LastIndexOfAnyNullSequence_String(string[] spanInput, string[] searchInput, int expected)
+        {
+            Span<string> theStrings = spanInput;
+            Assert.Equal(expected, theStrings.LastIndexOfAny(searchInput));
+            Assert.Equal(expected, theStrings.LastIndexOfAny((ReadOnlySpan<string>)searchInput));
+
+            if (searchInput != null)
+            {
+                if (searchInput.Length >= 3)
+                {
+                    Assert.Equal(expected, theStrings.LastIndexOfAny(searchInput[0], searchInput[1], searchInput[2]));
+                }
+
+                if (searchInput.Length >= 2)
+                {
+                    Assert.Equal(expected, theStrings.LastIndexOfAny(searchInput[0], searchInput[1]));
+                }
+            }
+        }
     }
 }
index 90134c4..d130e15 100644 (file)
@@ -248,6 +248,7 @@ namespace System.SpanTests
         {
             Span<string> theStrings = spanInput;
             Assert.Equal(expected, theStrings.LastIndexOf(searchInput));
+            Assert.Equal(expected, theStrings.LastIndexOf((ReadOnlySpan<string>)searchInput));
         }
     }
 }
index 9444cac..3df13fe 100644 (file)
@@ -130,5 +130,14 @@ namespace System.SpanTests
                 Assert.True(b);
             }
         }
+
+        [Theory]
+        [MemberData(nameof(TestHelpers.SequenceEqualsNullData), MemberType = typeof(TestHelpers))]
+        public static void SequenceEqualsNullData_String(string[] firstInput, string[] secondInput, bool expected)
+        {
+            Span<string> theStrings = firstInput;
+            Assert.Equal(expected, theStrings.SequenceEqual(secondInput));
+            Assert.Equal(expected, theStrings.SequenceEqual((ReadOnlySpan<string>)secondInput));
+        }
     }
 }
index e2bed79..11eb32d 100644 (file)
@@ -51,6 +51,7 @@
     <Compile Include="Span\AsSpan.cs" />
     <Compile Include="Span\Fill.cs" />
     <Compile Include="Span\Clear.cs" />
+    <Compile Include="Span\Contains.T.cs" />
     <Compile Include="Span\CopyTo.cs" />
     <Compile Include="Span\CtorArray.cs" />
     <Compile Include="Span\CtorArrayIntInt.cs" />
index f68a235..7e3485c 100644 (file)
@@ -411,6 +411,34 @@ namespace System
         public static ReadOnlyMemory<T> DangerousCreateReadOnlyMemory<T>(object obj, int offset, int length) =>
             DangerousCreateMemory<T>(obj, offset, length);
 
+        public static TheoryData<string[], bool> ContainsNullData => new TheoryData<string[], bool>()
+        {
+            { new string[] { "1", null, "2" }, true},
+            { new string[] { "1", "3", "2" }, false},
+            { null, false},
+            { new string[] { "1", null, null }, true},
+            { new string[] { null, null, null }, true},
+        };
+
+        public static TheoryData<string[], string[],  bool> SequenceEqualsNullData => new TheoryData<string[], string[], bool>()
+        {
+            { new string[] { "1", null, "2" }, new string[] { "1", null, "2" } , true},
+            { new string[] { "1", null, "2" }, new string[] { "1", "3", "2" } , false},
+            { new string[] { "1", null, "2" }, new string[] { null, "3", "2" } , false},
+            { new string[] { "1", null, "2" }, new string[] { null } , false},
+            { new string[] { "1", null, "2" }, null , false},
+
+            { new string[] { null, "2", "1" }, new string[] { null, "2" } , false},
+
+            { null, new string[] { null }, false},
+            { null, null , true},
+            { null, new string[] { "1", "3", "2" } , false},
+            { null, new string[] { "1", null, "2" } , false},
+
+            { new string[] { "1", null, null }, new string[] { "1", null, null }, true},
+            { new string[] { null, null, null }, new string[] { null, null, null }, true},
+        };
+
         public static TheoryData<string[], int> IndexOfNullData => new TheoryData<string[], int>()
         {
             { new string[] { "1", null, "2" }, 1},
@@ -436,6 +464,33 @@ namespace System
             { new string[] { null, null, null }, new string[] { null, null }, 0},
         };
 
+        public static TheoryData<string[], string[], int> IndexOfAnyNullSequenceData => new TheoryData<string[], string[], int>()
+        {
+            { new string[] { "1", null, "2" }, new string[] { "1", null, "2" }, 0},
+            { new string[] { "1", null, "2" }, new string[] { null, null }, 1},
+
+            { new string[] { "1", null, "2" }, new string[] { "3", null }, 1},
+            { new string[] { "1", null, "2" }, new string[] { "1", "2" }, 0},
+            { new string[] { "1", null, "2" }, new string[] { "3", "4" }, -1},
+
+            { new string[] { null, null, "2" }, new string[] { "3", null }, 0},
+            { new string[] { null, null, "2" }, new string[] { null, "1" }, 0},
+            { new string[] { null, null, "2" }, new string[] { null, "1" }, 0},
+
+            { new string[] { "1", "3", "2" }, new string[] { "1", null, "2" }, 0},
+            { new string[] { "1", "3", "2" }, new string[] { null, null }, -1},
+
+            { new string[] { "1", "3", "2" }, new string[] { null, "1" }, 0},
+
+            { null, new string[] { "1", null, "2" }, -1},
+
+            { new string[] { "1", null, null }, new string[] { null, null, "2" }, 1},
+            { new string[] { null, null, null }, new string[] { null, null }, 0},
+
+            { new string[] { "1", "3", "2" }, null, -1},
+            { new string[] { "1", null, "2" }, null, -1},
+        };
+
         public static TheoryData<string[], int> LastIndexOfNullData => new TheoryData<string[], int>()
         {
             { new string[] { "1", null, "2" }, 1},
@@ -462,5 +517,33 @@ namespace System
             { new string[] { "1", null, null }, new string[] { null, null, "2" }, -1},
             { new string[] { null, null, null }, new string[] { null, null }, 1},
         };
+
+        public static TheoryData<string[], string[], int> LastIndexOfAnyNullSequenceData => new TheoryData<string[], string[], int>()
+        {
+            { new string[] { "1", null, "2" }, new string[] { "1", null, "3" }, 1},
+            { new string[] { "1", null, "2" }, new string[] { null, null }, 1},
+            { new string[] { "1", null, "2" }, new string[] { "3", "4" }, -1},
+            { new string[] { "1", null, "2" }, new string[] { "3", null }, 1},
+            { new string[] { "1", null, "2" }, new string[] { "1", null }, 1},
+            { new string[] { "1", null, "2" }, new string[] { null, null }, 1},
+            { new string[] { "1", null, "2" }, new string[] { "1", "2" }, 2},
+            { null, new string[] { "1", null, "2" }, -1},
+
+            { new string[] { null, null, "2" }, new string[] { "3", null }, 1},
+            { new string[] { null, null, "2" }, new string[] { null, "1" }, 1},
+            { new string[] { null, null, "2" }, new string[] { null, "1" }, 1},
+
+            { new string[] { "1", "3", "2" }, new string[] { null, "1" }, 0},
+            { new string[] { "1", "3", "2" }, new string[] { "1", "2", null }, 2},
+            { new string[] { "1", "3", "2" }, new string[] { null, null }, -1},
+
+            { null, new string[] { null, "1" }, -1},
+
+            { new string[] { "1", null, null }, new string[] { null, null, "2" }, 2},
+            { new string[] { null, null, null }, new string[] { null, null }, 2},
+
+            { new string[] { "1", null, "2" }, null, -1},
+            { new string[] { "1", "3", "2" }, null, -1},
+        };
     }
 }