diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index f667694c3d18..e8f33c577d21 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -67,6 +67,10 @@ public static void Reverse(this System.Span span) { } 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 int ToLower(this System.ReadOnlySpan source, System.Span destination, System.Globalization.CultureInfo culture) { throw null; } + public static int ToLowerInvariant(this System.ReadOnlySpan source, System.Span destination) { throw null; } + public static int ToUpper(this System.ReadOnlySpan source, System.Span destination, System.Globalization.CultureInfo culture) { throw null; } + public static int ToUpperInvariant(this System.ReadOnlySpan source, System.Span destination) { throw null; } public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span) { throw null; } public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span, char trimChar) { throw null; } public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span, System.ReadOnlySpan trimChars) { throw null; } diff --git a/src/System.Memory/ref/System.Memory.csproj b/src/System.Memory/ref/System.Memory.csproj index bb50041d827a..4b2557007595 100644 --- a/src/System.Memory/ref/System.Memory.csproj +++ b/src/System.Memory/ref/System.Memory.csproj @@ -25,6 +25,7 @@ + diff --git a/src/System.Memory/src/System/MemoryExtensions.Portable.cs b/src/System.Memory/src/System/MemoryExtensions.Portable.cs index a56e02acb527..0cfbff8f37a5 100644 --- a/src/System.Memory/src/System/MemoryExtensions.Portable.cs +++ b/src/System.Memory/src/System/MemoryExtensions.Portable.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; +using System.Globalization; using System.Runtime.CompilerServices; namespace System @@ -12,6 +13,92 @@ namespace System /// public static partial class MemoryExtensions { + /// + /// Copies the characters from the source span into the destination, converting each character to lowercase, + /// using the casing rules of the specified culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + /// An object that supplies culture-specific casing rules. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. + /// + /// Thrown when is null. + /// + public static int ToLower(this ReadOnlySpan source, Span destination, CultureInfo culture) + { + if (culture == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture); + + // Assuming that changing case does not affect length + if (destination.Length < source.Length) + return -1; + + string sourceString = source.ToString(); +#if !netstandard11 + string resultString = sourceString.ToLower(culture); +#else + string resultString = culture.TextInfo.ToLower(sourceString); +#endif + Debug.Assert(sourceString.Length == resultString.Length); + resultString.AsSpan().CopyTo(destination); + return source.Length; + } + + /// + /// Copies the characters from the source span into the destination, converting each character to lowercase, + /// using the casing rules of the invariant culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. + public static int ToLowerInvariant(this ReadOnlySpan source, Span destination) + => ToLower(source, destination, CultureInfo.InvariantCulture); + + /// + /// Copies the characters from the source span into the destination, converting each character to uppercase, + /// using the casing rules of the specified culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + /// An object that supplies culture-specific casing rules. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. + /// + /// Thrown when is null. + /// + public static int ToUpper(this ReadOnlySpan source, Span destination, CultureInfo culture) + { + if (culture == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture); + + // Assuming that changing case does not affect length + if (destination.Length < source.Length) + return -1; + + string sourceString = source.ToString(); +#if !netstandard11 + string resultString = sourceString.ToUpper(culture); +#else + string resultString = culture.TextInfo.ToUpper(sourceString); +#endif + Debug.Assert(sourceString.Length == resultString.Length); + resultString.AsSpan().CopyTo(destination); + return source.Length; + } + + /// + /// Copies the characters from the source span into the destination, converting each character to uppercase + /// using the casing rules of the invariant culture. + /// + /// The source span. + /// The destination span which contains the transformed characters. + /// If the source and destinations overlap, this method behaves as if the original values are in + /// a temporary location before the destination is overwritten. + public static int ToUpperInvariant(this ReadOnlySpan source, Span destination) + => ToUpper(source, destination, CultureInfo.InvariantCulture); + /// /// Determines whether the end of the matches the specified when compared using the specified option. /// diff --git a/src/System.Memory/src/System/ThrowHelper.cs b/src/System.Memory/src/System/ThrowHelper.cs index aaf9bd26f4fa..62b5612a8b3b 100644 --- a/src/System.Memory/src/System/ThrowHelper.cs +++ b/src/System.Memory/src/System/ThrowHelper.cs @@ -188,6 +188,7 @@ internal enum ExceptionArgument startIndex, endIndex, array, - ownedMemory + ownedMemory, + culture } } diff --git a/src/System.Memory/tests/ReadOnlySpan/ToLower.cs b/src/System.Memory/tests/ReadOnlySpan/ToLower.cs new file mode 100644 index 000000000000..d0882fd6a115 --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/ToLower.cs @@ -0,0 +1,310 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Fact] + public static void ZeroLengthToLower() + { + char[] expectedSource = { 'a', 'B', 'c' }; + char[] a = { 'a', 'B', 'c' }; + var source = new ReadOnlySpan(a, 2, 0); + + var expectedDestination = new char[1] { 'a' }; + Span destination = new char[1] { 'a' }; + + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, a); + + source = ReadOnlySpan.Empty; + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, a); + } + + [Fact] + public static void SameSpanToLower() + { + var expected = new char[3] { 'a', 'b', 'c' }; + var a = new char[3] { 'a', 'B', 'c' }; + { + ReadOnlySpan source = a; + Span destination = a; + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expected, destination.ToArray()); + Assert.Equal(expected, source.ToArray()); + } + { + ReadOnlySpan source = a; + Span destination = a; + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expected, destination.ToArray()); + Assert.Equal(expected, source.ToArray()); + } + } + + [Fact] + public static void ToLowerOverlapping() + { + var expectedSource = new char[3] { 'B', 'c', 'b' }; + var expectedDestination = new char[3] { 'b', 'c', 'b' }; + + { + char[] a = { 'a', 'B', 'c', 'B', 'c', 'B' }; + var source = new ReadOnlySpan(a, 1, 3); + var destination = new Span(a, 3, 3); + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + { + char[] a = { 'a', 'B', 'c', 'B', 'c', 'B' }; + var source = new ReadOnlySpan(a, 1, 3); + var destination = new Span(a, 3, 3); + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + + [Fact] + public static void LengthMismatchToLower() + { + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + + var expectedDestination = new char[1] { 'a' }; + Span destination = new char[1] { 'a' }; + + Assert.Equal(-1, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(-1, source.ToLowerInvariant(destination)); + + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + + var expectedDestination = new char[4] { 'a', 'b', 'c', 'D' }; + Span destination = new char[4] { 'x', 'Y', 'z', 'D' }; + + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + + [Fact] + public static void ToLower() + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + var expectedDestination = new char[3] { 'a', 'b', 'c' }; + + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'x', 'Y', 'z' }; + + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'x', 'Y', 'z' }; + + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + + [Fact] + public static void MakeSureNoToLowerChecksGoOutOfRange() + { + for (int length = 0; length < 100; length++) + { + var first = new char[length + 2]; + var second = new char[length + 2]; + + for (int i = 0; i < first.Length; i++) + { + first[i] = 'A'; + second[i] = 'B'; + } + + first[0] = 'Z'; + first[length + 1] = 'Z'; + + second[0] = 'Y'; + second[length + 1] = 'Y'; + + var expectedSource = new char[length]; + var expectedDestination = new char[length]; + for (int i = 0; i < length; i++) + { + expectedSource[i] = 'A'; + expectedDestination[i] = 'a'; + } + + var source = new ReadOnlySpan(first, 1, length); + var destination = new Span(second, 1, length); + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + + Assert.Equal('Z', first[0]); + Assert.Equal('Z', first[length + 1]); + Assert.Equal('Y', second[0]); + Assert.Equal('Y', second[length + 1]); + } + } + + [Fact] + public static void ToLowerNullCulture() + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'a', 'B', 'c' }; + + try + { + source.ToLower(destination, null); + Assert.False(true, "Expected exception: " + typeof(ArgumentNullException).GetType()); + } + catch (ArgumentNullException) + { + } + catch (Exception wrongException) + { + Assert.False(true, "Wrong exception thrown: Expected " + typeof(ArgumentNullException).GetType() + ": Actual: " + wrongException.GetType()); + } + } + + [Theory] + [InlineData("HELLO", "hello")] + [InlineData("hello", "hello")] + [InlineData("", "")] + public static void ToLower(string s, string expected) + { + ReadOnlySpan source = s.AsSpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToLower(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expected, destination.ToString()); + } + + private static IEnumerable ToLower_Culture_TestData() + { + yield return new object[] { "H\u0049 World", "h\u0131 world", new CultureInfo("tr-TR") }; + yield return new object[] { "H\u0130 World", "h\u0069 world", new CultureInfo("tr-TR") }; + yield return new object[] { "H\u0131 World", "h\u0131 world", new CultureInfo("tr-TR") }; + + yield return new object[] { "H\u0049 World", "h\u0069 world", new CultureInfo("en-US") }; + yield return new object[] { "H\u0130 World", "h\u0069 world", new CultureInfo("en-US") }; + yield return new object[] { "H\u0131 World", "h\u0131 world", new CultureInfo("en-US") }; + + yield return new object[] { "H\u0049 World", "h\u0069 world", CultureInfo.InvariantCulture }; + yield return new object[] { "H\u0130 World", "h\u0130 world", CultureInfo.InvariantCulture }; + yield return new object[] { "H\u0131 World", "h\u0131 world", CultureInfo.InvariantCulture }; + } + + [Theory] + [MemberData(nameof(ToLower_Culture_TestData))] + public static void Test_ToLower_Culture(string actual, string expected, CultureInfo culture) + { + ReadOnlySpan source = actual.AsSpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToLower(destination, culture)); + Assert.Equal(expected, destination.ToString()); + } + + [Theory] + [InlineData("HELLO", "hello")] + [InlineData("hello", "hello")] + [InlineData("", "")] + public static void ToLowerInvariant(string s, string expected) + { + ReadOnlySpan source = s.AsSpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToLowerInvariant(destination)); + Assert.Equal(expected, destination.ToString()); + } + + [Fact] + public static void ToLowerToUpperInvariant_ASCII() + { + var asciiChars = new char[128]; + var asciiCharsUpper = new char[128]; + var asciiCharsLower = new char[128]; + + for (int i = 0; i < asciiChars.Length; i++) + { + char c = (char)i; + asciiChars[i] = c; + + // Purposefully avoiding char.ToUpper/ToLower here so as not to use the same thing we're testing. + asciiCharsLower[i] = (c >= 'A' && c <= 'Z') ? (char)(c - 'A' + 'a') : c; + asciiCharsUpper[i] = (c >= 'a' && c <= 'z') ? (char)(c - 'a' + 'A') : c; + } + + ReadOnlySpan source = asciiChars; + var ascii = new string(asciiChars); + Span destinationLower = new char[source.Length]; + Span destinationUpper = new char[source.Length]; + + Assert.Equal(source.Length, source.ToLowerInvariant(destinationLower)); + Assert.Equal(source.Length, source.ToUpperInvariant(destinationUpper)); + + Assert.Equal(ascii.ToLowerInvariant(), destinationLower.ToString()); + Assert.Equal(ascii.ToUpperInvariant(), destinationUpper.ToString()); + + Assert.Equal(ascii, source.ToString()); + } + + public static IEnumerable UpperLowerCasing_TestData() + { + //lower, upper, Culture + yield return new object[] { "abcd", "ABCD", "en-US" }; + yield return new object[] { "latin i", "LATIN I", "en-US" }; + yield return new object[] { "turky \u0131", "TURKY I", "tr-TR" }; + yield return new object[] { "turky i", "TURKY \u0130", "tr-TR" }; + yield return new object[] { "\ud801\udc29", PlatformDetection.IsWindows7 ? "\ud801\udc29" : "\ud801\udc01", "en-US" }; + } + + [Theory] + [MemberData(nameof(UpperLowerCasing_TestData))] + public static void CasingTest(string lowerForm, string upperForm, string cultureName) + { + CultureInfo ci = CultureInfo.GetCultureInfo(cultureName); + + ReadOnlySpan sourceLower = lowerForm.AsSpan(); + ReadOnlySpan sourceUpper = upperForm.AsSpan(); + Span destinationLower = new char[sourceUpper.Length]; + Span destinationUpper = new char[sourceLower.Length]; + + Assert.Equal(sourceUpper.Length, sourceUpper.ToLower(destinationLower, ci)); + Assert.Equal(sourceLower.Length, sourceLower.ToUpper(destinationUpper, ci)); + + Assert.Equal(upperForm.ToLower(ci), destinationLower.ToString()); + Assert.Equal(lowerForm.ToUpper(ci), destinationUpper.ToString()); + + Assert.Equal(lowerForm, sourceLower.ToString()); + Assert.Equal(upperForm, sourceUpper.ToString()); + } + } +} diff --git a/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs b/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs new file mode 100644 index 000000000000..0da84cb94fba --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/ToUpper.cs @@ -0,0 +1,329 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Fact] + public static void ZeroLengthToUpper() + { + char[] expectedSource = { 'a', 'B', 'c' }; + char[] a = { 'a', 'B', 'c' }; + var source = new ReadOnlySpan(a, 2, 0); + + var expectedDestination = new char[1] { 'a' }; + Span destination = new char[1] { 'a' }; + + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, a); + + source = ReadOnlySpan.Empty; + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, a); + } + + [Fact] + public static void SameSpanToUpper() + { + var expected = new char[3] { 'A', 'B', 'C' }; + var a = new char[3] { 'a', 'B', 'c' }; + { + ReadOnlySpan source = a; + Span destination = a; + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expected, destination.ToArray()); + Assert.Equal(expected, source.ToArray()); + } + { + ReadOnlySpan source = a; + Span destination = a; + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expected, destination.ToArray()); + Assert.Equal(expected, source.ToArray()); + } + } + + [Fact] + public static void ToUpperOverlapping() + { + var expectedSource = new char[3] { 'b', 'C', 'B' }; + var expectedDestination = new char[3] { 'B', 'C', 'B' }; + + { + char[] a = { 'a', 'b', 'C', 'b', 'C', 'b' }; + var source = new ReadOnlySpan(a, 1, 3); + var destination = new Span(a, 3, 3); + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + { + char[] a = { 'a', 'b', 'C', 'b', 'C', 'b' }; + var source = new ReadOnlySpan(a, 1, 3); + var destination = new Span(a, 3, 3); + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + + [Fact] + public static void LengthMismatchToUpper() + { + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + + var expectedDestination = new char[1] { 'a' }; + Span destination = new char[1] { 'a' }; + + Assert.Equal(-1, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(-1, source.ToUpperInvariant(destination)); + + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + + var expectedDestination = new char[4] { 'A', 'B', 'C', 'd' }; + Span destination = new char[4] { 'x', 'Y', 'z', 'd' }; + + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + + [Fact] + public static void ToUpper() + { + var expectedSource = new char[3] { 'a', 'B', 'c' }; + var expectedDestination = new char[3] { 'A', 'B', 'C' }; + + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'x', 'Y', 'z' }; + + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'x', 'Y', 'z' }; + + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + } + } + + [Fact] + public static void MakeSureNoToUpperChecksGoOutOfRange() + { + for (int length = 0; length < 100; length++) + { + var first = new char[length + 2]; + var second = new char[length + 2]; + + for (int i = 0; i < first.Length; i++) + { + first[i] = 'a'; + second[i] = 'b'; + } + + first[0] = 'z'; + first[length + 1] = 'z'; + + second[0] = 'y'; + second[length + 1] = 'y'; + + var expectedSource = new char[length]; + var expectedDestination = new char[length]; + for (int i = 0; i < length; i++) + { + expectedSource[i] = 'a'; + expectedDestination[i] = 'A'; + } + + var source = new ReadOnlySpan(first, 1, length); + var destination = new Span(second, 1, length); + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expectedDestination, destination.ToArray()); + Assert.Equal(expectedSource, source.ToArray()); + + Assert.Equal('z', first[0]); + Assert.Equal('z', first[length + 1]); + Assert.Equal('y', second[0]); + Assert.Equal('y', second[length + 1]); + } + } + + [Fact] + public static void ToUpperNullCulture() + { + ReadOnlySpan source = new char[3] { 'a', 'B', 'c' }; + Span destination = new char[3] { 'a', 'B', 'c' }; + + try + { + source.ToUpper(destination, null); + Assert.False(true, "Expected exception: " + typeof(ArgumentNullException).GetType()); + } + catch (ArgumentNullException) + { + } + catch (Exception wrongException) + { + Assert.False(true, "Wrong exception thrown: Expected " + typeof(ArgumentNullException).GetType() + ": Actual: " + wrongException.GetType()); + } + } + + [Theory] + [InlineData("hello", "HELLO")] + [InlineData("HELLO", "HELLO")] + [InlineData("", "")] + public static void ToUpper(string s, string expected) + { + ReadOnlySpan source = s.AsSpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, CultureInfo.CurrentCulture)); + Assert.Equal(expected, destination.ToString()); + } + + private static IEnumerable ToUpper_Culture_TestData() + { + yield return new object[] { "h\u0069 world", "H\u0130 WORLD", new CultureInfo("tr-TR") }; + yield return new object[] { "h\u0130 world", "H\u0130 WORLD", new CultureInfo("tr-TR") }; + yield return new object[] { "h\u0131 world", "H\u0049 WORLD", new CultureInfo("tr-TR") }; + + yield return new object[] { "h\u0069 world", "H\u0049 WORLD", new CultureInfo("en-US") }; + yield return new object[] { "h\u0130 world", "H\u0130 WORLD", new CultureInfo("en-US") }; + yield return new object[] { "h\u0131 world", "H\u0049 WORLD", new CultureInfo("en-US") }; + + yield return new object[] { "h\u0069 world", "H\u0049 WORLD", CultureInfo.InvariantCulture }; + yield return new object[] { "h\u0130 world", "H\u0130 WORLD", CultureInfo.InvariantCulture }; + yield return new object[] { "h\u0131 world", "H\u0131 WORLD", CultureInfo.InvariantCulture }; + } + + [Theory] + [MemberData(nameof(ToUpper_Culture_TestData))] + public static void Test_ToUpper_Culture(string actual, string expected, CultureInfo culture) + { + ReadOnlySpan source = actual.AsSpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + } + + [Fact] + public static void ToUpper_TurkishI_TurkishCulture() + { + CultureInfo culture = new CultureInfo("tr-TR"); + + string s = "H\u0069 World"; + string expected = "H\u0130 WORLD"; + ReadOnlySpan source = s.AsSpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0130 World"; + expected = "H\u0130 WORLD"; + source = s.AsSpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0131 World"; + expected = "H\u0049 WORLD"; + source = s.AsSpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + } + + [Fact] + public static void ToUpper_TurkishI_EnglishUSCulture() + { + CultureInfo culture = new CultureInfo("en-US"); + + string s = "H\u0069 World"; + string expected = "H\u0049 WORLD"; + ReadOnlySpan source = s.AsSpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0130 World"; + expected = "H\u0130 WORLD"; + source = s.AsSpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0131 World"; + expected = "H\u0049 WORLD"; + source = s.AsSpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + } + + [Fact] + public static void ToUpper_TurkishI_InvariantCulture() + { + CultureInfo culture = CultureInfo.InvariantCulture; + + string s = "H\u0069 World"; + string expected = "H\u0049 WORLD"; + ReadOnlySpan source = s.AsSpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0130 World"; + expected = "H\u0130 WORLD"; + source = s.AsSpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + + s = "H\u0131 World"; + expected = "H\u0131 WORLD"; + source = s.AsSpan(); + destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpper(destination, culture)); + Assert.Equal(expected, destination.ToString()); + } + + [Theory] + [InlineData("hello", "HELLO")] + [InlineData("HELLO", "HELLO")] + [InlineData("", "")] + public static void ToUpperInvariant(string s, string expected) + { + ReadOnlySpan source = s.AsSpan(); + Span destination = new char[source.Length]; + Assert.Equal(source.Length, source.ToUpperInvariant(destination)); + Assert.Equal(expected, destination.ToString()); + } + } +} diff --git a/src/System.Memory/tests/System.Memory.Tests.csproj b/src/System.Memory/tests/System.Memory.Tests.csproj index 027ecaa05d6f..7035a2af6e16 100644 --- a/src/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/System.Memory/tests/System.Memory.Tests.csproj @@ -121,7 +121,9 @@ + +