Add EnumerateRunes() ref APIs and unit tests (dotnet/corefx#33504)
authorLevi Broderick <GrabYourPitchforks@users.noreply.github.com>
Mon, 19 Nov 2018 23:07:08 +0000 (15:07 -0800)
committerJan Kotas <jkotas@microsoft.com>
Fri, 30 Nov 2018 06:41:32 +0000 (22:41 -0800)
Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs

index 11980fb..51ebd8d 100644 (file)
@@ -2,10 +2,12 @@
 // 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.Diagnostics;
 using System.Globalization;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
+using Xunit;
 
 using Internal.Runtime.CompilerServices;
 
@@ -380,6 +382,58 @@ namespace System
                     CultureInfo.CurrentCulture.CompareInfo.IsPrefix(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
         }
 
+        [Theory]
+        [InlineData(new char[0], new int[0])] // empty
+        [InlineData(new char[] { 'x', 'y', 'z' }, new int[] { 'x', 'y', 'z' })]
+        [InlineData(new char[] { 'x', '\uD86D', '\uDF54', 'y' }, new int[] { 'x', 0x2B754, 'y' })] // valid surrogate pair
+        [InlineData(new char[] { 'x', '\uD86D', 'y' }, new int[] { 'x', 0xFFFD, 'y' })] // standalone high surrogate
+        [InlineData(new char[] { 'x', '\uDF54', 'y' }, new int[] { 'x', 0xFFFD, 'y' })] // standalone low surrogate
+        [InlineData(new char[] { 'x', '\uD86D' }, new int[] { 'x', 0xFFFD })] // standalone high surrogate at end of string
+        [InlineData(new char[] { 'x', '\uDF54' }, new int[] { 'x', 0xFFFD })] // standalone low surrogate at end of string
+        [InlineData(new char[] { 'x', '\uD86D', '\uD86D', 'y' }, new int[] { 'x', 0xFFFD, 0xFFFD, 'y' })] // two high surrogates should be two replacement chars
+        [InlineData(new char[] { 'x', '\uFFFD', 'y' }, new int[] { 'x', 0xFFFD, 'y' })] // literal U+FFFD
+        public static void EnumerateRunes(char[] chars, int[] expected)
+        {
+            // Test data is smuggled as char[] instead of straight-up string since the test framework
+            // doesn't like invalid UTF-16 literals.
+
+            // first, test Span<char>
+
+            List<int> enumeratedValues = new List<int>();
+            foreach (Rune rune in ((Span<char>)chars).EnumerateRunes())
+            {
+                enumeratedValues.Add(rune.Value);
+            }
+            Assert.Equal(expected, enumeratedValues.ToArray());
+
+
+            // next, ROS<char>
+
+            enumeratedValues.Clear();
+            foreach (Rune rune in ((ReadOnlySpan<char>)chars).EnumerateRunes())
+            {
+                enumeratedValues.Add(rune.Value);
+            }
+            Assert.Equal(expected, enumeratedValues.ToArray());
+        }
+
+        [Fact]
+        public static void EnumerateRunes_DoesNotReadPastEndOfSpan(char[] chars, int[] expected)
+        {
+            // As an optimization, reading scalars from a string *may* read past the end of the string
+            // to the terminating null. This optimization is invalid for arbitrary spans, so this test
+            // ensures that we're not performing this optimization here.
+
+            ReadOnlySpan<char> span = "xy\U0002B754z".AsSpan(1, 2); // well-formed string, but span splits surrogate pair
+
+            List<int> enumeratedValues = new List<int>();
+            foreach (Rune rune in span.EnumerateRunes())
+            {
+                enumeratedValues.Add(rune.Value);
+            }
+            Assert.Equal(new int[] { 'y', '\uFFFD' }, enumeratedValues.ToArray());
+        }
+
         /// <summary>
         /// Creates a new span over the portion of the target array.
         /// </summary>