public static void TryFind_Char_Ordinal(ustring source, char searchTerm, Range?expectedForwardMatch, Range?expectedBackwardMatch) { using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(source.AsBytes()); Utf8Span searchSpan = boundedSpan.Span; source = null; // to avoid accidentally using this for the remainder of the test // First, search forward bool wasFound = searchSpan.TryFind(searchTerm, out Range actualForwardMatch); Assert.Equal(expectedForwardMatch.HasValue, wasFound); if (wasFound) { AssertRangesEqual(searchSpan.Length, expectedForwardMatch.Value, actualForwardMatch); } // Also check Contains / StartsWith / SplitOn Assert.Equal(wasFound, searchSpan.Contains(searchTerm)); Assert.Equal(wasFound && searchSpan.Bytes[..actualForwardMatch.Start].IsEmpty, searchSpan.StartsWith(searchTerm)); (var before, var after) = searchSpan.SplitOn(searchTerm); if (wasFound) { Assert.True(searchSpan.Bytes[..actualForwardMatch.Start] == before.Bytes); // check for referential equality
public static void Normalize(string utf16Source, string utf16Expected, NormalizationForm normalizationForm) { using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(utf16Source); Utf8Span utf8Source = boundedSpan.Span; // Quick IsNormalized tests Assert.Equal(utf16Source == utf16Expected, utf8Source.IsNormalized(normalizationForm)); // Normalize and return new Utf8String instances ustring utf8Normalized = utf8Source.Normalize(normalizationForm); Assert.True(ustring.AreEquivalent(utf8Normalized, utf16Expected)); // Normalize to byte arrays which are too small, expect -1 (failure) Assert.Equal(-1, utf8Source.Normalize(new byte[utf8Normalized.GetByteLength() - 1], normalizationForm)); // Normalize to byte arrays which are the correct length, expect success, // then compare buffer contents for ordinal equality. foreach (int bufferLength in new int[] { utf8Normalized.GetByteLength() /* just right */, utf8Normalized.GetByteLength() + 1 /* with extra room */ }) { byte[] dest = new byte[bufferLength]; Assert.Equal(utf8Normalized.GetByteLength(), utf8Source.Normalize(dest, normalizationForm)); Utf8Span normalizedSpan = Utf8Span.UnsafeCreateWithoutValidation(dest[..utf8Normalized.GetByteLength()]);
public static void RunesProperty_FromData() { using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("\U00000012\U00000123\U00001234\U00101234\U00000012\U00000123\U00001234\U00101234"); Utf8Span span = boundedSpan.Span; var runesEnumerator = span.Runes.GetEnumerator(); Assert.True(runesEnumerator.MoveNext()); Assert.Equal(new Rune(0x0012), runesEnumerator.Current); Assert.True(runesEnumerator.MoveNext()); Assert.Equal(new Rune(0x0123), runesEnumerator.Current); Assert.True(runesEnumerator.MoveNext()); Assert.Equal(new Rune(0x1234), runesEnumerator.Current); Assert.True(runesEnumerator.MoveNext()); Assert.Equal(new Rune(0x101234), runesEnumerator.Current); Assert.True(runesEnumerator.MoveNext()); Assert.Equal(new Rune(0x0012), runesEnumerator.Current); Assert.True(runesEnumerator.MoveNext()); Assert.Equal(new Rune(0x0123), runesEnumerator.Current); Assert.True(runesEnumerator.MoveNext()); Assert.Equal(new Rune(0x1234), runesEnumerator.Current); Assert.True(runesEnumerator.MoveNext()); Assert.Equal(new Rune(0x101234), runesEnumerator.Current); Assert.False(runesEnumerator.MoveNext()); }
public static void CharsProperty_FromData() { using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("\U00000012\U00000123\U00001234\U00101234\U00000012\U00000123\U00001234\U00101234"); Utf8Span span = boundedSpan.Span; var charsEnumerator = span.Chars.GetEnumerator(); Assert.True(charsEnumerator.MoveNext()); Assert.Equal('\U00000012', charsEnumerator.Current); Assert.True(charsEnumerator.MoveNext()); Assert.Equal('\U00000123', charsEnumerator.Current); Assert.True(charsEnumerator.MoveNext()); Assert.Equal('\U00001234', charsEnumerator.Current); Assert.True(charsEnumerator.MoveNext()); Assert.Equal('\uDBC4', charsEnumerator.Current); Assert.True(charsEnumerator.MoveNext()); Assert.Equal('\uDE34', charsEnumerator.Current); Assert.True(charsEnumerator.MoveNext()); Assert.Equal('\U00000012', charsEnumerator.Current); Assert.True(charsEnumerator.MoveNext()); Assert.Equal('\U00000123', charsEnumerator.Current); Assert.True(charsEnumerator.MoveNext()); Assert.Equal('\U00001234', charsEnumerator.Current); Assert.True(charsEnumerator.MoveNext()); Assert.Equal('\uDBC4', charsEnumerator.Current); Assert.True(charsEnumerator.MoveNext()); Assert.Equal('\uDE34', charsEnumerator.Current); Assert.False(charsEnumerator.MoveNext()); }
public static void Equals_NonOrdinal(string str1, string str2, StringComparison comparison, string culture, bool shouldCompareAsEqual) { Func <string, string, string, string, string, int> action = (str1, str2, comparison, culture, shouldCompareAsEqual) => { using (new ThreadCultureChange(culture)) { using BoundedUtf8Span boundedSpan1 = new BoundedUtf8Span(str1); using BoundedUtf8Span boundedSpan2 = new BoundedUtf8Span(str2); Utf8Span span1 = boundedSpan1.Span; Utf8Span span2 = boundedSpan2.Span; StringComparison comparisonType = Enum.Parse <StringComparison>(comparison); bool expected = bool.Parse(shouldCompareAsEqual); Assert.Equal(expected, span1.Equals(span2, comparisonType)); Assert.Equal(expected, span2.Equals(span1, comparisonType)); Assert.Equal(expected, Utf8Span.Equals(span1, span2, comparisonType)); Assert.Equal(expected, Utf8Span.Equals(span2, span1, comparisonType)); } return(RemoteExecutor.SuccessExitCode); }; if (culture != null && PlatformDetection.IsUap) // need to apply a culture to the current thread { RemoteExecutor.Invoke(action, str1, str2, comparison.ToString(), culture, shouldCompareAsEqual.ToString()).Dispose(); } else { action(str1, str2, comparison.ToString(), culture, shouldCompareAsEqual.ToString()); } }
private static void SplitTest_Common(ustring source, Utf8SpanSplitDelegate splitAction, Range[] expectedRanges) { using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(source.AsBytes()); Utf8Span span = boundedSpan.Span; int totalSpanLengthInBytes = span.Bytes.Length; source = null; // to avoid inadvertently using this for the remainder of the method // First, run the split with default options and make sure the ranges are equivalent List <Range> actualRanges = new List <Range>(); foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.None)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } Assert.Equal(expectedRanges, actualRanges, new RangeEqualityComparer(totalSpanLengthInBytes)); // Next, run the split with empty entries removed actualRanges = new List <Range>(); foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.RemoveEmptyEntries)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } Assert.Equal(expectedRanges.Where(range => !range.IsEmpty(totalSpanLengthInBytes)), actualRanges, new RangeEqualityComparer(totalSpanLengthInBytes)); // Next, run the split with results trimmed (but allowing empty results) expectedRanges = (Range[])expectedRanges.Clone(); // clone the array since we're about to mutate it for (int i = 0; i < expectedRanges.Length; i++) { expectedRanges[i] = GetRangeOfSubspan(span, span[expectedRanges[i]].Trim()); } actualRanges = new List <Range>(); foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.TrimEntries)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } Assert.Equal(expectedRanges, actualRanges, new RangeEqualityComparer(totalSpanLengthInBytes)); // Finally, run the split both trimmed and with empty entries removed actualRanges = new List <Range>(); foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.TrimEntries | Utf8StringSplitOptions.RemoveEmptyEntries)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } Assert.Equal(expectedRanges.Where(range => !range.IsEmpty(totalSpanLengthInBytes)), actualRanges, new RangeEqualityComparer(totalSpanLengthInBytes)); }
static void RunTest(string testData, string expected, CultureInfo culture) { using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(testData); Utf8Span inputSpan = boundedSpan.Span; // First try the allocating APIs ustring expectedUtf8 = u8(expected) ?? ustring.Empty; ustring actualUtf8; if (culture is null) { actualUtf8 = inputSpan.ToLowerInvariant(); } else { actualUtf8 = inputSpan.ToLower(culture); } Assert.Equal(expectedUtf8, actualUtf8); // Next, try the non-allocating APIs with too small a buffer if (expectedUtf8.Length > 0) { byte[] bufferTooSmall = new byte[expectedUtf8.Length - 1]; if (culture is null) { Assert.Equal(-1, inputSpan.ToLowerInvariant(bufferTooSmall)); } else { Assert.Equal(-1, inputSpan.ToLower(bufferTooSmall, culture)); } } // Then the non-allocating APIs with a properly sized buffer foreach (int bufferSize in new[] { expectedUtf8.Length, expectedUtf8.Length + 1 }) { byte[] buffer = new byte[expectedUtf8.Length]; if (culture is null) { Assert.Equal(expectedUtf8.Length, inputSpan.ToLowerInvariant(buffer)); } else { Assert.Equal(expectedUtf8.Length, inputSpan.ToLower(buffer, culture)); } Assert.True(expectedUtf8.AsBytes().SequenceEqual(buffer)); } }
public static void Equals_NonOrdinal(string str1, string str2, StringComparison comparison, string culture, bool shouldCompareAsEqual) { using (new ThreadCultureChange(culture)) { using BoundedUtf8Span boundedSpan1 = new BoundedUtf8Span(str1); using BoundedUtf8Span boundedSpan2 = new BoundedUtf8Span(str2); Utf8Span span1 = boundedSpan1.Span; Utf8Span span2 = boundedSpan2.Span; Assert.Equal(shouldCompareAsEqual, span1.Equals(span2, comparison)); Assert.Equal(shouldCompareAsEqual, span2.Equals(span1, comparison)); Assert.Equal(shouldCompareAsEqual, Utf8Span.Equals(span1, span2, comparison)); Assert.Equal(shouldCompareAsEqual, Utf8Span.Equals(span2, span1, comparison)); } }
public static void GetHashCode_WithComparison() { // Since hash code generation is randomized, it's possible (though unlikely) that // we might see unanticipated collisions. It's ok if this unit test fails once in // every few million runs, but if the unit test becomes truly flaky then that would // be indicative of a larger problem with hash code generation. // // These tests also make sure that the hash code is computed over the buffer rather // than over the reference. // Ordinal { using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("ababaaAA"); Utf8Span span = boundedSpan.Span; Assert.Equal(span[0..2].GetHashCode(StringComparison.Ordinal), span[2..4].GetHashCode(StringComparison.Ordinal));
public static void GetHashCode_Ordinal() { // Generate 17 all-null strings and make sure they have unique hash codes. // Assuming Marvin32 is a good PRF and has a full 32-bit output domain, we should // expect this test to fail only once every ~30 million runs due to the birthday paradox. // That should be good enough for inclusion as a unit test. HashSet <int> seenHashCodes = new HashSet <int>(); for (int i = 0; i <= 16; i++) { using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(new byte[i]); Utf8Span span = boundedSpan.Span; Assert.True(seenHashCodes.Add(span.GetHashCode()), "This hash code was previously seen."); } }
public static void Split_Deconstruct() { using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("a,b,c,d,e"); Utf8Span span = boundedSpan.Span; // Note referential equality checks below (since we want to know exact slices // into the original buffer), not deep (textual) equality checks. { (Utf8Span a, Utf8Span b) = span.Split('x'); // not found Assert.True(a.Bytes == span.Bytes, "Expected referential equality of input."); Assert.True(b.Bytes == default); } { (Utf8Span a, Utf8Span b) = span.Split(','); Assert.True(a.Bytes == span.Bytes[..1]); // "a" Assert.True(b.Bytes == span.Bytes[2..]); // "b,c,d,e" } { (Utf8Span a, Utf8Span b, Utf8Span c, Utf8Span d, Utf8Span e) = span.Split(','); Assert.True(a.Bytes == span.Bytes[0..1]); // "a" Assert.True(b.Bytes == span.Bytes[2..3]); // "b" Assert.True(c.Bytes == span.Bytes[4..5]); // "c" Assert.True(d.Bytes == span.Bytes[6..7]); // "d" Assert.True(e.Bytes == span.Bytes[8..9]); // "e" } { (Utf8Span a, Utf8Span b, Utf8Span c, Utf8Span d, Utf8Span e, Utf8Span f, Utf8Span g, Utf8Span h) = span.Split(','); Assert.True(a.Bytes == span.Bytes[0..1]); // "a" Assert.True(b.Bytes == span.Bytes[2..3]); // "b" Assert.True(c.Bytes == span.Bytes[4..5]); // "c" Assert.True(d.Bytes == span.Bytes[6..7]); // "d" Assert.True(e.Bytes == span.Bytes[8..9]); // "e" Assert.True(f.Bytes == default); Assert.True(g.Bytes == default); Assert.True(h.Bytes == default); } }
public static void Trim(string input) { // Arrange using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(input); Utf8Span span = boundedSpan.Span; // Act Utf8Span trimmed = span.Trim(); // Assert // Compute the trim manually and ensure it matches the trimmed span's characteristics. ReadOnlySpan <byte> utf8Bytes = span.Bytes; while (!utf8Bytes.IsEmpty) { OperationStatus status = Rune.DecodeFromUtf8(utf8Bytes, out Rune decodedRune, out int bytesConsumed); Assert.Equal(OperationStatus.Done, status); if (!Rune.IsWhiteSpace(decodedRune)) { break; } utf8Bytes = utf8Bytes.Slice(bytesConsumed); } while (!utf8Bytes.IsEmpty) { OperationStatus status = Rune.DecodeLastFromUtf8(utf8Bytes, out Rune decodedRune, out int bytesConsumed); Assert.Equal(OperationStatus.Done, status); if (!Rune.IsWhiteSpace(decodedRune)) { break; } utf8Bytes = utf8Bytes[..^ bytesConsumed];
public static void Split_Deconstruct_WithOptions() { using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("a, , b, c,, d, e"); Utf8Span span = boundedSpan.Span; // Note referential equality checks below (since we want to know exact slices // into the original buffer), not deep (textual) equality checks. { (Utf8Span a, Utf8Span b) = span.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries); Assert.True(a.Bytes == span.Bytes[..1]); // "a" Assert.True(b.Bytes == span.Bytes[2..]); // " , b, c,, d, e" } { (Utf8Span a, Utf8Span x, Utf8Span b, Utf8Span c, Utf8Span d, Utf8Span e) = span.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries); Assert.True(a.Bytes == span.Bytes[0..1]); // "a" Assert.True(x.Bytes == span.Bytes[2..3]); // " " Assert.True(b.Bytes == span.Bytes[4..6]); // " b" Assert.True(c.Bytes == span.Bytes[7..9]); // " c" Assert.True(d.Bytes == span.Bytes[11..13]); // " d" Assert.True(e.Bytes == span.Bytes[14..]); // " e" } { (Utf8Span a, Utf8Span b, Utf8Span c, Utf8Span d, Utf8Span e, Utf8Span f, Utf8Span g, Utf8Span h) = span.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries | Utf8StringSplitOptions.TrimEntries); Assert.True(a.Bytes == span.Bytes[0..1]); // "a" Assert.True(b.Bytes == span.Bytes[5..6]); // "b" Assert.True(c.Bytes == span.Bytes[8..9]); // "c" Assert.True(d.Bytes == span.Bytes[12..13]); // "d" Assert.True(e.Bytes == span.Bytes[15..]); // "e" Assert.True(f.Bytes == default); Assert.True(g.Bytes == default); Assert.True(h.Bytes == default); } }