diff --git a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json
index 0f193a2b57ef..415e45dd7e84 100644
--- a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json
+++ b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json
@@ -5307,6 +5307,12 @@
"uap10.0.16299": "4.0.1.0"
}
},
+ "System.Utf8String.Experimental": {
+ "InboxOn": {},
+ "AssemblyVersionInPackageVersion": {
+ "4.0.0.0": "4.6.0"
+ }
+ },
"System.ValueTuple": {
"StableVersions": [
"4.3.0",
diff --git a/pkg/descriptions.json b/pkg/descriptions.json
index 0512d12459de..e3b4369e6e8f 100644
--- a/pkg/descriptions.json
+++ b/pkg/descriptions.json
@@ -2131,6 +2131,14 @@
"System.Transactions.TransactionScope"
]
},
+ {
+ "Name": "System.Utf8String.Experimental",
+ "Description": "Provides types for representation of UTF-8 string data.",
+ "CommonTypes": [
+ "System.Char8",
+ "System.Utf8String"
+ ]
+ },
{
"Name": "System.ValueTuple",
"Description": "Provides the System.ValueTuple structs, which implement the underlying types for tuples in C# and Visual Basic.",
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 6653ddf22175..7063f069799d 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -31,4 +31,13 @@
+
+
+
+
+
diff --git a/src/System.Utf8String.Experimental/Directory.Build.props b/src/System.Utf8String.Experimental/Directory.Build.props
new file mode 100644
index 000000000000..25e3ba27477f
--- /dev/null
+++ b/src/System.Utf8String.Experimental/Directory.Build.props
@@ -0,0 +1,11 @@
+
+
+
+
+ 4.0.0.0
+
+ Open
+
+ true
+
+
diff --git a/src/System.Utf8String.Experimental/System.Utf8String.Experimental.sln b/src/System.Utf8String.Experimental/System.Utf8String.Experimental.sln
new file mode 100644
index 000000000000..2d4bdcc1c43a
--- /dev/null
+++ b/src/System.Utf8String.Experimental/System.Utf8String.Experimental.sln
@@ -0,0 +1,50 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27213.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Utf8String.Experimental.Tests", "tests\System.Utf8String.Experimental.Tests.csproj", "{72E9FB32-4692-4692-A10B-9F053F8F1A88}"
+ ProjectSection(ProjectDependencies) = postProject
+ {D4266847-6692-481B-9459-6141DB7DA339} = {D4266847-6692-481B-9459-6141DB7DA339}
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Utf8String.Experimental", "src\System.Utf8String.Experimental.csproj", "{D4266847-6692-481B-9459-6141DB7DA339}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Utf8String.Experimental", "ref\System.Utf8String.Experimental.csproj", "{7AF57E6B-2CED-45C9-8BCA-5BBA60D018E0}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7EC8921F-E96F-445B-AA33-453515641D93}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{8691446A-CA54-49FD-87B9-57A0C6B48095}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FEB087F5-EF72-429D-8A0E-7636B84A1537}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D4266847-6692-481B-9459-6141DB7DA339}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
+ {D4266847-6692-481B-9459-6141DB7DA339}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
+ {D4266847-6692-481B-9459-6141DB7DA339}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
+ {D4266847-6692-481B-9459-6141DB7DA339}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
+ {7AF57E6B-2CED-45C9-8BCA-5BBA60D018E0}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
+ {7AF57E6B-2CED-45C9-8BCA-5BBA60D018E0}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
+ {7AF57E6B-2CED-45C9-8BCA-5BBA60D018E0}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
+ {7AF57E6B-2CED-45C9-8BCA-5BBA60D018E0}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
+ {72E9FB32-4692-4692-A10B-9F053F8F1A88}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
+ {72E9FB32-4692-4692-A10B-9F053F8F1A88}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
+ {72E9FB32-4692-4692-A10B-9F053F8F1A88}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
+ {72E9FB32-4692-4692-A10B-9F053F8F1A88}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {D4266847-6692-481B-9459-6141DB7DA339} = {7EC8921F-E96F-445B-AA33-453515641D93}
+ {7AF57E6B-2CED-45C9-8BCA-5BBA60D018E0} = {8691446A-CA54-49FD-87B9-57A0C6B48095}
+ {72E9FB32-4692-4692-A10B-9F053F8F1A88} = {FEB087F5-EF72-429D-8A0E-7636B84A1537}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {7196F6AB-8F22-4E4D-B6D1-3C2CFF86229C}
+ EndGlobalSection
+EndGlobal
diff --git a/src/System.Utf8String.Experimental/pkg/System.Utf8String.Experimental.pkgproj b/src/System.Utf8String.Experimental/pkg/System.Utf8String.Experimental.pkgproj
new file mode 100644
index 000000000000..016952009282
--- /dev/null
+++ b/src/System.Utf8String.Experimental/pkg/System.Utf8String.Experimental.pkgproj
@@ -0,0 +1,11 @@
+
+
+
+
+
+ netcoreapp3.0;
+
+
+
+
+
diff --git a/src/System.Utf8String.Experimental/ref/Configurations.props b/src/System.Utf8String.Experimental/ref/Configurations.props
new file mode 100644
index 000000000000..d3ac8a63c74a
--- /dev/null
+++ b/src/System.Utf8String.Experimental/ref/Configurations.props
@@ -0,0 +1,8 @@
+
+
+
+
+ netcoreapp;
+
+
+
diff --git a/src/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.csproj b/src/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.csproj
new file mode 100644
index 000000000000..6563d2c44bdc
--- /dev/null
+++ b/src/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.csproj
@@ -0,0 +1,16 @@
+
+
+ true
+ {7AF57E6B-2CED-45C9-8BCA-5BBA60D018E0}
+ netcoreapp-Debug;netcoreapp-Release
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/System.Utf8String.Experimental/ref/System.Utf8String.cs b/src/System.Utf8String.Experimental/ref/System.Utf8String.cs
new file mode 100644
index 000000000000..7e9bc8f56e95
--- /dev/null
+++ b/src/System.Utf8String.Experimental/ref/System.Utf8String.cs
@@ -0,0 +1,125 @@
+// 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.
+// ------------------------------------------------------------------------------
+// Changes to this file must follow the http://aka.ms/api-review process.
+// ------------------------------------------------------------------------------
+
+namespace System
+{
+ public readonly partial struct Char8 : IComparable, IEquatable
+ {
+ private readonly int _dummy;
+ public static bool operator ==(Char8 left, Char8 right) => throw null;
+ public static bool operator !=(Char8 left, Char8 right) => throw null;
+ public static bool operator <(Char8 left, Char8 right) => throw null;
+ public static bool operator <=(Char8 left, Char8 right) => throw null;
+ public static bool operator >(Char8 left, Char8 right) => throw null;
+ public static bool operator >=(Char8 left, Char8 right) => throw null;
+ public static implicit operator byte(Char8 value) => throw null;
+ [CLSCompliant(false)]
+ public static explicit operator sbyte(Char8 value) => throw null;
+ public static explicit operator char(Char8 value) => throw null;
+ public static implicit operator short(Char8 value) => throw null;
+ [CLSCompliant(false)]
+ public static implicit operator ushort(Char8 value) => throw null;
+ public static implicit operator int(Char8 value) => throw null;
+ [CLSCompliant(false)]
+ public static implicit operator uint(Char8 value) => throw null;
+ public static implicit operator long(Char8 value) => throw null;
+ [CLSCompliant(false)]
+ public static implicit operator ulong(Char8 value) => throw null;
+ public static implicit operator Char8(byte value) => throw null;
+ [CLSCompliant(false)]
+ public static explicit operator Char8(sbyte value) => throw null;
+ public static explicit operator Char8(char value) => throw null;
+ public static explicit operator Char8(short value) => throw null;
+ [CLSCompliant(false)]
+ public static explicit operator Char8(ushort value) => throw null;
+ public static explicit operator Char8(int value) => throw null;
+ [CLSCompliant(false)]
+ public static explicit operator Char8(uint value) => throw null;
+ public static explicit operator Char8(long value) => throw null;
+ [CLSCompliant(false)]
+ public static explicit operator Char8(ulong value) => throw null;
+ public int CompareTo(Char8 other) => throw null;
+ public override bool Equals(object obj) => throw null;
+ public bool Equals(Char8 other) => throw null;
+ public override int GetHashCode() => throw null;
+ public override string ToString() => throw null;
+ }
+ public static partial class Utf8Extensions
+ {
+ public static ReadOnlySpan AsBytes(this ReadOnlySpan text) => throw null;
+ public static ReadOnlySpan AsBytes(this Utf8String text) => throw null;
+ public static ReadOnlySpan AsBytes(this Utf8String text, int start) => throw null;
+ public static ReadOnlySpan AsBytes(this Utf8String text, int start, int length) => throw null;
+ public static ReadOnlySpan AsSpan(this Utf8String text) => throw null;
+ public static ReadOnlySpan AsSpan(this Utf8String text, int start) => throw null;
+ public static ReadOnlySpan AsSpan(this Utf8String text, int start, int length) => throw null;
+ public static ReadOnlyMemory AsMemory(this Utf8String text) => throw null;
+ public static ReadOnlyMemory AsMemory(this Utf8String text, int start) => throw null;
+ public static ReadOnlyMemory AsMemory(this Utf8String text, Index startIndex) => throw null;
+ public static ReadOnlyMemory AsMemory(this Utf8String text, int start, int length) => throw null;
+ public static ReadOnlyMemory AsMemory(this Utf8String text, Range range) => throw null;
+ public static ReadOnlyMemory AsMemoryBytes(this Utf8String text) => throw null;
+ public static ReadOnlyMemory AsMemoryBytes(this Utf8String text, int start) => throw null;
+ public static ReadOnlyMemory AsMemoryBytes(this Utf8String text, Index startIndex) => throw null;
+ public static ReadOnlyMemory AsMemoryBytes(this Utf8String text, int start, int length) => throw null;
+ public static ReadOnlyMemory AsMemoryBytes(this Utf8String text, Range range) => throw null;
+ }
+ public sealed partial class Utf8String : IEquatable
+ {
+ public static readonly Utf8String Empty;
+ public Utf8String(ReadOnlySpan value) { }
+ public Utf8String(byte[] value, int startIndex, int length) { }
+ [CLSCompliant(false)]
+ public unsafe Utf8String(byte* value) { }
+ public Utf8String(ReadOnlySpan value) { }
+ public Utf8String(char[] value, int startIndex, int length) { }
+ [CLSCompliant(false)]
+ public unsafe Utf8String(char* value) { }
+ public Utf8String(string value) { }
+ public static explicit operator ReadOnlySpan(Utf8String value) => throw null;
+ public static implicit operator ReadOnlySpan(Utf8String value) => throw null;
+ public static bool operator ==(Utf8String left, Utf8String right) => throw null;
+ public static bool operator !=(Utf8String left, Utf8String right) => throw null;
+ public Char8 this[Index index] => throw null;
+ public Char8 this[int index] => throw null;
+ public Utf8String this[Range range] => throw null;
+ public int Length => throw null;
+ public bool Contains(char value) => throw null;
+ public bool Contains(System.Text.Rune value) => throw null;
+ public bool EndsWith(char value) => throw null;
+ public bool EndsWith(System.Text.Rune value) => throw null;
+ public override bool Equals(object obj) => throw null;
+ public bool Equals(Utf8String value) => throw null;
+ public static bool Equals(Utf8String left, Utf8String right) => throw null;
+ public override int GetHashCode() => throw null;
+ [ComponentModel.EditorBrowsable(ComponentModel.EditorBrowsableState.Never)] // for compiler use only
+ 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(Utf8String value) => throw null;
+ public bool StartsWith(char value) => throw null;
+ public bool StartsWith(System.Text.Rune value) => throw null;
+ public Utf8String Substring(Index startIndex) => throw null;
+ public Utf8String Substring(int startIndex) => throw null;
+ public Utf8String Substring(int startIndex, int length) => throw null;
+ public Utf8String Substring(Range range) => throw null;
+ public byte[] ToByteArray() => throw null;
+ public byte[] ToByteArray(int startIndex, int length) => throw null;
+ public override string ToString() => throw null;
+ }
+}
+namespace System.Net.Http
+{
+ public sealed partial class Utf8StringContent : System.Net.Http.HttpContent
+ {
+ public Utf8StringContent(Utf8String content) { }
+ public Utf8StringContent(Utf8String content, string mediaType) { }
+ protected override System.Threading.Tasks.Task CreateContentReadStreamAsync() => throw null;
+ protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context) => throw null;
+ protected override bool TryComputeLength(out long length) => throw null;
+ }
+}
diff --git a/src/System.Utf8String.Experimental/src/Configurations.props b/src/System.Utf8String.Experimental/src/Configurations.props
new file mode 100644
index 000000000000..e75400d142ff
--- /dev/null
+++ b/src/System.Utf8String.Experimental/src/Configurations.props
@@ -0,0 +1,9 @@
+
+
+
+
+ netcoreapp-Windows_NT;
+ netcoreapp-Unix;
+
+
+
diff --git a/src/System.Utf8String.Experimental/src/Resources/Strings.resx b/src/System.Utf8String.Experimental/src/Resources/Strings.resx
new file mode 100644
index 000000000000..1af7de150c99
--- /dev/null
+++ b/src/System.Utf8String.Experimental/src/Resources/Strings.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/src/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj b/src/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj
new file mode 100644
index 000000000000..964097b580ae
--- /dev/null
+++ b/src/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj
@@ -0,0 +1,19 @@
+
+
+ {D4266847-6692-481B-9459-6141DB7DA339}
+ true
+ true
+ netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;
+ System
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/System.Utf8String.Experimental/src/System/IO/Utf8StringStream.cs b/src/System.Utf8String.Experimental/src/System/IO/Utf8StringStream.cs
new file mode 100644
index 000000000000..c2ffa238f458
--- /dev/null
+++ b/src/System.Utf8String.Experimental/src/System/IO/Utf8StringStream.cs
@@ -0,0 +1,133 @@
+// 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.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+ internal sealed class Utf8StringStream : Stream
+ {
+ private readonly Utf8String _content;
+ private int _position;
+
+ public Utf8StringStream(Utf8String content)
+ {
+ _content = content ?? Utf8String.Empty;
+ }
+
+ public override bool CanRead => true;
+
+ public override bool CanSeek => true;
+
+ public override bool CanTimeout => true;
+
+ public override bool CanWrite => false;
+
+ public override long Length => _content.Length;
+
+ public override long Position
+ {
+ get => _position;
+ set
+ {
+ if ((ulong)value > (uint)_content.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ _position = (int)value;
+ }
+ }
+
+ public override void Flush()
+ {
+ /* no-op */
+ }
+
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ /* no-op */
+ return Task.CompletedTask;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return Read(new Span(buffer, offset, count));
+ }
+
+ public override int Read(Span buffer)
+ {
+ ReadOnlySpan contentToWrite = _content.AsBytes(_position);
+ if (buffer.Length < contentToWrite.Length)
+ {
+ contentToWrite = contentToWrite.Slice(buffer.Length);
+ }
+
+ contentToWrite.CopyTo(buffer);
+ _position += contentToWrite.Length;
+
+ return contentToWrite.Length;
+ }
+
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(Read(new Span(buffer, offset, count)));
+ }
+
+ public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default)
+ {
+ return new ValueTask(Read(buffer.Span));
+ }
+
+ public override int ReadByte()
+ {
+ int position = _position;
+ if ((uint)position >= (uint)_content.Length)
+ {
+ return -1;
+ }
+
+ _position++;
+ return _content[position];
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ switch (origin)
+ {
+ case SeekOrigin.Begin:
+ break;
+ case SeekOrigin.Current:
+ offset += _position;
+ break;
+ case SeekOrigin.End:
+ offset += _content.Length;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(origin));
+ }
+
+ if ((ulong)offset > (uint)_content.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+
+ _position = (int)offset;
+ return offset;
+ }
+
+ public override void SetLength(long value) => throw new NotSupportedException();
+
+ public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+
+ public override void Write(ReadOnlySpan buffer) => throw new NotSupportedException();
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw new NotSupportedException();
+
+ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => throw new NotSupportedException();
+
+ public override void WriteByte(byte value) => throw new NotSupportedException();
+ }
+}
diff --git a/src/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.cs b/src/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.cs
new file mode 100644
index 000000000000..18b36eed9f0e
--- /dev/null
+++ b/src/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.cs
@@ -0,0 +1,55 @@
+// 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.IO;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+
+namespace System.Net.Http
+{
+ public sealed class Utf8StringContent : HttpContent
+ {
+ private const string DefaultMediaType = "text/plain";
+
+ private readonly Utf8String _content;
+
+ public Utf8StringContent(Utf8String content)
+ : this(content, mediaType: null)
+ {
+ }
+
+ public Utf8StringContent(Utf8String content, string mediaType)
+ {
+ if (content is null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+
+ _content = content;
+
+ // Initialize the 'Content-Type' header with information provided by parameters.
+
+ Headers.ContentType = new MediaTypeHeaderValue(mediaType ?? DefaultMediaType)
+ {
+ CharSet = "utf-8" // Encoding.UTF8.WebName
+ };
+ }
+
+ protected override Task CreateContentReadStreamAsync()
+ {
+ return Task.FromResult(new Utf8StringStream(_content));
+ }
+
+ protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ return stream.WriteAsync(_content.AsMemoryBytes()).AsTask();
+ }
+
+ protected override bool TryComputeLength(out long length)
+ {
+ length = _content.Length;
+ return true;
+ }
+ }
+}
diff --git a/src/System.Utf8String.Experimental/tests/Configurations.props b/src/System.Utf8String.Experimental/tests/Configurations.props
new file mode 100644
index 000000000000..d3ac8a63c74a
--- /dev/null
+++ b/src/System.Utf8String.Experimental/tests/Configurations.props
@@ -0,0 +1,8 @@
+
+
+
+
+ netcoreapp;
+
+
+
diff --git a/src/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj b/src/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj
new file mode 100644
index 000000000000..5ce4935e8ebf
--- /dev/null
+++ b/src/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+ true
+ {72E9FB32-4692-4692-A10B-9F053F8F1A88}
+ true
+ netcoreapp-Debug;netcoreapp-Release;
+ true
+ System
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/System.Utf8String.Experimental/tests/System/Char8Tests.cs b/src/System.Utf8String.Experimental/tests/System/Char8Tests.cs
new file mode 100644
index 000000000000..7f3a132af7f6
--- /dev/null
+++ b/src/System.Utf8String.Experimental/tests/System/Char8Tests.cs
@@ -0,0 +1,112 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using Xunit;
+
+namespace System.Tests
+{
+ public unsafe partial class Char8Tests
+ {
+ [Theory]
+ [InlineData(10, 20, -1)]
+ [InlineData(20, 10, 1)]
+ [InlineData(30, 30, 0)]
+ public static void CompareTo(Char8 a, Char8 b, int expectedSign)
+ {
+ Assert.Equal(expectedSign, Math.Sign(a.CompareTo(b)));
+ }
+
+ [Theory]
+ [InlineData(-1)]
+ [InlineData(0xFF)]
+ [InlineData(0x80)]
+ [InlineData(0x00)]
+ [InlineData(0x1234)]
+ [InlineData(0x12345678)]
+ [InlineData(0x1234567812345678)]
+ public static void CastOperators(long value)
+ {
+ // Only the low byte is preserved when casting through Char8.
+
+ Assert.Equal((byte)value, (byte)(Char8)(byte)value);
+ Assert.Equal((sbyte)value, (sbyte)(Char8)(sbyte)value);
+ Assert.Equal((char)(value & 0xFF), (char)(Char8)(char)value);
+ Assert.Equal((short)(value & 0xFF), (short)(Char8)(short)value);
+ Assert.Equal((ushort)(value & 0xFF), (ushort)(Char8)(ushort)value);
+ Assert.Equal((int)(value & 0xFF), (int)(Char8)(int)value);
+ Assert.Equal((uint)(value & 0xFF), (uint)(Char8)(uint)value);
+ Assert.Equal((long)(value & 0xFF), (long)(Char8)(long)value);
+ Assert.Equal((ulong)(value & 0xFF), (ulong)(Char8)(ulong)value);
+ }
+
+ [Fact]
+ public static void EqualsObject()
+ {
+ Assert.False(((Char8)42).Equals((object)null));
+ Assert.False(((Char8)42).Equals((object)(int)42));
+ Assert.False(((Char8)42).Equals((object)(Char8)43));
+ Assert.True(((Char8)42).Equals((object)(Char8)42));
+ }
+
+ [Fact]
+ public static void EqualsChar8()
+ {
+ Assert.True(((Char8)42).Equals(42)); // implicit cast to Char8
+ Assert.False(((Char8)42).Equals(43)); // implicit cast to Char8
+ }
+
+ [Fact]
+ public static void GetHashCode_ReturnsValue()
+ {
+ for (int i = 0; i <= byte.MaxValue; i++)
+ {
+ Assert.Equal(i, ((Char8)i).GetHashCode());
+ }
+ }
+
+ [Theory]
+ [InlineData(10, 20, false)]
+ [InlineData(20, 10, false)]
+ [InlineData(30, 30, true)]
+ public static void OperatorEquals(Char8 a, Char8 b, bool expected)
+ {
+ Assert.Equal(expected, (Char8)a == (Char8)b);
+ Assert.NotEqual(expected, (Char8)a != (Char8)b);
+ }
+
+ [Theory]
+ [InlineData(10, 20, true)]
+ [InlineData(20, 10, false)]
+ [InlineData(29, 30, true)]
+ [InlineData(30, 30, false)]
+ [InlineData(31, 30, false)]
+ public static void OperatorLessThan(Char8 a, Char8 b, bool expected)
+ {
+ Assert.Equal(expected, (Char8)a < (Char8)b);
+ Assert.NotEqual(expected, (Char8)a >= (Char8)b);
+ }
+
+ [Theory]
+ [InlineData(10, 20, false)]
+ [InlineData(20, 10, true)]
+ [InlineData(29, 30, false)]
+ [InlineData(30, 30, false)]
+ [InlineData(31, 30, true)]
+ public static void OperatorGreaterThan(Char8 a, Char8 b, bool expected)
+ {
+ Assert.Equal(expected, (Char8)a > (Char8)b);
+ Assert.NotEqual(expected, (Char8)a <= (Char8)b);
+ }
+
+ [Fact]
+ public static void ToString_ReturnsHexValue()
+ {
+ for (int i = 0; i <= byte.MaxValue; i++)
+ {
+ Assert.Equal(i.ToString("X2", CultureInfo.InvariantCulture), ((Char8)i).ToString());
+ }
+ }
+ }
+}
diff --git a/src/System.Utf8String.Experimental/tests/System/MemoryTests.cs b/src/System.Utf8String.Experimental/tests/System/MemoryTests.cs
new file mode 100644
index 000000000000..2f168bf7c8ae
--- /dev/null
+++ b/src/System.Utf8String.Experimental/tests/System/MemoryTests.cs
@@ -0,0 +1,156 @@
+// 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.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Tests
+{
+ public partial class MemoryTests
+ {
+ [Fact]
+ public static void MemoryMarshal_TryGetArrayOfByte_Utf8String()
+ {
+ ReadOnlyMemory rom = u8("Hello").AsMemoryBytes();
+
+ Assert.False(MemoryMarshal.TryGetArray(rom, out ArraySegment segment));
+ Assert.True(default(ArraySegment).Equals(segment));
+ }
+
+ [Fact]
+ public static void MemoryMarshal_TryGetArrayOfChar8_Utf8String()
+ {
+ ReadOnlyMemory rom = u8("Hello").AsMemory();
+
+ Assert.False(MemoryMarshal.TryGetArray(rom, out ArraySegment segment));
+ Assert.True(default(ArraySegment).Equals(segment));
+ }
+
+ [Fact]
+ public unsafe static void MemoryOfByte_WithUtf8String_Pin()
+ {
+ Utf8String theString = u8("Hello");
+ ReadOnlyMemory rom = theString.AsMemoryBytes();
+ MemoryHandle memHandle = default;
+ try
+ {
+ memHandle = Unsafe.As, Memory>(ref rom).Pin();
+ Assert.True(memHandle.Pointer == Unsafe.AsPointer(ref Unsafe.AsRef(in theString.GetPinnableReference())));
+ }
+ finally
+ {
+ memHandle.Dispose();
+ }
+ }
+
+ [Fact]
+ public static void MemoryOfByte_WithUtf8String_ToString()
+ {
+ ReadOnlyMemory rom = u8("Hello").AsMemoryBytes();
+ Assert.Equal("System.Memory[5]", Unsafe.As, Memory>(ref rom).ToString());
+ }
+
+ [Fact]
+ public unsafe static void MemoryOfChar8_WithUtf8String_Pin()
+ {
+ Utf8String theString = u8("Hello");
+ ReadOnlyMemory rom = theString.AsMemory();
+ MemoryHandle memHandle = default;
+ try
+ {
+ memHandle = Unsafe.As, Memory>(ref rom).Pin();
+ Assert.True(memHandle.Pointer == Unsafe.AsPointer(ref Unsafe.AsRef(in theString.GetPinnableReference())));
+ }
+ finally
+ {
+ memHandle.Dispose();
+ }
+ }
+
+ [Fact]
+ public static void MemoryOfChar8_WithUtf8String_ToString()
+ {
+ ReadOnlyMemory rom = u8("Hello").AsMemory();
+ Assert.Equal("Hello", Unsafe.As, Memory>(ref rom).ToString());
+ }
+
+ [Fact]
+ public unsafe static void ReadOnlyMemoryOfByte_WithUtf8String_Pin()
+ {
+ Utf8String theString = u8("Hello");
+ ReadOnlyMemory rom = theString.AsMemoryBytes();
+ MemoryHandle memHandle = default;
+ try
+ {
+ memHandle = rom.Pin();
+ Assert.True(memHandle.Pointer == Unsafe.AsPointer(ref Unsafe.AsRef(in theString.GetPinnableReference())));
+ }
+ finally
+ {
+ memHandle.Dispose();
+ }
+ }
+
+ [Fact]
+ public static void ReadOnlyMemoryOfByte_WithUtf8String_ToString()
+ {
+ Assert.Equal("System.ReadOnlyMemory[5]", u8("Hello").AsMemoryBytes().ToString());
+ }
+
+ [Fact]
+ public unsafe static void ReadOnlyMemoryOfChar8_WithUtf8String_Pin()
+ {
+ Utf8String theString = u8("Hello");
+ ReadOnlyMemory rom = theString.AsMemory();
+ MemoryHandle memHandle = default;
+ try
+ {
+ memHandle = rom.Pin();
+ Assert.True(memHandle.Pointer == Unsafe.AsPointer(ref Unsafe.AsRef(in theString.GetPinnableReference())));
+ }
+ finally
+ {
+ memHandle.Dispose();
+ }
+ }
+
+ [Fact]
+ public static void ReadOnlyMemoryOfChar8_WithUtf8String_ToString()
+ {
+ Assert.Equal("Hello", u8("Hello").AsMemory().ToString());
+ }
+
+ [Fact]
+ public static void ReadOnlySpanOfByte_ToString()
+ {
+ ReadOnlySpan span = stackalloc byte[] { (byte)'H', (byte)'i' };
+ Assert.Equal("System.ReadOnlySpan[2]", span.ToString());
+ }
+
+ [Fact]
+ public static void ReadOnlySpanOfChar8_ToString()
+ {
+ ReadOnlySpan span = stackalloc Char8[] { (Char8)'H', (Char8)'i' };
+ Assert.Equal("Hi", span.ToString());
+ }
+
+ [Fact]
+ public static void SpanOfByte_ToString()
+ {
+ Span span = stackalloc byte[] { (byte)'H', (byte)'i' };
+ Assert.Equal("System.Span[2]", span.ToString());
+ }
+
+ [Fact]
+ public static void SpanOfChar8_ToString()
+ {
+ Span span = stackalloc Char8[] { (Char8)'H', (Char8)'i' };
+ Assert.Equal("Hi", span.ToString());
+ }
+ }
+}
diff --git a/src/System.Utf8String.Experimental/tests/System/Net/Http/Utf8StringContentTests.cs b/src/System.Utf8String.Experimental/tests/System/Net/Http/Utf8StringContentTests.cs
new file mode 100644
index 000000000000..87c31c6dce39
--- /dev/null
+++ b/src/System.Utf8String.Experimental/tests/System/Net/Http/Utf8StringContentTests.cs
@@ -0,0 +1,44 @@
+// 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.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Net.Http.Tests
+{
+ public partial class Utf8StringContentTests
+ {
+ [Fact]
+ public static void Ctor_NullContent_Throws()
+ {
+ Assert.Throws("content", () => new Utf8StringContent(null));
+ Assert.Throws("content", () => new Utf8StringContent(null, "application/json"));
+ }
+
+ [Theory]
+ [InlineData(null, "text/plain")]
+ [InlineData("application/json", "application/json")]
+ public static void Ctor_SetsContentTypeHeader(string mediaTypeForCtor, string expectedMediaType)
+ {
+ HttpContent httpContent = new Utf8StringContent(u8("Hello"), mediaTypeForCtor);
+
+ Assert.Equal(expectedMediaType, httpContent.Headers.ContentType.MediaType);
+ Assert.Equal(Encoding.UTF8.WebName, httpContent.Headers.ContentType.CharSet);
+ }
+
+ [Fact]
+ public static async Task Ctor_GetStream()
+ {
+ MemoryStream memoryStream = new MemoryStream();
+
+ await new Utf8StringContent(u8("Hello")).CopyToAsync(memoryStream);
+
+ Assert.Equal(u8("Hello").ToByteArray(), memoryStream.ToArray());
+ }
+ }
+}
diff --git a/src/System.Utf8String.Experimental/tests/System/ReflectionTests.cs b/src/System.Utf8String.Experimental/tests/System/ReflectionTests.cs
new file mode 100644
index 000000000000..4cbd4cf192d2
--- /dev/null
+++ b/src/System.Utf8String.Experimental/tests/System/ReflectionTests.cs
@@ -0,0 +1,36 @@
+// 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.Runtime.Serialization;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Tests
+{
+ public partial class ReflectionTests
+ {
+ [Fact]
+ public static void ActivatorCreateInstance_CanCallParameterfulCtor()
+ {
+ Utf8String theString = (Utf8String)Activator.CreateInstance(typeof(Utf8String), "Hello");
+ Assert.Equal(u8("Hello"), theString);
+ }
+
+ [Fact]
+ public static void ActivatorCreateInstance_CannotCallParameterlessCtor()
+ {
+ Assert.Throws(() => Activator.CreateInstance(typeof(Utf8String)));
+ }
+
+ [Fact]
+ public static void FormatterServices_GetUninitializedObject_Throws()
+ {
+ // Like String, shouldn't be able to create an uninitialized Utf8String.
+
+ Assert.Throws(() => FormatterServices.GetSafeUninitializedObject(typeof(Utf8String)));
+ Assert.Throws(() => FormatterServices.GetUninitializedObject(typeof(Utf8String)));
+ }
+ }
+}
diff --git a/src/System.Utf8String.Experimental/tests/System/Utf8ExtensionsTests.cs b/src/System.Utf8String.Experimental/tests/System/Utf8ExtensionsTests.cs
new file mode 100644
index 000000000000..a3c218230a3c
--- /dev/null
+++ b/src/System.Utf8String.Experimental/tests/System/Utf8ExtensionsTests.cs
@@ -0,0 +1,209 @@
+// 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.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Tests
+{
+ public partial class Utf8ExtensionsTests
+ {
+ [Fact]
+ public unsafe void AsBytes_FromSpan_Default()
+ {
+ // First, a default span should become a default span.
+
+ Assert.True(default(ReadOnlySpan) == new ReadOnlySpan().AsBytes());
+
+ // Next, an empty but non-default span should become an empty but non-default span.
+
+ Assert.True(new ReadOnlySpan((void*)0x12345, 0) == new ReadOnlySpan((void*)0x12345, 0).AsBytes());
+
+ // 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)theString).AsBytes());
+ }
+
+ [Fact]
+ public void AsBytes_FromUtf8String()
+ {
+ Assert.True(default(ReadOnlySpan) == ((Utf8String)null).AsBytes());
+
+ Utf8String theString = u8("Hello");
+ Assert.True(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in theString.GetPinnableReference()), 5) == theString.AsBytes());
+ }
+
+ [Fact]
+ public void AsBytes_FromUtf8String_WithStart()
+ {
+ Assert.True(default(ReadOnlySpan) == ((Utf8String)null).AsBytes(0));
+ Assert.True(u8("Hello").AsBytes(5).IsEmpty);
+
+ SpanAssert.Equal(new byte[] { (byte)'e', (byte)'l', (byte)'l', (byte)'o' }, u8("Hello").AsBytes(1));
+ }
+
+ [Fact]
+ public void AsBytes_FromUtf8String_WithStart_ArgOutOfRange()
+ {
+ Assert.Throws("start", () => ((Utf8String)null).AsBytes(1));
+ Assert.Throws("start", () => u8("Hello").AsBytes(-1));
+ Assert.Throws("start", () => u8("Hello").AsBytes(6));
+ }
+
+ [Fact]
+ public void AsBytes_FromUtf8String_WithStartAndLength()
+ {
+ Assert.True(default(ReadOnlySpan) == ((Utf8String)null).AsBytes(0, 0));
+ Assert.True(u8("Hello").AsBytes(5, 0).IsEmpty);
+
+ SpanAssert.Equal(new byte[] { (byte)'e', (byte)'l', (byte)'l' }, u8("Hello").AsBytes(1, 3));
+ }
+
+ [Fact]
+ public void AsBytes_FromUtf8String_WithStartAndLength_ArgOutOfRange()
+ {
+ Assert.Throws("start", () => ((Utf8String)null).AsBytes(0, 1));
+ Assert.Throws("start", () => ((Utf8String)null).AsBytes(1, 0));
+ Assert.Throws("start", () => u8("Hello").AsBytes(5, 1));
+ Assert.Throws("start", () => u8("Hello").AsBytes(4, -2));
+ }
+
+ [Fact]
+ public void AsMemory_FromUtf8String()
+ {
+ Assert.True(default(ReadOnlyMemory).Equals(((Utf8String)null).AsMemory()));
+
+ Utf8String theString = u8("Hello");
+ Assert.True(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in theString.GetPinnableReference())), 5) == theString.AsMemory().Span);
+ }
+
+ [Fact]
+ public void AsMemory_FromUtf8String_WithStart()
+ {
+ Assert.True(default(ReadOnlyMemory).Equals(((Utf8String)null).AsMemory(0)));
+ Assert.True(u8("Hello").AsMemory(5).IsEmpty);
+
+ SpanAssert.Equal(new Char8[] { (Char8)'e', (Char8)'l', (Char8)'l', (Char8)'o' }, u8("Hello").AsMemory(1).Span);
+ }
+
+ [Fact]
+ public void AsMemory_FromUtf8String_WithStart_ArgOutOfRange()
+ {
+ Assert.Throws("start", () => ((Utf8String)null).AsMemory(1));
+ Assert.Throws("start", () => u8("Hello").AsMemory(-1));
+ Assert.Throws("start", () => u8("Hello").AsMemory(6));
+ }
+
+ [Fact]
+ public void AsMemory_FromUtf8String_WithStartAndLength()
+ {
+ Assert.True(default(ReadOnlyMemory).Equals(((Utf8String)null).AsMemory(0, 0)));
+ Assert.True(u8("Hello").AsMemory(5, 0).IsEmpty);
+
+ SpanAssert.Equal(new Char8[] { (Char8)'e', (Char8)'l', (Char8)'l' }, u8("Hello").AsMemory(1, 3).Span);
+ }
+
+ [Fact]
+ public void AsMemory_FromUtf8String_WithStartAndLength_ArgOutOfRange()
+ {
+ Assert.Throws("start", () => ((Utf8String)null).AsMemory(0, 1));
+ Assert.Throws("start", () => ((Utf8String)null).AsMemory(1, 0));
+ Assert.Throws("start", () => u8("Hello").AsMemory(5, 1));
+ Assert.Throws("start", () => u8("Hello").AsMemory(4, -2));
+ }
+
+ [Fact]
+ public void AsMemoryBytes_FromUtf8String()
+ {
+ Assert.True(default(ReadOnlyMemory).Equals(((Utf8String)null).AsMemoryBytes()));
+
+ Utf8String theString = u8("Hello");
+ Assert.True(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in theString.GetPinnableReference()), 5) == theString.AsMemoryBytes().Span);
+ }
+
+ [Fact]
+ public void AsMemoryBytes_FromUtf8String_WithStart()
+ {
+ Assert.True(default(ReadOnlyMemory).Equals(((Utf8String)null).AsMemoryBytes(0)));
+ Assert.True(u8("Hello").AsMemoryBytes(5).IsEmpty);
+
+ SpanAssert.Equal(new byte[] { (byte)'e', (byte)'l', (byte)'l', (byte)'o' }, u8("Hello").AsMemoryBytes(1).Span);
+ }
+
+ [Fact]
+ public void AsMemoryBytes_FromUtf8String_WithStart_ArgOutOfRange()
+ {
+ Assert.Throws("start", () => ((Utf8String)null).AsMemoryBytes(1));
+ Assert.Throws("start", () => u8("Hello").AsMemoryBytes(-1));
+ Assert.Throws("start", () => u8("Hello").AsMemoryBytes(6));
+ }
+
+ [Fact]
+ public void AsMemoryBytes_FromUtf8String_WithStartAndLength()
+ {
+ Assert.True(default(ReadOnlyMemory).Equals(((Utf8String)null).AsMemoryBytes(0, 0)));
+ Assert.True(u8("Hello").AsMemoryBytes(5, 0).IsEmpty);
+
+ SpanAssert.Equal(new byte[] { (byte)'e', (byte)'l', (byte)'l' }, u8("Hello").AsMemoryBytes(1, 3).Span);
+ }
+
+ [Fact]
+ public void AsMemoryBytes_FromUtf8String_WithStartAndLength_ArgOutOfRange()
+ {
+ Assert.Throws("start", () => ((Utf8String)null).AsMemoryBytes(0, 1));
+ Assert.Throws("start", () => ((Utf8String)null).AsMemoryBytes(1, 0));
+ Assert.Throws("start", () => u8("Hello").AsMemoryBytes(5, 1));
+ Assert.Throws("start", () => u8("Hello").AsMemoryBytes(4, -2));
+ }
+
+ [Fact]
+ public void AsSpan_FromUtf8String()
+ {
+ Assert.True(default(ReadOnlySpan) == ((Utf8String)null).AsSpan());
+
+ Utf8String theString = u8("Hello");
+ Assert.True(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in theString.GetPinnableReference())), 5) == theString.AsSpan());
+ }
+
+ [Fact]
+ public void AsSpan_FromUtf8String_WithStart()
+ {
+ Assert.True(default(ReadOnlySpan) == ((Utf8String)null).AsSpan(0));
+ Assert.True(u8("Hello").AsSpan(5).IsEmpty);
+
+ SpanAssert.Equal(new Char8[] { (Char8)'e', (Char8)'l', (Char8)'l', (Char8)'o' }, u8("Hello").AsSpan(1));
+ }
+
+ [Fact]
+ public void AsSpan_FromUtf8String_WithStart_ArgOutOfRange()
+ {
+ Assert.Throws("start", () => ((Utf8String)null).AsSpan(1));
+ Assert.Throws("start", () => u8("Hello").AsSpan(-1));
+ Assert.Throws("start", () => u8("Hello").AsSpan(6));
+ }
+
+ [Fact]
+ public void AsSpan_FromUtf8String_WithStartAndLength()
+ {
+ Assert.True(default(ReadOnlySpan) == ((Utf8String)null).AsSpan(0, 0));
+ Assert.True(u8("Hello").AsSpan(5, 0).IsEmpty);
+
+ SpanAssert.Equal(new Char8[] { (Char8)'e', (Char8)'l', (Char8)'l' }, u8("Hello").AsSpan(1, 3));
+ }
+
+ [Fact]
+ public void AsSpan_FromUtf8String_WithStartAndLength_ArgOutOfRange()
+ {
+ Assert.Throws("start", () => ((Utf8String)null).AsSpan(0, 1));
+ Assert.Throws("start", () => ((Utf8String)null).AsSpan(1, 0));
+ Assert.Throws("start", () => u8("Hello").AsSpan(5, 1));
+ Assert.Throws("start", () => u8("Hello").AsSpan(4, -2));
+ }
+ }
+}
diff --git a/src/System.Utf8String.Experimental/tests/System/Utf8StringTests.Ctor.cs b/src/System.Utf8String.Experimental/tests/System/Utf8StringTests.Ctor.cs
new file mode 100644
index 000000000000..48628033c2c0
--- /dev/null
+++ b/src/System.Utf8String.Experimental/tests/System/Utf8StringTests.Ctor.cs
@@ -0,0 +1,209 @@
+// 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.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Tests
+{
+ public unsafe partial class Utf8StringTests
+ {
+ [Fact]
+ public static void Ctor_ByteArrayOffset_Empty_ReturnsEmpty()
+ {
+ byte[] inputData = new byte[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
+ Assert.Same(Utf8String.Empty, new Utf8String(inputData, 3, 0));
+ }
+
+ [Fact]
+ public static void Ctor_ByteArrayOffset_ValidData_ReturnsOriginalContents()
+ {
+ byte[] inputData = new byte[] { (byte)'x', (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)'x' };
+ Utf8String expected = u8("Hello");
+
+ var actual = new Utf8String(inputData, 1, 5);
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public static void Ctor_ByteArrayOffset_InvalidData_FixesUpData()
+ {
+ 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);
+ }
+
+ [Fact]
+ public static void Ctor_BytePointer_NullOrEmpty_ReturnsEmpty()
+ {
+ byte[] inputData = new byte[] { 0 }; // standalone null byte
+
+ using (BoundedMemory 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_ValidData_ReturnsOriginalContents()
+ {
+ byte[] inputData = new byte[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)'\0' };
+
+ using (BoundedMemory boundedMemory = BoundedMemory.AllocateFromExistingData(inputData))
+ {
+ Assert.Equal(u8("Hello"), new Utf8String((byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(boundedMemory.Span))));
+ }
+ }
+
+ [Fact]
+ public static void Ctor_BytePointer_InvalidData_FixesUpData()
+ {
+ byte[] inputData = new byte[] { (byte)'H', (byte)'e', (byte)0xFF, (byte)'l', (byte)'o', (byte)'\0' };
+
+ using (BoundedMemory boundedMemory = BoundedMemory.AllocateFromExistingData(inputData))
+ {
+ Assert.Equal(u8("He\uFFFDlo"), new Utf8String((byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(boundedMemory.Span))));
+ }
+ }
+
+ [Fact]
+ public static void Ctor_ByteSpan_Empty_ReturnsEmpty()
+ {
+ Assert.Same(Utf8String.Empty, new Utf8String(ReadOnlySpan.Empty));
+ }
+
+ [Fact]
+ public static void Ctor_ByteSpan_ValidData_ReturnsOriginalContents()
+ {
+ byte[] inputData = new byte[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
+ Utf8String expected = u8("Hello");
+
+ var actual = new Utf8String(inputData.AsSpan());
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public static void Ctor_ByteSpan_InvalidData_FixesUpData()
+ {
+ 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);
+ }
+
+ [Fact]
+ public static void Ctor_CharArrayOffset_Empty_ReturnsEmpty()
+ {
+ char[] inputData = "Hello".ToCharArray();
+ Assert.Same(Utf8String.Empty, new Utf8String(inputData, 3, 0));
+ }
+
+ [Fact]
+ public static void Ctor_CharArrayOffset_ValidData_ReturnsOriginalContents()
+ {
+ char[] inputData = "xHellox".ToCharArray();
+ Utf8String expected = u8("Hello");
+
+ var actual = new Utf8String(inputData, 1, 5);
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public static void Ctor_CharArrayOffset_InvalidData_FixesUpData()
+ {
+ char[] inputData = new char[] { 'x', 'H', 'e', '\uD800', 'l', 'o', 'x' };
+ Utf8String expected = u8("He\uFFFDlo");
+
+ var actual = new Utf8String(inputData, 1, 5);
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public static void Ctor_CharPointer_NullOrEmpty_ReturnsEmpty()
+ {
+ char[] inputData = new char[] { '\0' }; // standalone null char
+
+ using (BoundedMemory 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' };
+
+ using (BoundedMemory boundedMemory = BoundedMemory.AllocateFromExistingData(inputData))
+ {
+ Assert.Equal(u8("Hello"), new Utf8String((char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(boundedMemory.Span))));
+ }
+ }
+
+ [Fact]
+ public static void Ctor_CharPointer_InvalidData_FixesUpData()
+ {
+ char[] inputData = new char[] { 'H', 'e', '\uD800', 'l', 'o', '\0' }; // standalone surrogate
+
+ using (BoundedMemory boundedMemory = BoundedMemory.AllocateFromExistingData(inputData))
+ {
+ Assert.Equal(u8("He\uFFFDlo"), new Utf8String((char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(boundedMemory.Span))));
+ }
+ }
+
+ [Fact]
+ public static void Ctor_CharSpan_Empty_ReturnsEmpty()
+ {
+ Assert.Same(Utf8String.Empty, new Utf8String(ReadOnlySpan.Empty));
+ }
+
+ [Fact]
+ public static void Ctor_CharSpan_ValidData_ReturnsOriginalContents()
+ {
+ char[] inputData = "Hello".ToCharArray();
+ Utf8String expected = u8("Hello");
+
+ var actual = new Utf8String(inputData.AsSpan());
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public static void Ctor_CharSpan_InvalidData_FixesUpData()
+ {
+ char[] inputData = new char[] { 'H', 'e', '\uD800', 'l', 'o' };
+ Utf8String expected = u8("He\uFFFDlo");
+
+ var actual = new Utf8String(inputData.AsSpan());
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public static void Ctor_String_NullOrEmpty_ReturnsEmpty()
+ {
+ Assert.Same(Utf8String.Empty, new Utf8String((string)null));
+ Assert.Same(Utf8String.Empty, new Utf8String(string.Empty));
+ }
+
+ [Fact]
+ public static void Ctor_String_ValidData_ReturnsOriginalContents()
+ {
+ Assert.Equal(u8("Hello"), new Utf8String("Hello"));
+ }
+
+ [Fact]
+ public static void Ctor_String_InvalidData_FixesUpData()
+ {
+ Assert.Equal(u8("He\uFFFDlo"), new Utf8String("He\uD800lo"));
+ }
+ }
+}
diff --git a/src/System.Utf8String.Experimental/tests/System/Utf8StringTests.Searching.cs b/src/System.Utf8String.Experimental/tests/System/Utf8StringTests.Searching.cs
new file mode 100644
index 000000000000..3359cef7fa1d
--- /dev/null
+++ b/src/System.Utf8String.Experimental/tests/System/Utf8StringTests.Searching.cs
@@ -0,0 +1,97 @@
+// 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.Text;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Tests
+{
+ public unsafe partial class Utf8StringTests
+ {
+ [Theory]
+ [MemberData(nameof(IndexOfTestData))]
+ public static void Contains_And_IndexOf_CharRune_Ordinal(Utf8String utf8String, Rune searchValue, int expectedIndex)
+ {
+ // Contains
+
+ if (searchValue.IsBmp)
+ {
+ Assert.Equal(expectedIndex >= 0, utf8String.Contains((char)searchValue.Value));
+ }
+ Assert.Equal(expectedIndex >= 0, utf8String.Contains(searchValue));
+
+ // IndexOf
+
+ if (searchValue.IsBmp)
+ {
+ Assert.Equal(expectedIndex, utf8String.IndexOf((char)searchValue.Value));
+ }
+ Assert.Equal(expectedIndex, utf8String.IndexOf(searchValue));
+ }
+
+ [Theory]
+ [MemberData(nameof(IndexOfTestData))]
+ public static void StartsWith_And_EndsWith_CharRune_Ordinal(Utf8String utf8String, Rune searchValue, int expectedIndex)
+ {
+ // StartsWith
+
+ if (searchValue.IsBmp)
+ {
+ Assert.Equal(expectedIndex == 0, utf8String.StartsWith((char)searchValue.Value));
+ }
+ Assert.Equal(expectedIndex == 0, utf8String.StartsWith(searchValue));
+
+ // EndsWith
+
+ bool endsWithExpectedValue = (expectedIndex >= 0) && (expectedIndex + searchValue.Utf8SequenceLength) == utf8String.Length;
+
+ if (searchValue.IsBmp)
+ {
+ Assert.Equal(endsWithExpectedValue, utf8String.EndsWith((char)searchValue.Value));
+ }
+ Assert.Equal(endsWithExpectedValue, utf8String.EndsWith(searchValue));
+ }
+
+ [Fact]
+ public static void Searching_StandaloneSurrogate_Fails()
+ {
+ Utf8String utf8String = u8("\ud800\udfff");
+
+ Assert.False(utf8String.Contains('\ud800'));
+ Assert.False(utf8String.Contains('\udfff'));
+
+ Assert.Equal(-1, utf8String.IndexOf('\ud800'));
+ Assert.Equal(-1, utf8String.IndexOf('\udfff'));
+
+ Assert.False(utf8String.StartsWith('\ud800'));
+ Assert.False(utf8String.StartsWith('\udfff'));
+
+ Assert.False(utf8String.EndsWith('\ud800'));
+ Assert.False(utf8String.EndsWith('\udfff'));
+ }
+
+ public static IEnumerable