public static partial class Utf8Extensions
{
public static System.ReadOnlySpan<byte> AsBytes(this System.ReadOnlySpan<System.Char8> text) { throw null; }
- public static System.ReadOnlySpan<byte> AsBytes(this System.Utf8String text) { throw null; }
- public static System.ReadOnlySpan<byte> AsBytes(this System.Utf8String text, int start) { throw null; }
- public static System.ReadOnlySpan<byte> AsBytes(this System.Utf8String text, int start, int length) { throw null; }
- public static System.ReadOnlyMemory<System.Char8> AsMemory(this System.Utf8String text) { throw null; }
- public static System.ReadOnlyMemory<System.Char8> AsMemory(this System.Utf8String text, System.Index startIndex) { throw null; }
- public static System.ReadOnlyMemory<System.Char8> AsMemory(this System.Utf8String text, int start) { throw null; }
- public static System.ReadOnlyMemory<System.Char8> AsMemory(this System.Utf8String text, int start, int length) { throw null; }
- public static System.ReadOnlyMemory<System.Char8> AsMemory(this System.Utf8String text, System.Range range) { throw null; }
- public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String text) { throw null; }
- public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String text, System.Index startIndex) { throw null; }
- public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String text, int start) { throw null; }
- public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String text, int start, int length) { throw null; }
- public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String text, System.Range range) { throw null; }
+ public static System.ReadOnlySpan<byte> AsBytes(this System.Utf8String? text) { throw null; }
+ public static System.ReadOnlySpan<byte> AsBytes(this System.Utf8String? text, int start) { throw null; }
+ public static System.ReadOnlySpan<byte> AsBytes(this System.Utf8String? text, int start, int length) { throw null; }
+ public static System.ReadOnlyMemory<System.Char8> AsMemory(this System.Utf8String? text) { throw null; }
+ public static System.ReadOnlyMemory<System.Char8> AsMemory(this System.Utf8String? text, System.Index startIndex) { throw null; }
+ public static System.ReadOnlyMemory<System.Char8> AsMemory(this System.Utf8String? text, int start) { throw null; }
+ public static System.ReadOnlyMemory<System.Char8> AsMemory(this System.Utf8String? text, int start, int length) { throw null; }
+ public static System.ReadOnlyMemory<System.Char8> AsMemory(this System.Utf8String? text, System.Range range) { throw null; }
+ public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String? text) { throw null; }
+ public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String? text, System.Index startIndex) { throw null; }
+ public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String? text, int start) { throw null; }
+ public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String? text, int start, int length) { throw null; }
+ public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String? text, System.Range range) { throw null; }
public static System.Text.Utf8Span AsSpan(this System.Utf8String? text) { throw null; }
public static System.Text.Utf8Span AsSpan(this System.Utf8String? text, int start) { throw null; }
public static System.Text.Utf8Span AsSpan(this System.Utf8String? text, int start, int length) { throw null; }
public static System.Utf8String ToUtf8String(this System.Text.Rune rune) { throw null; }
}
- public sealed partial class Utf8String : System.IEquatable<System.Utf8String>
+ public sealed partial class Utf8String : System.IComparable<System.Utf8String?>,
+#nullable disable
+ System.IEquatable<System.Utf8String>
+#nullable restore
{
public static readonly System.Utf8String Empty;
[System.CLSCompliantAttribute(false)]
public Utf8String(System.ReadOnlySpan<byte> value) { }
public Utf8String(System.ReadOnlySpan<char> value) { }
public Utf8String(string value) { }
- public System.Char8 this[int index] { get { throw null; } }
+ public ByteEnumerable Bytes { get { throw null; } }
+ public CharEnumerable Chars { get { throw null; } }
public int Length { get { throw null; } }
+ public RuneEnumerable Runes { get { throw null; } }
public static bool AreEquivalent(System.Utf8String? utf8Text, string? utf16Text) { throw null; }
public static bool AreEquivalent(System.Text.Utf8Span utf8Text, System.ReadOnlySpan<char> utf16Text) { throw null; }
public static bool AreEquivalent(System.ReadOnlySpan<byte> utf8Text, System.ReadOnlySpan<char> utf16Text) { throw null; }
+ public int CompareTo(System.Utf8String? other) { throw null; }
+ public int CompareTo(System.Utf8String? other, System.StringComparison comparison) { throw null; }
public bool Contains(char value) { throw null; }
+ public bool Contains(char value, System.StringComparison comparison) { throw null; }
public bool Contains(System.Text.Rune value) { throw null; }
+ public bool Contains(System.Text.Rune value, System.StringComparison comparison) { throw null; }
+ public bool Contains(System.Utf8String value) { throw null; }
+ public bool Contains(System.Utf8String value, System.StringComparison comparison) { throw null; }
+ public static System.Utf8String Create<TState>(int length, TState state, System.Buffers.SpanAction<byte, TState> action) { throw null; }
+ public static System.Utf8String CreateFromRelaxed(System.ReadOnlySpan<byte> buffer) { throw null; }
+ public static System.Utf8String CreateFromRelaxed(System.ReadOnlySpan<char> buffer) { throw null; }
+ public static System.Utf8String CreateRelaxed<TState>(int length, TState state, System.Buffers.SpanAction<byte, TState> action) { throw null; }
public bool EndsWith(char value) { throw null; }
+ public bool EndsWith(char value, System.StringComparison comparison) { throw null; }
public bool EndsWith(System.Text.Rune value) { throw null; }
+ public bool EndsWith(System.Text.Rune value, System.StringComparison comparison) { throw null; }
+ public bool EndsWith(System.Utf8String value) { throw null; }
+ public bool EndsWith(System.Utf8String value, System.StringComparison comparison) { throw null; }
public override bool Equals(object? obj) { throw null; }
- public bool Equals(System.Utf8String value) { throw null; }
- public static bool Equals(System.Utf8String left, System.Utf8String right) { throw null; }
+ public static bool Equals(System.Utf8String? a, System.Utf8String? b, System.StringComparison comparison) { throw null; }
+ public static bool Equals(System.Utf8String? left, System.Utf8String? right) { throw null; }
+ public bool Equals(System.Utf8String? value) { throw null; }
+ public bool Equals(System.Utf8String? value, System.StringComparison comparison) { throw null; }
public override int GetHashCode() { throw null; }
+ public int GetHashCode(System.StringComparison comparison) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public ref readonly byte GetPinnableReference() { throw null; }
- public int IndexOf(char value) { throw null; }
- public int IndexOf(System.Text.Rune value) { throw null; }
- public static bool IsNullOrEmpty(System.Utf8String value) { throw null; }
- public static bool operator ==(System.Utf8String left, System.Utf8String right) { throw null; }
- public static explicit operator System.ReadOnlySpan<byte>(System.Utf8String value) { throw null; }
- public static implicit operator System.ReadOnlySpan<System.Char8>(System.Utf8String value) { throw null; }
- public static implicit operator System.Text.Utf8Span(System.Utf8String value) { throw null; }
- public static bool operator !=(System.Utf8String left, System.Utf8String right) { throw null; }
- [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
- public System.Utf8String Slice(int startIndex, int length) { throw null; }
+ public static implicit operator System.Text.Utf8Span(System.Utf8String? value) { throw null; }
+ public bool IsAscii() { throw null; }
+ public bool IsNormalized(System.Text.NormalizationForm normalizationForm = System.Text.NormalizationForm.FormC) { throw null; }
+ public static bool IsNullOrEmpty([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(false)] System.Utf8String? value) { throw null; }
+ public static bool IsNullOrWhiteSpace([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(false)] System.Utf8String? value) { throw null; }
+ public System.Utf8String Normalize(System.Text.NormalizationForm normalizationForm = System.Text.NormalizationForm.FormC) { throw null; }
+ public static bool operator !=(System.Utf8String? left, System.Utf8String? right) { throw null; }
+ public static bool operator ==(System.Utf8String? left, System.Utf8String? right) { throw null; }
+ public SplitResult Split(char separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; }
+ public SplitResult Split(System.Text.Rune separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; }
+ public SplitResult Split(System.Utf8String separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; }
+ public SplitOnResult SplitOn(char separator) { throw null; }
+ public SplitOnResult SplitOn(char separator, System.StringComparison comparisonType) { throw null; }
+ public SplitOnResult SplitOn(System.Text.Rune separator) { throw null; }
+ public SplitOnResult SplitOn(System.Text.Rune separator, System.StringComparison comparisonType) { throw null; }
+ public SplitOnResult SplitOn(System.Utf8String separator) { throw null; }
+ public SplitOnResult SplitOn(System.Utf8String separator, System.StringComparison comparisonType) { throw null; }
+ public SplitOnResult SplitOnLast(char separator) { throw null; }
+ public SplitOnResult SplitOnLast(char separator, System.StringComparison comparisonType) { throw null; }
+ public SplitOnResult SplitOnLast(System.Text.Rune separator) { throw null; }
+ public SplitOnResult SplitOnLast(System.Text.Rune separator, System.StringComparison comparisonType) { throw null; }
+ public SplitOnResult SplitOnLast(System.Utf8String separator) { throw null; }
+ public SplitOnResult SplitOnLast(System.Utf8String separator, System.StringComparison comparisonType) { throw null; }
public bool StartsWith(char value) { throw null; }
+ public bool StartsWith(char value, System.StringComparison comparison) { throw null; }
public bool StartsWith(System.Text.Rune value) { throw null; }
- public System.Utf8String Substring(int startIndex) { throw null; }
- public System.Utf8String Substring(int startIndex, int length) { throw null; }
+ public bool StartsWith(System.Text.Rune value, System.StringComparison comparison) { throw null; }
+ public bool StartsWith(System.Utf8String value) { throw null; }
+ public bool StartsWith(System.Utf8String value, System.StringComparison comparison) { throw null; }
+ public System.Utf8String this[System.Range range] { get { throw null; } }
public byte[] ToByteArray() { throw null; }
- public byte[] ToByteArray(int startIndex, int length) { throw null; }
+ public char[] ToCharArray() { throw null; }
+ public System.Utf8String ToLower(System.Globalization.CultureInfo culture) { throw null; }
+ public System.Utf8String ToLowerInvariant() { throw null; }
public override string ToString() { throw null; }
+ public System.Utf8String ToUpper(System.Globalization.CultureInfo culture) { throw null; }
+ public System.Utf8String ToUpperInvariant() { throw null; }
+ public System.Utf8String Trim() { throw null; }
+ public System.Utf8String TrimEnd() { throw null; }
+ public System.Utf8String TrimStart() { throw null; }
+ public static bool TryCreateFrom(System.ReadOnlySpan<byte> buffer, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Utf8String? value) { throw null; }
+ public static bool TryCreateFrom(System.ReadOnlySpan<char> buffer, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Utf8String? value) { throw null; }
+ public bool TryFind(char value, out System.Range range) { throw null; }
+ public bool TryFind(char value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+ public bool TryFind(System.Text.Rune value, out System.Range range) { throw null; }
+ public bool TryFind(System.Text.Rune value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+ public bool TryFind(System.Utf8String value, out System.Range range) { throw null; }
+ public bool TryFind(System.Utf8String value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+ public bool TryFindLast(char value, out System.Range range) { throw null; }
+ public bool TryFindLast(char value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+ public bool TryFindLast(System.Text.Rune value, out System.Range range) { throw null; }
+ public bool TryFindLast(System.Text.Rune value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+ public bool TryFindLast(System.Utf8String value, out System.Range range) { throw null; }
+ public bool TryFindLast(System.Utf8String value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+ public static System.Utf8String UnsafeCreateWithoutValidation(System.ReadOnlySpan<byte> utf8Contents) { throw null; }
+ public static System.Utf8String UnsafeCreateWithoutValidation<TState>(int length, TState state, System.Buffers.SpanAction<byte, TState> action) { throw null; }
+ public readonly partial struct ByteEnumerable : System.Collections.Generic.IEnumerable<byte>
+ {
+ private readonly object _dummy;
+ public Enumerator GetEnumerator() { throw null; }
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+ System.Collections.Generic.IEnumerator<byte> System.Collections.Generic.IEnumerable<byte>.GetEnumerator() { throw null; }
+ public struct Enumerator : System.Collections.Generic.IEnumerator<byte>
+ {
+ private readonly object _dummy;
+ private readonly int _dummyPrimitive;
+ public byte Current { get { throw null; } }
+ public bool MoveNext() { throw null; }
+ void System.IDisposable.Dispose() { }
+ object System.Collections.IEnumerator.Current { get { throw null; } }
+ void System.Collections.IEnumerator.Reset() { }
+ }
+ }
+ public readonly partial struct CharEnumerable : System.Collections.Generic.IEnumerable<char>
+ {
+ private readonly object _dummy;
+ public Enumerator GetEnumerator() { throw null; }
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+ System.Collections.Generic.IEnumerator<char> System.Collections.Generic.IEnumerable<char>.GetEnumerator() { throw null; }
+ public struct Enumerator : System.Collections.Generic.IEnumerator<char>
+ {
+ private readonly object _dummy;
+ private readonly int _dummyPrimitive;
+ public char Current { get { throw null; } }
+ public bool MoveNext() { throw null; }
+ void System.IDisposable.Dispose() { }
+ object System.Collections.IEnumerator.Current { get { throw null; } }
+ void System.Collections.IEnumerator.Reset() { }
+ }
+ }
+ public readonly partial struct RuneEnumerable : System.Collections.Generic.IEnumerable<System.Text.Rune>
+ {
+ private readonly object _dummy;
+ public Enumerator GetEnumerator() { throw null; }
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+ System.Collections.Generic.IEnumerator<System.Text.Rune> System.Collections.Generic.IEnumerable<System.Text.Rune>.GetEnumerator() { throw null; }
+ public struct Enumerator : System.Collections.Generic.IEnumerator<System.Text.Rune>
+ {
+ private readonly object _dummy;
+ private readonly int _dummyPrimitive;
+ public System.Text.Rune Current { get { throw null; } }
+ public bool MoveNext() { throw null; }
+ void System.IDisposable.Dispose() { }
+ object System.Collections.IEnumerator.Current { get { throw null; } }
+ void System.Collections.IEnumerator.Reset() { }
+ }
+ }
+ public readonly struct SplitResult : System.Collections.Generic.IEnumerable<Utf8String?>
+ {
+ private readonly object _dummy;
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public void Deconstruct(out System.Utf8String? item1, out System.Utf8String? item2) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public void Deconstruct(out System.Utf8String? item1, out System.Utf8String? item2, out System.Utf8String? item3) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public void Deconstruct(out System.Utf8String? item1, out System.Utf8String? item2, out System.Utf8String? item3, out System.Utf8String? item4) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public void Deconstruct(out System.Utf8String? item1, out System.Utf8String? item2, out System.Utf8String? item3, out System.Utf8String? item4, out System.Utf8String? item5) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public void Deconstruct(out System.Utf8String? item1, out System.Utf8String? item2, out System.Utf8String? item3, out System.Utf8String? item4, out System.Utf8String? item5, out System.Utf8String? item6) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public void Deconstruct(out System.Utf8String? item1, out System.Utf8String? item2, out System.Utf8String? item3, out System.Utf8String? item4, out System.Utf8String? item5, out System.Utf8String? item6, out System.Utf8String? item7) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public void Deconstruct(out System.Utf8String? item1, out System.Utf8String? item2, out System.Utf8String? item3, out System.Utf8String? item4, out System.Utf8String? item5, out System.Utf8String? item6, out System.Utf8String? item7, out System.Utf8String? item8) { throw null; }
+ public Enumerator GetEnumerator() { throw null; }
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+ System.Collections.Generic.IEnumerator<System.Utf8String?> System.Collections.Generic.IEnumerable<System.Utf8String?>.GetEnumerator() { throw null; }
+ public struct Enumerator : System.Collections.Generic.IEnumerator<System.Utf8String?>
+ {
+ private readonly object _dummy;
+ public System.Utf8String? Current { get { throw null; } }
+ public bool MoveNext() { throw null; }
+ void System.IDisposable.Dispose() { }
+ object? System.Collections.IEnumerator.Current { get { throw null; } }
+ void System.Collections.IEnumerator.Reset() { throw null; }
+ }
+ }
+ public readonly struct SplitOnResult
+ {
+ private readonly object _dummy;
+ public System.Utf8String? After { get { throw null; } }
+ public System.Utf8String Before { get { throw null; } }
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public void Deconstruct(out System.Utf8String before, out System.Utf8String? after) { throw null; }
+ }
}
[System.FlagsAttribute]
public enum Utf8StringSplitOptions
public CharEnumerable Chars { get { throw null; } }
public static System.Text.Utf8Span Empty { get { throw null; } }
public bool IsEmpty { get { throw null; } }
+ public int Length { get { throw null; } }
public RuneEnumerable Runes { get { throw null; } }
public int CompareTo(System.Text.Utf8Span other) { throw null; }
public int CompareTo(System.Text.Utf8Span other, System.StringComparison comparison) { throw null; }
public System.Text.Utf8Span Trim() { throw null; }
public System.Text.Utf8Span TrimEnd() { throw null; }
public System.Text.Utf8Span TrimStart() { throw null; }
+ public byte[] ToByteArray() { throw null; }
public char[] ToCharArray() { throw null; }
public int ToChars(System.Span<char> destination) { throw null; }
public System.Utf8String ToLower(System.Globalization.CultureInfo culture) { throw null; }
public override bool CanWrite => false;
- public override long Length => _content.AsBytes().Length;
+ public override long Length => _content.Length;
public override long Position
{
get => _position;
set
{
- if ((ulong)value > (uint)_content.AsBytes().Length)
+ if ((ulong)value > (uint)_content.Length)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
public override int ReadByte()
{
int position = _position;
- if ((uint)position >= (uint)_content.AsBytes().Length)
+ if ((uint)position >= (uint)_content.Length)
{
return -1;
}
offset += _position;
break;
case SeekOrigin.End:
- offset += _content.AsBytes().Length;
+ offset += _content.Length;
break;
default:
throw new ArgumentOutOfRangeException(nameof(origin));
}
- if ((ulong)offset > (uint)_content.AsBytes().Length)
+ if ((ulong)offset > (uint)_content.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
protected override bool TryComputeLength(out long length)
{
- length = _content.AsBytes().Length;
+ length = _content.Length;
return true;
}
}
<Compile Include="System\Utf8SpanTests.Searching.TestData.cs" />
<Compile Include="System\Utf8SpanTests.TestData.cs" />
<Compile Include="System\Utf8StringTests.cs" />
+ <Compile Include="System\Utf8StringTests.Comparison.cs" />
+ <Compile Include="System\Utf8StringTests.Conversion.cs" />
<Compile Include="System\Utf8StringTests.Ctor.cs" />
+ <Compile Include="System\Utf8StringTests.Enumeration.cs" />
+ <Compile Include="System\Utf8StringTests.Manipulation.cs" />
<Compile Include="System\Utf8StringTests.Searching.cs" />
- <Compile Include="System\Utf8StringTests.Substring.cs" />
<Compile Include="Xunit\SpanAssert.cs" />
<Compile Include="System\Utf8TestUtilities.cs" />
</ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
// Finally, a span wrapping data should become a span wrapping that same data.
Utf8String theString = u8("Hello");
- Assert.True(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in theString.GetPinnableReference()), 5) == ((ReadOnlySpan<Char8>)theString).AsBytes());
+ Assert.True(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in theString.GetPinnableReference()), 5) == (theString.AsMemory().Span).AsBytes());
}
[Fact]
Utf8String theString = u8("Hello");
Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in theString.GetPinnableReference()), ref Unsafe.AsRef(in theString.AsSpan().GetPinnableReference())));
- Assert.Equal(5, theString.AsSpan().Bytes.Length);
+ Assert.Equal(5, theString.AsSpan().Length);
}
[Fact]
// Normalize to byte arrays which are too small, expect -1 (failure)
- Assert.Equal(-1, utf8Source.Normalize(new byte[utf8Normalized.GetByteLength() - 1], normalizationForm));
+ Assert.Equal(-1, utf8Source.Normalize(new byte[utf8Normalized.Length - 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 */})
+ foreach (int bufferLength in new int[] { utf8Normalized.Length /* just right */, utf8Normalized.Length + 1 /* with extra room */})
{
byte[] dest = new byte[bufferLength];
- Assert.Equal(utf8Normalized.GetByteLength(), utf8Source.Normalize(dest, normalizationForm));
- Utf8Span normalizedSpan = Utf8Span.UnsafeCreateWithoutValidation(dest[..utf8Normalized.GetByteLength()]);
+ Assert.Equal(utf8Normalized.Length, utf8Source.Normalize(dest, normalizationForm));
+ Utf8Span normalizedSpan = Utf8Span.UnsafeCreateWithoutValidation(dest[..utf8Normalized.Length]);
Assert.True(utf8Normalized.AsSpan() == normalizedSpan); // ordinal equality
Assert.True(normalizedSpan.IsNormalized(normalizationForm));
}
// Next, try the non-allocating APIs with too small a buffer
- if (expectedUtf8.GetByteLength() > 0)
+ if (expectedUtf8.Length > 0)
{
- byte[] bufferTooSmall = new byte[expectedUtf8.GetByteLength() - 1];
+ byte[] bufferTooSmall = new byte[expectedUtf8.Length - 1];
if (culture is null)
{
// Then the non-allocating APIs with a properly sized buffer
- foreach (int bufferSize in new[] { expectedUtf8.GetByteLength(), expectedUtf8.GetByteLength() + 1 })
+ foreach (int bufferSize in new[] { expectedUtf8.Length, expectedUtf8.Length + 1 })
{
- byte[] buffer = new byte[expectedUtf8.GetByteLength()];
+ byte[] buffer = new byte[expectedUtf8.Length];
if (culture is null)
{
- Assert.Equal(expectedUtf8.GetByteLength(), inputSpan.ToLowerInvariant(buffer));
+ Assert.Equal(expectedUtf8.Length, inputSpan.ToLowerInvariant(buffer));
}
else
{
- Assert.Equal(expectedUtf8.GetByteLength(), inputSpan.ToLower(buffer, culture));
+ Assert.Equal(expectedUtf8.Length, inputSpan.ToLower(buffer, culture));
}
Assert.True(expectedUtf8.AsBytes().SequenceEqual(buffer));
// Next, try the non-allocating APIs with too small a buffer
- if (expectedUtf8.GetByteLength() > 0)
+ if (expectedUtf8.Length > 0)
{
- byte[] bufferTooSmall = new byte[expectedUtf8.GetByteLength() - 1];
+ byte[] bufferTooSmall = new byte[expectedUtf8.Length - 1];
if (culture is null)
{
// Then the non-allocating APIs with a properly sized buffer
- foreach (int bufferSize in new[] { expectedUtf8.GetByteLength(), expectedUtf8.GetByteLength() + 1 })
+ foreach (int bufferSize in new[] { expectedUtf8.Length, expectedUtf8.Length + 1 })
{
- byte[] buffer = new byte[expectedUtf8.GetByteLength()];
+ byte[] buffer = new byte[expectedUtf8.Length];
if (culture is null)
{
- Assert.Equal(expectedUtf8.GetByteLength(), inputSpan.ToUpperInvariant(buffer));
+ Assert.Equal(expectedUtf8.Length, inputSpan.ToUpperInvariant(buffer));
}
else
{
- Assert.Equal(expectedUtf8.GetByteLength(), inputSpan.ToUpper(buffer, culture));
+ Assert.Equal(expectedUtf8.Length, inputSpan.ToUpper(buffer, culture));
}
Assert.True(expectedUtf8.AsBytes().SequenceEqual(buffer));
Assert.True(span.IsEmpty);
Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref Unsafe.AsRef(in span.GetPinnableReference())));
Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in str.GetPinnableReference()), ref MemoryMarshal.GetReference(span.Bytes)));
- Assert.Equal(0, span.Bytes.Length);
+ Assert.Equal(0, span.Length);
}
[Fact]
Assert.False(span.IsEmpty);
Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in str.GetPinnableReference()), ref Unsafe.AsRef(in span.GetPinnableReference())));
Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in str.GetPinnableReference()), ref MemoryMarshal.GetReference(span.Bytes)));
- Assert.Equal(6, span.Bytes.Length);
+ Assert.Equal(6, span.Length);
}
[Fact]
Assert.True(span.IsEmpty);
Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref Unsafe.AsRef(in span.GetPinnableReference())));
Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span.Bytes)));
- Assert.Equal(0, span.Bytes.Length);
+ Assert.Equal(0, span.Length);
}
[Fact]
Assert.False(span.IsEmpty);
Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in original.GetPinnableReference()), ref Unsafe.AsRef(in span.GetPinnableReference())));
Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in original.GetPinnableReference()), ref MemoryMarshal.GetReference(span.Bytes)));
- Assert.Equal(5, span.Bytes.Length);
+ Assert.Equal(5, span.Length);
}
[Fact]
Assert.True(span.IsEmpty);
Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref Unsafe.AsRef(in span.GetPinnableReference())));
Assert.True(Unsafe.AreSame(ref MemoryMarshal.GetReference(original), ref MemoryMarshal.GetReference(span.Bytes)));
- Assert.Equal(0, span.Bytes.Length);
+ Assert.Equal(0, span.Length);
}
[Fact]
Assert.True(span.IsEmpty);
Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref Unsafe.AsRef(in span.GetPinnableReference())));
Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span.Bytes)));
- Assert.Equal(0, span.Bytes.Length);
+ Assert.Equal(0, span.Length);
}
}
}
}
[Fact]
- public static void RunesProperty_FromEmpty()
- {
- Assert.False(Utf8Span.Empty.Runes.GetEnumerator().MoveNext());
- }
-
- [Fact]
public static void RunesProperty_FromData()
{
using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("\U00000012\U00000123\U00001234\U00101234\U00000012\U00000123\U00001234\U00101234");
Assert.Equal(new Rune(0x101234), runesEnumerator.Current);
Assert.False(runesEnumerator.MoveNext());
}
+
+ [Fact]
+ public static void RunesProperty_FromEmpty()
+ {
+ Assert.False(Utf8Span.Empty.Runes.GetEnumerator().MoveNext());
+ }
}
}
{
using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(source.AsBytes());
Utf8Span span = boundedSpan.Span;
- int totalSpanLengthInBytes = span.Bytes.Length;
+ int totalSpanLengthInBytes = span.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
namespace System.Text.Tests
{
+ /*
+ * Please keep these tests in sync with those in Utf8StringTests.Searching.cs.
+ */
+
public unsafe partial class Utf8SpanTests
{
[Theory]
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedForwardMatch.Value, actualForwardMatch);
}
// Also check Contains / StartsWith / SplitOn
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedBackwardMatch.Value, actualBackwardMatch);
}
// Also check EndsWith / SplitOnLast
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedForwardMatch.Value, actualForwardMatch);
}
// Also check Contains / StartsWith / SplitOn
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedBackwardMatch.Value, actualBackwardMatch);
}
// Also check EndsWith / SplitOnLast
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedForwardMatch.Value, actualForwardMatch);
}
// Also check Contains / StartsWith / SplitOn
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedBackwardMatch.Value, actualBackwardMatch);
}
// Also check EndsWith / SplitOnLast
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedForwardMatch.Value, actualForwardMatch);
}
// Also check Contains / StartsWith / SplitOn
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedBackwardMatch.Value, actualBackwardMatch);
}
// Also check EndsWith / SplitOnLast
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedForwardMatch.Value, actualForwardMatch);
}
// Also check Contains / StartsWith / SplitOn
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedBackwardMatch.Value, actualBackwardMatch);
}
// Also check EndsWith / SplitOnLast
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedForwardMatch.Value, actualForwardMatch);
}
// Also check Contains / StartsWith / SplitOn
if (wasFound)
{
- AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ AssertRangesEqual(searchSpan.Length, expectedBackwardMatch.Value, actualBackwardMatch);
}
// Also check EndsWith / SplitOnLast
}
else if (searchTerm is ustring ustr)
{
- var asString = ustr.ToString();
- if (asString.Length == 1)
+ var enumerator = ustr.Chars.GetEnumerator();
+ if (enumerator.MoveNext())
{
- parsed = asString[0];
- return true;
+ parsed = enumerator.Current;
+ if (!enumerator.MoveNext())
+ {
+ return true;
+ }
}
}
else if (searchTerm is ustring ustr)
{
if (Rune.DecodeFromUtf8(ustr.AsBytes(), out parsed, out int bytesConsumed) == OperationStatus.Done
- && bytesConsumed == ustr.GetByteLength())
+ && bytesConsumed == ustr.Length)
{
return true;
}
}
else if (searchTerm is string str)
{
- ustring asUtf8 = new ustring(str);
- if (asUtf8.ToString() == str) // make sure this round-trips properly
+ if (ustring.TryCreateFrom(str, out parsed))
{
- parsed = asUtf8;
return true;
}
}
Assert.True(span.IsEmpty);
Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref Unsafe.AsRef(in span.GetPinnableReference())));
Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span.Bytes)));
- Assert.Equal(0, span.Bytes.Length);
+ Assert.Equal(0, span.Length);
}
[Fact]
// Now ensure the slice was correctly produced by comparing the references directly.
- (int offset, int length) = range.GetOffsetAndLength(originalSpan.Bytes.Length);
+ (int offset, int length) = range.GetOffsetAndLength(originalSpan.Length);
Assert.True(Unsafe.AreSame(ref startOfSlicedSpan, ref Unsafe.Add(ref startOfOriginalSpan, offset)));
- Assert.Equal(length, slicedSpan.Bytes.Length);
+ Assert.Equal(length, slicedSpan.Length);
}
[Theory]
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Tests
+{
+ public unsafe partial class Utf8StringTests
+ {
+ [Fact]
+ public static void AreEquivalent_Utf8StringAndString_NullHandling()
+ {
+ Assert.True(Utf8String.AreEquivalent((Utf8String)null, (string)null));
+ Assert.False(Utf8String.AreEquivalent(Utf8String.Empty, (string)null));
+ Assert.False(Utf8String.AreEquivalent((Utf8String)null, string.Empty));
+ Assert.True(Utf8String.AreEquivalent(Utf8String.Empty, string.Empty));
+ }
+
+ [Theory]
+ [InlineData("Hello", "hello", false)]
+ [InlineData("hello", "hello", true)]
+ [InlineData("hello", "helloo", false)]
+ [InlineData("hellooo", "helloo", false)]
+ [InlineData("encyclopaedia", "encyclopaedia", true)]
+ [InlineData("encyclopaedia", "encyclop\u00e6dia", false)]
+ [InlineData("encyclop\u00e6dia", "encyclop\u00e6dia", true)]
+ public static void AreEquivalent_Tests(string utf8Input, string utf16Input, bool expected)
+ {
+ Utf8String asUtf8 = u8(utf8Input);
+
+ // Call all three overloads
+
+ Assert.Equal(expected, Utf8String.AreEquivalent(asUtf8, utf16Input));
+ Assert.Equal(expected, Utf8String.AreEquivalent(asUtf8.AsSpan(), utf16Input.AsSpan()));
+ Assert.Equal(expected, Utf8String.AreEquivalent(asUtf8.AsBytes(), utf16Input.AsSpan()));
+ }
+
+ [Theory]
+ [InlineData(new byte[] { 0xED, 0xA0, 0x80 }, new char[] { '\uD800' })] // don't support "wobbly" UTF-8
+ [InlineData(new byte[] { 0xED, 0xA0, 0x80, 0xED, 0xBF, 0xBF }, new char[] { '\uD800', '\uDFFF' })] // don't support "wobbly" UTF-8
+ [InlineData(new byte[] { 0xED }, new char[] { '\uD800' })] // don't support partials
+ public static void AreEquivalent_IllFormedData_AlwaysReturnsFalse(byte[] asUtf8, char[] asUtf16)
+ {
+ Assert.False(Utf8String.AreEquivalent(asUtf8, asUtf16));
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Text.Tests;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Tests
+{
+ public unsafe partial class Utf8StringTests
+ {
+ public static IEnumerable<object[]> NormalizationData() => Utf8SpanTests.NormalizationData();
+
+ [Theory]
+ [MemberData(nameof(NormalizationData))]
+ public static void Normalize(string utf16Source, string utf16Expected, NormalizationForm normalizationForm)
+ {
+ Utf8String utf8Source = u8(utf16Source);
+
+ // Quick IsNormalized tests
+
+ Assert.Equal(utf16Source == utf16Expected, utf8Source.IsNormalized(normalizationForm));
+
+ // Normalize and return new Utf8String instances
+
+ Utf8String utf8Normalized = utf8Source.Normalize(normalizationForm);
+ Assert.True(Utf8String.AreEquivalent(utf8Normalized, utf16Expected));
+ }
+
+ public static IEnumerable<object[]> CaseConversionData() => Utf8SpanTests.CaseConversionData();
+
+ [Theory]
+ [MemberData(nameof(CaseConversionData))]
+ public static void ToLower(string testData)
+ {
+ static void RunTest(string testData, string expected, CultureInfo culture)
+ {
+ if (culture is null)
+ {
+ Assert.Equal(u8(expected), u8(testData).ToLowerInvariant());
+ }
+ else
+ {
+ Assert.Equal(u8(expected), u8(testData).ToLower(culture));
+ }
+ }
+
+ if (testData is null)
+ {
+ return; // no point in testing null "this" objects; we'll just null-ref
+ }
+
+ RunTest(testData, testData.ToLowerInvariant(), null);
+ RunTest(testData, testData.ToLower(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
+ RunTest(testData, testData.ToLower(CultureInfo.GetCultureInfo("en-US")), CultureInfo.GetCultureInfo("en-US"));
+ RunTest(testData, testData.ToLower(CultureInfo.GetCultureInfo("tr-TR")), CultureInfo.GetCultureInfo("tr-TR"));
+ }
+
+ [Theory]
+ [MemberData(nameof(CaseConversionData))]
+ public static void ToUpper(string testData)
+ {
+ static void RunTest(string testData, string expected, CultureInfo culture)
+ {
+ if (culture is null)
+ {
+ Assert.Equal(u8(expected), u8(testData).ToUpperInvariant());
+ }
+ else
+ {
+ Assert.Equal(u8(expected), u8(testData).ToUpper(culture));
+ }
+ }
+
+ if (testData is null)
+ {
+ return; // no point in testing null "this" objects; we'll just null-ref
+ }
+
+ RunTest(testData, testData.ToUpperInvariant(), null);
+ RunTest(testData, testData.ToUpper(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
+ RunTest(testData, testData.ToUpper(CultureInfo.GetCultureInfo("en-US")), CultureInfo.GetCultureInfo("en-US"));
+ RunTest(testData, testData.ToUpper(CultureInfo.GetCultureInfo("tr-TR")), CultureInfo.GetCultureInfo("tr-TR"));
+ }
+ }
+}
}
[Fact]
- public static void Ctor_ByteArrayOffset_InvalidData_FixesUpData()
+ public static void Ctor_ByteArrayOffset_InvalidData_Throws()
{
byte[] inputData = new byte[] { (byte)'x', (byte)'H', (byte)'e', (byte)0xFF, (byte)'l', (byte)'o', (byte)'x' };
- Utf8String expected = u8("He\uFFFDlo");
- var actual = new Utf8String(inputData, 1, 5);
- Assert.Equal(expected, actual);
+ Assert.Throws<ArgumentException>(() => new Utf8String(inputData, 0, inputData.Length));
+ }
+
+ [Fact]
+ public static void Ctor_ByteArrayOffset_NullValue_Throws()
+ {
+ var exception = Assert.Throws<ArgumentNullException>(() => new Utf8String((byte[])null, 0, 0));
+ Assert.Equal("value", exception.ParamName);
}
[Fact]
- public static void Ctor_BytePointer_NullOrEmpty_ReturnsEmpty()
+ public static void Ctor_ByteArrayOffset_InvalidStartIndexOrLength_Throws()
+ {
+ byte[] inputData = new byte[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
+
+ Assert.Throws<ArgumentOutOfRangeException>(() => new Utf8String(inputData, 1, 5));
+ }
+
+ [Fact]
+ public static void Ctor_BytePointer_Null_Throws()
+ {
+ var exception = Assert.Throws<ArgumentNullException>(() => new Utf8String((byte*)null));
+ Assert.Equal("value", exception.ParamName);
+ }
+
+ [Fact]
+ public static void Ctor_BytePointer_Empty_ReturnsEmpty()
{
byte[] inputData = new byte[] { 0 }; // standalone null byte
using (BoundedMemory<byte> boundedMemory = BoundedMemory.AllocateFromExistingData(inputData))
{
- Assert.Same(Utf8String.Empty, new Utf8String((byte*)null));
Assert.Same(Utf8String.Empty, new Utf8String((byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(boundedMemory.Span))));
}
}
}
[Fact]
- public static void Ctor_BytePointer_InvalidData_FixesUpData()
+ public static void Ctor_BytePointer_InvalidData_Throws()
{
byte[] inputData = new byte[] { (byte)'H', (byte)'e', (byte)0xFF, (byte)'l', (byte)'o', (byte)'\0' };
using (BoundedMemory<byte> boundedMemory = BoundedMemory.AllocateFromExistingData(inputData))
{
- Assert.Equal(u8("He\uFFFDlo"), new Utf8String((byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(boundedMemory.Span))));
+ Assert.Throws<ArgumentException>(() => new Utf8String((byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(boundedMemory.Span))));
}
}
}
[Fact]
- public static void Ctor_ByteSpan_InvalidData_FixesUpData()
+ public static void Ctor_ByteSpan_InvalidData_Throws()
{
byte[] inputData = new byte[] { (byte)'H', (byte)'e', (byte)0xFF, (byte)'l', (byte)'o' };
- Utf8String expected = u8("He\uFFFDlo");
- var actual = new Utf8String(inputData.AsSpan());
- Assert.Equal(expected, actual);
+ Assert.Throws<ArgumentException>(() => new Utf8String(inputData.AsSpan()));
}
[Fact]
public static void Ctor_CharArrayOffset_Empty_ReturnsEmpty()
{
- char[] inputData = "Hello".ToCharArray();
+ char[] inputData = "H\U00012345ello".ToCharArray(); // ok to have an empty slice in the middle of a multi-byte subsequence
Assert.Same(Utf8String.Empty, new Utf8String(inputData, 3, 0));
}
[Fact]
- public static void Ctor_CharArrayOffset_ValidData_ReturnsOriginalContents()
+ public static void Ctor_CharArrayOffset_ValidData_ReturnsAsUtf8()
{
- char[] inputData = "xHellox".ToCharArray();
- Utf8String expected = u8("Hello");
+ char[] inputData = "H\U00012345\u07ffello".ToCharArray();
+ Utf8String expected = u8("\u07ffello");
- var actual = new Utf8String(inputData, 1, 5);
+ var actual = new Utf8String(inputData, 3, 5);
Assert.Equal(expected, actual);
}
[Fact]
- public static void Ctor_CharArrayOffset_InvalidData_FixesUpData()
+ public static void Ctor_CharArrayOffset_InvalidData_Throws()
{
- char[] inputData = new char[] { 'x', 'H', 'e', '\uD800', 'l', 'o', 'x' };
- Utf8String expected = u8("He\uFFFDlo");
+ char[] inputData = "H\ud800ello".ToCharArray();
- var actual = new Utf8String(inputData, 1, 5);
- Assert.Equal(expected, actual);
+ Assert.Throws<ArgumentException>(() => new Utf8String(inputData, 0, inputData.Length));
+ }
+
+ [Fact]
+ public static void Ctor_CharArrayOffset_NullValue_Throws()
+ {
+ var exception = Assert.Throws<ArgumentNullException>(() => new Utf8String((char[])null, 0, 0));
+ Assert.Equal("value", exception.ParamName);
+ }
+
+ [Fact]
+ public static void Ctor_CharArrayOffset_InvalidStartIndexOrLength_Throws()
+ {
+ char[] inputData = "Hello".ToCharArray();
+
+ Assert.Throws<ArgumentOutOfRangeException>(() => new Utf8String(inputData, 1, 5));
+ }
+
+ [Fact]
+ public static void Ctor_CharPointer_Null_Throws()
+ {
+ var exception = Assert.Throws<ArgumentNullException>(() => new Utf8String((char*)null));
+ Assert.Equal("value", exception.ParamName);
}
[Fact]
- public static void Ctor_CharPointer_NullOrEmpty_ReturnsEmpty()
+ public static void Ctor_CharPointer_Empty_ReturnsEmpty()
{
char[] inputData = new char[] { '\0' }; // standalone null char
using (BoundedMemory<char> boundedMemory = BoundedMemory.AllocateFromExistingData(inputData))
{
- Assert.Same(Utf8String.Empty, new Utf8String((char*)null));
Assert.Same(Utf8String.Empty, new Utf8String((char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(boundedMemory.Span))));
}
}
[Fact]
public static void Ctor_CharPointer_ValidData_ReturnsOriginalContents()
{
- char[] inputData = new char[] { 'H', 'e', 'l', 'l', 'o', '\0' };
+ char[] inputData = "Hello\0".ToCharArray(); // need to manually null-terminate
using (BoundedMemory<char> boundedMemory = BoundedMemory.AllocateFromExistingData(inputData))
{
}
[Fact]
- public static void Ctor_CharPointer_InvalidData_FixesUpData()
+ public static void Ctor_CharPointer_InvalidData_Throws()
{
- char[] inputData = new char[] { 'H', 'e', '\uD800', 'l', 'o', '\0' }; // standalone surrogate
+ char[] inputData = "He\ud800llo\0".ToCharArray(); // need to manually null-terminate
using (BoundedMemory<char> boundedMemory = BoundedMemory.AllocateFromExistingData(inputData))
{
- Assert.Equal(u8("He\uFFFDlo"), new Utf8String((char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(boundedMemory.Span))));
+ Assert.Throws<ArgumentException>(() => new Utf8String((char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(boundedMemory.Span))));
}
}
}
[Fact]
- public static void Ctor_CharSpan_InvalidData_FixesUpData()
+ public static void Ctor_CharSpan_InvalidData_Throws()
{
- char[] inputData = new char[] { 'H', 'e', '\uD800', 'l', 'o' };
- Utf8String expected = u8("He\uFFFDlo");
+ char[] inputData = "He\ud800llo".ToCharArray();
- var actual = new Utf8String(inputData.AsSpan());
- Assert.Equal(expected, actual);
+ Assert.Throws<ArgumentException>(() => new Utf8String(inputData.AsSpan()));
}
[Fact]
- public static void Ctor_String_NullOrEmpty_ReturnsEmpty()
+ public static void Ctor_String_Null_Throws()
+ {
+ var exception = Assert.Throws<ArgumentNullException>(() => new Utf8String((string)null));
+ Assert.Equal("value", exception.ParamName);
+ }
+
+ [Fact]
+ public static void Ctor_String_Empty_ReturnsEmpty()
{
- Assert.Same(Utf8String.Empty, new Utf8String((string)null));
Assert.Same(Utf8String.Empty, new Utf8String(string.Empty));
}
}
[Fact]
- public static void Ctor_String_InvalidData_FixesUpData()
+ public static void Ctor_String_InvalidData_Throws()
+ {
+ Assert.Throws<ArgumentException>(() => new Utf8String("He\uD800lo"));
+ }
+
+ [Fact]
+ public static void Ctor_NonValidating_FromByteSpan()
+ {
+ byte[] inputData = new byte[] { (byte)'x', (byte)'y', (byte)'z' };
+ Utf8String actual = Utf8String.UnsafeCreateWithoutValidation(inputData);
+
+ Assert.Equal(u8("xyz"), actual);
+ }
+
+ [Fact]
+ public static void Ctor_NonValidating_FromDelegate()
+ {
+ object expectedState = new object();
+ SpanAction<byte, object> spanAction = (span, actualState) =>
+ {
+ Assert.Same(expectedState, actualState);
+ Assert.NotEqual(0, span.Length); // shouldn't have been called for a zero-length span
+
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.Equal(0, span[i]); // should've been zero-inited
+ span[i] = (byte)('a' + (i % 26)); // writes "abc...xyzabc...xyz..."
+ }
+ };
+
+ ArgumentException exception = Assert.Throws<ArgumentOutOfRangeException>(() => Utf8String.UnsafeCreateWithoutValidation(-1, expectedState, spanAction));
+ Assert.Equal("length", exception.ParamName);
+
+ exception = Assert.Throws<ArgumentNullException>(() => Utf8String.UnsafeCreateWithoutValidation(10, expectedState, action: null));
+ Assert.Equal("action", exception.ParamName);
+
+ Assert.Same(Utf8String.Empty, Utf8String.UnsafeCreateWithoutValidation(0, expectedState, spanAction));
+
+ Assert.Equal(u8("abcde"), Utf8String.UnsafeCreateWithoutValidation(5, expectedState, spanAction));
+ }
+
+ [Fact]
+ public static void Ctor_Validating_FromDelegate()
+ {
+ object expectedState = new object();
+ SpanAction<byte, object> spanAction = (span, actualState) =>
+ {
+ Assert.Same(expectedState, actualState);
+ Assert.NotEqual(0, span.Length); // shouldn't have been called for a zero-length span
+
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.Equal(0, span[i]); // should've been zero-inited
+ span[i] = (byte)('a' + (i % 26)); // writes "abc...xyzabc...xyz..."
+ }
+ };
+
+ ArgumentException exception = Assert.Throws<ArgumentOutOfRangeException>(() => Utf8String.Create(-1, expectedState, spanAction));
+ Assert.Equal("length", exception.ParamName);
+
+ exception = Assert.Throws<ArgumentNullException>(() => Utf8String.Create(10, expectedState, action: null));
+ Assert.Equal("action", exception.ParamName);
+
+ Assert.Same(Utf8String.Empty, Utf8String.Create(0, expectedState, spanAction));
+
+ Assert.Equal(u8("abcde"), Utf8String.Create(5, expectedState, spanAction));
+ }
+
+ [Fact]
+ public static void Ctor_Validating_FromDelegate_ThrowsIfDelegateProvidesInvalidData()
+ {
+ SpanAction<byte, object> spanAction = (span, actualState) =>
+ {
+ span[0] = 0xFF; // never a valid UTF-8 byte
+ };
+
+ Assert.Throws<ArgumentException>(() => Utf8String.Create(10, new object(), spanAction));
+ }
+
+ [Fact]
+ public static void Ctor_CreateFromRelaxed_Utf16()
+ {
+ Assert.Same(Utf8String.Empty, Utf8String.CreateFromRelaxed(ReadOnlySpan<char>.Empty));
+ Assert.Equal(u8("xy\uFFFDz"), Utf8String.CreateFromRelaxed("xy\ud800z"));
+ }
+
+ [Fact]
+ public static void Ctor_CreateFromRelaxed_Utf8()
+ {
+ Assert.Same(Utf8String.Empty, Utf8String.CreateFromRelaxed(ReadOnlySpan<byte>.Empty));
+ Assert.Equal(u8("xy\uFFFDz"), Utf8String.CreateFromRelaxed(new byte[] { (byte)'x', (byte)'y', 0xF4, 0x80, 0x80, (byte)'z' }));
+ }
+
+ [Fact]
+ public static void Ctor_CreateRelaxed_FromDelegate()
+ {
+ object expectedState = new object();
+ SpanAction<byte, object> spanAction = (span, actualState) =>
+ {
+ Assert.Same(expectedState, actualState);
+ Assert.NotEqual(0, span.Length); // shouldn't have been called for a zero-length span
+
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.Equal(0, span[i]); // should've been zero-inited
+ span[i] = 0xFF; // never a valid UTF-8 byte
+ }
+ };
+
+ ArgumentException exception = Assert.Throws<ArgumentOutOfRangeException>(() => Utf8String.CreateRelaxed(-1, expectedState, spanAction));
+ Assert.Equal("length", exception.ParamName);
+
+ exception = Assert.Throws<ArgumentNullException>(() => Utf8String.CreateRelaxed(10, expectedState, action: null));
+ Assert.Equal("action", exception.ParamName);
+
+ Assert.Same(Utf8String.Empty, Utf8String.CreateRelaxed(0, expectedState, spanAction));
+
+ Assert.Equal(u8("\uFFFD\uFFFD"), Utf8String.CreateRelaxed(2, expectedState, spanAction));
+ }
+
+ [Fact]
+ public static void Ctor_TryCreateFrom_Utf8()
{
- Assert.Equal(u8("He\uFFFDlo"), new Utf8String("He\uD800lo"));
+ Utf8String value;
+
+ // Empty string
+
+ Assert.True(Utf8String.TryCreateFrom(ReadOnlySpan<byte>.Empty, out value));
+ Assert.Same(Utf8String.Empty, value);
+
+ // Well-formed ASCII contents
+
+ Assert.True(Utf8String.TryCreateFrom(new byte[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' }, out value));
+ Assert.Equal(u8("Hello"), value);
+
+ // Well-formed non-ASCII contents
+
+ Assert.True(Utf8String.TryCreateFrom(new byte[] { 0xF0, 0x9F, 0x91, 0xBD }, out value)); // U+1F47D EXTRATERRESTRIAL ALIEN
+ Assert.Equal(u8("\U0001F47D"), value);
+
+ // Ill-formed contents
+
+ Assert.False(Utf8String.TryCreateFrom(new byte[] { 0xF0, 0x9F, 0x91, (byte)'x' }, out value));
+ Assert.Null(value);
+ }
+
+ [Fact]
+ public static void Ctor_TryCreateFrom_Utf16()
+ {
+ Utf8String value;
+
+ // Empty string
+
+ Assert.True(Utf8String.TryCreateFrom(ReadOnlySpan<char>.Empty, out value));
+ Assert.Same(Utf8String.Empty, value);
+
+ // Well-formed ASCII contents
+
+ Assert.True(Utf8String.TryCreateFrom("Hello", out value));
+ Assert.Equal(u8("Hello"), value);
+
+ // Well-formed non-ASCII contents
+
+ Assert.True(Utf8String.TryCreateFrom("\U0001F47D", out value)); // U+1F47D EXTRATERRESTRIAL ALIEN
+ Assert.Equal(u8("\U0001F47D"), value);
+
+ // Ill-formed contents
+
+ Assert.False(Utf8String.TryCreateFrom("\uD800x", out value));
+ Assert.Null(value);
}
}
}
--- /dev/null
+// 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.Text;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Tests
+{
+ public unsafe partial class Utf8StringTests
+ {
+ [Fact]
+ public static void BytesProperty_FromData()
+ {
+ Utf8String ustr = u8("\U00000012\U00000123\U00001234\U00101234\U00000012\U00000123\U00001234\U00101234");
+
+ Assert.Equal(new byte[]
+ {
+ 0x12,
+ 0xC4, 0xA3,
+ 0xE1, 0x88, 0xB4,
+ 0xF4, 0x81, 0x88, 0xB4,
+ 0x12,
+ 0xC4, 0xA3,
+ 0xE1, 0x88, 0xB4,
+ 0xF4, 0x81, 0x88, 0xB4,
+ }, ustr.Bytes);
+ }
+
+ [Fact]
+ public static void BytesProperty_FromEmpty()
+ {
+ Assert.False(Utf8String.Empty.Bytes.GetEnumerator().MoveNext());
+ }
+
+ [Fact]
+ public static void CharsProperty_FromData()
+ {
+ Utf8String ustr = u8("\U00000012\U00000123\U00001234\U00101234\U00000012\U00000123\U00001234\U00101234");
+
+ Assert.Equal(new char[]
+ {
+ '\u0012',
+ '\u0123',
+ '\u1234',
+ '\uDBC4', '\uDE34',
+ '\u0012',
+ '\u0123',
+ '\u1234',
+ '\uDBC4', '\uDE34',
+ }, ustr.Chars);
+ }
+
+ [Fact]
+ public static void CharsProperty_FromEmpty()
+ {
+ Assert.False(Utf8String.Empty.Chars.GetEnumerator().MoveNext());
+ }
+
+ [Fact]
+ public static void RunesProperty_FromData()
+ {
+ Utf8String ustr = u8("\U00000012\U00000123\U00001234\U00101234\U00000012\U00000123\U00001234\U00101234");
+
+ Assert.Equal(new Rune[]
+ {
+ new Rune(0x0012),
+ new Rune(0x0123),
+ new Rune(0x1234),
+ new Rune(0x101234),
+ new Rune(0x0012),
+ new Rune(0x0123),
+ new Rune(0x1234),
+ new Rune(0x101234),
+ }, ustr.Runes);
+ }
+
+ [Fact]
+ public static void RunesProperty_FromEmpty()
+ {
+ Assert.False(Utf8String.Empty.Runes.GetEnumerator().MoveNext());
+ }
+ }
+}
--- /dev/null
+// 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.Linq;
+using System.Text;
+using System.Text.Tests;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Tests
+{
+ public unsafe partial class Utf8StringTests
+ {
+ private delegate Utf8String.SplitResult Utf8StringSplitDelegate(Utf8String ustr, Utf8StringSplitOptions splitOptions);
+
+ [Fact]
+ public static void Split_Utf8StringSeparator_WithNullOrEmptySeparator_Throws()
+ {
+ var ex = Assert.Throws<ArgumentException>(() => { u8("Hello").Split((Utf8String)null); });
+ Assert.Equal("separator", ex.ParamName);
+
+ // Shouldn't be able to split on an empty Utf8String.
+ // Such an enumerator would iterate forever, so we forbid it.
+
+ ex = Assert.Throws<ArgumentException>(() => { u8("Hello").Split(Utf8String.Empty); });
+ Assert.Equal("separator", ex.ParamName);
+ }
+
+ [Fact]
+ public static void Split_InvalidChar_Throws()
+ {
+ // Shouldn't be able to split on a standalone surrogate char
+ // Other search methods (TryFind) return false when given a standalone surrogate char as input,
+ // but the Split methods returns a complex data structure instead of a simple bool. So to keep
+ // the logic of that data structure relatively simple we'll forbid the bad char at the call site.
+
+ var ex = Assert.Throws<ArgumentOutOfRangeException>(() => { u8("Hello").Split('\ud800'); });
+ Assert.Equal("separator", ex.ParamName);
+ }
+
+ public static IEnumerable<object[]> SplitData_CharSeparator => Utf8SpanTests.SplitData_CharSeparator();
+
+ [Theory]
+ [MemberData(nameof(SplitData_CharSeparator))]
+ public static void Split_Char(Utf8String source, char separator, Range[] expectedRanges)
+ {
+ SplitTest_Common(source ?? Utf8String.Empty, (ustr, splitOptions) => ustr.Split(separator, splitOptions), expectedRanges);
+ }
+
+ [Fact]
+ public static void Split_Deconstruct()
+ {
+ Utf8String ustr = u8("a,b,c,d,e");
+
+ {
+ (Utf8String a, Utf8String b) = ustr.Split('x'); // not found
+ Assert.Same(ustr, a); // Expected referential equality of input
+ Assert.Null(b);
+ }
+
+ {
+ (Utf8String a, Utf8String b) = ustr.Split(',');
+ Assert.Equal(u8("a"), a);
+ Assert.Equal(u8("b,c,d,e"), b);
+ }
+
+ {
+ (Utf8String a, Utf8String b, Utf8String c, Utf8String d, Utf8String e) = ustr.Split(',');
+ Assert.Equal(u8("a"), a);
+ Assert.Equal(u8("b"), b);
+ Assert.Equal(u8("c"), c);
+ Assert.Equal(u8("d"), d);
+ Assert.Equal(u8("e"), e);
+ }
+
+ {
+ (Utf8String a, Utf8String b, Utf8String c, Utf8String d, Utf8String e, Utf8String f, Utf8String g, Utf8String h) = ustr.Split(',');
+ Assert.Equal(u8("a"), a);
+ Assert.Equal(u8("b"), b);
+ Assert.Equal(u8("c"), c);
+ Assert.Equal(u8("d"), d);
+ Assert.Equal(u8("e"), e);
+ Assert.Null(f);
+ Assert.Null(g);
+ Assert.Null(h);
+ }
+ }
+
+ [Fact]
+ public static void Split_Deconstruct_WithOptions()
+ {
+ Utf8String ustr = u8("a, , b, c,, d, e");
+
+ // Note referential equality checks below (since we want to know exact slices
+ // into the original buffer), not deep (textual) equality checks.
+
+ {
+ (Utf8String a, Utf8String b) = ustr.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries);
+ Assert.Equal(u8("a"), a);
+ Assert.Equal(u8(" , b, c,, d, e"), b);
+ }
+
+ {
+ (Utf8String a, Utf8String x, Utf8String b, Utf8String c, Utf8String d, Utf8String e) = ustr.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries);
+ Assert.Equal(u8("a"), a); // "a"
+ Assert.Equal(u8(" "), x); // " "
+ Assert.Equal(u8(" b"), b); // " b"
+ Assert.Equal(u8(" c"), c); // " c"
+ Assert.Equal(u8(" d"), d); // " d"
+ Assert.Equal(u8(" e"), e); // " e"
+ }
+
+ {
+ (Utf8String a, Utf8String b, Utf8String c, Utf8String d, Utf8String e, Utf8String f, Utf8String g, Utf8String h) = ustr.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries | Utf8StringSplitOptions.TrimEntries);
+ Assert.Equal(u8("a"), a);
+ Assert.Equal(u8("b"), b);
+ Assert.Equal(u8("c"), c);
+ Assert.Equal(u8("d"), d);
+ Assert.Equal(u8("e"), e);
+ Assert.Null(f);
+ Assert.Null(g);
+ Assert.Null(h);
+ }
+ }
+
+ public static IEnumerable<object[]> SplitData_RuneSeparator() => Utf8SpanTests.SplitData_RuneSeparator();
+
+ [Theory]
+ [MemberData(nameof(SplitData_RuneSeparator))]
+ public static void Split_Rune(Utf8String source, Rune separator, Range[] expectedRanges)
+ {
+ SplitTest_Common(source ?? Utf8String.Empty, (ustr, splitOptions) => ustr.Split(separator, splitOptions), expectedRanges);
+ }
+
+ public static IEnumerable<object[]> SplitData_Utf8StringSeparator() => Utf8SpanTests.SplitData_Utf8SpanSeparator();
+
+ [Theory]
+ [MemberData(nameof(SplitData_Utf8StringSeparator))]
+ public static void Split_Utf8String(Utf8String source, Utf8String separator, Range[] expectedRanges)
+ {
+ SplitTest_Common(source ?? Utf8String.Empty, (ustr, splitOptions) => ustr.Split(separator, splitOptions), expectedRanges);
+ }
+
+ private static void SplitTest_Common(Utf8String source, Utf8StringSplitDelegate splitAction, Range[] expectedRanges)
+ {
+ // First, run the split with default options and make sure the results are equivalent
+
+ Assert.Equal(
+ expected: expectedRanges.Select(range => source[range]),
+ actual: splitAction(source, Utf8StringSplitOptions.None));
+
+ // Next, run the split with empty entries removed
+
+ Assert.Equal(
+ expected: expectedRanges.Select(range => source[range]).Where(ustr => ustr.Length != 0),
+ actual: splitAction(source, Utf8StringSplitOptions.RemoveEmptyEntries));
+
+ // Next, run the split with results trimmed (but allowing empty results)
+
+ Assert.Equal(
+ expected: expectedRanges.Select(range => source[range].Trim()),
+ actual: splitAction(source, Utf8StringSplitOptions.TrimEntries));
+
+ // Finally, run the split both trimmed and with empty entries removed
+
+ Assert.Equal(
+ expected: expectedRanges.Select(range => source[range].Trim()).Where(ustr => ustr.Length != 0),
+ actual: splitAction(source, Utf8StringSplitOptions.TrimEntries | Utf8StringSplitOptions.RemoveEmptyEntries));
+ }
+
+ public static IEnumerable<object[]> Trim_TestData() => Utf8SpanTests.Trim_TestData();
+
+ [Theory]
+ [MemberData(nameof(Trim_TestData))]
+ public static void Trim(string input)
+ {
+ if (input is null)
+ {
+ return; // don't want to null ref
+ }
+
+ Utf8String utf8Input = u8(input);
+
+ void RunTest(Func<Utf8String, Utf8String> utf8TrimAction, Func<string, string> utf16TrimAction)
+ {
+ Utf8String utf8Trimmed = utf8TrimAction(utf8Input);
+ string utf16Trimmed = utf16TrimAction(input);
+
+ if (utf16Trimmed.Length == input.Length)
+ {
+ Assert.Same(utf8Input, utf8Trimmed); // Trimming should no-op, return original input
+ }
+ else if (utf16Trimmed.Length == 0)
+ {
+ Assert.Same(Utf8String.Empty, utf8Trimmed); // Trimming an all-whitespace input, return Empty
+ }
+ else
+ {
+ Assert.True(Utf8String.AreEquivalent(utf8Trimmed, utf16Trimmed));
+ }
+ }
+
+ RunTest(ustr => ustr.Trim(), str => str.Trim());
+ RunTest(ustr => ustr.TrimStart(), str => str.TrimStart());
+ RunTest(ustr => ustr.TrimEnd(), str => str.TrimEnd());
+ }
+ }
+}
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Globalization;
using System.Text;
+using System.Text.Tests;
using Xunit;
using static System.Tests.Utf8TestUtilities;
+using ustring = System.Utf8String;
+
namespace System.Tests
{
+ /*
+ * Please keep these tests in sync with those in Utf8SpanTests.Searching.cs.
+ */
+
public unsafe partial class Utf8StringTests
{
+ public static IEnumerable<object[]> TryFindData_Char_Ordinal() => Utf8SpanTests.TryFindData_Char_Ordinal();
+
[Theory]
- [MemberData(nameof(IndexOfTestData))]
- public static void Contains_And_IndexOf_CharRune_Ordinal(Utf8String utf8String, Rune searchValue, int expectedIndex)
+ [MemberData(nameof(TryFindData_Char_Ordinal))]
+ public static void TryFind_Char_Ordinal(ustring source, char searchTerm, Range? expectedForwardMatch, Range? expectedBackwardMatch)
{
- // Contains
+ if (source is null)
+ {
+ return; // don't null ref
+ }
+
+ // First, search forward
+
+ bool wasFound = source.TryFind(searchTerm, out Range actualForwardMatch);
+ Assert.Equal(expectedForwardMatch.HasValue, wasFound);
+
+ if (wasFound)
+ {
+ AssertRangesEqual(source.Length, expectedForwardMatch.Value, actualForwardMatch);
+ }
+
+ // Also check Contains / StartsWith / SplitOn
+
+ Assert.Equal(wasFound, source.Contains(searchTerm));
+ Assert.Equal(wasFound && source[..actualForwardMatch.Start].Length == 0, source.StartsWith(searchTerm));
+
+ (var before, var after) = source.SplitOn(searchTerm);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualForwardMatch.Start], before);
+ Assert.Equal(source[actualForwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
+ }
- if (searchValue.IsBmp)
+ // Now search backward
+
+ wasFound = source.TryFindLast(searchTerm, out Range actualBackwardMatch);
+ Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+ if (wasFound)
{
- Assert.Equal(expectedIndex >= 0, utf8String.Contains((char)searchValue.Value));
+ AssertRangesEqual(source.Length, expectedBackwardMatch.Value, actualBackwardMatch);
}
- Assert.Equal(expectedIndex >= 0, utf8String.Contains(searchValue));
- // IndexOf
+ // Also check EndsWith / SplitOnLast
+
+ Assert.Equal(wasFound && source[actualBackwardMatch.End..].Length == 0, source.EndsWith(searchTerm));
- if (searchValue.IsBmp)
+ (before, after) = source.SplitOnLast(searchTerm);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualBackwardMatch.Start], before);
+ Assert.Equal(source[actualBackwardMatch.End..], after);
+ }
+ else
{
- Assert.Equal(expectedIndex, utf8String.IndexOf((char)searchValue.Value));
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
}
- Assert.Equal(expectedIndex, utf8String.IndexOf(searchValue));
}
+ public static IEnumerable<object[]> TryFindData_Char_WithComparison() => Utf8SpanTests.TryFindData_Char_WithComparison();
+
[Theory]
- [MemberData(nameof(IndexOfTestData))]
- public static void StartsWith_And_EndsWith_CharRune_Ordinal(Utf8String utf8String, Rune searchValue, int expectedIndex)
+ [PlatformSpecific(TestPlatforms.Windows)]
+ [MemberData(nameof(TryFindData_Char_WithComparison))]
+ public static void TryFind_Char_WithComparison(ustring source, char searchTerm, StringComparison comparison, CultureInfo currentCulture, Range? expectedForwardMatch, Range? expectedBackwardMatch)
{
- // StartsWith
+ if (source is null)
+ {
+ return; // don't null ref
+ }
- if (searchValue.IsBmp)
+ RunOnDedicatedThread(() =>
{
- Assert.Equal(expectedIndex == 0, utf8String.StartsWith((char)searchValue.Value));
+ if (currentCulture != null)
+ {
+ CultureInfo.CurrentCulture = currentCulture;
+ }
+
+ // First, search forward
+
+ bool wasFound = source.TryFind(searchTerm, comparison, out Range actualForwardMatch);
+ Assert.Equal(expectedForwardMatch.HasValue, wasFound);
+
+ if (wasFound)
+ {
+ AssertRangesEqual(source.Length, expectedForwardMatch.Value, actualForwardMatch);
+ }
+
+ // Also check Contains / StartsWith / SplitOn
+
+ Assert.Equal(wasFound, source.Contains(searchTerm, comparison));
+ Assert.Equal(wasFound && source[..actualForwardMatch.Start].Length == 0, source.StartsWith(searchTerm, comparison));
+
+ (var before, var after) = source.SplitOn(searchTerm, comparison);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualForwardMatch.Start], before);
+ Assert.Equal(source[actualForwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
+ }
+
+ // Now search backward
+
+ wasFound = source.TryFindLast(searchTerm, comparison, out Range actualBackwardMatch);
+ Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+ if (wasFound)
+ {
+ AssertRangesEqual(source.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ }
+
+ // Also check EndsWith / SplitOnLast
+
+ Assert.Equal(wasFound && source[actualBackwardMatch.End..].Length == 0, source.EndsWith(searchTerm, comparison));
+
+ (before, after) = source.SplitOnLast(searchTerm, comparison);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualBackwardMatch.Start], before);
+ Assert.Equal(source[actualBackwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
+ }
+ });
+ }
+
+ public static IEnumerable<object[]> TryFindData_Rune_Ordinal() => Utf8SpanTests.TryFindData_Rune_Ordinal();
+
+ [Theory]
+ [MemberData(nameof(TryFindData_Rune_Ordinal))]
+ public static void TryFind_Rune_Ordinal(ustring source, Rune searchTerm, Range? expectedForwardMatch, Range? expectedBackwardMatch)
+ {
+ if (source is null)
+ {
+ return; // don't null ref
}
- Assert.Equal(expectedIndex == 0, utf8String.StartsWith(searchValue));
- // EndsWith
+ // First, search forward
- bool endsWithExpectedValue = (expectedIndex >= 0) && (expectedIndex + searchValue.Utf8SequenceLength) == utf8String.Length;
+ bool wasFound = source.TryFind(searchTerm, out Range actualForwardMatch);
+ Assert.Equal(expectedForwardMatch.HasValue, wasFound);
- if (searchValue.IsBmp)
+ if (wasFound)
{
- Assert.Equal(endsWithExpectedValue, utf8String.EndsWith((char)searchValue.Value));
+ AssertRangesEqual(source.Length, expectedForwardMatch.Value, actualForwardMatch);
+ }
+
+ // Also check Contains / StartsWith / SplitOn
+
+ Assert.Equal(wasFound, source.Contains(searchTerm));
+ Assert.Equal(wasFound && source[..actualForwardMatch.Start].Length == 0, source.StartsWith(searchTerm));
+
+ (var before, var after) = source.SplitOn(searchTerm);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualForwardMatch.Start], before);
+ Assert.Equal(source[actualForwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
+ }
+
+ // Now search backward
+
+ wasFound = source.TryFindLast(searchTerm, out Range actualBackwardMatch);
+ Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+ if (wasFound)
+ {
+ AssertRangesEqual(source.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ }
+
+ // Also check EndsWith / SplitOnLast
+
+ Assert.Equal(wasFound && source[actualBackwardMatch.End..].Length == 0, source.EndsWith(searchTerm));
+
+ (before, after) = source.SplitOnLast(searchTerm);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualBackwardMatch.Start], before);
+ Assert.Equal(source[actualBackwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
}
- Assert.Equal(endsWithExpectedValue, utf8String.EndsWith(searchValue));
}
- [Fact]
- public static void Searching_StandaloneSurrogate_Fails()
+ public static IEnumerable<object[]> TryFindData_Rune_WithComparison() => Utf8SpanTests.TryFindData_Rune_WithComparison();
+
+ [Theory]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ [MemberData(nameof(TryFindData_Rune_WithComparison))]
+ public static void TryFind_Rune_WithComparison(ustring source, Rune searchTerm, StringComparison comparison, CultureInfo currentCulture, Range? expectedForwardMatch, Range? expectedBackwardMatch)
+ {
+ if (source is null)
+ {
+ return; // don't null ref
+ }
+
+ RunOnDedicatedThread(() =>
+ {
+ if (currentCulture != null)
+ {
+ CultureInfo.CurrentCulture = currentCulture;
+ }
+
+ // First, search forward
+
+ bool wasFound = source.TryFind(searchTerm, comparison, out Range actualForwardMatch);
+ Assert.Equal(expectedForwardMatch.HasValue, wasFound);
+
+ if (wasFound)
+ {
+ AssertRangesEqual(source.Length, expectedForwardMatch.Value, actualForwardMatch);
+ }
+
+ // Also check Contains / StartsWith / SplitOn
+
+ Assert.Equal(wasFound, source.Contains(searchTerm, comparison));
+ Assert.Equal(wasFound && source[..actualForwardMatch.Start].Length == 0, source.StartsWith(searchTerm, comparison));
+
+ (var before, var after) = source.SplitOn(searchTerm, comparison);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualForwardMatch.Start], before);
+ Assert.Equal(source[actualForwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
+ }
+
+ // Now search backward
+
+ wasFound = source.TryFindLast(searchTerm, comparison, out Range actualBackwardMatch);
+ Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+ if (wasFound)
+ {
+ AssertRangesEqual(source.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ }
+
+ // Also check EndsWith / SplitOnLast
+
+ Assert.Equal(wasFound && source[actualBackwardMatch.End..].Length == 0, source.EndsWith(searchTerm, comparison));
+
+ (before, after) = source.SplitOnLast(searchTerm, comparison);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualBackwardMatch.Start], before);
+ Assert.Equal(source[actualBackwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
+ }
+ });
+ }
+
+ public static IEnumerable<object[]> TryFindData_Utf8String_Ordinal() => Utf8SpanTests.TryFindData_Utf8Span_Ordinal();
+
+ [Theory]
+ [MemberData(nameof(TryFindData_Utf8String_Ordinal))]
+ public static void TryFind_Utf8String_Ordinal(ustring source, ustring searchTerm, Range? expectedForwardMatch, Range? expectedBackwardMatch)
{
- Utf8String utf8String = u8("\ud800\udfff");
+ if (source is null)
+ {
+ return; // don't null ref
+ }
- Assert.False(utf8String.Contains('\ud800'));
- Assert.False(utf8String.Contains('\udfff'));
+ // First, search forward
- Assert.Equal(-1, utf8String.IndexOf('\ud800'));
- Assert.Equal(-1, utf8String.IndexOf('\udfff'));
+ bool wasFound = source.TryFind(searchTerm, out Range actualForwardMatch);
+ Assert.Equal(expectedForwardMatch.HasValue, wasFound);
- Assert.False(utf8String.StartsWith('\ud800'));
- Assert.False(utf8String.StartsWith('\udfff'));
+ if (wasFound)
+ {
+ AssertRangesEqual(source.Length, expectedForwardMatch.Value, actualForwardMatch);
+ }
+
+ // Also check Contains / StartsWith / SplitOn
- Assert.False(utf8String.EndsWith('\ud800'));
- Assert.False(utf8String.EndsWith('\udfff'));
+ Assert.Equal(wasFound, source.Contains(searchTerm));
+ Assert.Equal(wasFound && source[..actualForwardMatch.Start].Length == 0, source.StartsWith(searchTerm));
+
+ (var before, var after) = source.SplitOn(searchTerm);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualForwardMatch.Start], before);
+ Assert.Equal(source[actualForwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
+ }
+
+ // Now search backward
+
+ wasFound = source.TryFindLast(searchTerm, out Range actualBackwardMatch);
+ Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+ if (wasFound)
+ {
+ AssertRangesEqual(source.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ }
+
+ // Also check EndsWith / SplitOnLast
+
+ Assert.Equal(wasFound && source[actualBackwardMatch.End..].Length == 0, source.EndsWith(searchTerm));
+
+ (before, after) = source.SplitOnLast(searchTerm);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualBackwardMatch.Start], before);
+ Assert.Equal(source[actualBackwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
+ }
}
- public static IEnumerable<object[]> IndexOfTestData
+ public static IEnumerable<object[]> TryFindData_Utf8String_WithComparison() => Utf8SpanTests.TryFindData_Utf8Span_WithComparison();
+
+ [Theory]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ [MemberData(nameof(TryFindData_Utf8String_WithComparison))]
+ public static void TryFind_Utf8String_WithComparison(ustring source, ustring searchTerm, StringComparison comparison, CultureInfo currentCulture, Range? expectedForwardMatch, Range? expectedBackwardMatch)
{
- get
- {
- yield return new object[] { Utf8String.Empty, default(Rune), -1 };
- yield return new object[] { u8("Hello"), (Rune)'H', 0 };
- yield return new object[] { u8("Hello"), (Rune)'h', -1 };
- yield return new object[] { u8("Hello"), (Rune)'O', -1 };
- yield return new object[] { u8("Hello"), (Rune)'o', 4 };
- yield return new object[] { u8("Hello"), (Rune)'L', -1 };
- yield return new object[] { u8("Hello"), (Rune)'l', 2 };
- yield return new object[] { u8("\U00012345\U0010ABCD"), (Rune)0x00012345, 0 };
- yield return new object[] { u8("\U00012345\U0010ABCD"), (Rune)0x0010ABCD, 4 };
- yield return new object[] { u8("abc\ufffdef"), (Rune)'c', 2 };
- yield return new object[] { u8("abc\ufffdef"), (Rune)'\ufffd', 3 };
- yield return new object[] { u8("abc\ufffdef"), (Rune)'d', -1 };
- yield return new object[] { u8("abc\ufffdef"), (Rune)'e', 6 };
+ if (source is null)
+ {
+ return; // don't null ref
}
+
+ RunOnDedicatedThread(() =>
+ {
+ if (currentCulture != null)
+ {
+ CultureInfo.CurrentCulture = currentCulture;
+ }
+
+ // First, search forward
+
+ bool wasFound = source.TryFind(searchTerm, comparison, out Range actualForwardMatch);
+ Assert.Equal(expectedForwardMatch.HasValue, wasFound);
+
+ if (wasFound)
+ {
+ AssertRangesEqual(source.Length, expectedForwardMatch.Value, actualForwardMatch);
+ }
+
+ // Also check Contains / StartsWith / SplitOn
+
+ Assert.Equal(wasFound, source.Contains(searchTerm, comparison));
+ Assert.Equal(wasFound && source[..actualForwardMatch.Start].Length == 0, source.StartsWith(searchTerm, comparison));
+
+ (var before, var after) = source.SplitOn(searchTerm, comparison);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualForwardMatch.Start], before);
+ Assert.Equal(source[actualForwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
+ }
+
+ // Now search backward
+
+ wasFound = source.TryFindLast(searchTerm, comparison, out Range actualBackwardMatch);
+ Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+ if (wasFound)
+ {
+ AssertRangesEqual(source.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+ }
+
+ // Also check EndsWith / SplitOnLast
+
+ Assert.Equal(wasFound && source[actualBackwardMatch.End..].Length == 0, source.EndsWith(searchTerm, comparison));
+
+ (before, after) = source.SplitOnLast(searchTerm, comparison);
+ if (wasFound)
+ {
+ Assert.Equal(source[..actualBackwardMatch.Start], before);
+ Assert.Equal(source[actualBackwardMatch.End..], after);
+ }
+ else
+ {
+ Assert.Same(source, before); // check for reference equality
+ Assert.Null(after);
+ }
+ });
+ }
+
+ [Fact]
+ public static void TryFind_WithNullUtf8String_Throws()
+ {
+ static void RunTest(Action action)
+ {
+ var exception = Assert.Throws<ArgumentNullException>(action);
+ Assert.Equal("value", exception.ParamName);
+ }
+
+ ustring str = u8("Hello!");
+ const ustring value = null;
+ const StringComparison comparison = StringComparison.OrdinalIgnoreCase;
+
+ // Run this test for a bunch of methods, not simply TryFind
+
+ RunTest(() => str.Contains(value));
+ RunTest(() => str.Contains(value, comparison));
+ RunTest(() => str.EndsWith(value));
+ RunTest(() => str.EndsWith(value, comparison));
+ RunTest(() => str.SplitOn(value));
+ RunTest(() => str.SplitOn(value, comparison));
+ RunTest(() => str.SplitOnLast(value));
+ RunTest(() => str.SplitOnLast(value, comparison));
+ RunTest(() => str.StartsWith(value));
+ RunTest(() => str.StartsWith(value, comparison));
+ RunTest(() => str.TryFind(value, out _));
+ RunTest(() => str.TryFind(value, comparison, out _));
+ RunTest(() => str.TryFindLast(value, out _));
+ RunTest(() => str.TryFindLast(value, comparison, out _));
}
}
}
+++ /dev/null
-// 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;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Reflection;
-using Xunit;
-
-using static System.Tests.Utf8TestUtilities;
-
-namespace System.Tests
-{
- public unsafe partial class Utf8StringTests
- {
- [Theory]
- [InlineData("Hello", 0, false, "Hello")]
- [InlineData("Hello", 0, true, "")]
- [InlineData("Hello", 2, false, "llo")]
- [InlineData("Hello", 2, true, "lo")]
- [InlineData("Hello", 5, false, "")]
- [InlineData("Hello", 5, true, "Hello")]
- [InlineData("", 0, true, "")]
- [InlineData("", 0, false, "")]
- public static void Substring_Index(string sAsString, int indexValue, bool fromEnd, string expectedAsString)
- {
- Index index = new Index(indexValue, fromEnd);
-
- void Substring_IndexCore(Utf8String s, Utf8String expected)
- {
- Range r = new Range(index, ^0);
- Assert.Equal(expected, s[r]);
-
- if (index.Value == 0)
- {
- Assert.Same(index.IsFromEnd ? Utf8String.Empty : s, s[r]);
- }
-
- if (index.Value == s.Length)
- {
- Assert.Same(index.IsFromEnd ? s : Utf8String.Empty, s[r]);
- }
- };
-
- Substring_IndexCore(new Utf8String(sAsString), new Utf8String(expectedAsString));
- }
-
- [Theory]
- [InlineData("Hello", 0, 5, "Hello")]
- [InlineData("Hello", 0, 3, "Hel")]
- [InlineData("Hello", 2, 3, "llo")]
- [InlineData("Hello", 5, 0, "")]
- [InlineData("", 0, 0, "")]
- public static void Substring_Int(string sAsString, int startIndex, int length, string expectedAsString)
- {
- void Substring_IntCore(Utf8String s, Utf8String expected)
- {
- if (startIndex + length == s.Length)
- {
- Assert.Equal(expected, s.Substring(startIndex));
- Assert.Equal(expected, new Utf8String(s.AsBytes(startIndex)));
-
- if (length == 0)
- {
- Assert.Same(Utf8String.Empty, s.Substring(startIndex));
- }
- }
- Assert.Equal(expected, s.Substring(startIndex, length));
-
- Assert.Equal(expected, new Utf8String(s.AsBytes(startIndex, length)));
-
- if (length == s.Length)
- {
- Assert.Same(s, s.Substring(startIndex));
- Assert.Same(s, s.Substring(startIndex, length));
- }
- else if (length == 0)
- {
- Assert.Same(Utf8String.Empty, s.Substring(startIndex, length));
- }
- };
-
- Substring_IntCore(new Utf8String(sAsString), new Utf8String(expectedAsString));
- }
-
- [Fact]
- public static void Substring_Range()
- {
- void Substring_RangeCore(Utf8String s, Range range, Utf8String expected)
- {
- Assert.Equal(expected, s[range]);
-
- if (expected.Length == s.Length)
- {
- Assert.Same(s, s[range]);
- }
-
- if (expected.Length == 0)
- {
- Assert.Same(Utf8String.Empty, s[range]);
- }
- };
-
- Substring_RangeCore(u8("Hello"), .., u8("Hello"));
- Substring_RangeCore(u8("Hello"), 0..3, u8("Hel"));
- Substring_RangeCore(u8("Hello"), ..^4, u8("H"));
- Substring_RangeCore(u8("Hello"), 1.., u8("ello"));
- Substring_RangeCore(u8("Hello"), ..^5, Utf8String.Empty);
- }
-
- [Fact]
- public static void Substring_Invalid()
- {
- // Start index < 0
- AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => u8("foo").Substring(-1));
- AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => u8("foo").Substring(-1, 0));
-
- // Start index > string.Length
- AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => u8("foo").Substring(4));
- AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => u8("foo").Substring(4, 0));
-
- // Length < 0 or length > string.Length
- AssertExtensions.Throws<ArgumentOutOfRangeException>("length", () => u8("foo").Substring(0, -1));
- AssertExtensions.Throws<ArgumentOutOfRangeException>("length", () => u8("foo").Substring(0, 4));
-
- // Start index + length > string.Length
- AssertExtensions.Throws<ArgumentOutOfRangeException>("length", () => u8("foo").Substring(3, 2));
- AssertExtensions.Throws<ArgumentOutOfRangeException>("length", () => u8("foo").Substring(2, 2));
- }
- }
-}
-// Licensed to the .NET Foundation under one or more agreements.
+// 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;
using static System.Tests.Utf8TestUtilities;
// Static methods
Assert.Equal(expected, Utf8String.Equals(a, b));
+ Assert.Equal(expected, Utf8String.Equals(a, b, StringComparison.Ordinal));
// Instance methods
if (a != null)
{
- Assert.Equal(expected, a.Equals(b));
Assert.Equal(expected, a.Equals((object)b));
+ Assert.Equal(expected, a.Equals(b));
+ Assert.Equal(expected, a.Equals(b, StringComparison.Ordinal));
+ }
+ }
+
+ [Fact]
+ 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++)
+ {
+ Utf8String ustr = new Utf8String(new byte[i]);
+
+ Assert.True(seenHashCodes.Add(ustr.GetHashCode()), "This hash code was previously seen.");
}
}
[Fact]
- public static void GetHashCode_ReturnsRandomized()
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public static void GetHashCode_WithComparison()
{
- Utf8String a = u8("Hello");
- Utf8String b = new Utf8String(a.AsBytes());
+ // 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
+
+ {
+ Utf8String ustr = u8("ababaaAA");
+
+ Assert.Equal(ustr[0..2].GetHashCode(StringComparison.Ordinal), ustr[2..4].GetHashCode(StringComparison.Ordinal));
+ Assert.NotEqual(ustr[4..6].GetHashCode(StringComparison.Ordinal), ustr[6..8].GetHashCode(StringComparison.Ordinal));
+ Assert.Equal(Utf8String.Empty.GetHashCode(StringComparison.Ordinal), ustr[^0..].GetHashCode(StringComparison.Ordinal));
+ }
- Assert.NotSame(a, b);
- Assert.Equal(a.GetHashCode(), b.GetHashCode());
+ // OrdinalIgnoreCase
- Utf8String c = u8("Goodbye");
- Utf8String d = new Utf8String(c.AsBytes());
+ {
+ Utf8String ustr = u8("ababaaAA");
+
+ Assert.Equal(ustr[0..2].GetHashCode(StringComparison.OrdinalIgnoreCase), ustr[2..4].GetHashCode(StringComparison.OrdinalIgnoreCase));
+ Assert.Equal(ustr[4..6].GetHashCode(StringComparison.OrdinalIgnoreCase), ustr[6..8].GetHashCode(StringComparison.OrdinalIgnoreCase));
+ Assert.NotEqual(ustr[0..2].GetHashCode(StringComparison.OrdinalIgnoreCase), ustr[6..8].GetHashCode(StringComparison.OrdinalIgnoreCase));
+ Assert.Equal(Utf8String.Empty.GetHashCode(StringComparison.OrdinalIgnoreCase), ustr[^0..].GetHashCode(StringComparison.OrdinalIgnoreCase));
+ }
+
+ // InvariantCulture
+
+ {
+ Utf8String ustr = u8("ae\u00e6AE\u00c6"); // U+00E6 = 'æ' LATIN SMALL LETTER AE, U+00E6 = 'Æ' LATIN CAPITAL LETTER AE
+
+ Assert.Equal(ustr[0..2].GetHashCode(StringComparison.InvariantCulture), ustr[2..4].GetHashCode(StringComparison.InvariantCulture));
+ Assert.NotEqual(ustr[0..2].GetHashCode(StringComparison.InvariantCulture), ustr[4..6].GetHashCode(StringComparison.InvariantCulture));
+ Assert.Equal(Utf8String.Empty.GetHashCode(StringComparison.InvariantCulture), ustr[^0..].GetHashCode(StringComparison.InvariantCulture));
+ }
- Assert.NotSame(c, d);
- Assert.Equal(c.GetHashCode(), d.GetHashCode());
+ // InvariantCultureIgnoreCase
- Assert.NotEqual(a.GetHashCode(), c.GetHashCode());
+ {
+ Utf8String ustr = u8("ae\u00e6AE\u00c6EA");
+
+ Assert.Equal(ustr[0..2].GetHashCode(StringComparison.InvariantCultureIgnoreCase), ustr[2..4].GetHashCode(StringComparison.InvariantCultureIgnoreCase));
+ Assert.Equal(ustr[0..2].GetHashCode(StringComparison.InvariantCultureIgnoreCase), ustr[6..8].GetHashCode(StringComparison.InvariantCultureIgnoreCase));
+ Assert.NotEqual(ustr[0..2].GetHashCode(StringComparison.InvariantCultureIgnoreCase), ustr[8..10].GetHashCode(StringComparison.InvariantCultureIgnoreCase));
+ Assert.Equal(Utf8String.Empty.GetHashCode(StringComparison.InvariantCultureIgnoreCase), ustr[^0..].GetHashCode(StringComparison.InvariantCultureIgnoreCase));
+ }
+
+ // Invariant culture should not match Turkish I case conversion
+
+ {
+ Utf8String ustr = u8("i\u0130"); // U+0130 = 'İ' LATIN CAPITAL LETTER I WITH DOT ABOVE
+
+ Assert.NotEqual(ustr[0..1].GetHashCode(StringComparison.InvariantCultureIgnoreCase), ustr[1..3].GetHashCode(StringComparison.InvariantCultureIgnoreCase));
+ }
+
+ // CurrentCulture (we'll use tr-TR)
+
+ RunOnDedicatedThread(() =>
+ {
+ Utf8String ustr = u8("i\u0131\u0130Ii\u0131\u0130I"); // U+0131 = 'ı' LATIN SMALL LETTER DOTLESS I
+
+ CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR");
+
+ Assert.Equal(ustr[0..6].GetHashCode(StringComparison.CurrentCulture), ustr[6..12].GetHashCode(StringComparison.CurrentCulture));
+ Assert.NotEqual(ustr[0..1].GetHashCode(StringComparison.CurrentCulture), ustr[1..3].GetHashCode(StringComparison.CurrentCulture));
+ Assert.Equal(Utf8String.Empty.GetHashCode(StringComparison.CurrentCulture), ustr[^0..].GetHashCode(StringComparison.CurrentCulture));
+ });
+
+ // CurrentCultureIgnoreCase (we'll use tr-TR)
+
+ RunOnDedicatedThread(() =>
+ {
+ Utf8String ustr = u8("i\u0131\u0130Ii\u0131\u0130I");
+
+ CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR");
+
+ Assert.Equal(ustr[0..6].GetHashCode(StringComparison.CurrentCultureIgnoreCase), ustr[6..12].GetHashCode(StringComparison.CurrentCultureIgnoreCase));
+ Assert.NotEqual(ustr[0..1].GetHashCode(StringComparison.CurrentCultureIgnoreCase), ustr[1..3].GetHashCode(StringComparison.CurrentCultureIgnoreCase)); // 'i' shouldn't match 'ı'
+ Assert.Equal(ustr[0..1].GetHashCode(StringComparison.CurrentCultureIgnoreCase), ustr[3..5].GetHashCode(StringComparison.CurrentCultureIgnoreCase)); // 'i' should match 'İ'
+ Assert.NotEqual(ustr[0..1].GetHashCode(StringComparison.CurrentCultureIgnoreCase), ustr[5..6].GetHashCode(StringComparison.CurrentCultureIgnoreCase)); // 'i' shouldn't match 'I'
+ Assert.Equal(Utf8String.Empty.GetHashCode(StringComparison.CurrentCultureIgnoreCase), ustr[^0..].GetHashCode(StringComparison.CurrentCultureIgnoreCase));
+ });
}
[Fact]
}
[Theory]
+ [InlineData(null, true)]
[InlineData("", true)]
+ [InlineData("\r\n", false)]
[InlineData("not empty", false)]
public static void IsNullOrEmpty(string value, bool expectedIsNullOrEmpty)
{
- Assert.Equal(expectedIsNullOrEmpty, Utf8String.IsNullOrEmpty(new Utf8String(value)));
+ Assert.Equal(expectedIsNullOrEmpty, Utf8String.IsNullOrEmpty(u8(value)));
}
- [Fact]
- public static void IsNullOrEmpty_Null_ReturnsTrue()
+ [Theory]
+ [InlineData(null, true)]
+ [InlineData("", true)]
+ [InlineData(" \u2028\u2029\t\v", true)]
+ [InlineData(" x\r\n", false)]
+ [InlineData("\r\nhello\r\n", false)]
+ [InlineData("\r\n\0\r\n", false)]
+ [InlineData("\r\n\r\n", true)]
+ public static void IsNullOrWhiteSpace(string input, bool expected)
{
- Assert.True(Utf8String.IsNullOrEmpty(null));
+ Assert.Equal(expected, Utf8String.IsNullOrWhiteSpace(u8(input)));
}
[Fact]
public static void ToByteArray_Empty()
{
Assert.Same(Array.Empty<byte>(), Utf8String.Empty.ToByteArray());
- Assert.Same(Array.Empty<byte>(), u8("Hello!").ToByteArray(0, 0));
- Assert.Same(Array.Empty<byte>(), u8("Hello!").ToByteArray(3, 0));
- Assert.Same(Array.Empty<byte>(), u8("Hello!").ToByteArray(6, 0));
}
[Fact]
public static void ToByteArray_NotEmpty()
{
Assert.Equal(new byte[] { (byte)'H', (byte)'i' }, u8("Hi").ToByteArray());
- Assert.Equal(new byte[] { (byte)'l', (byte)'l', (byte)'o' }, u8("Hello!").ToByteArray(2, 3));
}
- [Theory]
- [InlineData("", 1, 0, "startIndex")]
- [InlineData("", 0, 1, "length")]
- [InlineData("Hello", 5, 2, "length")]
- [InlineData("Hello", 5, -1, "length")]
- [InlineData("Hello", -1, 4, "startIndex")]
- public static void ToByteArray_Invalid(string value, int startIndex, int length, string exceptionParamName)
+ [Fact]
+ public static void ToCharArray_NotEmpty()
{
- Utf8String utf8String = u8(value);
- Assert.Throws<ArgumentOutOfRangeException>(exceptionParamName, () => utf8String.ToByteArray(startIndex, length));
+ Assert.Equal("Hi".ToCharArray(), u8("Hi").ToCharArray());
+ }
+
+ [Fact]
+ public static void ToCharArray_Empty()
+ {
+ Assert.Same(Array.Empty<char>(), Utf8String.Empty.ToCharArray());
}
[Theory]
Assert.Equal(value, u8(value).ToString());
}
+ [Theory]
+ [InlineData("Hello", "6..")]
+ [InlineData("Hello", "3..7")]
+ [InlineData("Hello", "2..1")]
+ [InlineData("Hello", "^10..")]
+ public static void Indexer_Range_ArgOutOfRange_Throws(string strAsUtf16, string rangeAsString)
+ {
+ Utf8String utf8String = u8(strAsUtf16);
+ Range range = ParseRangeExpr(rangeAsString);
+
+ Assert.Throws<ArgumentOutOfRangeException>(() => utf8String[range]);
+ }
+
[Fact]
- public static void ToString_ReturnsUtf16_WithFixups()
+ public static void Indexer_Range_Success()
{
- Utf8String newString = new Utf8String("Hello");
+ Utf8String utf8String = u8("Hello\u0800world.");
- fixed (byte* pNewString = newString)
- {
- pNewString[2] = 0xFF; // corrupt this data
- }
+ Assert.Equal(u8("Hello"), utf8String[..5]);
+ Assert.Equal(u8("world."), utf8String[^6..]);
+ Assert.Equal(u8("o\u0800w"), utf8String[4..9]);
+
+ Assert.Same(utf8String, utf8String[..]); // don't allocate new instance if slicing to entire string
+ Assert.Same(Utf8String.Empty, utf8String[1..1]); // don't allocare new zero-length string instane
+ Assert.Same(Utf8String.Empty, utf8String[6..6]); // ok to have a zero-length slice within a multi-byte sequence
+ }
+
+ [Fact]
+ public static void Indexer_Range_TriesToSplitMultiByteSequence_Throws()
+ {
+ Utf8String utf8String = u8("Hello\u0800world.");
- Assert.Equal("He\uFFFDlo", newString.ToString());
+ Assert.Throws<InvalidOperationException>(() => utf8String[..6]);
+ Assert.Throws<InvalidOperationException>(() => utf8String[6..]);
+ Assert.Throws<InvalidOperationException>(() => utf8String[7..8]);
}
}
}
});
}
- public static int GetByteLength(this Utf8String value)
- {
- return value.AsBytes().Length;
- }
-
public unsafe static bool IsNull(this Utf8Span span)
{
return Unsafe.AreSame(ref Unsafe.AsRef<byte>(null), ref MemoryMarshal.GetReference(span.Bytes));
/// <summary>
/// Mimics returning a literal <see cref="Utf8String"/> instance.
/// </summary>
- public static unsafe Utf8String u8(string str)
+ public static Utf8String u8(string str)
{
if (str is null)
{
Assert.True(memStream.TryGetBuffer(out ArraySegment<byte> buffer));
// Now allocate a UTF-8 string instance and set this as the contents.
- // We do it this way rather than go through a public ctor because we don't
- // want the "control" part of our unit tests to depend on the code under test.
-
- Utf8String newUtf8String = _utf8StringFactory.Value(buffer.Count);
- fixed (byte* pNewUtf8String = newUtf8String)
- {
- buffer.AsSpan().CopyTo(new Span<byte>(pNewUtf8String, newUtf8String.GetByteLength()));
- }
- return newUtf8String;
+ return Utf8String.UnsafeCreateWithoutValidation(buffer);
}
public unsafe static Range GetRangeOfSubspan<T>(ReadOnlySpan<T> outerSpan, ReadOnlySpan<T> innerSpan)