From 53c16cff9b0898683987a4ad39f1b6a134221353 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Mon, 5 Feb 2018 18:29:33 -0800 Subject: [PATCH 01/17] Add ROSpan StartsWith and EndsWith string-like APIs with StringComparison --- src/System.Memory/ref/System.Memory.cs | 24 +++++++-------- .../src/System/MemoryExtensions.cs | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index 68b17c44839a..b7bc3f081268 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -91,7 +91,6 @@ public void CopyTo(System.Memory destination) { } public override bool Equals(object obj) { throw null; } [System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)(1))] public override int GetHashCode() { throw null; } - public override string ToString() { throw null; } public static implicit operator System.Memory (System.ArraySegment arraySegment) { throw null; } public static implicit operator System.ReadOnlyMemory (System.Memory memory) { throw null; } public static implicit operator System.Memory (T[] array) { throw null; } @@ -117,7 +116,6 @@ public void CopyTo(System.Memory destination) { } public bool Equals(System.ReadOnlyMemory other) { throw null; } [System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)(1))] public override int GetHashCode() { throw null; } - public override string ToString() { throw null; } public static implicit operator System.ReadOnlyMemory (System.ArraySegment arraySegment) { throw null; } public static implicit operator System.ReadOnlyMemory (T[] array) { throw null; } public System.Buffers.MemoryHandle Retain(bool pin = false) { throw null; } @@ -145,7 +143,6 @@ public void CopyTo(System.Span destination) { } [System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)(1))] [System.ObsoleteAttribute("GetHashCode() on ReadOnlySpan will always throw an exception.")] public override int GetHashCode() { throw null; } - public override string ToString() { throw null; } public static bool operator ==(System.ReadOnlySpan left, System.ReadOnlySpan right) { throw null; } public static implicit operator System.ReadOnlySpan (System.ArraySegment arraySegment) { throw null; } public static implicit operator System.ReadOnlySpan (T[] array) { throw null; } @@ -153,6 +150,7 @@ public void CopyTo(System.Span destination) { } public System.ReadOnlySpan Slice(int start) { throw null; } public System.ReadOnlySpan Slice(int start, int length) { throw null; } public T[] ToArray() { throw null; } + public override string ToString() { throw null; } public bool TryCopyTo(System.Span destination) { throw null; } public ref partial struct Enumerator { @@ -182,7 +180,6 @@ public void Fill(T value) { } [System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)(1))] [System.ObsoleteAttribute("GetHashCode() on Span will always throw an exception.")] public override int GetHashCode() { throw null; } - public override string ToString() { throw null; } public static bool operator ==(System.Span left, System.Span right) { throw null; } public static implicit operator System.Span (System.ArraySegment arraySegment) { throw null; } public static implicit operator System.ReadOnlySpan (System.Span span) { throw null; } @@ -191,6 +188,7 @@ public void Fill(T value) { } public System.Span Slice(int start) { throw null; } public System.Span Slice(int start, int length) { throw null; } public T[] ToArray() { throw null; } + public override string ToString() { throw null; } public bool TryCopyTo(System.Span destination) { throw null; } public ref partial struct Enumerator { @@ -217,14 +215,14 @@ public partial struct MemoryHandle : System.IDisposable public unsafe void* Pointer { get { throw null; } } public void Dispose() { } } - public abstract class MemoryPool : IDisposable + public abstract partial class MemoryPool : System.IDisposable { - public static System.Buffers.MemoryPool Shared { get; } - public abstract System.Buffers.OwnedMemory Rent(int minBufferSize=-1); + protected MemoryPool() { } public abstract int MaxBufferSize { get; } - protected MemoryPool() { throw null; } - public void Dispose() { throw null; } + public static System.Buffers.MemoryPool Shared { get { throw null; } } + public void Dispose() { } protected abstract void Dispose(bool disposing); + public abstract System.Buffers.OwnedMemory Rent(int minBufferSize = -1); } public enum OperationStatus { @@ -426,15 +424,15 @@ namespace System.Runtime.InteropServices public static partial class MemoryMarshal { public static System.Memory AsMemory(System.ReadOnlyMemory readOnlyMemory) { throw null; } - public static ref T GetReference(System.ReadOnlySpan span) { throw null; } - public static ref T GetReference(System.Span span) { throw null; } - public static bool TryGetArray(System.ReadOnlyMemory readOnlyMemory, out System.ArraySegment arraySegment) { throw null; } - public static System.Collections.Generic.IEnumerable ToEnumerable(ReadOnlyMemory memory) { throw null; } public static System.ReadOnlySpan Cast(System.ReadOnlySpan source) where TFrom : struct where TTo : struct { throw null; } public static System.Span Cast(System.Span source) where TFrom : struct where TTo : struct { throw null; } #if !FEATURE_PORTABLE_SPAN public static System.ReadOnlySpan CreateReadOnlySpan(ref T reference, int length) { throw null; } public static System.Span CreateSpan(ref T reference, int length) { throw null; } #endif + public static ref T GetReference(System.ReadOnlySpan span) { throw null; } + public static ref T GetReference(System.Span span) { throw null; } + public static System.Collections.Generic.IEnumerable ToEnumerable(System.ReadOnlyMemory memory) { throw null; } + public static bool TryGetArray(System.ReadOnlyMemory readOnlyMemory, out System.ArraySegment arraySegment) { throw null; } } } diff --git a/src/System.Memory/src/System/MemoryExtensions.cs b/src/System.Memory/src/System/MemoryExtensions.cs index 2859beb15856..ce34b3e8e184 100644 --- a/src/System.Memory/src/System/MemoryExtensions.cs +++ b/src/System.Memory/src/System/MemoryExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -167,6 +168,34 @@ public static bool IsWhiteSpace(this ReadOnlySpan span) return true; } + /// + /// Determines whether the end of the matches the specified when compared using the specified option. + /// + /// The source span. + /// The sequence to compare to the end of the source span. + /// One of the enumeration values that determines how the and are compared. + public static bool EndsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) + { + string sourceString = span.ToString(); + string valueString = value.ToString(); + + return sourceString.EndsWith(valueString, comparison); + } + + /// + /// Determines whether the beginning of the matches the specified when compared using the specified option. + /// + /// The source span. + /// The sequence to compare to the beginning of the source span. + /// One of the enumeration values that determines how the and are compared. + public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) + { + string sourceString = span.ToString(); + string valueString = value.ToString(); + + return sourceString.StartsWith(valueString, comparison); + } + /// /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). /// From 18e7f6997864c2007d8ca1d6d5976d50b59e4399 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Mon, 5 Feb 2018 18:36:31 -0800 Subject: [PATCH 02/17] We have separate implementations of slow and fast span. --- .../src/System/MemoryExtensions.Fast.cs | 18 ++++++++++++ .../src/System/MemoryExtensions.Portable.cs | 28 +++++++++++++++++++ .../src/System/MemoryExtensions.cs | 28 ------------------- 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/System.Memory/src/System/MemoryExtensions.Fast.cs b/src/System.Memory/src/System/MemoryExtensions.Fast.cs index 2a540f43e8ba..f2053a027fc2 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Fast.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Fast.cs @@ -11,6 +11,24 @@ namespace System /// public static partial class MemoryExtensions { + /// + /// Determines whether the end of the matches the specified when compared using the specified option. + /// + /// The source span. + /// The sequence to compare to the end of the source span. + /// One of the enumeration values that determines how the and are compared. + public static bool EndsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) + => Span.EndsWith(span, value, comparison); + + /// + /// Determines whether the beginning of the matches the specified when compared using the specified option. + /// + /// The source span. + /// The sequence to compare to the beginning of the source span. + /// One of the enumeration values that determines how the and are compared. + public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) + => Span.StartsWith(span, value, comparison); + /// /// 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..75649b202931 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -12,6 +12,34 @@ namespace System /// public static partial class MemoryExtensions { + /// + /// Determines whether the end of the matches the specified when compared using the specified option. + /// + /// The source span. + /// The sequence to compare to the end of the source span. + /// One of the enumeration values that determines how the and are compared. + public static bool EndsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) + { + string sourceString = span.ToString(); + string valueString = value.ToString(); + + return sourceString.EndsWith(valueString, comparison); + } + + /// + /// Determines whether the beginning of the matches the specified when compared using the specified option. + /// + /// The source span. + /// The sequence to compare to the beginning of the source span. + /// One of the enumeration values that determines how the and are compared. + public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) + { + string sourceString = span.ToString(); + string valueString = value.ToString(); + + return sourceString.StartsWith(valueString, comparison); + } + /// /// 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.cs b/src/System.Memory/src/System/MemoryExtensions.cs index ce34b3e8e184..22482fdd2008 100644 --- a/src/System.Memory/src/System/MemoryExtensions.cs +++ b/src/System.Memory/src/System/MemoryExtensions.cs @@ -168,34 +168,6 @@ public static bool IsWhiteSpace(this ReadOnlySpan span) return true; } - /// - /// Determines whether the end of the matches the specified when compared using the specified option. - /// - /// The source span. - /// The sequence to compare to the end of the source span. - /// One of the enumeration values that determines how the and are compared. - public static bool EndsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) - { - string sourceString = span.ToString(); - string valueString = value.ToString(); - - return sourceString.EndsWith(valueString, comparison); - } - - /// - /// Determines whether the beginning of the matches the specified when compared using the specified option. - /// - /// The source span. - /// The sequence to compare to the beginning of the source span. - /// One of the enumeration values that determines how the and are compared. - public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) - { - string sourceString = span.ToString(); - string valueString = value.ToString(); - - return sourceString.StartsWith(valueString, comparison); - } - /// /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). /// From a257e3b9531c150dbd5434e2172cd7307a76ee3d Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Mon, 5 Feb 2018 18:38:10 -0800 Subject: [PATCH 03/17] Remove unused using directive. --- src/System.Memory/src/System/MemoryExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/System.Memory/src/System/MemoryExtensions.cs b/src/System.Memory/src/System/MemoryExtensions.cs index 22482fdd2008..2859beb15856 100644 --- a/src/System.Memory/src/System/MemoryExtensions.cs +++ b/src/System.Memory/src/System/MemoryExtensions.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; From ddfb49ddd7eb7830530decdb878d6776dbdd4410 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Tue, 6 Feb 2018 15:50:37 -0800 Subject: [PATCH 04/17] Address PR feedback and add tests. --- src/System.Memory/ref/System.Memory.cs | 2 ++ .../src/System/MemoryExtensions.Fast.cs | 16 ++++++------- .../src/System/MemoryExtensions.Portable.cs | 24 ++++++++++++------- .../tests/System.Memory.Tests.csproj | 4 +++- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index b7bc3f081268..d5209572044e 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -31,6 +31,7 @@ public static partial class MemoryExtensions public static int BinarySearch(this System.Span span, TComparable comparable) where TComparable : System.IComparable { throw null; } 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, System.StringComparison comparisonType) { throw null; } 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 int IndexOfAny(this System.ReadOnlySpan span, System.ReadOnlySpan values) where T : System.IEquatable { throw null; } @@ -63,6 +64,7 @@ public static void Reverse(this System.Span span) { } public static int SequenceCompareTo(this System.Span first, System.ReadOnlySpan second) where T : System.IComparable { throw null; } public static bool SequenceEqual(this System.ReadOnlySpan first, System.ReadOnlySpan second) where T : System.IEquatable { throw null; } public static bool SequenceEqual(this System.Span first, System.ReadOnlySpan second) where T : System.IEquatable { throw null; } + public static bool StartsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } public static bool StartsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } public static bool StartsWith(this System.Span span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span) { throw null; } diff --git a/src/System.Memory/src/System/MemoryExtensions.Fast.cs b/src/System.Memory/src/System/MemoryExtensions.Fast.cs index f2053a027fc2..9cff85921f80 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Fast.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Fast.cs @@ -12,22 +12,22 @@ namespace System public static partial class MemoryExtensions { /// - /// Determines whether the end of the matches the specified when compared using the specified option. + /// Determines whether the end of the matches the specified when compared using the specified option. /// /// The source span. /// The sequence to compare to the end of the source span. - /// One of the enumeration values that determines how the and are compared. - public static bool EndsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) - => Span.EndsWith(span, value, comparison); + /// One of the enumeration values that determines how the and are compared. + public static bool EndsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + => Span.EndsWith(span, value, comparisonType); /// - /// Determines whether the beginning of the matches the specified when compared using the specified option. + /// Determines whether the beginning of the matches the specified when compared using the specified option. /// /// The source span. /// The sequence to compare to the beginning of the source span. - /// One of the enumeration values that determines how the and are compared. - public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) - => Span.StartsWith(span, value, comparison); + /// One of the enumeration values that determines how the and are compared. + public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + => Span.StartsWith(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 75649b202931..d1bbb6d3d348 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -13,31 +13,39 @@ namespace System public static partial class MemoryExtensions { /// - /// Determines whether the end of the matches the specified when compared using the specified option. + /// Determines whether the end of the matches the specified when compared using the specified option. /// /// The source span. /// The sequence to compare to the end of the source span. - /// One of the enumeration values that determines how the and are compared. - public static bool EndsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) + /// One of the enumeration values that determines how the and are compared. + public static bool EndsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) { + if (comparisonType == StringComparison.Ordinal) + { + return span.EndsWith(value); + } string sourceString = span.ToString(); string valueString = value.ToString(); - return sourceString.EndsWith(valueString, comparison); + return sourceString.EndsWith(valueString, comparisonType); } /// - /// Determines whether the beginning of the matches the specified when compared using the specified option. + /// Determines whether the beginning of the matches the specified when compared using the specified option. /// /// The source span. /// The sequence to compare to the beginning of the source span. - /// One of the enumeration values that determines how the and are compared. - public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparison) + /// One of the enumeration values that determines how the and are compared. + public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) { + if (comparisonType == StringComparison.Ordinal) + { + return span.StartsWith(value); + } string sourceString = span.ToString(); string valueString = value.ToString(); - return sourceString.StartsWith(valueString, comparison); + return sourceString.StartsWith(valueString, comparisonType); } /// diff --git a/src/System.Memory/tests/System.Memory.Tests.csproj b/src/System.Memory/tests/System.Memory.Tests.csproj index 2e3e0df07fdf..1f2266859eb7 100644 --- a/src/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/System.Memory/tests/System.Memory.Tests.csproj @@ -79,6 +79,7 @@ + @@ -107,8 +108,9 @@ - + + From 8df2742847e6ac81af72addb2e155c32517f81fd Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Tue, 6 Feb 2018 15:51:18 -0800 Subject: [PATCH 05/17] Add test files --- .../tests/ReadOnlySpan/EndsWith.char.cs | 183 ++++++++++++++++++ .../tests/ReadOnlySpan/StartsWith.char.cs | 183 ++++++++++++++++++ 2 files changed, 366 insertions(+) create mode 100644 src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs create mode 100644 src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs diff --git a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs new file mode 100644 index 000000000000..2b51ddf8d968 --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs @@ -0,0 +1,183 @@ +// 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 ZeroLengthEndsWith_StringComparison() + { + var a = new char[3]; + + var span = new ReadOnlySpan(a); + var slice = new ReadOnlySpan(a, 2, 0); + Assert.True(span.EndsWith(slice, StringComparison.Ordinal)); + } + + [Fact] + public static void SameSpanEndsWith_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + Assert.True(span.EndsWith(span, StringComparison.Ordinal)); + } + + [Fact] + public static void LengthMismatchEndsWith_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 2); + var slice = new ReadOnlySpan(a, 0, 3); + Assert.False(span.EndsWith(slice, StringComparison.Ordinal)); + } + + [Fact] + public static void EndsWithMatch_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 3); + var slice = new ReadOnlySpan(a, 1, 2); + Assert.True(span.EndsWith(slice, StringComparison.Ordinal)); + } + + [Fact] + public static void EndsWithMatchDifferentSpans_StringComparison() + { + char[] a = { '7', '4', '5', '6' }; + char[] b = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 3); + var slice = new ReadOnlySpan(b, 0, 3); + Assert.True(span.EndsWith(slice, StringComparison.Ordinal)); + } + + [Fact] + public static void EndsWithNoMatch_StringComparison() + { + for (int length = 1; length < 32; 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.EndsWith(secondSpan, StringComparison.Ordinal)); + } + } + } + + [Fact] + public static void MakeSureNoEndsWithChecksGoOutOfRange_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.EndsWith(span2, StringComparison.Ordinal)); + } + } + + [Fact] + public static void EndsWithUnknownComparisonType_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + TestHelpers.AssertThrows(span, (_span) => _span.EndsWith(_span, StringComparison.CurrentCulture - 1)); + TestHelpers.AssertThrows(span, (_span) => _span.EndsWith(_span, StringComparison.OrdinalIgnoreCase + 1)); + TestHelpers.AssertThrows(span, (_span) => _span.EndsWith(_span, (StringComparison)6)); + } + + [Fact] + public static void EndsWithMatchNonOrdinal_StringComparison() + { + ReadOnlySpan span = new char[] { 'd', 'a', 'b', 'c' }; + ReadOnlySpan value = new char[] { 'a', 'B', 'c' }; + Assert.False(span.EndsWith(value, StringComparison.Ordinal)); + Assert.True(span.EndsWith(value, StringComparison.OrdinalIgnoreCase)); + + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = new CultureInfo("el-GR"); + + span = new char[] { '\u03b4', '\u03b1', '\u03b2', '\u03b3' }; // δαβγ + value = new char[] { '\u03b1', '\u03b2', '\u03b3' }; // αβγ + + Assert.True(span.EndsWith(value, StringComparison.CurrentCulture)); + Assert.True(span.EndsWith(value, StringComparison.CurrentCultureIgnoreCase)); + + value = new char[] { '\u03b1', '\u0392', '\u03b3' }; // αΒγ + Assert.False(span.EndsWith(value, StringComparison.CurrentCulture)); + Assert.True(span.EndsWith(value, StringComparison.CurrentCultureIgnoreCase)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + + span = new char[] { '\u03b4', '\u0069', '\u00df', '\u0049' }; // δißI + value = new char[] { '\u0069', '\u0073', '\u0073', '\u0049' }; // issI + + Assert.False(span.EndsWith(value, StringComparison.Ordinal)); + Assert.True(span.EndsWith(value, StringComparison.InvariantCulture)); + Assert.True(span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); + + value = new char[] { '\u0049', '\u0073', '\u0073', '\u0049' }; // IssI + Assert.False(span.EndsWith(value, StringComparison.OrdinalIgnoreCase)); + Assert.False(span.EndsWith(value, StringComparison.InvariantCulture)); + Assert.True(span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); + } + + [Fact] + public static void EndsWithNoMatchNonOrdinal_StringComparison() + { + ReadOnlySpan span = new char[] { 'd', 'a', 'b', 'c' }; + ReadOnlySpan value = new char[] { 'a', 'D', 'c' }; + Assert.False(span.EndsWith(value, StringComparison.Ordinal)); + Assert.False(span.EndsWith(value, StringComparison.OrdinalIgnoreCase)); + + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = new CultureInfo("el-GR"); + + span = new char[] { '\u03b4', '\u03b1', '\u03b2', '\u03b3' }; // δαβγ + value = new char[] { '\u03b1', '\u03b4', '\u03b3' }; // αδγ + + Assert.False(span.EndsWith(value, StringComparison.CurrentCulture)); + Assert.False(span.EndsWith(value, StringComparison.CurrentCultureIgnoreCase)); + + value = new char[] { '\u03b1', '\u0394', '\u03b3' }; // αΔγ + Assert.False(span.EndsWith(value, StringComparison.CurrentCulture)); + Assert.False(span.EndsWith(value, StringComparison.CurrentCultureIgnoreCase)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + + span = new char[] { '\u03b4', '\u0069', '\u00df', '\u0049' }; // δißI + value = new char[] { '\u0069', '\u03b4', '\u03b4', '\u0049' }; // iδδI + + Assert.False(span.EndsWith(value, StringComparison.Ordinal)); + Assert.False(span.EndsWith(value, StringComparison.InvariantCulture)); + Assert.False(span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); + + value = new char[] { '\u0049', '\u03b4', '\u03b4', '\u0049' }; // IδδI + Assert.False(span.EndsWith(value, StringComparison.OrdinalIgnoreCase)); + Assert.False(span.EndsWith(value, StringComparison.InvariantCulture)); + Assert.False(span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); + } + } +} diff --git a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs new file mode 100644 index 000000000000..931a78b55d74 --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs @@ -0,0 +1,183 @@ +// 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 ZeroLengthStartsWith_StringComparison() + { + var a = new char[3]; + + var span = new ReadOnlySpan(a); + var slice = new ReadOnlySpan(a, 2, 0); + Assert.True(span.StartsWith(slice, StringComparison.Ordinal)); + } + + [Fact] + public static void SameSpanStartsWith_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + Assert.True(span.StartsWith(span, StringComparison.Ordinal)); + } + + [Fact] + public static void LengthMismatchStartsWith_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 2); + var slice = new ReadOnlySpan(a, 0, 3); + Assert.False(span.StartsWith(slice, StringComparison.Ordinal)); + } + + [Fact] + public static void StartsWithMatch_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a, 0, 3); + var slice = new ReadOnlySpan(a, 0, 2); + Assert.True(span.StartsWith(slice, StringComparison.Ordinal)); + } + + [Fact] + public static void StartsWithMatchDifferentSpans_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.StartsWith(slice, StringComparison.Ordinal)); + } + + [Fact] + public static void StartsWithNoMatch_StringComparison() + { + for (int length = 1; length < 32; 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.StartsWith(secondSpan, StringComparison.Ordinal)); + } + } + } + + [Fact] + public static void MakeSureNoStartsWithChecksGoOutOfRange_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.StartsWith(span2, StringComparison.Ordinal)); + } + } + + [Fact] + public static void StartsWithUnknownComparisonType_StringComparison() + { + char[] a = { '4', '5', '6' }; + var span = new ReadOnlySpan(a); + TestHelpers.AssertThrows(span, (_span) => _span.StartsWith(_span, StringComparison.CurrentCulture - 1)); + TestHelpers.AssertThrows(span, (_span) => _span.StartsWith(_span, StringComparison.OrdinalIgnoreCase + 1)); + TestHelpers.AssertThrows(span, (_span) => _span.StartsWith(_span, (StringComparison)6)); + } + + [Fact] + public static void StartsWithMatchNonOrdinal_StringComparison() + { + ReadOnlySpan span = new char[] { 'a', 'b', 'c', 'd' }; + ReadOnlySpan value = new char[] { 'a', 'B', 'c' }; + Assert.False(span.StartsWith(value, StringComparison.Ordinal)); + Assert.True(span.StartsWith(value, StringComparison.OrdinalIgnoreCase)); + + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = new CultureInfo("el-GR"); + + span = new char[] { '\u03b1', '\u03b2', '\u03b3', '\u03b4' }; // αβγδ + value = new char[] { '\u03b1', '\u03b2', '\u03b3' }; // αβγ + + Assert.True(span.StartsWith(value, StringComparison.CurrentCulture)); + Assert.True(span.StartsWith(value, StringComparison.CurrentCultureIgnoreCase)); + + value = new char[] { '\u03b1', '\u0392', '\u03b3' }; // αΒγ + Assert.False(span.StartsWith(value, StringComparison.CurrentCulture)); + Assert.True(span.StartsWith(value, StringComparison.CurrentCultureIgnoreCase)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + + span = new char[] { '\u0069', '\u00df', '\u0049', '\u03b4' }; // ißIδ + value = new char[] { '\u0069', '\u0073', '\u0073', '\u0049' }; // issI + + Assert.False(span.StartsWith(value, StringComparison.Ordinal)); + Assert.True(span.StartsWith(value, StringComparison.InvariantCulture)); + Assert.True(span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); + + value = new char[] { '\u0049', '\u0073', '\u0073', '\u0049' }; // IssI + Assert.False(span.StartsWith(value, StringComparison.OrdinalIgnoreCase)); + Assert.False(span.StartsWith(value, StringComparison.InvariantCulture)); + Assert.True(span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); + } + + [Fact] + public static void StartsWithNoMatchNonOrdinal_StringComparison() + { + ReadOnlySpan span = new char[] { 'a', 'b', 'c', 'd' }; + ReadOnlySpan value = new char[] { 'a', 'D', 'c' }; + Assert.False(span.StartsWith(value, StringComparison.Ordinal)); + Assert.False(span.StartsWith(value, StringComparison.OrdinalIgnoreCase)); + + CultureInfo backupCulture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = new CultureInfo("el-GR"); + + span = new char[] { '\u03b1', '\u03b2', '\u03b3', '\u03b4' }; // αβγδ + value = new char[] { '\u03b1', '\u03b4', '\u03b3' }; // αδγ + + Assert.False(span.StartsWith(value, StringComparison.CurrentCulture)); + Assert.False(span.StartsWith(value, StringComparison.CurrentCultureIgnoreCase)); + + value = new char[] { '\u03b1', '\u0394', '\u03b3' }; // αΔγ + Assert.False(span.StartsWith(value, StringComparison.CurrentCulture)); + Assert.False(span.StartsWith(value, StringComparison.CurrentCultureIgnoreCase)); + + Thread.CurrentThread.CurrentCulture = backupCulture; + + span = new char[] { '\u0069', '\u00df', '\u0049', '\u03b4' }; // ißIδ + value = new char[] { '\u0069', '\u03b4', '\u03b4', '\u0049' }; // iδδI + + Assert.False(span.StartsWith(value, StringComparison.Ordinal)); + Assert.False(span.StartsWith(value, StringComparison.InvariantCulture)); + Assert.False(span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); + + value = new char[] { '\u0049', '\u03b4', '\u03b4', '\u0049' }; // IδδI + Assert.False(span.StartsWith(value, StringComparison.OrdinalIgnoreCase)); + Assert.False(span.StartsWith(value, StringComparison.InvariantCulture)); + Assert.False(span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); + } + } +} From 5c8114d1eb6eab64cebe94bb924d4e69e271dfcb Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Tue, 6 Feb 2018 16:09:45 -0800 Subject: [PATCH 06/17] Get the string from the underlying span to avoid allocation when possible. --- src/System.Memory/src/System/MemoryExtensions.Portable.cs | 4 ++-- src/System.Memory/src/System/ReadOnlySpan.Portable.cs | 4 ++++ src/System.Memory/src/System/Span.Portable.cs | 4 ++++ src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/System.Memory/src/System/MemoryExtensions.Portable.cs b/src/System.Memory/src/System/MemoryExtensions.Portable.cs index d1bbb6d3d348..b42ef9ec6608 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -24,9 +24,9 @@ public static bool EndsWith(this ReadOnlySpan span, ReadOnlySpan val { return span.EndsWith(value); } + string sourceString = span.ToString(); string valueString = value.ToString(); - return sourceString.EndsWith(valueString, comparisonType); } @@ -42,9 +42,9 @@ public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan v { return span.StartsWith(value); } + string sourceString = span.ToString(); string valueString = value.ToString(); - return sourceString.StartsWith(valueString, comparisonType); } diff --git a/src/System.Memory/src/System/ReadOnlySpan.Portable.cs b/src/System.Memory/src/System/ReadOnlySpan.Portable.cs index 7cf3dde9014a..6a46bfd56673 100644 --- a/src/System.Memory/src/System/ReadOnlySpan.Portable.cs +++ b/src/System.Memory/src/System/ReadOnlySpan.Portable.cs @@ -193,6 +193,10 @@ public override string ToString() { if (typeof(T) == typeof(char)) { + if (_pinnable is string str) + { + return str; + } unsafe { fixed (char* src = &Unsafe.As(ref DangerousGetPinnableReference())) diff --git a/src/System.Memory/src/System/Span.Portable.cs b/src/System.Memory/src/System/Span.Portable.cs index e37430a2ace1..38ed4d82faf5 100644 --- a/src/System.Memory/src/System/Span.Portable.cs +++ b/src/System.Memory/src/System/Span.Portable.cs @@ -305,6 +305,10 @@ public override string ToString() { if (typeof(T) == typeof(char)) { + if (_pinnable is string str) + { + return str; + } unsafe { fixed (char* src = &Unsafe.As(ref DangerousGetPinnableReference())) diff --git a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs index 2b51ddf8d968..ade20b18d00f 100644 --- a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs @@ -51,7 +51,7 @@ public static void EndsWithMatchDifferentSpans_StringComparison() { char[] a = { '7', '4', '5', '6' }; char[] b = { '4', '5', '6' }; - var span = new ReadOnlySpan(a, 0, 3); + var span = new ReadOnlySpan(a, 1, 3); var slice = new ReadOnlySpan(b, 0, 3); Assert.True(span.EndsWith(slice, StringComparison.Ordinal)); } From 273bc7eefd335ce8e5fe733dd72b9da1301e57b8 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Tue, 6 Feb 2018 17:35:41 -0800 Subject: [PATCH 07/17] Update ToString and add tests --- .../src/System/ReadOnlySpan.Portable.cs | 3 +- src/System.Memory/src/System/Span.Portable.cs | 4 -- .../tests/ReadOnlySpan/ToString.cs | 56 ++++++++++++++++++- src/System.Memory/tests/Span/ToString.cs | 2 +- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/System.Memory/src/System/ReadOnlySpan.Portable.cs b/src/System.Memory/src/System/ReadOnlySpan.Portable.cs index 6a46bfd56673..620d48cc0f7f 100644 --- a/src/System.Memory/src/System/ReadOnlySpan.Portable.cs +++ b/src/System.Memory/src/System/ReadOnlySpan.Portable.cs @@ -193,7 +193,8 @@ public override string ToString() { if (typeof(T) == typeof(char)) { - if (_pinnable is string str) + object obj = Unsafe.As, object>(ref Unsafe.AsRef(_pinnable)); + if (obj is string str && _byteOffset == MemoryExtensions.StringAdjustment && _length == str.Length) { return str; } diff --git a/src/System.Memory/src/System/Span.Portable.cs b/src/System.Memory/src/System/Span.Portable.cs index 38ed4d82faf5..e37430a2ace1 100644 --- a/src/System.Memory/src/System/Span.Portable.cs +++ b/src/System.Memory/src/System/Span.Portable.cs @@ -305,10 +305,6 @@ public override string ToString() { if (typeof(T) == typeof(char)) { - if (_pinnable is string str) - { - return str; - } unsafe { fixed (char* src = &Unsafe.As(ref DangerousGetPinnableReference())) diff --git a/src/System.Memory/tests/ReadOnlySpan/ToString.cs b/src/System.Memory/tests/ReadOnlySpan/ToString.cs index c8326428f6a6..bed62d835d54 100644 --- a/src/System.Memory/tests/ReadOnlySpan/ToString.cs +++ b/src/System.Memory/tests/ReadOnlySpan/ToString.cs @@ -2,6 +2,7 @@ // 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.Text; using Xunit; namespace System.SpanTests @@ -43,11 +44,64 @@ public static void ToStringChar_Empty() } [Fact] - public static unsafe void ToStringForSpanOfString() + public static void ToStringForSpanOfString() { string[] a = { "a", "b", "c" }; var span = new ReadOnlySpan(a); Assert.Equal("System.ReadOnlySpan[3]", span.ToString()); } + + [Fact] + public static unsafe void ToStringFromString() + { + string original = BuildString(42); + ReadOnlySpan span = original.AsReadOnlySpan(); + + string returnedString = span.ToString(); + string returnedStringUsingSlice = span.Slice(0, original.Length).ToString(); + + string subString1 = span.Slice(1).ToString(); + string subString2 = span.Slice(0, 2).ToString(); + string subString3 = span.Slice(1, 2).ToString(); + + Assert.Equal("RDDNEGSNET", returnedString); + Assert.Equal("RDDNEGSNET", returnedStringUsingSlice); + + Assert.Equal("DDNEGSNET", subString1); + Assert.Equal("RD", subString2); + Assert.Equal("DD", subString3); + + fixed(char* pOriginal = original) + fixed (char* pString1 = returnedString) + fixed (char* pString2 = returnedStringUsingSlice) + { + Assert.Equal((int)pOriginal, (int)pString1); + Assert.Equal((int)pOriginal, (int)pString2); + } + + fixed (char* pOriginal = original) + fixed (char* pSubString1 = subString1) + fixed (char* pSubString2 = subString2) + fixed (char* pSubString3 = subString3) + { + Assert.NotEqual((int)pOriginal, (int)pSubString1); + Assert.NotEqual((int)pOriginal, (int)pSubString2); + Assert.NotEqual((int)pOriginal, (int)pSubString3); + Assert.NotEqual((int)pSubString1, (int)pSubString2); + Assert.NotEqual((int)pSubString1, (int)pSubString3); + Assert.NotEqual((int)pSubString2, (int)pSubString3); + } + } + + private static string BuildString(int seed) + { + Random rnd = new Random(seed); + var builder = new StringBuilder(); + for (int i = 0; i < 10; i++) + { + builder.Append((char)rnd.Next(65, 91)); + } + return builder.ToString(); + } } } diff --git a/src/System.Memory/tests/Span/ToString.cs b/src/System.Memory/tests/Span/ToString.cs index b46631f6fbd3..82831c9446f2 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 2d5444bb2680bc3fc709d85529453400a518dcac Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Tue, 6 Feb 2018 18:09:14 -0800 Subject: [PATCH 08/17] Update Span ToString and add tests --- src/System.Memory/src/System/Span.Portable.cs | 5 ++ .../tests/ReadOnlySpan/ToString.cs | 16 +------ src/System.Memory/tests/Span/ToString.cs | 47 +++++++++++++++++++ src/System.Memory/tests/TestHelpers.cs | 12 +++++ 4 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/System.Memory/src/System/Span.Portable.cs b/src/System.Memory/src/System/Span.Portable.cs index e37430a2ace1..619ce7e01b80 100644 --- a/src/System.Memory/src/System/Span.Portable.cs +++ b/src/System.Memory/src/System/Span.Portable.cs @@ -305,6 +305,11 @@ public override string ToString() { if (typeof(T) == typeof(char)) { + object obj = Unsafe.As, object>(ref Unsafe.AsRef(_pinnable)); + if (obj is string str && _byteOffset == MemoryExtensions.StringAdjustment && _length == str.Length) + { + return str; + } unsafe { fixed (char* src = &Unsafe.As(ref DangerousGetPinnableReference())) diff --git a/src/System.Memory/tests/ReadOnlySpan/ToString.cs b/src/System.Memory/tests/ReadOnlySpan/ToString.cs index bed62d835d54..e3000c47d249 100644 --- a/src/System.Memory/tests/ReadOnlySpan/ToString.cs +++ b/src/System.Memory/tests/ReadOnlySpan/ToString.cs @@ -2,7 +2,6 @@ // 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.Text; using Xunit; namespace System.SpanTests @@ -54,7 +53,7 @@ public static void ToStringForSpanOfString() [Fact] public static unsafe void ToStringFromString() { - string original = BuildString(42); + string original = TestHelpers.BuildString(10, 42); ReadOnlySpan span = original.AsReadOnlySpan(); string returnedString = span.ToString(); @@ -71,7 +70,7 @@ public static unsafe void ToStringFromString() Assert.Equal("RD", subString2); Assert.Equal("DD", subString3); - fixed(char* pOriginal = original) + fixed (char* pOriginal = original) fixed (char* pString1 = returnedString) fixed (char* pString2 = returnedStringUsingSlice) { @@ -92,16 +91,5 @@ public static unsafe void ToStringFromString() Assert.NotEqual((int)pSubString2, (int)pSubString3); } } - - private static string BuildString(int seed) - { - Random rnd = new Random(seed); - var builder = new StringBuilder(); - for (int i = 0; i < 10; i++) - { - builder.Append((char)rnd.Next(65, 91)); - } - return builder.ToString(); - } } } diff --git a/src/System.Memory/tests/Span/ToString.cs b/src/System.Memory/tests/Span/ToString.cs index 82831c9446f2..85e277b40d21 100644 --- a/src/System.Memory/tests/Span/ToString.cs +++ b/src/System.Memory/tests/Span/ToString.cs @@ -55,5 +55,52 @@ public static void ToStringForSpanOfString() var span = new Span(a); Assert.Equal("System.Span[3]", span.ToString()); } + + [Fact] + public static unsafe void ToStringFromString() + { + string original = TestHelpers.BuildString(10, 42); + + ReadOnlyMemory readOnlyMemory = original.AsReadOnlyMemory(); + Memory memory = MemoryMarshal.AsMemory(readOnlyMemory); + + Span span = memory.Span; + + string returnedString = span.ToString(); + string returnedStringUsingSlice = span.Slice(0, original.Length).ToString(); + + string subString1 = span.Slice(1).ToString(); + string subString2 = span.Slice(0, 2).ToString(); + string subString3 = span.Slice(1, 2).ToString(); + + Assert.Equal("RDDNEGSNET", returnedString); + Assert.Equal("RDDNEGSNET", returnedStringUsingSlice); + + Assert.Equal("DDNEGSNET", subString1); + Assert.Equal("RD", subString2); + Assert.Equal("DD", subString3); + + fixed (char* pOriginal = original) + fixed (char* pString1 = returnedString) + fixed (char* pString2 = returnedStringUsingSlice) + { + Assert.Equal((int)pOriginal, (int)pString1); + Assert.Equal((int)pOriginal, (int)pString2); + } + + fixed (char* pOriginal = original) + fixed (char* pSubString1 = subString1) + fixed (char* pSubString2 = subString2) + fixed (char* pSubString3 = subString3) + { + Assert.NotEqual((int)pOriginal, (int)pSubString1); + Assert.NotEqual((int)pOriginal, (int)pSubString2); + Assert.NotEqual((int)pOriginal, (int)pSubString3); + Assert.NotEqual((int)pSubString1, (int)pSubString2); + Assert.NotEqual((int)pSubString1, (int)pSubString3); + Assert.NotEqual((int)pSubString2, (int)pSubString3); + + } + } } } diff --git a/src/System.Memory/tests/TestHelpers.cs b/src/System.Memory/tests/TestHelpers.cs index a4829d7fc3f5..72f440f67ea4 100644 --- a/src/System.Memory/tests/TestHelpers.cs +++ b/src/System.Memory/tests/TestHelpers.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using static System.Buffers.Binary.BinaryPrimitives; +using System.Text; namespace System { @@ -238,6 +239,17 @@ public static Span GetSpanLE() return spanLE; } + public static string BuildString(int length, int seed) + { + Random rnd = new Random(seed); + var builder = new StringBuilder(); + for (int i = 0; i < length; i++) + { + builder.Append((char)rnd.Next(65, 91)); + } + return builder.ToString(); + } + [StructLayout(LayoutKind.Explicit)] public struct TestStructExplicit { From 162a710490b7b80f8cb4d9a260bd8092b42e31d6 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Thu, 8 Feb 2018 16:34:31 -0800 Subject: [PATCH 09/17] Address PR feedback and disable ToString test for fast span --- src/System.Memory/tests/ReadOnlySpan/ToString.cs | 13 ++++++++----- src/System.Memory/tests/Span/ToString.cs | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/System.Memory/tests/ReadOnlySpan/ToString.cs b/src/System.Memory/tests/ReadOnlySpan/ToString.cs index e3000c47d249..6eb955e6979c 100644 --- a/src/System.Memory/tests/ReadOnlySpan/ToString.cs +++ b/src/System.Memory/tests/ReadOnlySpan/ToString.cs @@ -50,7 +50,10 @@ public static void ToStringForSpanOfString() Assert.Equal("System.ReadOnlySpan[3]", span.ToString()); } + // This test is only relevant for portable span +#if FEATURE_PORTABLE_SPAN [Fact] +#endif public static unsafe void ToStringFromString() { string original = TestHelpers.BuildString(10, 42); @@ -63,12 +66,12 @@ public static unsafe void ToStringFromString() string subString2 = span.Slice(0, 2).ToString(); string subString3 = span.Slice(1, 2).ToString(); - Assert.Equal("RDDNEGSNET", returnedString); - Assert.Equal("RDDNEGSNET", returnedStringUsingSlice); + Assert.Equal(original, returnedString); + Assert.Equal(original, returnedStringUsingSlice); - Assert.Equal("DDNEGSNET", subString1); - Assert.Equal("RD", subString2); - Assert.Equal("DD", subString3); + Assert.Equal(original.Substring(1), subString1); + Assert.Equal(original.Substring(0, 2), subString2); + Assert.Equal(original.Substring(1, 2), subString3); fixed (char* pOriginal = original) fixed (char* pString1 = returnedString) diff --git a/src/System.Memory/tests/Span/ToString.cs b/src/System.Memory/tests/Span/ToString.cs index 85e277b40d21..db92b0440df9 100644 --- a/src/System.Memory/tests/Span/ToString.cs +++ b/src/System.Memory/tests/Span/ToString.cs @@ -56,7 +56,10 @@ public static void ToStringForSpanOfString() Assert.Equal("System.Span[3]", span.ToString()); } + // This test is only relevant for portable span +#if FEATURE_PORTABLE_SPAN [Fact] +#endif public static unsafe void ToStringFromString() { string original = TestHelpers.BuildString(10, 42); @@ -73,12 +76,12 @@ public static unsafe void ToStringFromString() string subString2 = span.Slice(0, 2).ToString(); string subString3 = span.Slice(1, 2).ToString(); - Assert.Equal("RDDNEGSNET", returnedString); - Assert.Equal("RDDNEGSNET", returnedStringUsingSlice); + Assert.Equal(original, returnedString); + Assert.Equal(original, returnedStringUsingSlice); - Assert.Equal("DDNEGSNET", subString1); - Assert.Equal("RD", subString2); - Assert.Equal("DD", subString3); + Assert.Equal(original.Substring(1), subString1); + Assert.Equal(original.Substring(0, 2), subString2); + Assert.Equal(original.Substring(1, 2), subString3); fixed (char* pOriginal = original) fixed (char* pString1 = returnedString) From a0907764b2ea7b5c7f7990af88c4d780e4edb5a2 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Thu, 8 Feb 2018 17:17:42 -0800 Subject: [PATCH 10/17] Borrow additional tests from the existing StringTests test bed. --- .../tests/ReadOnlySpan/EndsWith.char.cs | 150 +++++++++++++++++ .../tests/ReadOnlySpan/StartsWith.char.cs | 158 ++++++++++++++++++ 2 files changed, 308 insertions(+) diff --git a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs index ade20b18d00f..95e79fc9e522 100644 --- a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs @@ -18,6 +18,21 @@ public static void ZeroLengthEndsWith_StringComparison() var span = new ReadOnlySpan(a); var slice = new ReadOnlySpan(a, 2, 0); Assert.True(span.EndsWith(slice, StringComparison.Ordinal)); + + Assert.True(span.EndsWith(slice, StringComparison.CurrentCulture)); + Assert.True(span.EndsWith(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.EndsWith(slice, StringComparison.InvariantCulture)); + Assert.True(span.EndsWith(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.EndsWith(slice, StringComparison.OrdinalIgnoreCase)); + + span = ReadOnlySpan.Empty; + Assert.True(span.EndsWith(slice, StringComparison.Ordinal)); + + Assert.True(span.EndsWith(slice, StringComparison.CurrentCulture)); + Assert.True(span.EndsWith(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.EndsWith(slice, StringComparison.InvariantCulture)); + Assert.True(span.EndsWith(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.EndsWith(slice, StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -26,6 +41,12 @@ public static void SameSpanEndsWith_StringComparison() char[] a = { '4', '5', '6' }; var span = new ReadOnlySpan(a); Assert.True(span.EndsWith(span, StringComparison.Ordinal)); + + Assert.True(span.EndsWith(span, StringComparison.CurrentCulture)); + Assert.True(span.EndsWith(span, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.EndsWith(span, StringComparison.InvariantCulture)); + Assert.True(span.EndsWith(span, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.EndsWith(span, StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -35,6 +56,12 @@ public static void LengthMismatchEndsWith_StringComparison() var span = new ReadOnlySpan(a, 0, 2); var slice = new ReadOnlySpan(a, 0, 3); Assert.False(span.EndsWith(slice, StringComparison.Ordinal)); + + Assert.False(span.EndsWith(slice, StringComparison.CurrentCulture)); + Assert.False(span.EndsWith(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.False(span.EndsWith(slice, StringComparison.InvariantCulture)); + Assert.False(span.EndsWith(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.False(span.EndsWith(slice, StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -44,6 +71,12 @@ public static void EndsWithMatch_StringComparison() var span = new ReadOnlySpan(a, 0, 3); var slice = new ReadOnlySpan(a, 1, 2); Assert.True(span.EndsWith(slice, StringComparison.Ordinal)); + + Assert.True(span.EndsWith(slice, StringComparison.CurrentCulture)); + Assert.True(span.EndsWith(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.EndsWith(slice, StringComparison.InvariantCulture)); + Assert.True(span.EndsWith(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.EndsWith(slice, StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -54,6 +87,12 @@ public static void EndsWithMatchDifferentSpans_StringComparison() var span = new ReadOnlySpan(a, 1, 3); var slice = new ReadOnlySpan(b, 0, 3); Assert.True(span.EndsWith(slice, StringComparison.Ordinal)); + + Assert.True(span.EndsWith(slice, StringComparison.CurrentCulture)); + Assert.True(span.EndsWith(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.EndsWith(slice, StringComparison.InvariantCulture)); + Assert.True(span.EndsWith(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.EndsWith(slice, StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -75,6 +114,12 @@ public static void EndsWithNoMatch_StringComparison() var firstSpan = new ReadOnlySpan(first); var secondSpan = new ReadOnlySpan(second); Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.Ordinal)); + + Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.CurrentCulture)); + Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.CurrentCultureIgnoreCase)); + Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.InvariantCulture)); + Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.InvariantCultureIgnoreCase)); + Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.OrdinalIgnoreCase)); } } } @@ -93,6 +138,12 @@ public static void MakeSureNoEndsWithChecksGoOutOfRange_StringComparison() var span1 = new ReadOnlySpan(first, 1, length); var span2 = new ReadOnlySpan(second, 1, length); Assert.True(span1.EndsWith(span2, StringComparison.Ordinal)); + + Assert.True(span1.EndsWith(span2, StringComparison.CurrentCulture)); + Assert.True(span1.EndsWith(span2, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span1.EndsWith(span2, StringComparison.InvariantCulture)); + Assert.True(span1.EndsWith(span2, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span1.EndsWith(span2, StringComparison.OrdinalIgnoreCase)); } } @@ -179,5 +230,104 @@ public static void EndsWithNoMatchNonOrdinal_StringComparison() Assert.False(span.EndsWith(value, StringComparison.InvariantCulture)); Assert.False(span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); } + + [Theory] + // CurrentCulture + [InlineData("", "Foo", StringComparison.CurrentCulture, false)] + [InlineData("Hello", "llo", StringComparison.CurrentCulture, true)] + [InlineData("Hello", "Hello", StringComparison.CurrentCulture, true)] + [InlineData("Hello", "", StringComparison.CurrentCulture, true)] + [InlineData("Hello", "HELLO", StringComparison.CurrentCulture, false)] + [InlineData("Hello", "Abc", StringComparison.CurrentCulture, false)] + [InlineData("Hello", "llo" + SoftHyphen, StringComparison.CurrentCulture, true)] + [InlineData("", "", StringComparison.CurrentCulture, true)] + [InlineData("", "a", StringComparison.CurrentCulture, false)] + // CurrentCultureIgnoreCase + [InlineData("Hello", "llo", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("Hello", "Hello", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("Hello", "", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("Hello", "LLO", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("Hello", "Abc", StringComparison.CurrentCultureIgnoreCase, false)] + [InlineData("Hello", "llo" + SoftHyphen, StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("", "", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("", "a", StringComparison.CurrentCultureIgnoreCase, false)] + // InvariantCulture + [InlineData("", "Foo", StringComparison.InvariantCulture, false)] + [InlineData("Hello", "llo", StringComparison.InvariantCulture, true)] + [InlineData("Hello", "Hello", StringComparison.InvariantCulture, true)] + [InlineData("Hello", "", StringComparison.InvariantCulture, true)] + [InlineData("Hello", "HELLO", StringComparison.InvariantCulture, false)] + [InlineData("Hello", "Abc", StringComparison.InvariantCulture, false)] + [InlineData("Hello", "llo" + SoftHyphen, StringComparison.InvariantCulture, true)] + [InlineData("", "", StringComparison.InvariantCulture, true)] + [InlineData("", "a", StringComparison.InvariantCulture, false)] + // InvariantCultureIgnoreCase + [InlineData("Hello", "llo", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("Hello", "Hello", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("Hello", "", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("Hello", "LLO", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("Hello", "Abc", StringComparison.InvariantCultureIgnoreCase, false)] + [InlineData("Hello", "llo" + SoftHyphen, StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("", "", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("", "a", StringComparison.InvariantCultureIgnoreCase, false)] + // Ordinal + [InlineData("Hello", "o", StringComparison.Ordinal, true)] + [InlineData("Hello", "llo", StringComparison.Ordinal, true)] + [InlineData("Hello", "Hello", StringComparison.Ordinal, true)] + [InlineData("Hello", "Larger Hello", StringComparison.Ordinal, false)] + [InlineData("Hello", "", StringComparison.Ordinal, true)] + [InlineData("Hello", "LLO", StringComparison.Ordinal, false)] + [InlineData("Hello", "Abc", StringComparison.Ordinal, false)] + [InlineData("Hello", "llo" + SoftHyphen, StringComparison.Ordinal, false)] + [InlineData("", "", StringComparison.Ordinal, true)] + [InlineData("", "a", StringComparison.Ordinal, false)] + // OrdinalIgnoreCase + [InlineData("Hello", "llo", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("Hello", "Hello", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("Hello", "Larger Hello", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("Hello", "", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("Hello", "LLO", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("Hello", "Abc", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("Hello", "llo" + SoftHyphen, StringComparison.OrdinalIgnoreCase, false)] + [InlineData("", "", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("", "a", StringComparison.OrdinalIgnoreCase, false)] + public static void EndsWith(string s, string value, StringComparison comparisonType, bool expected) + { + if (comparisonType == StringComparison.CurrentCulture) + { + Assert.Equal(expected, s.AsReadOnlySpan().EndsWith(value.AsReadOnlySpan())); + } + Assert.Equal(expected, s.AsReadOnlySpan().EndsWith(value.AsReadOnlySpan(), comparisonType)); + } + + [Theory] + [InlineData(StringComparison.Ordinal)] + [InlineData(StringComparison.OrdinalIgnoreCase)] + public static void EndsWith_NullInStrings(StringComparison comparison) + { + Assert.True("\0test".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); + Assert.True("te\0st".AsReadOnlySpan().EndsWith("e\0st".AsReadOnlySpan(), comparison)); + Assert.False("te\0st".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); + Assert.False("test\0".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); + Assert.False("test".AsReadOnlySpan().EndsWith("\0st".AsReadOnlySpan(), comparison)); + } + + // NOTE: This is by design on Unix as Unix ignores the null characters (i.e. null characters has no weights for the string comparison). + // For desired behavior, use ordinal comparisons 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(StringComparison.CurrentCulture)] + [InlineData(StringComparison.CurrentCultureIgnoreCase)] + [InlineData(StringComparison.InvariantCulture)] + [InlineData(StringComparison.InvariantCultureIgnoreCase)] + public static void EndsWith_NullInStrings_NonOrdinal(StringComparison comparison) + { + Assert.True("\0test".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); + Assert.True("te\0st".AsReadOnlySpan().EndsWith("e\0st".AsReadOnlySpan(), comparison)); + Assert.False("te\0st".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); + Assert.False("test\0".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); + Assert.False("test".AsReadOnlySpan().EndsWith("\0st".AsReadOnlySpan(), comparison)); + } } } diff --git a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs index 931a78b55d74..ec28277d9c94 100644 --- a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs @@ -10,6 +10,8 @@ namespace System.SpanTests { public static partial class ReadOnlySpanTests { + private const string SoftHyphen = "\u00AD"; + [Fact] public static void ZeroLengthStartsWith_StringComparison() { @@ -18,6 +20,21 @@ public static void ZeroLengthStartsWith_StringComparison() var span = new ReadOnlySpan(a); var slice = new ReadOnlySpan(a, 2, 0); Assert.True(span.StartsWith(slice, StringComparison.Ordinal)); + + Assert.True(span.StartsWith(slice, StringComparison.CurrentCulture)); + Assert.True(span.StartsWith(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.StartsWith(slice, StringComparison.InvariantCulture)); + Assert.True(span.StartsWith(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.StartsWith(slice, StringComparison.OrdinalIgnoreCase)); + + span = ReadOnlySpan.Empty; + Assert.True(span.StartsWith(slice, StringComparison.Ordinal)); + + Assert.True(span.StartsWith(slice, StringComparison.CurrentCulture)); + Assert.True(span.StartsWith(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.StartsWith(slice, StringComparison.InvariantCulture)); + Assert.True(span.StartsWith(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.StartsWith(slice, StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -26,6 +43,12 @@ public static void SameSpanStartsWith_StringComparison() char[] a = { '4', '5', '6' }; var span = new ReadOnlySpan(a); Assert.True(span.StartsWith(span, StringComparison.Ordinal)); + + Assert.True(span.StartsWith(span, StringComparison.CurrentCulture)); + Assert.True(span.StartsWith(span, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.StartsWith(span, StringComparison.InvariantCulture)); + Assert.True(span.StartsWith(span, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.StartsWith(span, StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -35,6 +58,12 @@ public static void LengthMismatchStartsWith_StringComparison() var span = new ReadOnlySpan(a, 0, 2); var slice = new ReadOnlySpan(a, 0, 3); Assert.False(span.StartsWith(slice, StringComparison.Ordinal)); + + Assert.False(span.StartsWith(slice, StringComparison.CurrentCulture)); + Assert.False(span.StartsWith(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.False(span.StartsWith(slice, StringComparison.InvariantCulture)); + Assert.False(span.StartsWith(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.False(span.StartsWith(slice, StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -44,6 +73,12 @@ public static void StartsWithMatch_StringComparison() var span = new ReadOnlySpan(a, 0, 3); var slice = new ReadOnlySpan(a, 0, 2); Assert.True(span.StartsWith(slice, StringComparison.Ordinal)); + + Assert.True(span.StartsWith(slice, StringComparison.CurrentCulture)); + Assert.True(span.StartsWith(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.StartsWith(slice, StringComparison.InvariantCulture)); + Assert.True(span.StartsWith(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.StartsWith(slice, StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -54,6 +89,12 @@ public static void StartsWithMatchDifferentSpans_StringComparison() var span = new ReadOnlySpan(a, 0, 3); var slice = new ReadOnlySpan(b, 0, 3); Assert.True(span.StartsWith(slice, StringComparison.Ordinal)); + + Assert.True(span.StartsWith(slice, StringComparison.CurrentCulture)); + Assert.True(span.StartsWith(slice, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span.StartsWith(slice, StringComparison.InvariantCulture)); + Assert.True(span.StartsWith(slice, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span.StartsWith(slice, StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -75,6 +116,12 @@ public static void StartsWithNoMatch_StringComparison() var firstSpan = new ReadOnlySpan(first); var secondSpan = new ReadOnlySpan(second); Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.Ordinal)); + + Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.CurrentCulture)); + Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.CurrentCultureIgnoreCase)); + Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.InvariantCulture)); + Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.InvariantCultureIgnoreCase)); + Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.OrdinalIgnoreCase)); } } } @@ -93,6 +140,12 @@ public static void MakeSureNoStartsWithChecksGoOutOfRange_StringComparison() var span1 = new ReadOnlySpan(first, 1, length); var span2 = new ReadOnlySpan(second, 1, length); Assert.True(span1.StartsWith(span2, StringComparison.Ordinal)); + + Assert.True(span1.StartsWith(span2, StringComparison.CurrentCulture)); + Assert.True(span1.StartsWith(span2, StringComparison.CurrentCultureIgnoreCase)); + Assert.True(span1.StartsWith(span2, StringComparison.InvariantCulture)); + Assert.True(span1.StartsWith(span2, StringComparison.InvariantCultureIgnoreCase)); + Assert.True(span1.StartsWith(span2, StringComparison.OrdinalIgnoreCase)); } } @@ -179,5 +232,110 @@ public static void StartsWithNoMatchNonOrdinal_StringComparison() Assert.False(span.StartsWith(value, StringComparison.InvariantCulture)); Assert.False(span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); } + + [Theory] + // CurrentCulture + [InlineData("Hello", "Hel", StringComparison.CurrentCulture, true)] + [InlineData("Hello", "Hello", StringComparison.CurrentCulture, true)] + [InlineData("Hello", "", StringComparison.CurrentCulture, true)] + [InlineData("Hello", "HELLO", StringComparison.CurrentCulture, false)] + [InlineData("Hello", "Abc", StringComparison.CurrentCulture, false)] + [InlineData("Hello", SoftHyphen + "Hel", StringComparison.CurrentCulture, true)] + [InlineData("", "", StringComparison.CurrentCulture, true)] + [InlineData("", "hello", StringComparison.CurrentCulture, false)] + // CurrentCultureIgnoreCase + [InlineData("Hello", "Hel", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("Hello", "Hello", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("Hello", "", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("Hello", "HEL", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("Hello", "Abc", StringComparison.CurrentCultureIgnoreCase, false)] + [InlineData("Hello", SoftHyphen + "Hel", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("", "", StringComparison.CurrentCultureIgnoreCase, true)] + [InlineData("", "hello", StringComparison.CurrentCultureIgnoreCase, false)] + // InvariantCulture + [InlineData("Hello", "Hel", StringComparison.InvariantCulture, true)] + [InlineData("Hello", "Hello", StringComparison.InvariantCulture, true)] + [InlineData("Hello", "", StringComparison.InvariantCulture, true)] + [InlineData("Hello", "HELLO", StringComparison.InvariantCulture, false)] + [InlineData("Hello", "Abc", StringComparison.InvariantCulture, false)] + [InlineData("Hello", SoftHyphen + "Hel", StringComparison.InvariantCulture, true)] + [InlineData("", "", StringComparison.InvariantCulture, true)] + [InlineData("", "hello", StringComparison.InvariantCulture, false)] + // InvariantCultureIgnoreCase + [InlineData("Hello", "Hel", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("Hello", "Hello", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("Hello", "", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("Hello", "HEL", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("Hello", "Abc", StringComparison.InvariantCultureIgnoreCase, false)] + [InlineData("Hello", SoftHyphen + "Hel", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("", "", StringComparison.InvariantCultureIgnoreCase, true)] + [InlineData("", "hello", StringComparison.InvariantCultureIgnoreCase, false)] + // Ordinal + [InlineData("Hello", "H", StringComparison.Ordinal, true)] + [InlineData("Hello", "Hel", StringComparison.Ordinal, true)] + [InlineData("Hello", "Hello", StringComparison.Ordinal, true)] + [InlineData("Hello", "Hello Larger", StringComparison.Ordinal, false)] + [InlineData("Hello", "", StringComparison.Ordinal, true)] + [InlineData("Hello", "HEL", StringComparison.Ordinal, false)] + [InlineData("Hello", "Abc", StringComparison.Ordinal, false)] + [InlineData("Hello", SoftHyphen + "Hel", StringComparison.Ordinal, false)] + [InlineData("", "", StringComparison.Ordinal, true)] + [InlineData("", "hello", StringComparison.Ordinal, false)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz", StringComparison.Ordinal, true)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwx", StringComparison.Ordinal, true)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "abcdefghijklm", StringComparison.Ordinal, true)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "ab_defghijklmnopqrstu", StringComparison.Ordinal, false)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "abcdef_hijklmn", StringComparison.Ordinal, false)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "abcdefghij_lmn", StringComparison.Ordinal, false)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "a", StringComparison.Ordinal, true)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyza", StringComparison.Ordinal, false)] + // OrdinalIgnoreCase + [InlineData("Hello", "Hel", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("Hello", "Hello", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("Hello", "Hello Larger", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("Hello", "", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("Hello", "HEL", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("Hello", "Abc", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("Hello", SoftHyphen + "Hel", StringComparison.OrdinalIgnoreCase, false)] + [InlineData("", "", StringComparison.OrdinalIgnoreCase, true)] + [InlineData("", "hello", StringComparison.OrdinalIgnoreCase, false)] + public static void StartsWith(string s, string value, StringComparison comparisonType, bool expected) + { + if (comparisonType == StringComparison.CurrentCulture) + { + Assert.Equal(expected, s.AsReadOnlySpan().StartsWith(value.AsReadOnlySpan())); + } + Assert.Equal(expected, s.AsReadOnlySpan().StartsWith(value.AsReadOnlySpan(), comparisonType)); + } + + [Theory] + [InlineData(StringComparison.Ordinal)] + [InlineData(StringComparison.OrdinalIgnoreCase)] + public static void StartsWith_NullInStrings(StringComparison comparison) + { + Assert.False("\0test".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); + Assert.False("te\0st".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); + Assert.True("te\0st".AsReadOnlySpan().StartsWith("te\0s".AsReadOnlySpan(), comparison)); + Assert.True("test\0".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); + Assert.False("test".AsReadOnlySpan().StartsWith("te\0".AsReadOnlySpan(), comparison)); + } + + // NOTE: This is by design on Unix as Unix ignores the null characters (i.e. null characters has no weights for the string comparison). + // For desired behavior, use ordinal comparisons 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(StringComparison.CurrentCulture)] + [InlineData(StringComparison.CurrentCultureIgnoreCase)] + [InlineData(StringComparison.InvariantCulture)] + [InlineData(StringComparison.InvariantCultureIgnoreCase)] + public static void StartsWith_NullInStrings_NonOrdinal(StringComparison comparison) + { + Assert.False("\0test".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); + Assert.False("te\0st".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); + Assert.True("te\0st".AsReadOnlySpan().StartsWith("te\0s".AsReadOnlySpan(), comparison)); + Assert.True("test\0".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); + Assert.False("test".AsReadOnlySpan().StartsWith("te\0".AsReadOnlySpan(), comparison)); + } } } From 839bf294694c4f9e9d85d49583d6afd92f9e65b9 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Thu, 8 Feb 2018 17:22:58 -0800 Subject: [PATCH 11/17] Fix comment grammar --- src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs | 4 ++-- src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs index 95e79fc9e522..87ecc54d927b 100644 --- a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs @@ -312,8 +312,8 @@ public static void EndsWith_NullInStrings(StringComparison comparison) Assert.False("test".AsReadOnlySpan().EndsWith("\0st".AsReadOnlySpan(), comparison)); } - // NOTE: This is by design on Unix as Unix ignores the null characters (i.e. null characters has no weights for the string comparison). - // For desired behavior, use ordinal comparisons instead of linguistic comparison. + // 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)] diff --git a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs index ec28277d9c94..5b0a662f6d2b 100644 --- a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs @@ -320,8 +320,8 @@ public static void StartsWith_NullInStrings(StringComparison comparison) Assert.False("test".AsReadOnlySpan().StartsWith("te\0".AsReadOnlySpan(), comparison)); } - // NOTE: This is by design on Unix as Unix ignores the null characters (i.e. null characters has no weights for the string comparison). - // For desired behavior, use ordinal comparisons instead of linguistic comparison. + // 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)] From 8aaf7c238fded028998c0b60dcf410a27ee99682 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Thu, 8 Feb 2018 18:18:48 -0800 Subject: [PATCH 12/17] No StringComparison results in generic StartsWith being called which does ordinal comparison --- src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs | 4 ---- src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs index 87ecc54d927b..52f8cabe79c0 100644 --- a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs @@ -293,10 +293,6 @@ public static void EndsWithNoMatchNonOrdinal_StringComparison() [InlineData("", "a", StringComparison.OrdinalIgnoreCase, false)] public static void EndsWith(string s, string value, StringComparison comparisonType, bool expected) { - if (comparisonType == StringComparison.CurrentCulture) - { - Assert.Equal(expected, s.AsReadOnlySpan().EndsWith(value.AsReadOnlySpan())); - } Assert.Equal(expected, s.AsReadOnlySpan().EndsWith(value.AsReadOnlySpan(), comparisonType)); } diff --git a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs index 5b0a662f6d2b..f0aa939c0ed9 100644 --- a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs @@ -301,10 +301,6 @@ public static void StartsWithNoMatchNonOrdinal_StringComparison() [InlineData("", "hello", StringComparison.OrdinalIgnoreCase, false)] public static void StartsWith(string s, string value, StringComparison comparisonType, bool expected) { - if (comparisonType == StringComparison.CurrentCulture) - { - Assert.Equal(expected, s.AsReadOnlySpan().StartsWith(value.AsReadOnlySpan())); - } Assert.Equal(expected, s.AsReadOnlySpan().StartsWith(value.AsReadOnlySpan(), comparisonType)); } From 7d0fa7161a57203949b2b4f2a4da134dd10d3812 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Thu, 15 Feb 2018 18:28:34 -0800 Subject: [PATCH 13/17] Address feedback related to ToString and tests --- .../src/System/ReadOnlySpan.Portable.cs | 12 +++-- src/System.Memory/src/System/Span.Portable.cs | 5 --- .../tests/ReadOnlySpan/ToString.cs | 45 +++++++++---------- src/System.Memory/tests/Span/ToString.cs | 32 ++++--------- 4 files changed, 40 insertions(+), 54 deletions(-) diff --git a/src/System.Memory/src/System/ReadOnlySpan.Portable.cs b/src/System.Memory/src/System/ReadOnlySpan.Portable.cs index 620d48cc0f7f..8cba7ef264af 100644 --- a/src/System.Memory/src/System/ReadOnlySpan.Portable.cs +++ b/src/System.Memory/src/System/ReadOnlySpan.Portable.cs @@ -193,11 +193,17 @@ public override string ToString() { if (typeof(T) == typeof(char)) { - object obj = Unsafe.As, object>(ref Unsafe.AsRef(_pinnable)); - if (obj is string str && _byteOffset == MemoryExtensions.StringAdjustment && _length == str.Length) + // If this wraps a string and represents the full length of the string, just return the wrapped string. + if (_byteOffset == MemoryExtensions.StringAdjustment) { - return str; + object obj = Unsafe.As(_pinnable); // minimize chances the compilers will optimize away the 'is' check + if (obj is string str && _length == str.Length) + { + return str; + } } + + // Otherwise, copy the data to a new string. unsafe { fixed (char* src = &Unsafe.As(ref DangerousGetPinnableReference())) diff --git a/src/System.Memory/src/System/Span.Portable.cs b/src/System.Memory/src/System/Span.Portable.cs index 619ce7e01b80..e37430a2ace1 100644 --- a/src/System.Memory/src/System/Span.Portable.cs +++ b/src/System.Memory/src/System/Span.Portable.cs @@ -305,11 +305,6 @@ public override string ToString() { if (typeof(T) == typeof(char)) { - object obj = Unsafe.As, object>(ref Unsafe.AsRef(_pinnable)); - if (obj is string str && _byteOffset == MemoryExtensions.StringAdjustment && _length == str.Length) - { - return str; - } unsafe { fixed (char* src = &Unsafe.As(ref DangerousGetPinnableReference())) diff --git a/src/System.Memory/tests/ReadOnlySpan/ToString.cs b/src/System.Memory/tests/ReadOnlySpan/ToString.cs index 6eb955e6979c..0b8b1b05a414 100644 --- a/src/System.Memory/tests/ReadOnlySpan/ToString.cs +++ b/src/System.Memory/tests/ReadOnlySpan/ToString.cs @@ -50,11 +50,20 @@ public static void ToStringForSpanOfString() Assert.Equal("System.ReadOnlySpan[3]", span.ToString()); } + [Fact] + public static void ToStringFromString() + { + string orig = "hello world"; + Assert.Equal(orig, orig.AsReadOnlySpan().ToString()); + Assert.Equal(orig.Substring(0, 5), orig.AsReadOnlySpan(0, 5).ToString()); + Assert.Equal(orig.Substring(5), orig.AsReadOnlySpan(5).ToString()); + Assert.Equal(orig.Substring(1, 3), orig.AsReadOnlySpan(1, 3).ToString()); + } + // This test is only relevant for portable span -#if FEATURE_PORTABLE_SPAN + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework, "Optimization only applies to portable span.")] [Fact] -#endif - public static unsafe void ToStringFromString() + public static void ToStringSpanOverFullStringReturnsOriginal() { string original = TestHelpers.BuildString(10, 42); ReadOnlySpan span = original.AsReadOnlySpan(); @@ -73,26 +82,16 @@ public static unsafe void ToStringFromString() Assert.Equal(original.Substring(0, 2), subString2); Assert.Equal(original.Substring(1, 2), subString3); - fixed (char* pOriginal = original) - fixed (char* pString1 = returnedString) - fixed (char* pString2 = returnedStringUsingSlice) - { - Assert.Equal((int)pOriginal, (int)pString1); - Assert.Equal((int)pOriginal, (int)pString2); - } - - fixed (char* pOriginal = original) - fixed (char* pSubString1 = subString1) - fixed (char* pSubString2 = subString2) - fixed (char* pSubString3 = subString3) - { - Assert.NotEqual((int)pOriginal, (int)pSubString1); - Assert.NotEqual((int)pOriginal, (int)pSubString2); - Assert.NotEqual((int)pOriginal, (int)pSubString3); - Assert.NotEqual((int)pSubString1, (int)pSubString2); - Assert.NotEqual((int)pSubString1, (int)pSubString3); - Assert.NotEqual((int)pSubString2, (int)pSubString3); - } + Assert.Same(original, returnedString); + Assert.Same(original, returnedStringUsingSlice); + + Assert.NotSame(original, subString1); + Assert.NotSame(original, subString2); + Assert.NotSame(original, subString3); + + Assert.NotSame(subString1, subString2); + Assert.NotSame(subString1, subString3); + Assert.NotSame(subString2, subString3); } } } diff --git a/src/System.Memory/tests/Span/ToString.cs b/src/System.Memory/tests/Span/ToString.cs index db92b0440df9..961c892c93f5 100644 --- a/src/System.Memory/tests/Span/ToString.cs +++ b/src/System.Memory/tests/Span/ToString.cs @@ -56,11 +56,8 @@ public static void ToStringForSpanOfString() Assert.Equal("System.Span[3]", span.ToString()); } - // This test is only relevant for portable span -#if FEATURE_PORTABLE_SPAN [Fact] -#endif - public static unsafe void ToStringFromString() + public static void ToStringSpanOverFullStringDoesNotReturnOriginal() { string original = TestHelpers.BuildString(10, 42); @@ -83,27 +80,16 @@ public static unsafe void ToStringFromString() Assert.Equal(original.Substring(0, 2), subString2); Assert.Equal(original.Substring(1, 2), subString3); - fixed (char* pOriginal = original) - fixed (char* pString1 = returnedString) - fixed (char* pString2 = returnedStringUsingSlice) - { - Assert.Equal((int)pOriginal, (int)pString1); - Assert.Equal((int)pOriginal, (int)pString2); - } + Assert.NotSame(original, returnedString); + Assert.NotSame(original, returnedStringUsingSlice); - fixed (char* pOriginal = original) - fixed (char* pSubString1 = subString1) - fixed (char* pSubString2 = subString2) - fixed (char* pSubString3 = subString3) - { - Assert.NotEqual((int)pOriginal, (int)pSubString1); - Assert.NotEqual((int)pOriginal, (int)pSubString2); - Assert.NotEqual((int)pOriginal, (int)pSubString3); - Assert.NotEqual((int)pSubString1, (int)pSubString2); - Assert.NotEqual((int)pSubString1, (int)pSubString3); - Assert.NotEqual((int)pSubString2, (int)pSubString3); + Assert.NotSame(original, subString1); + Assert.NotSame(original, subString2); + Assert.NotSame(original, subString3); - } + Assert.NotSame(subString1, subString2); + Assert.NotSame(subString1, subString3); + Assert.NotSame(subString2, subString3); } } } From 4bac971bf57f65f588e27439dd58555991ae7674 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Thu, 15 Feb 2018 21:27:33 -0800 Subject: [PATCH 14/17] Fix tests for culture specific cases, for Unix. --- .../tests/ReadOnlySpan/EndsWith.char.cs | 34 ++++++++++++++----- .../tests/ReadOnlySpan/StartsWith.char.cs | 34 ++++++++++++++----- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs index 52f8cabe79c0..ad3fc2dfd99f 100644 --- a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs @@ -98,7 +98,7 @@ public static void EndsWithMatchDifferentSpans_StringComparison() [Fact] public static void EndsWithNoMatch_StringComparison() { - for (int length = 1; length < 32; length++) + for (int length = 1; length < 150; length++) { for (int mismatchIndex = 0; mismatchIndex < length; mismatchIndex++) { @@ -115,11 +115,21 @@ public static void EndsWithNoMatch_StringComparison() var secondSpan = new ReadOnlySpan(second); Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.Ordinal)); - Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.CurrentCulture)); - Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.CurrentCultureIgnoreCase)); - Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.InvariantCulture)); - Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.InvariantCultureIgnoreCase)); Assert.False(firstSpan.EndsWith(secondSpan, StringComparison.OrdinalIgnoreCase)); + + // Different behavior depending on OS + Assert.Equal( + span.ToString().EndsWith(value.ToString(), StringComparison.CurrentCulture), + span.EndsWith(value, StringComparison.CurrentCulture)); + Assert.Equal( + span.ToString().EndsWith(value.ToString(), StringComparison.CurrentCulture), + span.EndsWith(value, StringComparison.CurrentCulture)); + Assert.Equal( + span.ToString().EndsWith(value.ToString(), StringComparison.InvariantCulture), + span.EndsWith(value, StringComparison.InvariantCulture)); + Assert.Equal( + span.ToString().EndsWith(value.ToString(), StringComparison.InvariantCultureIgnoreCase), + span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); } } } @@ -185,13 +195,21 @@ public static void EndsWithMatchNonOrdinal_StringComparison() value = new char[] { '\u0069', '\u0073', '\u0073', '\u0049' }; // issI Assert.False(span.EndsWith(value, StringComparison.Ordinal)); - Assert.True(span.EndsWith(value, StringComparison.InvariantCulture)); - Assert.True(span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); + // Different behavior depending on OS - True on Windows, False on Unix + Assert.Equal( + span.ToString().EndsWith(value.ToString(), StringComparison.InvariantCulture), + span.EndsWith(value, StringComparison.InvariantCulture)); + Assert.Equal( + span.ToString().EndsWith(value.ToString(), StringComparison.InvariantCultureIgnoreCase), + span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); value = new char[] { '\u0049', '\u0073', '\u0073', '\u0049' }; // IssI Assert.False(span.EndsWith(value, StringComparison.OrdinalIgnoreCase)); Assert.False(span.EndsWith(value, StringComparison.InvariantCulture)); - Assert.True(span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); + // Different behavior depending on OS - True on Windows, False on Unix + Assert.Equal( + span.ToString().EndsWith(value.ToString(), StringComparison.InvariantCultureIgnoreCase), + span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); } [Fact] diff --git a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs index f0aa939c0ed9..501cd4ad5cc9 100644 --- a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs @@ -100,7 +100,7 @@ public static void StartsWithMatchDifferentSpans_StringComparison() [Fact] public static void StartsWithNoMatch_StringComparison() { - for (int length = 1; length < 32; length++) + for (int length = 1; length < 150; length++) { for (int mismatchIndex = 0; mismatchIndex < length; mismatchIndex++) { @@ -117,11 +117,21 @@ public static void StartsWithNoMatch_StringComparison() var secondSpan = new ReadOnlySpan(second); Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.Ordinal)); - Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.CurrentCulture)); - Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.CurrentCultureIgnoreCase)); - Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.InvariantCulture)); - Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.InvariantCultureIgnoreCase)); Assert.False(firstSpan.StartsWith(secondSpan, StringComparison.OrdinalIgnoreCase)); + + // Different behavior depending on OS + Assert.Equal( + span.ToString().StartsWith(value.ToString(), StringComparison.CurrentCulture), + span.StartsWith(value, StringComparison.CurrentCulture)); + Assert.Equal( + span.ToString().StartsWith(value.ToString(), StringComparison.CurrentCulture), + span.StartsWith(value, StringComparison.CurrentCulture)); + Assert.Equal( + span.ToString().StartsWith(value.ToString(), StringComparison.InvariantCulture), + span.StartsWith(value, StringComparison.InvariantCulture)); + Assert.Equal( + span.ToString().StartsWith(value.ToString(), StringComparison.InvariantCultureIgnoreCase), + span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); } } } @@ -187,13 +197,21 @@ public static void StartsWithMatchNonOrdinal_StringComparison() value = new char[] { '\u0069', '\u0073', '\u0073', '\u0049' }; // issI Assert.False(span.StartsWith(value, StringComparison.Ordinal)); - Assert.True(span.StartsWith(value, StringComparison.InvariantCulture)); - Assert.True(span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); + // Different behavior depending on OS - True on Windows, False on Unix + Assert.Equal( + span.ToString().StartsWith(value.ToString(), StringComparison.InvariantCulture), + span.StartsWith(value, StringComparison.InvariantCulture)); + Assert.Equal( + span.ToString().StartsWith(value.ToString(), StringComparison.InvariantCultureIgnoreCase), + span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); value = new char[] { '\u0049', '\u0073', '\u0073', '\u0049' }; // IssI Assert.False(span.StartsWith(value, StringComparison.OrdinalIgnoreCase)); Assert.False(span.StartsWith(value, StringComparison.InvariantCulture)); - Assert.True(span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); + // Different behavior depending on OS - True on Windows, False on Unix + Assert.Equal( + span.ToString().StartsWith(value.ToString(), StringComparison.InvariantCultureIgnoreCase), + span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); } [Fact] From a2ada655d56fa0196481c60709c58934a8a3c9aa Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Thu, 15 Feb 2018 21:54:34 -0800 Subject: [PATCH 15/17] Fix typo in variable names --- .../tests/ReadOnlySpan/EndsWith.char.cs | 16 ++++++++-------- .../tests/ReadOnlySpan/StartsWith.char.cs | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs index ad3fc2dfd99f..6d8be490b2c1 100644 --- a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs @@ -119,17 +119,17 @@ public static void EndsWithNoMatch_StringComparison() // Different behavior depending on OS Assert.Equal( - span.ToString().EndsWith(value.ToString(), StringComparison.CurrentCulture), - span.EndsWith(value, StringComparison.CurrentCulture)); + firstSpan.ToString().EndsWith(secondSpan.ToString(), StringComparison.CurrentCulture), + firstSpan.EndsWith(secondSpan, StringComparison.CurrentCulture)); Assert.Equal( - span.ToString().EndsWith(value.ToString(), StringComparison.CurrentCulture), - span.EndsWith(value, StringComparison.CurrentCulture)); + firstSpan.ToString().EndsWith(secondSpan.ToString(), StringComparison.CurrentCulture), + firstSpan.EndsWith(secondSpan, StringComparison.CurrentCulture)); Assert.Equal( - span.ToString().EndsWith(value.ToString(), StringComparison.InvariantCulture), - span.EndsWith(value, StringComparison.InvariantCulture)); + firstSpan.ToString().EndsWith(secondSpan.ToString(), StringComparison.InvariantCulture), + firstSpan.EndsWith(secondSpan, StringComparison.InvariantCulture)); Assert.Equal( - span.ToString().EndsWith(value.ToString(), StringComparison.InvariantCultureIgnoreCase), - span.EndsWith(value, StringComparison.InvariantCultureIgnoreCase)); + firstSpan.ToString().EndsWith(secondSpan.ToString(), StringComparison.InvariantCultureIgnoreCase), + firstSpan.EndsWith(secondSpan, StringComparison.InvariantCultureIgnoreCase)); } } } diff --git a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs index 501cd4ad5cc9..4fbdbf9606e8 100644 --- a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs @@ -121,17 +121,17 @@ public static void StartsWithNoMatch_StringComparison() // Different behavior depending on OS Assert.Equal( - span.ToString().StartsWith(value.ToString(), StringComparison.CurrentCulture), - span.StartsWith(value, StringComparison.CurrentCulture)); + firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCulture), + firstSpan.StartsWith(secondSpan, StringComparison.CurrentCulture)); Assert.Equal( - span.ToString().StartsWith(value.ToString(), StringComparison.CurrentCulture), - span.StartsWith(value, StringComparison.CurrentCulture)); + firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCulture), + firstSpan.StartsWith(secondSpan, StringComparison.CurrentCulture)); Assert.Equal( - span.ToString().StartsWith(value.ToString(), StringComparison.InvariantCulture), - span.StartsWith(value, StringComparison.InvariantCulture)); + firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.InvariantCulture), + firstSpan.StartsWith(secondSpan, StringComparison.InvariantCulture)); Assert.Equal( - span.ToString().StartsWith(value.ToString(), StringComparison.InvariantCultureIgnoreCase), - span.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); + firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.InvariantCultureIgnoreCase), + firstSpan.StartsWith(secondSpan, StringComparison.InvariantCultureIgnoreCase)); } } } From b9167c2c173a7fac15a25e9729d4240957abde14 Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 13:57:03 -0800 Subject: [PATCH 16/17] Respond to recent change AsReadOnlySpan -> AsSpan --- .../tests/ReadOnlySpan/EndsWith.char.cs | 22 +++++++++---------- .../tests/ReadOnlySpan/StartsWith.char.cs | 22 +++++++++---------- .../tests/ReadOnlySpan/ToString.cs | 10 ++++----- src/System.Memory/tests/Span/ToString.cs | 2 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs index 6d8be490b2c1..8ace64ab9dad 100644 --- a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs @@ -311,7 +311,7 @@ public static void EndsWithNoMatchNonOrdinal_StringComparison() [InlineData("", "a", StringComparison.OrdinalIgnoreCase, false)] public static void EndsWith(string s, string value, StringComparison comparisonType, bool expected) { - Assert.Equal(expected, s.AsReadOnlySpan().EndsWith(value.AsReadOnlySpan(), comparisonType)); + Assert.Equal(expected, s.AsSpan().EndsWith(value.AsSpan(), comparisonType)); } [Theory] @@ -319,11 +319,11 @@ public static void EndsWith(string s, string value, StringComparison comparisonT [InlineData(StringComparison.OrdinalIgnoreCase)] public static void EndsWith_NullInStrings(StringComparison comparison) { - Assert.True("\0test".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); - Assert.True("te\0st".AsReadOnlySpan().EndsWith("e\0st".AsReadOnlySpan(), comparison)); - Assert.False("te\0st".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); - Assert.False("test\0".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); - Assert.False("test".AsReadOnlySpan().EndsWith("\0st".AsReadOnlySpan(), comparison)); + Assert.True("\0test".AsSpan().EndsWith("test".AsSpan(), comparison)); + Assert.True("te\0st".AsSpan().EndsWith("e\0st".AsSpan(), comparison)); + Assert.False("te\0st".AsSpan().EndsWith("test".AsSpan(), comparison)); + Assert.False("test\0".AsSpan().EndsWith("test".AsSpan(), comparison)); + Assert.False("test".AsSpan().EndsWith("\0st".AsSpan(), comparison)); } // NOTE: This is by design. Unix ignores the null characters (i.e. null characters have no weights for the string comparison). @@ -337,11 +337,11 @@ public static void EndsWith_NullInStrings(StringComparison comparison) [InlineData(StringComparison.InvariantCultureIgnoreCase)] public static void EndsWith_NullInStrings_NonOrdinal(StringComparison comparison) { - Assert.True("\0test".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); - Assert.True("te\0st".AsReadOnlySpan().EndsWith("e\0st".AsReadOnlySpan(), comparison)); - Assert.False("te\0st".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); - Assert.False("test\0".AsReadOnlySpan().EndsWith("test".AsReadOnlySpan(), comparison)); - Assert.False("test".AsReadOnlySpan().EndsWith("\0st".AsReadOnlySpan(), comparison)); + Assert.True("\0test".AsSpan().EndsWith("test".AsSpan(), comparison)); + Assert.True("te\0st".AsSpan().EndsWith("e\0st".AsSpan(), comparison)); + Assert.False("te\0st".AsSpan().EndsWith("test".AsSpan(), comparison)); + Assert.False("test\0".AsSpan().EndsWith("test".AsSpan(), comparison)); + Assert.False("test".AsSpan().EndsWith("\0st".AsSpan(), comparison)); } } } diff --git a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs index 4fbdbf9606e8..3e1c3bf4e838 100644 --- a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs @@ -319,7 +319,7 @@ public static void StartsWithNoMatchNonOrdinal_StringComparison() [InlineData("", "hello", StringComparison.OrdinalIgnoreCase, false)] public static void StartsWith(string s, string value, StringComparison comparisonType, bool expected) { - Assert.Equal(expected, s.AsReadOnlySpan().StartsWith(value.AsReadOnlySpan(), comparisonType)); + Assert.Equal(expected, s.AsSpan().StartsWith(value.AsSpan(), comparisonType)); } [Theory] @@ -327,11 +327,11 @@ public static void StartsWith(string s, string value, StringComparison compariso [InlineData(StringComparison.OrdinalIgnoreCase)] public static void StartsWith_NullInStrings(StringComparison comparison) { - Assert.False("\0test".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); - Assert.False("te\0st".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); - Assert.True("te\0st".AsReadOnlySpan().StartsWith("te\0s".AsReadOnlySpan(), comparison)); - Assert.True("test\0".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); - Assert.False("test".AsReadOnlySpan().StartsWith("te\0".AsReadOnlySpan(), comparison)); + Assert.False("\0test".AsSpan().StartsWith("test".AsSpan(), comparison)); + Assert.False("te\0st".AsSpan().StartsWith("test".AsSpan(), comparison)); + Assert.True("te\0st".AsSpan().StartsWith("te\0s".AsSpan(), comparison)); + Assert.True("test\0".AsSpan().StartsWith("test".AsSpan(), comparison)); + Assert.False("test".AsSpan().StartsWith("te\0".AsSpan(), comparison)); } // NOTE: This is by design. Unix ignores the null characters (i.e. null characters have no weights for the string comparison). @@ -345,11 +345,11 @@ public static void StartsWith_NullInStrings(StringComparison comparison) [InlineData(StringComparison.InvariantCultureIgnoreCase)] public static void StartsWith_NullInStrings_NonOrdinal(StringComparison comparison) { - Assert.False("\0test".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); - Assert.False("te\0st".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); - Assert.True("te\0st".AsReadOnlySpan().StartsWith("te\0s".AsReadOnlySpan(), comparison)); - Assert.True("test\0".AsReadOnlySpan().StartsWith("test".AsReadOnlySpan(), comparison)); - Assert.False("test".AsReadOnlySpan().StartsWith("te\0".AsReadOnlySpan(), comparison)); + Assert.False("\0test".AsSpan().StartsWith("test".AsSpan(), comparison)); + Assert.False("te\0st".AsSpan().StartsWith("test".AsSpan(), comparison)); + Assert.True("te\0st".AsSpan().StartsWith("te\0s".AsSpan(), comparison)); + Assert.True("test\0".AsSpan().StartsWith("test".AsSpan(), comparison)); + Assert.False("test".AsSpan().StartsWith("te\0".AsSpan(), comparison)); } } } diff --git a/src/System.Memory/tests/ReadOnlySpan/ToString.cs b/src/System.Memory/tests/ReadOnlySpan/ToString.cs index 76d39c33c86e..6a7735d92b84 100644 --- a/src/System.Memory/tests/ReadOnlySpan/ToString.cs +++ b/src/System.Memory/tests/ReadOnlySpan/ToString.cs @@ -54,10 +54,10 @@ public static void ToStringForSpanOfString() public static void ToStringFromString() { string orig = "hello world"; - Assert.Equal(orig, orig.AsReadOnlySpan().ToString()); - Assert.Equal(orig.Substring(0, 5), orig.AsReadOnlySpan(0, 5).ToString()); - Assert.Equal(orig.Substring(5), orig.AsReadOnlySpan(5).ToString()); - Assert.Equal(orig.Substring(1, 3), orig.AsReadOnlySpan(1, 3).ToString()); + Assert.Equal(orig, orig.AsSpan().ToString()); + Assert.Equal(orig.Substring(0, 5), orig.AsSpan(0, 5).ToString()); + Assert.Equal(orig.Substring(5), orig.AsSpan(5).ToString()); + Assert.Equal(orig.Substring(1, 3), orig.AsSpan(1, 3).ToString()); } // This test is only relevant for portable span @@ -66,7 +66,7 @@ public static void ToStringFromString() public static void ToStringSpanOverFullStringReturnsOriginal() { string original = TestHelpers.BuildString(10, 42); - ReadOnlySpan span = original.AsReadOnlySpan(); + ReadOnlySpan span = original.AsSpan(); string returnedString = span.ToString(); string returnedStringUsingSlice = span.Slice(0, original.Length).ToString(); diff --git a/src/System.Memory/tests/Span/ToString.cs b/src/System.Memory/tests/Span/ToString.cs index 028f652d9e3f..1875a91d798d 100644 --- a/src/System.Memory/tests/Span/ToString.cs +++ b/src/System.Memory/tests/Span/ToString.cs @@ -61,7 +61,7 @@ public static void ToStringSpanOverFullStringDoesNotReturnOriginal() { string original = TestHelpers.BuildString(10, 42); - ReadOnlyMemory readOnlyMemory = original.AsReadOnlyMemory(); + ReadOnlyMemory readOnlyMemory = original.AsMemory(); Memory memory = MemoryMarshal.AsMemory(readOnlyMemory); Span span = memory.Span; From 4b9d13e1d444d3cdcd2387dfd42d20af6542705d Mon Sep 17 00:00:00 2001 From: ahsonkhan Date: Wed, 21 Feb 2018 13:58:32 -0800 Subject: [PATCH 17/17] Fix typo in test. --- src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs | 4 ++-- src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs index 8ace64ab9dad..62d0fcd0d1e5 100644 --- a/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/EndsWith.char.cs @@ -122,8 +122,8 @@ public static void EndsWithNoMatch_StringComparison() firstSpan.ToString().EndsWith(secondSpan.ToString(), StringComparison.CurrentCulture), firstSpan.EndsWith(secondSpan, StringComparison.CurrentCulture)); Assert.Equal( - firstSpan.ToString().EndsWith(secondSpan.ToString(), StringComparison.CurrentCulture), - firstSpan.EndsWith(secondSpan, StringComparison.CurrentCulture)); + firstSpan.ToString().EndsWith(secondSpan.ToString(), StringComparison.CurrentCultureIgnoreCase), + firstSpan.EndsWith(secondSpan, StringComparison.CurrentCultureIgnoreCase)); Assert.Equal( firstSpan.ToString().EndsWith(secondSpan.ToString(), StringComparison.InvariantCulture), firstSpan.EndsWith(secondSpan, StringComparison.InvariantCulture)); diff --git a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs index 3e1c3bf4e838..ba9df189fb9f 100644 --- a/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs +++ b/src/System.Memory/tests/ReadOnlySpan/StartsWith.char.cs @@ -124,8 +124,8 @@ public static void StartsWithNoMatch_StringComparison() firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCulture), firstSpan.StartsWith(secondSpan, StringComparison.CurrentCulture)); Assert.Equal( - firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCulture), - firstSpan.StartsWith(secondSpan, StringComparison.CurrentCulture)); + firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.CurrentCultureIgnoreCase), + firstSpan.StartsWith(secondSpan, StringComparison.CurrentCultureIgnoreCase)); Assert.Equal( firstSpan.ToString().StartsWith(secondSpan.ToString(), StringComparison.InvariantCulture), firstSpan.StartsWith(secondSpan, StringComparison.InvariantCulture));