From b10228f33e1437583bea270f05c587042af2cf25 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Tue, 20 Feb 2018 23:26:55 -0800 Subject: [PATCH 1/5] Add ROSpan Equals/CompareTo/IndexOf/Contains string-like APIs with StringComparison --- src/System.Memory/System.Memory.sln | 1 + src/System.Memory/ref/System.Memory.cs | 8 + .../src/System/MemoryExtensions.Fast.cs | 39 ++ .../src/System/MemoryExtensions.Portable.cs | 20 + .../tests/ReadOnlySpan/CompareTo.cs | 306 +++++++++++++ .../tests/ReadOnlySpan/Contains.cs | 181 ++++++++ .../tests/ReadOnlySpan/Equals.cs | 264 +++++++++++ .../tests/ReadOnlySpan/IndexOf.charSpan.cs | 409 ++++++++++++++++++ .../tests/System.Memory.Tests.csproj | 4 + 9 files changed, 1232 insertions(+) create mode 100644 src/System.Memory/tests/ReadOnlySpan/CompareTo.cs create mode 100644 src/System.Memory/tests/ReadOnlySpan/Contains.cs create mode 100644 src/System.Memory/tests/ReadOnlySpan/Equals.cs create mode 100644 src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs diff --git a/src/System.Memory/System.Memory.sln b/src/System.Memory/System.Memory.sln index 7c6822978059..00692c230fde 100644 --- a/src/System.Memory/System.Memory.sln +++ b/src/System.Memory/System.Memory.sln @@ -27,6 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{2E666815-2ED EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3F794AE4-319F-4498-A9DA-C49A4EB11D9E}" ProjectSection(SolutionItems) = preProject + ..\Common\src\CoreLib\System\Runtime\InteropServices\MemoryMarshal.Fast.cs = ..\Common\src\CoreLib\System\Runtime\InteropServices\MemoryMarshal.Fast.cs ..\Common\src\CoreLib\System\ReadOnlySpan.Fast.cs = ..\Common\src\CoreLib\System\ReadOnlySpan.Fast.cs ..\Common\src\CoreLib\System\Span.Fast.cs = ..\Common\src\CoreLib\System\Span.Fast.cs ..\Common\src\CoreLib\System\Span.NonGeneric.cs = ..\Common\src\CoreLib\System\Span.NonGeneric.cs diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index 0a55e89d5a43..ea4741cbff83 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -29,10 +29,18 @@ public static partial class MemoryExtensions public static int BinarySearch(this System.ReadOnlySpan span, TComparable comparable) where TComparable : System.IComparable { throw null; } public static int BinarySearch(this System.Span span, T value, TComparer comparer) where TComparer : System.Collections.Generic.IComparer { throw null; } public static int BinarySearch(this System.Span span, TComparable comparable) where TComparable : System.IComparable { throw null; } + public static int CompareTo(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } +#if !FEATURE_PORTABLE_SPAN + public static bool Contains(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } +#endif public static void CopyTo(this T[] array, System.Memory destination) { } public static void CopyTo(this T[] array, System.Span destination) { } public static bool EndsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } public static bool EndsWith(this System.Span span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } + public static bool Equals(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } +#if !FEATURE_PORTABLE_SPAN + public static int IndexOf(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType, out int matchedLength) { throw null; } +#endif public static int IndexOfAny(this System.ReadOnlySpan span, System.ReadOnlySpan values) where T : System.IEquatable { throw null; } public static int IndexOfAny(this System.ReadOnlySpan span, T value0, T value1) where T : System.IEquatable { throw null; } public static int IndexOfAny(this System.ReadOnlySpan span, T value0, T value1, T value2) where T : System.IEquatable { throw null; } diff --git a/src/System.Memory/src/System/MemoryExtensions.Fast.cs b/src/System.Memory/src/System/MemoryExtensions.Fast.cs index 2a540f43e8ba..933f2b938dfc 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Fast.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Fast.cs @@ -11,6 +11,45 @@ namespace System /// public static partial class MemoryExtensions { + /// + /// Returns a value indicating whether the specified occurs within the . + /// The source span. + /// The value to seek within the source span. + /// One of the enumeration values that determines how the and are compared. + /// + public static bool Contains(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + => Span.Contains(span, value, comparisonType); + + /// + /// Determines whether this and the specified span have the same characters + /// when compared using the specified option. + /// The source span. + /// The value to compare with the source span. + /// One of the enumeration values that determines how the and are compared. + /// + public static bool Equals(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + => Span.Equals(span, value, comparisonType); + + /// + /// Compares the specified and using the specified , + /// and returns an integer that indicates their relative position in the sort order. + /// The source span. + /// The value to compare with the source span. + /// One of the enumeration values that determines how the and are compared. + /// + public static int CompareTo(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + => Span.CompareTo(span, value, comparisonType); + + /// + /// Reports the zero-based index of the first occurrence of the specified in the current . + /// The source span. + /// The value to seek within the source span. + /// One of the enumeration values that determines how the and are compared. + /// Returns the length of the matched span. + /// + public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType, out int matchedLength) + => Span.IndexOf(span, value, comparisonType, out matchedLength); + /// /// Casts a Span of one primitive type to Span of bytes. /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. diff --git a/src/System.Memory/src/System/MemoryExtensions.Portable.cs b/src/System.Memory/src/System/MemoryExtensions.Portable.cs index 8e306acf6e65..777435003fc3 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -12,6 +12,26 @@ namespace System /// public static partial class MemoryExtensions { + /// + /// Determines whether this and the specified span have the same characters + /// when compared using the specified option. + /// The source span. + /// The value to compare with the source span. + /// One of the enumeration values that determines how the and are compared. + /// + public static bool Equals(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + => span.ToString().Equals(value.ToString(), comparisonType); + + /// + /// Compares the specified and using the specified , + /// and returns an integer that indicates their relative position in the sort order. + /// The source span. + /// The value to compare with the source span. + /// One of the enumeration values that determines how the and are compared. + /// + public static int CompareTo(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + => string.Compare(span.ToString(), value.ToString(), comparisonType); + /// /// Casts a Span of one primitive type to Span of bytes. /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. diff --git a/src/System.Memory/tests/ReadOnlySpan/CompareTo.cs b/src/System.Memory/tests/ReadOnlySpan/CompareTo.cs new file mode 100644 index 000000000000..7cdffbeaf1bc --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/CompareTo.cs @@ -0,0 +1,306 @@ +// 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 System.Globalization; +using System.Threading; +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Fact] + public static void ZeroLengthCompareTo_StringComparison() + { + char[] a = { '4', '5', '6' }; + + var span = new ReadOnlySpan(a); + var slice = new ReadOnlySpan(a, 2, 0); + Assert.True(0 < span.CompareTo(slice, StringComparison.Ordinal)); + + Assert.True(0 < span.CompareTo(slice, StringComparison.CurrentCulture)); + Assert.True(0 < span.CompareTo(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(0 < span.CompareTo(slice, StringComparison.InvariantCulture)); + Assert.True(0 < span.CompareTo(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(0 < span.CompareTo(slice, StringComparison.OrdinalIgnoreCase)); + + span = new ReadOnlySpan(a, 1, 0); + Assert.Equal(0, span.CompareTo(slice, StringComparison.Ordinal)); + + Assert.Equal(0, span.CompareTo(slice, StringComparison.CurrentCulture)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.InvariantCulture)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void SameSpanCompareTo_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + Assert.Equal(0, span.CompareTo(span, StringComparison.Ordinal)); + + Assert.Equal(0, span.CompareTo(span, StringComparison.CurrentCulture)); + Assert.Equal(0, span.CompareTo(span, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(0, span.CompareTo(span, StringComparison.InvariantCulture)); + Assert.Equal(0, span.CompareTo(span, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal(0, span.CompareTo(span, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void LengthMismatchCompareTo_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 2); + var slice = new ReadOnlySpan(a, 0, 3); + Assert.True(0 > span.CompareTo(slice, StringComparison.Ordinal)); + + Assert.True(0 > span.CompareTo(slice, StringComparison.CurrentCulture)); + Assert.True(0 > span.CompareTo(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(0 > span.CompareTo(slice, StringComparison.InvariantCulture)); + Assert.True(0 > span.CompareTo(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(0 > span.CompareTo(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void CompareToOverlappingMatch_StringComparison() + { + char[] a = { '4', '5', '6', '5', '6', '5' }; + var span = new ReadOnlySpan(a, 1, 3); + var slice = new ReadOnlySpan(a, 3, 3); + Assert.Equal(0, span.CompareTo(slice, StringComparison.Ordinal)); + + Assert.Equal(0, span.CompareTo(slice, StringComparison.CurrentCulture)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.InvariantCulture)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void CompareToMatchDifferentSpans_StringComparison() + { + char[] a = { '4', '5', '6', '7' }; + char[] b = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 3); + var slice = new ReadOnlySpan(b, 0, 3); + Assert.Equal(0, span.CompareTo(slice, StringComparison.Ordinal)); + + Assert.Equal(0, span.CompareTo(slice, StringComparison.CurrentCulture)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.InvariantCulture)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal(0, span.CompareTo(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void CompareToNoMatch_StringComparison() + { + for (int length = 1; length < 150; length++) + { + for (int mismatchIndex = 0; mismatchIndex < length; mismatchIndex++) + { + var first = new char[length]; + var second = new char[length]; + for (int i = 0; i < length; i++) + { + first[i] = second[i] = (char)(i + 1); + } + + second[mismatchIndex] = (char)(second[mismatchIndex] + 1); + + var firstSpan = new ReadOnlySpan(first); + var secondSpan = new ReadOnlySpan(second); + Assert.True(0 > firstSpan.CompareTo(secondSpan, StringComparison.Ordinal)); + + Assert.Equal( + string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.OrdinalIgnoreCase), + firstSpan.CompareTo(secondSpan, StringComparison.OrdinalIgnoreCase)); + Assert.Equal( + string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.CurrentCulture), + firstSpan.CompareTo(secondSpan, StringComparison.CurrentCulture)); + Assert.Equal( + string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.CurrentCultureIgnoreCase), + firstSpan.CompareTo(secondSpan, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal( + string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.InvariantCulture), + firstSpan.CompareTo(secondSpan, StringComparison.InvariantCulture)); + Assert.Equal( + string.Compare(firstSpan.ToString(), secondSpan.ToString(), StringComparison.InvariantCultureIgnoreCase), + firstSpan.CompareTo(secondSpan, StringComparison.InvariantCultureIgnoreCase)); + } + } + } + + [Fact] + public static void MakeSureNoCompareToChecksGoOutOfRange_StringComparison() + { + for (int length = 0; length < 100; length++) + { + var first = new char[length + 2]; + first[0] = (char)99; + first[length + 1] = (char)99; + var second = new char[length + 2]; + second[0] = (char)100; + second[length + 1] = (char)100; + var span1 = new ReadOnlySpan(first, 1, length); + var span2 = new ReadOnlySpan(second, 1, length); + Assert.Equal(0, span1.CompareTo(span2, StringComparison.Ordinal)); + + Assert.Equal(0, span1.CompareTo(span2, StringComparison.CurrentCulture)); + Assert.Equal(0, span1.CompareTo(span2, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(0, span1.CompareTo(span2, StringComparison.InvariantCulture)); + Assert.Equal(0, span1.CompareTo(span2, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal(0, span1.CompareTo(span2, StringComparison.OrdinalIgnoreCase)); + } + } + + [Fact] + public static void CompareToUnknownComparisonType_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + TestHelpers.AssertThrows(span, (_span) => _span.CompareTo(_span, StringComparison.CurrentCulture - 1)); + TestHelpers.AssertThrows(span, (_span) => _span.CompareTo(_span, StringComparison.OrdinalIgnoreCase + 1)); + TestHelpers.AssertThrows(span, (_span) => _span.CompareTo(_span, (StringComparison)6)); + } + + [Theory] + // CurrentCulture + [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.CurrentCulture, 0)] + [InlineData("Hello", 0, "Goodbye", 0, 5, StringComparison.CurrentCulture, 1)] + [InlineData("Goodbye", 0, "Hello", 0, 5, StringComparison.CurrentCulture, -1)] + [InlineData("HELLO", 2, "hello", 2, 3, StringComparison.CurrentCulture, 1)] + [InlineData("hello", 2, "HELLO", 2, 3, StringComparison.CurrentCulture, -1)] + [InlineData("Hello", 2, "Hello", 2, 3, StringComparison.CurrentCulture, 0)] + [InlineData("Hello", 2, "Goodbye", 2, 3, StringComparison.CurrentCulture, -1)] + [InlineData("A", 0, "B", 0, 1, StringComparison.CurrentCulture, -1)] + [InlineData("B", 0, "A", 0, 1, StringComparison.CurrentCulture, 1)] + //[InlineData(null, 0, null, 0, 0, StringComparison.CurrentCulture, 0)] + //[InlineData("Hello", 0, null, 0, 0, StringComparison.CurrentCulture, 1)] + //[InlineData(null, 0, "Hello", 0, 0, StringComparison.CurrentCulture, -1)] + //[InlineData(null, -1, null, -1, -1, StringComparison.CurrentCulture, 0)] + //[InlineData("foo", -1, null, -1, -1, StringComparison.CurrentCulture, 1)] + //[InlineData(null, -1, "foo", -1, -1, StringComparison.CurrentCulture, -1)] + // CurrentCultureIgnoreCase + [InlineData("HELLO", 0, "hello", 0, 5, StringComparison.CurrentCultureIgnoreCase, 0)] + [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.CurrentCultureIgnoreCase, 0)] + [InlineData("Hello", 2, "Hello", 2, 3, StringComparison.CurrentCultureIgnoreCase, 0)] + [InlineData("Hello", 2, "Yellow", 2, 3, StringComparison.CurrentCultureIgnoreCase, 0)] + [InlineData("Hello", 0, "Goodbye", 0, 5, StringComparison.CurrentCultureIgnoreCase, 1)] + [InlineData("Goodbye", 0, "Hello", 0, 5, StringComparison.CurrentCultureIgnoreCase, -1)] + [InlineData("HELLO", 2, "hello", 2, 3, StringComparison.CurrentCultureIgnoreCase, 0)] + [InlineData("Hello", 2, "Goodbye", 2, 3, StringComparison.CurrentCultureIgnoreCase, -1)] + //[InlineData(null, 0, null, 0, 0, StringComparison.CurrentCultureIgnoreCase, 0)] + //[InlineData("Hello", 0, null, 0, 0, StringComparison.CurrentCultureIgnoreCase, 1)] + //[InlineData(null, 0, "Hello", 0, 0, StringComparison.CurrentCultureIgnoreCase, -1)] + //[InlineData(null, -1, null, -1, -1, StringComparison.CurrentCultureIgnoreCase, 0)] + //[InlineData("foo", -1, null, -1, -1, StringComparison.CurrentCultureIgnoreCase, 1)] + //[InlineData(null, -1, "foo", -1, -1, StringComparison.CurrentCultureIgnoreCase, -1)] + // InvariantCulture + [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.InvariantCulture, 0)] + [InlineData("Hello", 0, "Goodbye", 0, 5, StringComparison.InvariantCulture, 1)] + [InlineData("Goodbye", 0, "Hello", 0, 5, StringComparison.InvariantCulture, -1)] + [InlineData("HELLO", 2, "hello", 2, 3, StringComparison.InvariantCulture, 1)] + [InlineData("hello", 2, "HELLO", 2, 3, StringComparison.InvariantCulture, -1)] + //[InlineData(null, 0, null, 0, 0, StringComparison.InvariantCulture, 0)] + //[InlineData("Hello", 0, null, 0, 5, StringComparison.InvariantCulture, 1)] + //[InlineData(null, 0, "Hello", 0, 5, StringComparison.InvariantCulture, -1)] + // InvariantCultureIgnoreCase + [InlineData("HELLO", 0, "hello", 0, 5, StringComparison.InvariantCultureIgnoreCase, 0)] + [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.InvariantCultureIgnoreCase, 0)] + [InlineData("Hello", 2, "Hello", 2, 3, StringComparison.InvariantCultureIgnoreCase, 0)] + [InlineData("Hello", 2, "Yellow", 2, 3, StringComparison.InvariantCultureIgnoreCase, 0)] + [InlineData("Hello", 0, "Goodbye", 0, 5, StringComparison.InvariantCultureIgnoreCase, 1)] + [InlineData("Goodbye", 0, "Hello", 0, 5, StringComparison.InvariantCultureIgnoreCase, -1)] + [InlineData("HELLO", 2, "hello", 2, 3, StringComparison.InvariantCultureIgnoreCase, 0)] + [InlineData("Hello", 2, "Goodbye", 2, 3, StringComparison.InvariantCultureIgnoreCase, -1)] + //[InlineData(null, 0, null, 0, 0, StringComparison.InvariantCultureIgnoreCase, 0)] + //[InlineData("Hello", 0, null, 0, 5, StringComparison.InvariantCultureIgnoreCase, 1)] + //[InlineData(null, 0, "Hello", 0, 5, StringComparison.InvariantCultureIgnoreCase, -1)] + // Ordinal + [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.Ordinal, 0)] + [InlineData("Hello", 0, "Goodbye", 0, 5, StringComparison.Ordinal, 1)] + [InlineData("Goodbye", 0, "Hello", 0, 5, StringComparison.Ordinal, -1)] + [InlineData("Hello", 2, "Hello", 2, 3, StringComparison.Ordinal, 0)] + [InlineData("HELLO", 2, "hello", 2, 3, StringComparison.Ordinal, -1)] + [InlineData("Hello", 2, "Goodbye", 2, 3, StringComparison.Ordinal, -1)] + [InlineData("Hello", 0, "Hello", 0, 0, StringComparison.Ordinal, 0)] + [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.Ordinal, 0)] + [InlineData("Hello", 0, "Hello", 0, 3, StringComparison.Ordinal, 0)] + [InlineData("Hello", 2, "Hello", 2, 3, StringComparison.Ordinal, 0)] + [InlineData("Hello", 0, "He" + SoftHyphen + "llo", 0, 5, StringComparison.Ordinal, -1)] + [InlineData("Hello", 0, "-==-", 3, 5, StringComparison.Ordinal, 0)] + [InlineData("\uD83D\uDD53Hello\uD83D\uDD50", 1, "\uD83D\uDD53Hello\uD83D\uDD54", 1, 7, StringComparison.Ordinal, 0)] // Surrogate split + [InlineData("Hello", 0, "Hello123", 0, int.MaxValue, StringComparison.Ordinal, -1)] // Recalculated length, second string longer + [InlineData("Hello123", 0, "Hello", 0, int.MaxValue, StringComparison.Ordinal, 1)] // Recalculated length, first string longer + [InlineData("---aaaaaaaaaaa", 3, "+++aaaaaaaaaaa", 3, 100, StringComparison.Ordinal, 0)] // Equal long alignment 2, equal compare + [InlineData("aaaaaaaaaaaaaa", 3, "aaaxaaaaaaaaaa", 3, 100, StringComparison.Ordinal, -1)] // Equal long alignment 2, different compare at n=1 + [InlineData("-aaaaaaaaaaaaa", 1, "+aaaaaaaaaaaaa", 1, 100, StringComparison.Ordinal, 0)] // Equal long alignment 6, equal compare + [InlineData("aaaaaaaaaaaaaa", 1, "axaaaaaaaaaaaa", 1, 100, StringComparison.Ordinal, -1)] // Equal long alignment 6, different compare at n=1 + [InlineData("aaaaaaaaaaaaaa", 0, "aaaaaaaaaaaaaa", 0, 100, StringComparison.Ordinal, 0)] // Equal long alignment 4, equal compare + [InlineData("aaaaaaaaaaaaaa", 0, "xaaaaaaaaaaaaa", 0, 100, StringComparison.Ordinal, -1)] // Equal long alignment 4, different compare at n=1 + [InlineData("aaaaaaaaaaaaaa", 0, "axaaaaaaaaaaaa", 0, 100, StringComparison.Ordinal, -1)] // Equal long alignment 4, different compare at n=2 + [InlineData("--aaaaaaaaaaaa", 2, "++aaaaaaaaaaaa", 2, 100, StringComparison.Ordinal, 0)] // Equal long alignment 0, equal compare + [InlineData("aaaaaaaaaaaaaa", 2, "aaxaaaaaaaaaaa", 2, 100, StringComparison.Ordinal, -1)] // Equal long alignment 0, different compare at n=1 + [InlineData("aaaaaaaaaaaaaa", 2, "aaaxaaaaaaaaaa", 2, 100, StringComparison.Ordinal, -1)] // Equal long alignment 0, different compare at n=2 + [InlineData("aaaaaaaaaaaaaa", 2, "aaaaxaaaaaaaaa", 2, 100, StringComparison.Ordinal, -1)] // Equal long alignment 0, different compare at n=3 + [InlineData("aaaaaaaaaaaaaa", 2, "aaaaaxaaaaaaaa", 2, 100, StringComparison.Ordinal, -1)] // Equal long alignment 0, different compare at n=4 + [InlineData("aaaaaaaaaaaaaa", 2, "aaaaaaxaaaaaaa", 2, 100, StringComparison.Ordinal, -1)] // Equal long alignment 0, different compare at n=5 + [InlineData("aaaaaaaaaaaaaa", 0, "+aaaaaaaaaaaaa", 1, 13, StringComparison.Ordinal, 0)] // Different int alignment, equal compare + [InlineData("aaaaaaaaaaaaaa", 0, "aaaaaaaaaaaaax", 1, 100, StringComparison.Ordinal, -1)] // Different int alignment + [InlineData("aaaaaaaaaaaaaa", 1, "aaaxaaaaaaaaaa", 3, 100, StringComparison.Ordinal, -1)] // Different long alignment, abs of 4, one of them is 2, different at n=1 + [InlineData("-aaaaaaaaaaaaa", 1, "++++aaaaaaaaaa", 4, 10, StringComparison.Ordinal, 0)] // Different long alignment, equal compare + [InlineData("aaaaaaaaaaaaaa", 1, "aaaaaaaaaaaaax", 4, 100, StringComparison.Ordinal, -1)] // Different long alignment + [InlineData("\0", 0, "", 0, 1, StringComparison.Ordinal, 1)] // Same memory layout, except for m_stringLength (m_firstChars are both 0) + [InlineData("\0\0", 0, "", 0, 2, StringComparison.Ordinal, 1)] // Same as above, except m_stringLength for one is 2 + [InlineData("", 0, "\0b", 0, 2, StringComparison.Ordinal, -1)] // strA's second char != strB's second char codepath + [InlineData("", 0, "b", 0, 1, StringComparison.Ordinal, -1)] // Should hit strA.m_firstChar != strB.m_firstChar codepath + [InlineData("abcxxxxxxxxxxxxxxxxxxxxxx", 0, "abdxxxxxxxxxxxxxxx", 0, int.MaxValue, StringComparison.Ordinal, -1)] // 64-bit: first long compare is different + [InlineData("abcdefgxxxxxxxxxxxxxxxxxx", 0, "abcdefhxxxxxxxxxxx", 0, int.MaxValue, StringComparison.Ordinal, -1)] // 64-bit: second long compare is different + [InlineData("abcdefghijkxxxxxxxxxxxxxx", 0, "abcdefghijlxxxxxxx", 0, int.MaxValue, StringComparison.Ordinal, -1)] // 64-bit: third long compare is different + [InlineData("abcdexxxxxxxxxxxxxxxxxxxx", 0, "abcdfxxxxxxxxxxxxx", 0, int.MaxValue, StringComparison.Ordinal, -1)] // 32-bit: second int compare is different + [InlineData("abcdefghixxxxxxxxxxxxxxxx", 0, "abcdefghjxxxxxxxxx", 0, int.MaxValue, StringComparison.Ordinal, -1)] // 32-bit: fourth int compare is different + //[InlineData(null, 0, null, 0, 0, StringComparison.Ordinal, 0)] + //[InlineData("Hello", 0, null, 0, 5, StringComparison.Ordinal, 1)] + //[InlineData(null, 0, "Hello", 0, 5, StringComparison.Ordinal, -1)] + //[InlineData(null, -1, null, -1, -1, StringComparison.Ordinal, 0)] + //[InlineData("foo", -1, null, -1, -1, StringComparison.Ordinal, 1)] + //[InlineData(null, -1, "foo", -1, -1, StringComparison.Ordinal, -1)] + // OrdinalIgnoreCase + [InlineData("HELLO", 0, "hello", 0, 5, StringComparison.OrdinalIgnoreCase, 0)] + [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.OrdinalIgnoreCase, 0)] + [InlineData("Hello", 2, "Hello", 2, 3, StringComparison.OrdinalIgnoreCase, 0)] + [InlineData("Hello", 2, "Yellow", 2, 3, StringComparison.OrdinalIgnoreCase, 0)] + [InlineData("Hello", 0, "Goodbye", 0, 5, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("Goodbye", 0, "Hello", 0, 5, StringComparison.OrdinalIgnoreCase, -1)] + [InlineData("HELLO", 2, "hello", 2, 3, StringComparison.OrdinalIgnoreCase, 0)] + [InlineData("Hello", 2, "Goodbye", 2, 3, StringComparison.OrdinalIgnoreCase, -1)] + [InlineData("A", 0, "x", 0, 1, StringComparison.OrdinalIgnoreCase, -1)] + [InlineData("a", 0, "X", 0, 1, StringComparison.OrdinalIgnoreCase, -1)] + [InlineData("[", 0, "A", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("[", 0, "a", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("\\", 0, "A", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("\\", 0, "a", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("]", 0, "A", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("]", 0, "a", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("^", 0, "A", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("^", 0, "a", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("_", 0, "A", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("_", 0, "a", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("`", 0, "A", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + [InlineData("`", 0, "a", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] + //[InlineData(null, 0, null, 0, 0, StringComparison.OrdinalIgnoreCase, 0)] + //[InlineData("Hello", 0, null, 0, 5, StringComparison.OrdinalIgnoreCase, 1)] + //[InlineData(null, 0, "Hello", 0, 5, StringComparison.OrdinalIgnoreCase, -1)] + public static void Compare(string strA, int indexA, string strB, int indexB, int length, StringComparison comparisonType, int expected) + { + ReadOnlySpan span = length <= (strA.Length - indexA) ? strA.AsReadOnlySpan(indexA, length) : strA.AsReadOnlySpan(indexA); + ReadOnlySpan value = length <= (strB.Length - indexB) ? strB.AsReadOnlySpan(indexB, length) : strB.AsReadOnlySpan(indexB); + //Assert.True(expected == Math.Sign(span.CompareTo(value, comparisonType)), span.ToString() + "|" + value.ToString() + "|" + length); + Assert.Equal(expected, Math.Sign(span.CompareTo(value, comparisonType))); + } + } +} diff --git a/src/System.Memory/tests/ReadOnlySpan/Contains.cs b/src/System.Memory/tests/ReadOnlySpan/Contains.cs new file mode 100644 index 000000000000..ffbe5f5733a8 --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/Contains.cs @@ -0,0 +1,181 @@ +// 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 +{ + public static partial class ReadOnlySpanTests + { + [Fact] + public static void ZeroLengthContains_StringComparison() + { + var a = new char[3]; + + var span = new ReadOnlySpan(a); + var slice = new ReadOnlySpan(a, 2, 0); + Assert.True(span.Contains(slice, StringComparison.Ordinal)); + + Assert.True(span.Contains(slice, StringComparison.CurrentCulture)); + Assert.True(span.Contains(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.Contains(slice, StringComparison.InvariantCulture)); + Assert.True(span.Contains(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.Contains(slice, StringComparison.OrdinalIgnoreCase)); + + span = ReadOnlySpan.Empty; + Assert.True(span.Contains(slice, StringComparison.Ordinal)); + + Assert.True(span.Contains(slice, StringComparison.CurrentCulture)); + Assert.True(span.Contains(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.Contains(slice, StringComparison.InvariantCulture)); + Assert.True(span.Contains(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.Contains(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void SameSpanContains_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + Assert.True(span.Contains(span, StringComparison.Ordinal)); + + Assert.True(span.Contains(span, StringComparison.CurrentCulture)); + Assert.True(span.Contains(span, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.Contains(span, StringComparison.InvariantCulture)); + Assert.True(span.Contains(span, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.Contains(span, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void LengthMismatchContains_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 2); + var slice = new ReadOnlySpan(a, 0, 3); + Assert.False(span.Contains(slice, StringComparison.Ordinal)); + + Assert.False(span.Contains(slice, StringComparison.CurrentCulture)); + Assert.False(span.Contains(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.False(span.Contains(slice, StringComparison.InvariantCulture)); + Assert.False(span.Contains(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.False(span.Contains(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void ContainsMatch_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 3); + var slice = new ReadOnlySpan(a, 0, 2); + Assert.True(span.Contains(slice, StringComparison.Ordinal)); + + Assert.True(span.Contains(slice, StringComparison.CurrentCulture)); + Assert.True(span.Contains(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.Contains(slice, StringComparison.InvariantCulture)); + Assert.True(span.Contains(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.Contains(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void ContainsMatchDifferentSpans_StringComparison() + { + char[] a = { '4', '5', '6', '7' }; + char[] b = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 3); + var slice = new ReadOnlySpan(b, 0, 3); + Assert.True(span.Contains(slice, StringComparison.Ordinal)); + + Assert.True(span.Contains(slice, StringComparison.CurrentCulture)); + Assert.True(span.Contains(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.Contains(slice, StringComparison.InvariantCulture)); + Assert.True(span.Contains(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.Contains(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void ContainsNoMatch_StringComparison() + { + for (int length = 1; length < 150; length++) + { + for (int mismatchIndex = 0; mismatchIndex < length; mismatchIndex++) + { + var first = new char[length]; + var second = new char[length]; + for (int i = 0; i < length; i++) + { + first[i] = second[i] = (char)(i + 1); + } + + second[mismatchIndex] = (char)(second[mismatchIndex] + 1); + + var firstSpan = new ReadOnlySpan(first); + var secondSpan = new ReadOnlySpan(second); + Assert.False(firstSpan.Contains(secondSpan, StringComparison.Ordinal)); + + Assert.False(firstSpan.Contains(secondSpan, StringComparison.OrdinalIgnoreCase)); + + // Different behavior depending on OS + Assert.Equal( + firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCulture), + firstSpan.Contains(secondSpan, StringComparison.CurrentCulture)); + Assert.Equal( + firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCulture), + firstSpan.Contains(secondSpan, StringComparison.CurrentCulture)); + Assert.Equal( + firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.InvariantCulture), + firstSpan.Contains(secondSpan, StringComparison.InvariantCulture)); + Assert.Equal( + firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.InvariantCultureIgnoreCase), + firstSpan.Contains(secondSpan, StringComparison.InvariantCultureIgnoreCase)); + } + } + } + + [Fact] + public static void MakeSureNoContainsChecksGoOutOfRange_StringComparison() + { + for (int length = 0; length < 100; length++) + { + var first = new char[length + 2]; + first[0] = (char)99; + first[length + 1] = (char)99; + var second = new char[length + 2]; + second[0] = (char)100; + second[length + 1] = (char)100; + var span1 = new ReadOnlySpan(first, 1, length); + var span2 = new ReadOnlySpan(second, 1, length); + Assert.True(span1.Contains(span2, StringComparison.Ordinal)); + + Assert.True(span1.Contains(span2, StringComparison.CurrentCulture)); + Assert.True(span1.Contains(span2, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span1.Contains(span2, StringComparison.InvariantCulture)); + Assert.True(span1.Contains(span2, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span1.Contains(span2, StringComparison.OrdinalIgnoreCase)); + } + } + + [Fact] + public static void ContainsUnknownComparisonType_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + TestHelpers.AssertThrows(span, (_span) => _span.Contains(_span, StringComparison.CurrentCulture - 1)); + TestHelpers.AssertThrows(span, (_span) => _span.Contains(_span, StringComparison.OrdinalIgnoreCase + 1)); + TestHelpers.AssertThrows(span, (_span) => _span.Contains(_span, (StringComparison)6)); + } + + [Theory] + [InlineData("Hello", "ello", true)] + [InlineData("Hello", "ELL", false)] + [InlineData("Hello", "Larger Hello", false)] + [InlineData("Hello", "Goodbye", false)] + [InlineData("", "", true)] + [InlineData("", "hello", false)] + [InlineData("Hello", "", true)] + public static void Contains(string s, string value, bool expected) + { + Assert.Equal(expected, s.AsReadOnlySpan().Contains(value.AsReadOnlySpan(), StringComparison.Ordinal)); + } + } +} diff --git a/src/System.Memory/tests/ReadOnlySpan/Equals.cs b/src/System.Memory/tests/ReadOnlySpan/Equals.cs new file mode 100644 index 000000000000..87137d2e98e6 --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/Equals.cs @@ -0,0 +1,264 @@ +// 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 System.Collections.Generic; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Threading; +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + private const string SoftHyphen = "\u00AD"; + + [Fact] + public static unsafe void ZeroLengthEquals_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + var slice = new ReadOnlySpan(a, 2, 0); + Assert.False(span.Equals(slice, StringComparison.Ordinal)); + + Assert.False(span.Equals(slice, StringComparison.CurrentCulture)); + Assert.False(span.Equals(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.False(span.Equals(slice, StringComparison.InvariantCulture)); + Assert.False(span.Equals(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.False(span.Equals(slice, StringComparison.OrdinalIgnoreCase)); + + span = new ReadOnlySpan(a, 1, 0); + Assert.True(span.Equals(slice, StringComparison.Ordinal)); + + Assert.True(span.Equals(slice, StringComparison.CurrentCulture)); + Assert.True(span.Equals(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.Equals(slice, StringComparison.InvariantCulture)); + Assert.True(span.Equals(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.Equals(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void SameSpanEquals_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + Assert.True(span.Equals(span, StringComparison.Ordinal)); + + Assert.True(span.Equals(span, StringComparison.CurrentCulture)); + Assert.True(span.Equals(span, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.Equals(span, StringComparison.InvariantCulture)); + Assert.True(span.Equals(span, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.Equals(span, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void LengthMismatchEquals_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 2); + var slice = new ReadOnlySpan(a, 0, 3); + Assert.False(span.Equals(slice, StringComparison.Ordinal)); + + Assert.False(span.Equals(slice, StringComparison.CurrentCulture)); + Assert.False(span.Equals(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.False(span.Equals(slice, StringComparison.InvariantCulture)); + Assert.False(span.Equals(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.False(span.Equals(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void EqualsOverlappingMatch_StringComparison() + { + char[] a = { '4', '5', '6', '5', '6', '5' }; + var span = new ReadOnlySpan(a, 1, 3); + var slice = new ReadOnlySpan(a, 3, 3); + Assert.True(span.Equals(slice, StringComparison.Ordinal)); + + Assert.True(span.Equals(slice, StringComparison.CurrentCulture)); + Assert.True(span.Equals(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.Equals(slice, StringComparison.InvariantCulture)); + Assert.True(span.Equals(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.Equals(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void EqualsMatchDifferentSpans_StringComparison() + { + char[] a = { '4', '5', '6', '7' }; + char[] b = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 3); + var slice = new ReadOnlySpan(b, 0, 3); + Assert.True(span.Equals(slice, StringComparison.Ordinal)); + + Assert.True(span.Equals(slice, StringComparison.CurrentCulture)); + Assert.True(span.Equals(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.Equals(slice, StringComparison.InvariantCulture)); + Assert.True(span.Equals(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.Equals(slice, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public static void EqualsNoMatch_StringComparison() + { + for (int length = 1; length < 150; length++) + { + for (int mismatchIndex = 0; mismatchIndex < length; mismatchIndex++) + { + var first = new char[length]; + var second = new char[length]; + for (int i = 0; i < length; i++) + { + first[i] = second[i] = (char)(i + 1); + } + + second[mismatchIndex] = (char)(second[mismatchIndex] + 1); + + var firstSpan = new ReadOnlySpan(first); + var secondSpan = new ReadOnlySpan(second); + Assert.False(firstSpan.Equals(secondSpan, StringComparison.Ordinal)); + + Assert.False(firstSpan.Equals(secondSpan, StringComparison.OrdinalIgnoreCase)); + + // Different behavior depending on OS + Assert.Equal( + firstSpan.ToString().Equals(secondSpan.ToString(), StringComparison.CurrentCulture), + firstSpan.Equals(secondSpan, StringComparison.CurrentCulture)); + Assert.Equal( + firstSpan.ToString().Equals(secondSpan.ToString(), StringComparison.CurrentCultureIgnoreCase), + firstSpan.Equals(secondSpan, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal( + firstSpan.ToString().Equals(secondSpan.ToString(), StringComparison.InvariantCulture), + firstSpan.Equals(secondSpan, StringComparison.InvariantCulture)); + Assert.Equal( + firstSpan.ToString().Equals(secondSpan.ToString(), StringComparison.InvariantCultureIgnoreCase), + firstSpan.Equals(secondSpan, StringComparison.InvariantCultureIgnoreCase)); + } + } + } + + [Fact] + public static void MakeSureNoEqualsChecksGoOutOfRange_StringComparison() + { + for (int length = 0; length < 100; length++) + { + var first = new char[length + 2]; + first[0] = (char)99; + first[length + 1] = (char)99; + var second = new char[length + 2]; + second[0] = (char)100; + second[length + 1] = (char)100; + var span1 = new ReadOnlySpan(first, 1, length); + var span2 = new ReadOnlySpan(second, 1, length); + Assert.True(span1.Equals(span2, StringComparison.Ordinal)); + + Assert.True(span1.Equals(span2, StringComparison.CurrentCulture)); + Assert.True(span1.Equals(span2, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span1.Equals(span2, StringComparison.InvariantCulture)); + Assert.True(span1.Equals(span2, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span1.Equals(span2, StringComparison.OrdinalIgnoreCase)); + } + } + + [Fact] + public static void EqualsUnknownComparisonType_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + TestHelpers.AssertThrows(span, (_span) => _span.Equals(_span, StringComparison.CurrentCulture - 1)); + TestHelpers.AssertThrows(span, (_span) => _span.Equals(_span, StringComparison.OrdinalIgnoreCase + 1)); + TestHelpers.AssertThrows(span, (_span) => _span.Equals(_span, (StringComparison)6)); + } + + [Theory] + // CurrentCulture + [InlineData("Hello", "Hello", StringComparison.CurrentCulture, true)] + [InlineData("Hello", "hello", StringComparison.CurrentCulture, false)] + [InlineData("Hello", "Helloo", StringComparison.CurrentCulture, false)] + [InlineData("Hello", "Hell", StringComparison.CurrentCulture, false)] + [InlineData("Hello", SoftHyphen + "Hello" + SoftHyphen, StringComparison.CurrentCulture, true)] + [InlineData("Hello", "", StringComparison.CurrentCulture, false)] + [InlineData("", "Hello", StringComparison.CurrentCulture, false)] + [InlineData("", "", StringComparison.CurrentCulture, true)] + // CurrentCultureIgnoreCase + [InlineData("Hello", "Hello", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("Hello", "hello", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("Hello", "helloo", StringComparison.CurrentCultureIgnoreCase, false)] + [InlineData("Hello", "hell", StringComparison.CurrentCultureIgnoreCase, false)] + [InlineData("Hello", SoftHyphen + "Hello" + SoftHyphen, StringComparison.CurrentCulture, true)] + [InlineData("Hello", "", StringComparison.CurrentCultureIgnoreCase, false)] + [InlineData("", "Hello", StringComparison.CurrentCultureIgnoreCase, false)] + [InlineData("", "", StringComparison.CurrentCultureIgnoreCase, true)] + // InvariantCulture + [InlineData("Hello", "Hello", StringComparison.InvariantCulture, true)] + [InlineData("Hello", "hello", StringComparison.InvariantCulture, false)] + [InlineData("Hello", "Helloo", StringComparison.InvariantCulture, false)] + [InlineData("Hello", "Hell", StringComparison.InvariantCulture, false)] + [InlineData("Hello", SoftHyphen + "Hello" + SoftHyphen, StringComparison.CurrentCulture, true)] + [InlineData("Hello", "", StringComparison.InvariantCulture, false)] + [InlineData("", "Hello", StringComparison.InvariantCulture, false)] + [InlineData("", "", StringComparison.InvariantCulture, true)] + // InvariantCultureIgnoreCase + [InlineData("Hello", "Hello", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("Hello", "hello", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("Hello", "Helloo", StringComparison.InvariantCultureIgnoreCase, false)] + [InlineData("Hello", "Hell", StringComparison.InvariantCultureIgnoreCase, false)] + [InlineData("Hello", SoftHyphen + "Hello" + SoftHyphen, StringComparison.CurrentCulture, true)] + [InlineData("Hello", "", StringComparison.InvariantCultureIgnoreCase, false)] + [InlineData("", "Hello", StringComparison.InvariantCultureIgnoreCase, false)] + [InlineData("", "", StringComparison.InvariantCultureIgnoreCase, true)] + // Ordinal + [InlineData("Hello", "Hello", StringComparison.Ordinal, true)] + [InlineData("Hello", "hello", StringComparison.Ordinal, false)] + [InlineData("Hello", "Helloo", StringComparison.Ordinal, false)] + [InlineData("Hello", "Hell", StringComparison.Ordinal, false)] + [InlineData("Hello", SoftHyphen + "Hello" + SoftHyphen, StringComparison.Ordinal, false)] + [InlineData("Hello", "", StringComparison.Ordinal, false)] + [InlineData("", "Hello", StringComparison.Ordinal, false)] + [InlineData("", "", StringComparison.Ordinal, true)] + // OridinalIgnoreCase + [InlineData("Hello", "Hello", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("HELLO", "hello", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("Hello", "Helloo", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("Hello", "Hell", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("Hello", SoftHyphen + "Hello" + SoftHyphen, StringComparison.OrdinalIgnoreCase, false)] + [InlineData("\u1234\u5678", "\u1234\u5678", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("\u1234\u5678", "\u1234\u5679", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("\u1234\u5678", "\u1235\u5678", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("\u1234\u5678", "\u1234", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("\u1234\u5678", "\u1234\u56789\u1234", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("Hello", "", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("", "Hello", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("", "", StringComparison.OrdinalIgnoreCase, true)] + public static void Equals(string s1, string s2, StringComparison comparisonType, bool expected) + { + Assert.Equal(expected, s1.AsReadOnlySpan().Equals(s2.AsReadOnlySpan(), comparisonType)); + } + + public static IEnumerable Equals_EncyclopaediaData() + { + yield return new object[] { StringComparison.CurrentCulture, false }; + yield return new object[] { StringComparison.CurrentCultureIgnoreCase, false }; + yield return new object[] { StringComparison.Ordinal, false }; + yield return new object[] { StringComparison.OrdinalIgnoreCase, false }; + + // Windows and ICU disagree about how these strings compare in the default locale. + yield return new object[] { StringComparison.InvariantCulture, PlatformDetection.IsWindows }; + yield return new object[] { StringComparison.InvariantCultureIgnoreCase, PlatformDetection.IsWindows }; + } + + [Theory] + [MemberData(nameof(Equals_EncyclopaediaData))] + public static void Equals_Encyclopaedia_ReturnsExpected(StringComparison comparison, bool expected) + { + string source = "encyclop\u00e6dia"; + string target = "encyclopaedia"; + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = new CultureInfo("se-SE"); + Assert.Equal(expected, source.AsReadOnlySpan().Equals(target.AsReadOnlySpan(), comparison)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + } + } +} diff --git a/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs b/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs new file mode 100644 index 000000000000..32cd8fe6efc6 --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs @@ -0,0 +1,409 @@ +// 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 System.Collections.Generic; +using System.Globalization; +using System.Threading; +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Theory] + [InlineData("Hello", 'l', 0, 5, 2)] + [InlineData("Hello", 'x', 0, 5, -1)] + [InlineData("Hello", 'l', 1, 4, 2)] + [InlineData("Hello", 'l', 3, 2, 3)] + [InlineData("Hello", 'l', 4, 1, -1)] + [InlineData("Hello", 'x', 1, 4, -1)] + [InlineData("Hello", 'l', 3, 0, -1)] + [InlineData("Hello", 'l', 0, 2, -1)] + [InlineData("Hello", 'l', 0, 3, 2)] + [InlineData("Hello", 'l', 4, 1, -1)] + [InlineData("Hello", 'x', 1, 4, -1)] + [InlineData("Hello", 'o', 5, 0, -1)] + [InlineData("H" + SoftHyphen + "ello", 'e', 0, 3, 2)] + [InlineData("\ud800\udfff", '\ud800', 0, 1, 0)] // Surrogate characters + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'A', 0, 26, 0)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'B', 1, 25, 1)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'C', 2, 24, 2)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'D', 3, 23, 3)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'G', 2, 24, 6)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'K', 2, 24, 10)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'O', 2, 24, 14)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'P', 2, 24, 15)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'Q', 2, 24, 16)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'R', 2, 24, 17)] + [InlineData("________\u8080\u8080\u8080________", '\u0080', 0, 19, -1)] + [InlineData("________\u8000\u8000\u8000________", '\u0080', 0, 19, -1)] + [InlineData("__\u8080\u8000\u0080______________", '\u0080', 0, 19, 4)] + [InlineData("__\u8080\u8000__\u0080____________", '\u0080', 0, 19, 6)] + [InlineData("__________________________________", '\ufffd', 0, 34, -1)] + [InlineData("____________________________\ufffd", '\ufffd', 0, 29, 28)] + [InlineData("ABCDEFGHIJKLM", 'M', 0, 13, 12)] + [InlineData("ABCDEFGHIJKLMN", 'N', 0, 14, 13)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", '@', 0, 26, -1)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXY", '@', 0, 25, -1)] + [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ#", '@', 0, 27, -1)] + [InlineData("_____________\u807f", '\u007f', 0, 14, -1)] + [InlineData("_____________\u807f__", '\u007f', 0, 16, -1)] + [InlineData("_____________\u807f\u007f_", '\u007f', 0, 16, 14)] + [InlineData("__\u807f_______________", '\u007f', 0, 18, -1)] + [InlineData("__\u807f___\u007f___________", '\u007f', 0, 18, 6)] + [InlineData("ABCDEFGHIJKLMN", 'N', 2, 11, -1)] + [InlineData("!@#$%^&", '%', 0, 7, 4)] + [InlineData("!@#$", '!', 0, 4, 0)] + [InlineData("!@#$", '@', 0, 4, 1)] + [InlineData("!@#$", '#', 0, 4, 2)] + [InlineData("!@#$", '$', 0, 4, 3)] + [InlineData("!@#$%^&*", '%', 0, 8, 4)] + public static void IndexOf_SingleLetter(string s, char target, int startIndex, int count, int expected) + { + bool safeForCurrentCulture = + IsSafeForCurrentCultureComparisons(s) + && IsSafeForCurrentCultureComparisons(target.ToString()); + + ReadOnlySpan span = s.AsReadOnlySpan(); + var charArray = new char[1]; + charArray[0] = target; + ReadOnlySpan targetSpan = charArray; + + int expectedFromSpan = expected == -1 ? expected : expected - startIndex; + + if (count + startIndex == s.Length) + { + if (startIndex == 0) + { + Assert.Equal(expectedFromSpan, span.IndexOf(targetSpan, StringComparison.Ordinal, out _)); + Assert.Equal(expectedFromSpan, span.IndexOf(targetSpan, StringComparison.OrdinalIgnoreCase, out _)); + + // To be safe we only want to run CurrentCulture comparisons if + // we know the results will not vary depending on location + if (safeForCurrentCulture) + { + Assert.Equal(expectedFromSpan, span.IndexOf(targetSpan, StringComparison.CurrentCulture, out _)); + } + } + + Assert.Equal(expectedFromSpan, span.Slice(startIndex).IndexOf(targetSpan, StringComparison.Ordinal, out _)); + Assert.Equal(expectedFromSpan, span.Slice(startIndex).IndexOf(targetSpan, StringComparison.OrdinalIgnoreCase, out _)); + + if (safeForCurrentCulture) + { + Assert.Equal(expectedFromSpan, span.Slice(startIndex).IndexOf(targetSpan, StringComparison.CurrentCulture, out _)); + } + } + Assert.Equal(expectedFromSpan, span.Slice(startIndex, count).IndexOf(targetSpan, StringComparison.Ordinal, out _)); + Assert.Equal(expectedFromSpan, span.Slice(startIndex, count).IndexOf(targetSpan, StringComparison.OrdinalIgnoreCase, out _)); + + if (safeForCurrentCulture) + { + Assert.Equal(expectedFromSpan, span.Slice(startIndex, count).IndexOf(targetSpan, StringComparison.CurrentCulture, out _)); + } + } + + private static bool IsSafeForCurrentCultureComparisons(string str) + { + for (int i = 0; i < str.Length; i++) + { + char c = str[i]; + // We only want ASCII chars that you can see + // No controls, no delete, nothing >= 0x80 + if (c < 0x20 || c == 0x7f || c >= 0x80) + { + return false; + } + } + return true; + } + + + // NOTE: This is by design. Unix ignores the null characters (i.e. null characters have no weights for the string comparison). + // For desired behavior, use ordinal comparison instead of linguistic comparison. + // This is a known difference between Windows and Unix (https://github.com/dotnet/coreclr/issues/2051). + [Theory] + [PlatformSpecific(TestPlatforms.Windows)] + [InlineData("He\0lo", "He\0lo", 0)] + [InlineData("He\0lo", "He\0", 0)] + [InlineData("He\0lo", "\0", 2)] + [InlineData("He\0lo", "\0lo", 2)] + [InlineData("He\0lo", "lo", 3)] + [InlineData("Hello", "lo\0", -1)] + [InlineData("Hello", "\0lo", -1)] + [InlineData("Hello", "l\0o", -1)] + public static void IndexOf_NullInStrings(string s, string value, int expected) + { + Assert.Equal(expected, s.AsReadOnlySpan().IndexOf(value.AsReadOnlySpan(), StringComparison.Ordinal, out _)); + } + + [Theory] + [MemberData(nameof(AllSubstringsAndComparisons), new object[] { "abcde" })] + public static void IndexOf_AllSubstrings(string s, string value, int startIndex, StringComparison comparison) + { + bool ignoringCase = comparison == StringComparison.OrdinalIgnoreCase || comparison == StringComparison.CurrentCultureIgnoreCase; + + // First find the substring. We should be able to with all comparison types. + Assert.Equal(startIndex, s.AsReadOnlySpan().IndexOf(value.AsReadOnlySpan(), comparison, out _)); // in the whole string + Assert.Equal(0, s.AsReadOnlySpan(startIndex).IndexOf(value.AsReadOnlySpan(), comparison, out _)); // starting at substring + if (startIndex > 0) + { + Assert.Equal(1, s.AsReadOnlySpan(startIndex - 1).IndexOf(value.AsReadOnlySpan(), comparison, out _)); // starting just before substring + } + Assert.Equal(-1, s.AsReadOnlySpan(startIndex + 1).IndexOf(value.AsReadOnlySpan(), comparison, out _)); // starting just after start of substring + + // Shouldn't be able to find the substring if the count is less than substring's length + Assert.Equal(-1, s.AsReadOnlySpan(0, value.Length - 1).IndexOf(value.AsReadOnlySpan(), comparison, out _)); + + // Now double the source. Make sure we find the first copy of the substring. + int halfLen = s.Length; + s += s; + Assert.Equal(startIndex, s.AsReadOnlySpan().IndexOf(value.AsReadOnlySpan(), comparison, out _)); + + // Now change the case of a letter. + s = s.ToUpperInvariant(); + Assert.Equal(ignoringCase ? startIndex : -1, s.AsReadOnlySpan().IndexOf(value.AsReadOnlySpan(), comparison, out _)); + } + + public static IEnumerable AllSubstringsAndComparisons(string source) + { + var comparisons = new StringComparison[] + { + StringComparison.CurrentCulture, + StringComparison.CurrentCultureIgnoreCase, + StringComparison.Ordinal, + StringComparison.OrdinalIgnoreCase + }; + + foreach (StringComparison comparison in comparisons) + { + for (int i = 0; i <= source.Length; i++) + { + for (int subLen = source.Length - i; subLen > 0; subLen--) + { + yield return new object[] { source, source.Substring(i, subLen), i, comparison }; + } + } + } + } + + [Fact] + public static void IndexOf_TurkishI_TurkishCulture() + { + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR"); + + string str = "Turkish I \u0131s TROUBL\u0130NG!"; + string valueString = "\u0130"; + ReadOnlySpan s = str.AsReadOnlySpan(); + ReadOnlySpan value = valueString.AsReadOnlySpan(); + Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(19, s.IndexOf(value, StringComparison.Ordinal, out _)); + Assert.Equal(19, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + + valueString = "\u0131"; + value = valueString.AsReadOnlySpan(); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.Ordinal, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + } + + [Fact] + public static void IndexOf_TurkishI_InvariantCulture() + { + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + + string str = "Turkish I \u0131s TROUBL\u0130NG!"; + string valueString = "\u0130"; + ReadOnlySpan s = str.AsReadOnlySpan(); + ReadOnlySpan value = valueString.AsReadOnlySpan(); + + Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + + valueString = "\u0131"; + value = valueString.AsReadOnlySpan(); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + } + + [Fact] + public static void IndexOf_TurkishI_EnglishUSCulture() + { + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + + string str = "Turkish I \u0131s TROUBL\u0130NG!"; + string valueString = "\u0130"; + ReadOnlySpan s = str.AsReadOnlySpan(); + ReadOnlySpan value = valueString.AsReadOnlySpan(); + + Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + + valueString = "\u0131"; + value = valueString.AsReadOnlySpan(); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + } + + [Fact] + public static void IndexOf_HungarianDoubleCompression_HungarianCulture() + { + string str = "dzsdzs"; + string valueString = "ddzs"; + + ReadOnlySpan s = str.AsReadOnlySpan(); + ReadOnlySpan value = valueString.AsReadOnlySpan(); + + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = new CultureInfo("hu-HU"); + /* + There are differences between Windows and ICU regarding contractions. + Windows has equal contraction collation weights, including case (target="Ddzs" same behavior as "ddzs"). + ICU has different contraction collation weights, depending on locale collation rules. + If CurrentCultureIgnoreCase is specified, ICU will use 'secondary' collation rules + which ignore the contraction collation weights (defined as 'tertiary' rules) + */ + Assert.Equal(PlatformDetection.IsWindows ? 0 : -1, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + + Assert.Equal(0, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.Ordinal, out _)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + } + + [Fact] + public static void IndexOf_HungarianDoubleCompression_InvariantCulture() + { + string str = "dzsdzs"; + string valueString = "ddzs"; + + ReadOnlySpan s = str.AsReadOnlySpan(); + ReadOnlySpan value = valueString.AsReadOnlySpan(); + + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + } + + [Fact] + public static void IndexOf_EquivalentDiacritics_EnglishUSCulture() + { + string str = "Exhibit a\u0300\u00C0"; + string valueString = "\u00C0"; + + ReadOnlySpan s = str.AsReadOnlySpan(); + ReadOnlySpan value = valueString.AsReadOnlySpan(); + + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.Ordinal, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + + valueString = "a\u0300"; // this diacritic combines with preceding character + value = valueString.AsReadOnlySpan(); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(8, s.IndexOf(value, StringComparison.Ordinal, out _)); + Assert.Equal(8, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + } + + [Fact] + public static void IndexOf_EquivalentDiacritics_InvariantCulture() + { + string str = "Exhibit a\u0300\u00C0"; + string valueString = "\u00C0"; + + ReadOnlySpan s = str.AsReadOnlySpan(); + ReadOnlySpan value = valueString.AsReadOnlySpan(); + + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + + valueString = "a\u0300"; // this diacritic combines with preceding character + value = valueString.AsReadOnlySpan(); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + } + + [Fact] + public static void IndexOf_CyrillicE_EnglishUSCulture() + { + string str = "Foo\u0400Bar"; + string valueString = "\u0400"; + + ReadOnlySpan s = str.AsReadOnlySpan(); + ReadOnlySpan value = valueString.AsReadOnlySpan(); + + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(3, s.IndexOf(value, StringComparison.Ordinal, out _)); + Assert.Equal(3, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + + valueString = "bar"; + value = valueString.AsReadOnlySpan(); + Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.Ordinal, out _)); + Assert.Equal(4, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + } + + [Fact] + public static void IndexOf_CyrillicE_InvariantCulture() + { + string str = "Foo\u0400Bar"; + string valueString = "\u0400"; + + ReadOnlySpan s = str.AsReadOnlySpan(); + ReadOnlySpan value = valueString.AsReadOnlySpan(); + + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + + valueString = "bar"; + value = valueString.AsReadOnlySpan(); + Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + } + } +} diff --git a/src/System.Memory/tests/System.Memory.Tests.csproj b/src/System.Memory/tests/System.Memory.Tests.csproj index 119253812185..e85d72b2419d 100644 --- a/src/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/System.Memory/tests/System.Memory.Tests.csproj @@ -16,6 +16,8 @@ + + @@ -80,6 +82,7 @@ + @@ -88,6 +91,7 @@ + From bec2f0956c7ee0578b6e56f7ff7a6f7a4969949b Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 14:28:05 -0800 Subject: [PATCH 2/5] Respond to recent change AsReadOnlySpan -> AsSpan --- .../tests/ReadOnlySpan/CompareTo.cs | 31 +-------- .../tests/ReadOnlySpan/Contains.cs | 2 +- .../tests/ReadOnlySpan/Equals.cs | 4 +- .../tests/ReadOnlySpan/IndexOf.charSpan.cs | 68 +++++++++---------- 4 files changed, 39 insertions(+), 66 deletions(-) diff --git a/src/System.Memory/tests/ReadOnlySpan/CompareTo.cs b/src/System.Memory/tests/ReadOnlySpan/CompareTo.cs index 7cdffbeaf1bc..ab1f294fe16e 100644 --- a/src/System.Memory/tests/ReadOnlySpan/CompareTo.cs +++ b/src/System.Memory/tests/ReadOnlySpan/CompareTo.cs @@ -178,12 +178,6 @@ public static void CompareToUnknownComparisonType_StringComparison() [InlineData("Hello", 2, "Goodbye", 2, 3, StringComparison.CurrentCulture, -1)] [InlineData("A", 0, "B", 0, 1, StringComparison.CurrentCulture, -1)] [InlineData("B", 0, "A", 0, 1, StringComparison.CurrentCulture, 1)] - //[InlineData(null, 0, null, 0, 0, StringComparison.CurrentCulture, 0)] - //[InlineData("Hello", 0, null, 0, 0, StringComparison.CurrentCulture, 1)] - //[InlineData(null, 0, "Hello", 0, 0, StringComparison.CurrentCulture, -1)] - //[InlineData(null, -1, null, -1, -1, StringComparison.CurrentCulture, 0)] - //[InlineData("foo", -1, null, -1, -1, StringComparison.CurrentCulture, 1)] - //[InlineData(null, -1, "foo", -1, -1, StringComparison.CurrentCulture, -1)] // CurrentCultureIgnoreCase [InlineData("HELLO", 0, "hello", 0, 5, StringComparison.CurrentCultureIgnoreCase, 0)] [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.CurrentCultureIgnoreCase, 0)] @@ -193,21 +187,12 @@ public static void CompareToUnknownComparisonType_StringComparison() [InlineData("Goodbye", 0, "Hello", 0, 5, StringComparison.CurrentCultureIgnoreCase, -1)] [InlineData("HELLO", 2, "hello", 2, 3, StringComparison.CurrentCultureIgnoreCase, 0)] [InlineData("Hello", 2, "Goodbye", 2, 3, StringComparison.CurrentCultureIgnoreCase, -1)] - //[InlineData(null, 0, null, 0, 0, StringComparison.CurrentCultureIgnoreCase, 0)] - //[InlineData("Hello", 0, null, 0, 0, StringComparison.CurrentCultureIgnoreCase, 1)] - //[InlineData(null, 0, "Hello", 0, 0, StringComparison.CurrentCultureIgnoreCase, -1)] - //[InlineData(null, -1, null, -1, -1, StringComparison.CurrentCultureIgnoreCase, 0)] - //[InlineData("foo", -1, null, -1, -1, StringComparison.CurrentCultureIgnoreCase, 1)] - //[InlineData(null, -1, "foo", -1, -1, StringComparison.CurrentCultureIgnoreCase, -1)] // InvariantCulture [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.InvariantCulture, 0)] [InlineData("Hello", 0, "Goodbye", 0, 5, StringComparison.InvariantCulture, 1)] [InlineData("Goodbye", 0, "Hello", 0, 5, StringComparison.InvariantCulture, -1)] [InlineData("HELLO", 2, "hello", 2, 3, StringComparison.InvariantCulture, 1)] [InlineData("hello", 2, "HELLO", 2, 3, StringComparison.InvariantCulture, -1)] - //[InlineData(null, 0, null, 0, 0, StringComparison.InvariantCulture, 0)] - //[InlineData("Hello", 0, null, 0, 5, StringComparison.InvariantCulture, 1)] - //[InlineData(null, 0, "Hello", 0, 5, StringComparison.InvariantCulture, -1)] // InvariantCultureIgnoreCase [InlineData("HELLO", 0, "hello", 0, 5, StringComparison.InvariantCultureIgnoreCase, 0)] [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.InvariantCultureIgnoreCase, 0)] @@ -217,9 +202,6 @@ public static void CompareToUnknownComparisonType_StringComparison() [InlineData("Goodbye", 0, "Hello", 0, 5, StringComparison.InvariantCultureIgnoreCase, -1)] [InlineData("HELLO", 2, "hello", 2, 3, StringComparison.InvariantCultureIgnoreCase, 0)] [InlineData("Hello", 2, "Goodbye", 2, 3, StringComparison.InvariantCultureIgnoreCase, -1)] - //[InlineData(null, 0, null, 0, 0, StringComparison.InvariantCultureIgnoreCase, 0)] - //[InlineData("Hello", 0, null, 0, 5, StringComparison.InvariantCultureIgnoreCase, 1)] - //[InlineData(null, 0, "Hello", 0, 5, StringComparison.InvariantCultureIgnoreCase, -1)] // Ordinal [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.Ordinal, 0)] [InlineData("Hello", 0, "Goodbye", 0, 5, StringComparison.Ordinal, 1)] @@ -263,12 +245,6 @@ public static void CompareToUnknownComparisonType_StringComparison() [InlineData("abcdefghijkxxxxxxxxxxxxxx", 0, "abcdefghijlxxxxxxx", 0, int.MaxValue, StringComparison.Ordinal, -1)] // 64-bit: third long compare is different [InlineData("abcdexxxxxxxxxxxxxxxxxxxx", 0, "abcdfxxxxxxxxxxxxx", 0, int.MaxValue, StringComparison.Ordinal, -1)] // 32-bit: second int compare is different [InlineData("abcdefghixxxxxxxxxxxxxxxx", 0, "abcdefghjxxxxxxxxx", 0, int.MaxValue, StringComparison.Ordinal, -1)] // 32-bit: fourth int compare is different - //[InlineData(null, 0, null, 0, 0, StringComparison.Ordinal, 0)] - //[InlineData("Hello", 0, null, 0, 5, StringComparison.Ordinal, 1)] - //[InlineData(null, 0, "Hello", 0, 5, StringComparison.Ordinal, -1)] - //[InlineData(null, -1, null, -1, -1, StringComparison.Ordinal, 0)] - //[InlineData("foo", -1, null, -1, -1, StringComparison.Ordinal, 1)] - //[InlineData(null, -1, "foo", -1, -1, StringComparison.Ordinal, -1)] // OrdinalIgnoreCase [InlineData("HELLO", 0, "hello", 0, 5, StringComparison.OrdinalIgnoreCase, 0)] [InlineData("Hello", 0, "Hello", 0, 5, StringComparison.OrdinalIgnoreCase, 0)] @@ -292,13 +268,10 @@ public static void CompareToUnknownComparisonType_StringComparison() [InlineData("_", 0, "a", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] [InlineData("`", 0, "A", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] [InlineData("`", 0, "a", 0, 1, StringComparison.OrdinalIgnoreCase, 1)] - //[InlineData(null, 0, null, 0, 0, StringComparison.OrdinalIgnoreCase, 0)] - //[InlineData("Hello", 0, null, 0, 5, StringComparison.OrdinalIgnoreCase, 1)] - //[InlineData(null, 0, "Hello", 0, 5, StringComparison.OrdinalIgnoreCase, -1)] public static void Compare(string strA, int indexA, string strB, int indexB, int length, StringComparison comparisonType, int expected) { - ReadOnlySpan span = length <= (strA.Length - indexA) ? strA.AsReadOnlySpan(indexA, length) : strA.AsReadOnlySpan(indexA); - ReadOnlySpan value = length <= (strB.Length - indexB) ? strB.AsReadOnlySpan(indexB, length) : strB.AsReadOnlySpan(indexB); + ReadOnlySpan span = length <= (strA.Length - indexA) ? strA.AsSpan(indexA, length) : strA.AsSpan(indexA); + ReadOnlySpan value = length <= (strB.Length - indexB) ? strB.AsSpan(indexB, length) : strB.AsSpan(indexB); //Assert.True(expected == Math.Sign(span.CompareTo(value, comparisonType)), span.ToString() + "|" + value.ToString() + "|" + length); Assert.Equal(expected, Math.Sign(span.CompareTo(value, comparisonType))); } diff --git a/src/System.Memory/tests/ReadOnlySpan/Contains.cs b/src/System.Memory/tests/ReadOnlySpan/Contains.cs index ffbe5f5733a8..a38f9049668b 100644 --- a/src/System.Memory/tests/ReadOnlySpan/Contains.cs +++ b/src/System.Memory/tests/ReadOnlySpan/Contains.cs @@ -175,7 +175,7 @@ public static void ContainsUnknownComparisonType_StringComparison() [InlineData("Hello", "", true)] public static void Contains(string s, string value, bool expected) { - Assert.Equal(expected, s.AsReadOnlySpan().Contains(value.AsReadOnlySpan(), StringComparison.Ordinal)); + Assert.Equal(expected, s.AsSpan().Contains(value.AsSpan(), StringComparison.Ordinal)); } } } diff --git a/src/System.Memory/tests/ReadOnlySpan/Equals.cs b/src/System.Memory/tests/ReadOnlySpan/Equals.cs index 87137d2e98e6..9d88f4ff2523 100644 --- a/src/System.Memory/tests/ReadOnlySpan/Equals.cs +++ b/src/System.Memory/tests/ReadOnlySpan/Equals.cs @@ -232,7 +232,7 @@ public static void EqualsUnknownComparisonType_StringComparison() [InlineData("", "", StringComparison.OrdinalIgnoreCase, true)] public static void Equals(string s1, string s2, StringComparison comparisonType, bool expected) { - Assert.Equal(expected, s1.AsReadOnlySpan().Equals(s2.AsReadOnlySpan(), comparisonType)); + Assert.Equal(expected, s1.AsSpan().Equals(s2.AsSpan(), comparisonType)); } public static IEnumerable Equals_EncyclopaediaData() @@ -256,7 +256,7 @@ public static void Equals_Encyclopaedia_ReturnsExpected(StringComparison compari CultureInfo backupCulture = CultureInfo.CurrentCulture; Thread.CurrentThread.CurrentCulture = new CultureInfo("se-SE"); - Assert.Equal(expected, source.AsReadOnlySpan().Equals(target.AsReadOnlySpan(), comparison)); + Assert.Equal(expected, source.AsSpan().Equals(target.AsSpan(), comparison)); Thread.CurrentThread.CurrentCulture = backupCulture; } diff --git a/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs b/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs index 32cd8fe6efc6..e4c98ad4d63c 100644 --- a/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs +++ b/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs @@ -65,7 +65,7 @@ public static void IndexOf_SingleLetter(string s, char target, int startIndex, i IsSafeForCurrentCultureComparisons(s) && IsSafeForCurrentCultureComparisons(target.ToString()); - ReadOnlySpan span = s.AsReadOnlySpan(); + ReadOnlySpan span = s.AsSpan(); var charArray = new char[1]; charArray[0] = target; ReadOnlySpan targetSpan = charArray; @@ -135,7 +135,7 @@ private static bool IsSafeForCurrentCultureComparisons(string str) [InlineData("Hello", "l\0o", -1)] public static void IndexOf_NullInStrings(string s, string value, int expected) { - Assert.Equal(expected, s.AsReadOnlySpan().IndexOf(value.AsReadOnlySpan(), StringComparison.Ordinal, out _)); + Assert.Equal(expected, s.AsSpan().IndexOf(value.AsSpan(), StringComparison.Ordinal, out _)); } [Theory] @@ -145,25 +145,25 @@ public static void IndexOf_AllSubstrings(string s, string value, int startIndex, bool ignoringCase = comparison == StringComparison.OrdinalIgnoreCase || comparison == StringComparison.CurrentCultureIgnoreCase; // First find the substring. We should be able to with all comparison types. - Assert.Equal(startIndex, s.AsReadOnlySpan().IndexOf(value.AsReadOnlySpan(), comparison, out _)); // in the whole string - Assert.Equal(0, s.AsReadOnlySpan(startIndex).IndexOf(value.AsReadOnlySpan(), comparison, out _)); // starting at substring + Assert.Equal(startIndex, s.AsSpan().IndexOf(value.AsSpan(), comparison, out _)); // in the whole string + Assert.Equal(0, s.AsSpan(startIndex).IndexOf(value.AsSpan(), comparison, out _)); // starting at substring if (startIndex > 0) { - Assert.Equal(1, s.AsReadOnlySpan(startIndex - 1).IndexOf(value.AsReadOnlySpan(), comparison, out _)); // starting just before substring + Assert.Equal(1, s.AsSpan(startIndex - 1).IndexOf(value.AsSpan(), comparison, out _)); // starting just before substring } - Assert.Equal(-1, s.AsReadOnlySpan(startIndex + 1).IndexOf(value.AsReadOnlySpan(), comparison, out _)); // starting just after start of substring + Assert.Equal(-1, s.AsSpan(startIndex + 1).IndexOf(value.AsSpan(), comparison, out _)); // starting just after start of substring // Shouldn't be able to find the substring if the count is less than substring's length - Assert.Equal(-1, s.AsReadOnlySpan(0, value.Length - 1).IndexOf(value.AsReadOnlySpan(), comparison, out _)); + Assert.Equal(-1, s.AsSpan(0, value.Length - 1).IndexOf(value.AsSpan(), comparison, out _)); // Now double the source. Make sure we find the first copy of the substring. int halfLen = s.Length; s += s; - Assert.Equal(startIndex, s.AsReadOnlySpan().IndexOf(value.AsReadOnlySpan(), comparison, out _)); + Assert.Equal(startIndex, s.AsSpan().IndexOf(value.AsSpan(), comparison, out _)); // Now change the case of a letter. s = s.ToUpperInvariant(); - Assert.Equal(ignoringCase ? startIndex : -1, s.AsReadOnlySpan().IndexOf(value.AsReadOnlySpan(), comparison, out _)); + Assert.Equal(ignoringCase ? startIndex : -1, s.AsSpan().IndexOf(value.AsSpan(), comparison, out _)); } public static IEnumerable AllSubstringsAndComparisons(string source) @@ -197,15 +197,15 @@ public static void IndexOf_TurkishI_TurkishCulture() string str = "Turkish I \u0131s TROUBL\u0130NG!"; string valueString = "\u0130"; - ReadOnlySpan s = str.AsReadOnlySpan(); - ReadOnlySpan value = valueString.AsReadOnlySpan(); + ReadOnlySpan s = str.AsSpan(); + ReadOnlySpan value = valueString.AsSpan(); Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture, out _)); Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); Assert.Equal(19, s.IndexOf(value, StringComparison.Ordinal, out _)); Assert.Equal(19, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); valueString = "\u0131"; - value = valueString.AsReadOnlySpan(); + value = valueString.AsSpan(); Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); Assert.Equal(10, s.IndexOf(value, StringComparison.Ordinal, out _)); @@ -223,14 +223,14 @@ public static void IndexOf_TurkishI_InvariantCulture() string str = "Turkish I \u0131s TROUBL\u0130NG!"; string valueString = "\u0130"; - ReadOnlySpan s = str.AsReadOnlySpan(); - ReadOnlySpan value = valueString.AsReadOnlySpan(); + ReadOnlySpan s = str.AsSpan(); + ReadOnlySpan value = valueString.AsSpan(); Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture, out _)); Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); valueString = "\u0131"; - value = valueString.AsReadOnlySpan(); + value = valueString.AsSpan(); Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); @@ -246,14 +246,14 @@ public static void IndexOf_TurkishI_EnglishUSCulture() string str = "Turkish I \u0131s TROUBL\u0130NG!"; string valueString = "\u0130"; - ReadOnlySpan s = str.AsReadOnlySpan(); - ReadOnlySpan value = valueString.AsReadOnlySpan(); + ReadOnlySpan s = str.AsSpan(); + ReadOnlySpan value = valueString.AsSpan(); Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture, out _)); Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); valueString = "\u0131"; - value = valueString.AsReadOnlySpan(); + value = valueString.AsSpan(); Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); @@ -266,8 +266,8 @@ public static void IndexOf_HungarianDoubleCompression_HungarianCulture() string str = "dzsdzs"; string valueString = "ddzs"; - ReadOnlySpan s = str.AsReadOnlySpan(); - ReadOnlySpan value = valueString.AsReadOnlySpan(); + ReadOnlySpan s = str.AsSpan(); + ReadOnlySpan value = valueString.AsSpan(); CultureInfo backupCulture = CultureInfo.CurrentCulture; @@ -294,8 +294,8 @@ public static void IndexOf_HungarianDoubleCompression_InvariantCulture() string str = "dzsdzs"; string valueString = "ddzs"; - ReadOnlySpan s = str.AsReadOnlySpan(); - ReadOnlySpan value = valueString.AsReadOnlySpan(); + ReadOnlySpan s = str.AsSpan(); + ReadOnlySpan value = valueString.AsSpan(); CultureInfo backupCulture = CultureInfo.CurrentCulture; @@ -312,8 +312,8 @@ public static void IndexOf_EquivalentDiacritics_EnglishUSCulture() string str = "Exhibit a\u0300\u00C0"; string valueString = "\u00C0"; - ReadOnlySpan s = str.AsReadOnlySpan(); - ReadOnlySpan value = valueString.AsReadOnlySpan(); + ReadOnlySpan s = str.AsSpan(); + ReadOnlySpan value = valueString.AsSpan(); CultureInfo backupCulture = CultureInfo.CurrentCulture; @@ -324,7 +324,7 @@ public static void IndexOf_EquivalentDiacritics_EnglishUSCulture() Assert.Equal(10, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); valueString = "a\u0300"; // this diacritic combines with preceding character - value = valueString.AsReadOnlySpan(); + value = valueString.AsSpan(); Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCulture, out _)); Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); Assert.Equal(8, s.IndexOf(value, StringComparison.Ordinal, out _)); @@ -339,8 +339,8 @@ public static void IndexOf_EquivalentDiacritics_InvariantCulture() string str = "Exhibit a\u0300\u00C0"; string valueString = "\u00C0"; - ReadOnlySpan s = str.AsReadOnlySpan(); - ReadOnlySpan value = valueString.AsReadOnlySpan(); + ReadOnlySpan s = str.AsSpan(); + ReadOnlySpan value = valueString.AsSpan(); CultureInfo backupCulture = CultureInfo.CurrentCulture; @@ -349,7 +349,7 @@ public static void IndexOf_EquivalentDiacritics_InvariantCulture() Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); valueString = "a\u0300"; // this diacritic combines with preceding character - value = valueString.AsReadOnlySpan(); + value = valueString.AsSpan(); Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCulture, out _)); Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); @@ -362,8 +362,8 @@ public static void IndexOf_CyrillicE_EnglishUSCulture() string str = "Foo\u0400Bar"; string valueString = "\u0400"; - ReadOnlySpan s = str.AsReadOnlySpan(); - ReadOnlySpan value = valueString.AsReadOnlySpan(); + ReadOnlySpan s = str.AsSpan(); + ReadOnlySpan value = valueString.AsSpan(); CultureInfo backupCulture = CultureInfo.CurrentCulture; @@ -374,7 +374,7 @@ public static void IndexOf_CyrillicE_EnglishUSCulture() Assert.Equal(3, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); valueString = "bar"; - value = valueString.AsReadOnlySpan(); + value = valueString.AsSpan(); Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture, out _)); Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); Assert.Equal(-1, s.IndexOf(value, StringComparison.Ordinal, out _)); @@ -389,8 +389,8 @@ public static void IndexOf_CyrillicE_InvariantCulture() string str = "Foo\u0400Bar"; string valueString = "\u0400"; - ReadOnlySpan s = str.AsReadOnlySpan(); - ReadOnlySpan value = valueString.AsReadOnlySpan(); + ReadOnlySpan s = str.AsSpan(); + ReadOnlySpan value = valueString.AsSpan(); CultureInfo backupCulture = CultureInfo.CurrentCulture; @@ -399,7 +399,7 @@ public static void IndexOf_CyrillicE_InvariantCulture() Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); valueString = "bar"; - value = valueString.AsReadOnlySpan(); + value = valueString.AsSpan(); Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture, out _)); Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); From 8dd0adf6fecb18e13e490ba4cdb7d2d6cf10e1f4 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 18:45:16 -0800 Subject: [PATCH 3/5] Remove the out parameter from IndexOf --- src/System.Memory/ref/System.Memory.cs | 6 +- .../src/System/MemoryExtensions.Fast.cs | 5 +- .../src/System/MemoryExtensions.Portable.cs | 18 +++ .../tests/ReadOnlySpan/IndexOf.charSpan.cs | 126 +++++++++--------- .../tests/System.Memory.Tests.csproj | 4 +- 5 files changed, 86 insertions(+), 73 deletions(-) diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index b0baffb99c04..e91396d396e0 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -30,17 +30,13 @@ public static partial class MemoryExtensions public static int BinarySearch(this System.Span span, T value, TComparer comparer) where TComparer : System.Collections.Generic.IComparer { throw null; } public static int BinarySearch(this System.Span span, TComparable comparable) where TComparable : System.IComparable { throw null; } public static int CompareTo(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } -#if !FEATURE_PORTABLE_SPAN public static bool Contains(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } -#endif public static void CopyTo(this T[] array, System.Memory destination) { } public static void CopyTo(this T[] array, System.Span destination) { } public static bool EndsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } public static bool EndsWith(this System.Span span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } public static bool Equals(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } -#if !FEATURE_PORTABLE_SPAN - public static int IndexOf(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType, out int matchedLength) { throw null; } -#endif + public static int IndexOf(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } public static int IndexOfAny(this System.ReadOnlySpan span, System.ReadOnlySpan values) where T : System.IEquatable { throw null; } public static int IndexOfAny(this System.ReadOnlySpan span, T value0, T value1) where T : System.IEquatable { throw null; } public static int IndexOfAny(this System.ReadOnlySpan span, T value0, T value1, T value2) where T : System.IEquatable { throw null; } diff --git a/src/System.Memory/src/System/MemoryExtensions.Fast.cs b/src/System.Memory/src/System/MemoryExtensions.Fast.cs index aae618484ef6..089cb4c42c54 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Fast.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Fast.cs @@ -45,10 +45,9 @@ public static int CompareTo(this ReadOnlySpan span, ReadOnlySpan val /// The source span. /// The value to seek within the source span. /// One of the enumeration values that determines how the and are compared. - /// Returns the length of the matched span. /// - public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType, out int matchedLength) - => Span.IndexOf(span, value, comparisonType, out matchedLength); + public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + => Span.IndexOf(span, value, comparisonType); /// /// Casts a Span of one primitive type to Span of bytes. diff --git a/src/System.Memory/src/System/MemoryExtensions.Portable.cs b/src/System.Memory/src/System/MemoryExtensions.Portable.cs index 3ee129f8cc95..9a0d4882e050 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -12,6 +12,15 @@ namespace System /// public static partial class MemoryExtensions { + /// + /// Returns a value indicating whether the specified occurs within the . + /// The source span. + /// The value to seek within the source span. + /// One of the enumeration values that determines how the and are compared. + /// + public static bool Contains(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + => return (IndexOf(span, value, comparisonType) >= 0); + /// /// Determines whether this and the specified span have the same characters /// when compared using the specified option. @@ -31,6 +40,15 @@ public static bool Equals(this ReadOnlySpan span, ReadOnlySpan value /// public static int CompareTo(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) => string.Compare(span.ToString(), value.ToString(), comparisonType); + + /// + /// Reports the zero-based index of the first occurrence of the specified in the current . + /// The source span. + /// The value to seek within the source span. + /// One of the enumeration values that determines how the and are compared. + /// + public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + => span.ToString().IndexOf(value.ToString(), comparisonType); /// /// Casts a Span of one primitive type to Span of bytes. diff --git a/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs b/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs index e4c98ad4d63c..b505deaa87ab 100644 --- a/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs +++ b/src/System.Memory/tests/ReadOnlySpan/IndexOf.charSpan.cs @@ -76,31 +76,31 @@ public static void IndexOf_SingleLetter(string s, char target, int startIndex, i { if (startIndex == 0) { - Assert.Equal(expectedFromSpan, span.IndexOf(targetSpan, StringComparison.Ordinal, out _)); - Assert.Equal(expectedFromSpan, span.IndexOf(targetSpan, StringComparison.OrdinalIgnoreCase, out _)); + Assert.Equal(expectedFromSpan, span.IndexOf(targetSpan, StringComparison.Ordinal)); + Assert.Equal(expectedFromSpan, span.IndexOf(targetSpan, StringComparison.OrdinalIgnoreCase)); // To be safe we only want to run CurrentCulture comparisons if // we know the results will not vary depending on location if (safeForCurrentCulture) { - Assert.Equal(expectedFromSpan, span.IndexOf(targetSpan, StringComparison.CurrentCulture, out _)); + Assert.Equal(expectedFromSpan, span.IndexOf(targetSpan, StringComparison.CurrentCulture)); } } - Assert.Equal(expectedFromSpan, span.Slice(startIndex).IndexOf(targetSpan, StringComparison.Ordinal, out _)); - Assert.Equal(expectedFromSpan, span.Slice(startIndex).IndexOf(targetSpan, StringComparison.OrdinalIgnoreCase, out _)); + Assert.Equal(expectedFromSpan, span.Slice(startIndex).IndexOf(targetSpan, StringComparison.Ordinal)); + Assert.Equal(expectedFromSpan, span.Slice(startIndex).IndexOf(targetSpan, StringComparison.OrdinalIgnoreCase)); if (safeForCurrentCulture) { - Assert.Equal(expectedFromSpan, span.Slice(startIndex).IndexOf(targetSpan, StringComparison.CurrentCulture, out _)); + Assert.Equal(expectedFromSpan, span.Slice(startIndex).IndexOf(targetSpan, StringComparison.CurrentCulture)); } } - Assert.Equal(expectedFromSpan, span.Slice(startIndex, count).IndexOf(targetSpan, StringComparison.Ordinal, out _)); - Assert.Equal(expectedFromSpan, span.Slice(startIndex, count).IndexOf(targetSpan, StringComparison.OrdinalIgnoreCase, out _)); + Assert.Equal(expectedFromSpan, span.Slice(startIndex, count).IndexOf(targetSpan, StringComparison.Ordinal)); + Assert.Equal(expectedFromSpan, span.Slice(startIndex, count).IndexOf(targetSpan, StringComparison.OrdinalIgnoreCase)); if (safeForCurrentCulture) { - Assert.Equal(expectedFromSpan, span.Slice(startIndex, count).IndexOf(targetSpan, StringComparison.CurrentCulture, out _)); + Assert.Equal(expectedFromSpan, span.Slice(startIndex, count).IndexOf(targetSpan, StringComparison.CurrentCulture)); } } @@ -135,7 +135,7 @@ private static bool IsSafeForCurrentCultureComparisons(string str) [InlineData("Hello", "l\0o", -1)] public static void IndexOf_NullInStrings(string s, string value, int expected) { - Assert.Equal(expected, s.AsSpan().IndexOf(value.AsSpan(), StringComparison.Ordinal, out _)); + Assert.Equal(expected, s.AsSpan().IndexOf(value.AsSpan(), StringComparison.Ordinal)); } [Theory] @@ -145,25 +145,25 @@ public static void IndexOf_AllSubstrings(string s, string value, int startIndex, bool ignoringCase = comparison == StringComparison.OrdinalIgnoreCase || comparison == StringComparison.CurrentCultureIgnoreCase; // First find the substring. We should be able to with all comparison types. - Assert.Equal(startIndex, s.AsSpan().IndexOf(value.AsSpan(), comparison, out _)); // in the whole string - Assert.Equal(0, s.AsSpan(startIndex).IndexOf(value.AsSpan(), comparison, out _)); // starting at substring + Assert.Equal(startIndex, s.AsSpan().IndexOf(value.AsSpan(), comparison)); // in the whole string + Assert.Equal(0, s.AsSpan(startIndex).IndexOf(value.AsSpan(), comparison)); // starting at substring if (startIndex > 0) { - Assert.Equal(1, s.AsSpan(startIndex - 1).IndexOf(value.AsSpan(), comparison, out _)); // starting just before substring + Assert.Equal(1, s.AsSpan(startIndex - 1).IndexOf(value.AsSpan(), comparison)); // starting just before substring } - Assert.Equal(-1, s.AsSpan(startIndex + 1).IndexOf(value.AsSpan(), comparison, out _)); // starting just after start of substring + Assert.Equal(-1, s.AsSpan(startIndex + 1).IndexOf(value.AsSpan(), comparison)); // starting just after start of substring // Shouldn't be able to find the substring if the count is less than substring's length - Assert.Equal(-1, s.AsSpan(0, value.Length - 1).IndexOf(value.AsSpan(), comparison, out _)); + Assert.Equal(-1, s.AsSpan(0, value.Length - 1).IndexOf(value.AsSpan(), comparison)); // Now double the source. Make sure we find the first copy of the substring. int halfLen = s.Length; s += s; - Assert.Equal(startIndex, s.AsSpan().IndexOf(value.AsSpan(), comparison, out _)); + Assert.Equal(startIndex, s.AsSpan().IndexOf(value.AsSpan(), comparison)); // Now change the case of a letter. s = s.ToUpperInvariant(); - Assert.Equal(ignoringCase ? startIndex : -1, s.AsSpan().IndexOf(value.AsSpan(), comparison, out _)); + Assert.Equal(ignoringCase ? startIndex : -1, s.AsSpan().IndexOf(value.AsSpan(), comparison)); } public static IEnumerable AllSubstringsAndComparisons(string source) @@ -199,17 +199,17 @@ public static void IndexOf_TurkishI_TurkishCulture() string valueString = "\u0130"; ReadOnlySpan s = str.AsSpan(); ReadOnlySpan value = valueString.AsSpan(); - Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); - Assert.Equal(19, s.IndexOf(value, StringComparison.Ordinal, out _)); - Assert.Equal(19, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(19, s.IndexOf(value, StringComparison.Ordinal)); + Assert.Equal(19, s.IndexOf(value, StringComparison.OrdinalIgnoreCase)); valueString = "\u0131"; value = valueString.AsSpan(); - Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); - Assert.Equal(10, s.IndexOf(value, StringComparison.Ordinal, out _)); - Assert.Equal(10, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(10, s.IndexOf(value, StringComparison.Ordinal)); + Assert.Equal(10, s.IndexOf(value, StringComparison.OrdinalIgnoreCase)); Thread.CurrentThread.CurrentCulture = backupCulture; } @@ -226,13 +226,13 @@ public static void IndexOf_TurkishI_InvariantCulture() ReadOnlySpan s = str.AsSpan(); ReadOnlySpan value = valueString.AsSpan(); - Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); valueString = "\u0131"; value = valueString.AsSpan(); - Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); Thread.CurrentThread.CurrentCulture = backupCulture; } @@ -249,13 +249,13 @@ public static void IndexOf_TurkishI_EnglishUSCulture() ReadOnlySpan s = str.AsSpan(); ReadOnlySpan value = valueString.AsSpan(); - Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(19, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); valueString = "\u0131"; value = valueString.AsSpan(); - Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); Thread.CurrentThread.CurrentCulture = backupCulture; } @@ -279,11 +279,11 @@ There are differences between Windows and ICU regarding contractions. If CurrentCultureIgnoreCase is specified, ICU will use 'secondary' collation rules which ignore the contraction collation weights (defined as 'tertiary' rules) */ - Assert.Equal(PlatformDetection.IsWindows ? 0 : -1, s.IndexOf(value, StringComparison.CurrentCulture, out _)); + Assert.Equal(PlatformDetection.IsWindows ? 0 : -1, s.IndexOf(value, StringComparison.CurrentCulture)); - Assert.Equal(0, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); - Assert.Equal(-1, s.IndexOf(value, StringComparison.Ordinal, out _)); - Assert.Equal(-1, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + Assert.Equal(0, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.Ordinal)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.OrdinalIgnoreCase)); Thread.CurrentThread.CurrentCulture = backupCulture; } @@ -300,8 +300,8 @@ public static void IndexOf_HungarianDoubleCompression_InvariantCulture() CultureInfo backupCulture = CultureInfo.CurrentCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; - Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); Thread.CurrentThread.CurrentCulture = backupCulture; } @@ -318,17 +318,17 @@ public static void IndexOf_EquivalentDiacritics_EnglishUSCulture() CultureInfo backupCulture = CultureInfo.CurrentCulture; Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); - Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); - Assert.Equal(10, s.IndexOf(value, StringComparison.Ordinal, out _)); - Assert.Equal(10, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(10, s.IndexOf(value, StringComparison.Ordinal)); + Assert.Equal(10, s.IndexOf(value, StringComparison.OrdinalIgnoreCase)); valueString = "a\u0300"; // this diacritic combines with preceding character value = valueString.AsSpan(); - Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); - Assert.Equal(8, s.IndexOf(value, StringComparison.Ordinal, out _)); - Assert.Equal(8, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(8, s.IndexOf(value, StringComparison.Ordinal)); + Assert.Equal(8, s.IndexOf(value, StringComparison.OrdinalIgnoreCase)); Thread.CurrentThread.CurrentCulture = backupCulture; } @@ -345,13 +345,13 @@ public static void IndexOf_EquivalentDiacritics_InvariantCulture() CultureInfo backupCulture = CultureInfo.CurrentCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; - Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(10, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); valueString = "a\u0300"; // this diacritic combines with preceding character value = valueString.AsSpan(); - Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(8, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); Thread.CurrentThread.CurrentCulture = backupCulture; } @@ -368,17 +368,17 @@ public static void IndexOf_CyrillicE_EnglishUSCulture() CultureInfo backupCulture = CultureInfo.CurrentCulture; Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); - Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); - Assert.Equal(3, s.IndexOf(value, StringComparison.Ordinal, out _)); - Assert.Equal(3, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(3, s.IndexOf(value, StringComparison.Ordinal)); + Assert.Equal(3, s.IndexOf(value, StringComparison.OrdinalIgnoreCase)); valueString = "bar"; value = valueString.AsSpan(); - Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); - Assert.Equal(-1, s.IndexOf(value, StringComparison.Ordinal, out _)); - Assert.Equal(4, s.IndexOf(value, StringComparison.OrdinalIgnoreCase, out _)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.Ordinal)); + Assert.Equal(4, s.IndexOf(value, StringComparison.OrdinalIgnoreCase)); Thread.CurrentThread.CurrentCulture = backupCulture; } @@ -395,13 +395,13 @@ public static void IndexOf_CyrillicE_InvariantCulture() CultureInfo backupCulture = CultureInfo.CurrentCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; - Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(3, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); valueString = "bar"; value = valueString.AsSpan(); - Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture, out _)); - Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase, out _)); + Assert.Equal(-1, s.IndexOf(value, StringComparison.CurrentCulture)); + Assert.Equal(4, s.IndexOf(value, StringComparison.CurrentCultureIgnoreCase)); Thread.CurrentThread.CurrentCulture = backupCulture; } diff --git a/src/System.Memory/tests/System.Memory.Tests.csproj b/src/System.Memory/tests/System.Memory.Tests.csproj index e85d72b2419d..d4e3519ef0f7 100644 --- a/src/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/System.Memory/tests/System.Memory.Tests.csproj @@ -16,8 +16,6 @@ - - @@ -83,6 +81,7 @@ + @@ -98,6 +97,7 @@ + From 23589abd3453918f93968047ee382a78fc803636 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 20:57:04 -0800 Subject: [PATCH 4/5] Add tests and avoid allocations for StringComparison.Ordinal --- .../src/System/MemoryExtensions.Portable.cs | 20 ++++++++++++++++--- .../tests/ReadOnlySpan/Equals.cs | 2 +- .../ReadOnlySpan/IndexOfSequence.char.cs | 12 +++++++++++ src/System.Memory/tests/Span/ToString.cs | 2 +- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/System.Memory/src/System/MemoryExtensions.Portable.cs b/src/System.Memory/src/System/MemoryExtensions.Portable.cs index 9a0d4882e050..94717f6900f8 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -19,7 +19,7 @@ public static partial class MemoryExtensions /// One of the enumeration values that determines how the and are compared. /// public static bool Contains(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) - => return (IndexOf(span, value, comparisonType) >= 0); + => (IndexOf(span, value, comparisonType) >= 0); /// /// Determines whether this and the specified span have the same characters @@ -29,7 +29,14 @@ public static bool Contains(this ReadOnlySpan span, ReadOnlySpan val /// One of the enumeration values that determines how the and are compared. /// public static bool Equals(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) - => span.ToString().Equals(value.ToString(), comparisonType); + { + if (comparisonType == StringComparison.Ordinal) + { + return span.SequenceEqual(value); + } + + return span.ToString().Equals(value.ToString(), comparisonType); + } /// /// Compares the specified and using the specified , @@ -48,7 +55,14 @@ public static int CompareTo(this ReadOnlySpan span, ReadOnlySpan val /// One of the enumeration values that determines how the and are compared. /// public static int IndexOf(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) - => span.ToString().IndexOf(value.ToString(), comparisonType); + { + if (comparisonType == StringComparison.Ordinal) + { + return span.IndexOf(value); + } + + return span.ToString().IndexOf(value.ToString(), comparisonType); + } /// /// Casts a Span of one primitive type to Span of bytes. diff --git a/src/System.Memory/tests/ReadOnlySpan/Equals.cs b/src/System.Memory/tests/ReadOnlySpan/Equals.cs index 9d88f4ff2523..18af3c7fa8e8 100644 --- a/src/System.Memory/tests/ReadOnlySpan/Equals.cs +++ b/src/System.Memory/tests/ReadOnlySpan/Equals.cs @@ -15,7 +15,7 @@ public static partial class ReadOnlySpanTests private const string SoftHyphen = "\u00AD"; [Fact] - public static unsafe void ZeroLengthEquals_StringComparison() + public static void ZeroLengthEquals_StringComparison() { char[] a = { '4', '5', '6' }; var span = new ReadOnlySpan(a); diff --git a/src/System.Memory/tests/ReadOnlySpan/IndexOfSequence.char.cs b/src/System.Memory/tests/ReadOnlySpan/IndexOfSequence.char.cs index eedc14ac0c6c..9841b76fa1a1 100644 --- a/src/System.Memory/tests/ReadOnlySpan/IndexOfSequence.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/IndexOfSequence.char.cs @@ -15,6 +15,7 @@ public static void IndexOfSequenceMatchAtStart_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { '5', '1', '7' }); int index = span.IndexOf(value); Assert.Equal(0, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -24,6 +25,7 @@ public static void IndexOfSequenceMultipleMatch_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { '2', '3' }); int index = span.IndexOf(value); Assert.Equal(1, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -33,6 +35,7 @@ public static void IndexOfSequenceRestart_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { '7', '7', '8' }); int index = span.IndexOf(value); Assert.Equal(10, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -42,6 +45,7 @@ public static void IndexOfSequenceNoMatch_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { '7', '7', '8', 'X' }); int index = span.IndexOf(value); Assert.Equal(-1, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -51,6 +55,7 @@ public static void IndexOfSequenceNotEvenAHeadMatch_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { 'X', '7', '8', '9' }); int index = span.IndexOf(value); Assert.Equal(-1, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -60,6 +65,7 @@ public static void IndexOfSequenceMatchAtVeryEnd_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { '3', '4', '5' }); int index = span.IndexOf(value); Assert.Equal(3, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -69,6 +75,7 @@ public static void IndexOfSequenceJustPastVeryEnd_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { '3', '4', '5' }); int index = span.IndexOf(value); Assert.Equal(-1, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -79,6 +86,7 @@ public static void IndexOfSequenceZeroLengthValue_Char() ReadOnlySpan value = new ReadOnlySpan(Array.Empty()); int index = span.IndexOf(value); Assert.Equal(0, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -88,6 +96,7 @@ public static void IndexOfSequenceZeroLengthSpan_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { '1', '2', '3' }); int index = span.IndexOf(value); Assert.Equal(-1, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -98,6 +107,7 @@ public static void IndexOfSequenceLengthOneValue_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { '2' }); int index = span.IndexOf(value); Assert.Equal(2, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -108,6 +118,7 @@ public static void IndexOfSequenceLengthOneValueAtVeryEnd_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { '5' }); int index = span.IndexOf(value); Assert.Equal(5, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } [Fact] @@ -118,6 +129,7 @@ public static void IndexOfSequenceLengthOneValueJustPasttVeryEnd_Char() ReadOnlySpan value = new ReadOnlySpan(new char[] { '5' }); int index = span.IndexOf(value); Assert.Equal(-1, index); + Assert.Equal(index, span.IndexOf(value, StringComparison.Ordinal)); } } } diff --git a/src/System.Memory/tests/Span/ToString.cs b/src/System.Memory/tests/Span/ToString.cs index b37a41bee18b..798b9f1cbc84 100644 --- a/src/System.Memory/tests/Span/ToString.cs +++ b/src/System.Memory/tests/Span/ToString.cs @@ -49,7 +49,7 @@ public static void ToStringChar_Empty() } [Fact] - public static unsafe void ToStringForSpanOfString() + public static void ToStringForSpanOfString() { string[] a = { "a", "b", "c" }; var span = new Span(a); From 06c34b42b4a927c32c7e577fc254a9657029b9d7 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 21:12:20 -0800 Subject: [PATCH 5/5] Remove duplicate definition of SoftHyphen --- src/System.Memory/tests/ReadOnlySpan/Equals.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/System.Memory/tests/ReadOnlySpan/Equals.cs b/src/System.Memory/tests/ReadOnlySpan/Equals.cs index 18af3c7fa8e8..b2462d5a0056 100644 --- a/src/System.Memory/tests/ReadOnlySpan/Equals.cs +++ b/src/System.Memory/tests/ReadOnlySpan/Equals.cs @@ -12,8 +12,6 @@ namespace System.SpanTests { public static partial class ReadOnlySpanTests { - private const string SoftHyphen = "\u00AD"; - [Fact] public static void ZeroLengthEquals_StringComparison() {