| #region Copyright notice and license |
| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file or at |
| // https://developers.google.com/open-source/licenses/bsd |
| #endregion |
| |
| using System; |
| using System.Text; |
| using NUnit.Framework; |
| using System.IO; |
| using System.Collections.Generic; |
| using System.Collections; |
| using System.Linq; |
| using System.Buffers; |
| using System.Runtime.InteropServices; |
| using System.Threading; |
| using System.Runtime.CompilerServices; |
| using System.Threading.Tasks; |
| |
| namespace Google.Protobuf |
| { |
| public class ByteStringTest |
| { |
| [Test] |
| public void Equality() |
| { |
| ByteString b1 = ByteString.CopyFrom(1, 2, 3); |
| ByteString b2 = ByteString.CopyFrom(1, 2, 3); |
| ByteString b3 = ByteString.CopyFrom(1, 2, 4); |
| ByteString b4 = ByteString.CopyFrom(1, 2, 3, 4); |
| EqualityTester.AssertEquality(b1, b1); |
| EqualityTester.AssertEquality(b1, b2); |
| EqualityTester.AssertInequality(b1, b3); |
| EqualityTester.AssertInequality(b1, b4); |
| EqualityTester.AssertInequality(b1, null); |
| EqualityTester.AssertEquality(ByteString.Empty, ByteString.Empty); |
| #pragma warning disable 1718 // Deliberately calling ==(b1, b1) and !=(b1, b1) |
| Assert.IsTrue(b1 == b1); |
| Assert.IsTrue(b1 == b2); |
| Assert.IsFalse(b1 == b3); |
| Assert.IsFalse(b1 == b4); |
| Assert.IsFalse(b1 == null); |
| Assert.IsTrue((ByteString) null == null); |
| Assert.IsFalse(b1 != b1); |
| Assert.IsFalse(b1 != b2); |
| Assert.IsTrue(ByteString.Empty == ByteString.Empty); |
| #pragma warning restore 1718 |
| Assert.IsTrue(b1 != b3); |
| Assert.IsTrue(b1 != b4); |
| Assert.IsTrue(b1 != null); |
| Assert.IsFalse((ByteString) null != null); |
| } |
| |
| [Test] |
| public void EmptyByteStringHasZeroSize() |
| { |
| Assert.AreEqual(0, ByteString.Empty.Length); |
| } |
| |
| [Test] |
| public void CopyFromStringWithExplicitEncoding() |
| { |
| ByteString bs = ByteString.CopyFrom("AB", Encoding.Unicode); |
| Assert.AreEqual(4, bs.Length); |
| Assert.AreEqual(65, bs[0]); |
| Assert.AreEqual(0, bs[1]); |
| Assert.AreEqual(66, bs[2]); |
| Assert.AreEqual(0, bs[3]); |
| } |
| |
| [Test] |
| public void IsEmptyWhenEmpty() |
| { |
| Assert.IsTrue(ByteString.CopyFromUtf8("").IsEmpty); |
| } |
| |
| [Test] |
| public void IsEmptyWhenNotEmpty() |
| { |
| Assert.IsFalse(ByteString.CopyFromUtf8("X").IsEmpty); |
| } |
| |
| [Test] |
| public void CopyFromByteArrayCopiesContents() |
| { |
| byte[] data = new byte[1]; |
| data[0] = 10; |
| ByteString bs = ByteString.CopyFrom(data); |
| Assert.AreEqual(10, bs[0]); |
| data[0] = 5; |
| Assert.AreEqual(10, bs[0]); |
| } |
| |
| [Test] |
| public void CopyFromReadOnlySpanCopiesContents() |
| { |
| byte[] data = new byte[1]; |
| data[0] = 10; |
| ReadOnlySpan<byte> byteSpan = data; |
| var bs = ByteString.CopyFrom(byteSpan); |
| Assert.AreEqual(10, bs[0]); |
| data[0] = 5; |
| Assert.AreEqual(10, bs[0]); |
| } |
| |
| [Test] |
| public void ToByteArrayCopiesContents() |
| { |
| ByteString bs = ByteString.CopyFromUtf8("Hello"); |
| byte[] data = bs.ToByteArray(); |
| Assert.AreEqual((byte)'H', data[0]); |
| Assert.AreEqual((byte)'H', bs[0]); |
| data[0] = 0; |
| Assert.AreEqual(0, data[0]); |
| Assert.AreEqual((byte)'H', bs[0]); |
| } |
| |
| [Test] |
| public void CopyFromUtf8UsesUtf8() |
| { |
| ByteString bs = ByteString.CopyFromUtf8("\u20ac"); |
| Assert.AreEqual(3, bs.Length); |
| Assert.AreEqual(0xe2, bs[0]); |
| Assert.AreEqual(0x82, bs[1]); |
| Assert.AreEqual(0xac, bs[2]); |
| } |
| |
| [Test] |
| public void CopyFromPortion() |
| { |
| byte[] data = new byte[] {0, 1, 2, 3, 4, 5, 6}; |
| ByteString bs = ByteString.CopyFrom(data, 2, 3); |
| Assert.AreEqual(3, bs.Length); |
| Assert.AreEqual(2, bs[0]); |
| Assert.AreEqual(3, bs[1]); |
| } |
| |
| [Test] |
| public void CopyTo() |
| { |
| byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; |
| ByteString bs = ByteString.CopyFrom(data); |
| |
| byte[] dest = new byte[data.Length]; |
| bs.CopyTo(dest, 0); |
| |
| CollectionAssert.AreEqual(data, dest); |
| } |
| |
| [Test] |
| public void GetEnumerator() |
| { |
| byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; |
| ByteString bs = ByteString.CopyFrom(data); |
| |
| IEnumerator<byte> genericEnumerator = bs.GetEnumerator(); |
| Assert.IsTrue(genericEnumerator.MoveNext()); |
| Assert.AreEqual(0, genericEnumerator.Current); |
| |
| IEnumerator enumerator = ((IEnumerable)bs).GetEnumerator(); |
| Assert.IsTrue(enumerator.MoveNext()); |
| Assert.AreEqual(0, enumerator.Current); |
| |
| // Call via LINQ |
| CollectionAssert.AreEqual(bs.Span.ToArray(), bs.ToArray()); |
| } |
| |
| [Test] |
| public void UnsafeWrap() |
| { |
| byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; |
| ByteString bs = UnsafeByteOperations.UnsafeWrap(data.AsMemory(2, 3)); |
| ReadOnlySpan<byte> s = bs.Span; |
| |
| Assert.AreEqual(3, s.Length); |
| Assert.AreEqual(2, s[0]); |
| Assert.AreEqual(3, s[1]); |
| Assert.AreEqual(4, s[2]); |
| |
| // Check that the value is not a copy |
| data[2] = byte.MaxValue; |
| Assert.AreEqual(byte.MaxValue, s[0]); |
| } |
| |
| [Test] |
| public void CreateCodedInput_FromArraySegment() |
| { |
| byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; |
| ByteString bs = UnsafeByteOperations.UnsafeWrap(data.AsMemory(2, 3)); |
| CodedInputStream codedInputStream = bs.CreateCodedInput(); |
| |
| byte[] bytes = codedInputStream.ReadRawBytes(3); |
| |
| Assert.AreEqual(3, bytes.Length); |
| Assert.AreEqual(2, bytes[0]); |
| Assert.AreEqual(3, bytes[1]); |
| Assert.AreEqual(4, bytes[2]); |
| Assert.IsTrue(codedInputStream.IsAtEnd); |
| } |
| |
| [Test] |
| public void WriteToStream() |
| { |
| byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; |
| ByteString bs = ByteString.CopyFrom(data); |
| |
| MemoryStream ms = new MemoryStream(); |
| bs.WriteTo(ms); |
| |
| CollectionAssert.AreEqual(data, ms.ToArray()); |
| } |
| |
| [Test] |
| public void WriteToStream_Stackalloc() |
| { |
| byte[] data = Encoding.UTF8.GetBytes("Hello world"); |
| Span<byte> s = stackalloc byte[data.Length]; |
| data.CopyTo(s); |
| |
| MemoryStream ms = new MemoryStream(); |
| |
| using (UnmanagedMemoryManager<byte> manager = new UnmanagedMemoryManager<byte>(s)) |
| { |
| ByteString bs = ByteString.AttachBytes(manager.Memory); |
| |
| bs.WriteTo(ms); |
| } |
| |
| CollectionAssert.AreEqual(data, ms.ToArray()); |
| } |
| |
| [Test] |
| public void ToStringUtf8() |
| { |
| ByteString bs = ByteString.CopyFromUtf8("\u20ac"); |
| Assert.AreEqual("\u20ac", bs.ToStringUtf8()); |
| } |
| |
| [Test] |
| public void ToStringWithExplicitEncoding() |
| { |
| ByteString bs = ByteString.CopyFrom("\u20ac", Encoding.Unicode); |
| Assert.AreEqual("\u20ac", bs.ToString(Encoding.Unicode)); |
| } |
| |
| [Test] |
| public void ToString_Stackalloc() |
| { |
| byte[] data = Encoding.UTF8.GetBytes("Hello world"); |
| Span<byte> s = stackalloc byte[data.Length]; |
| data.CopyTo(s); |
| |
| using var manager = new UnmanagedMemoryManager<byte>(s); |
| ByteString bs = ByteString.AttachBytes(manager.Memory); |
| Assert.AreEqual("Hello world", bs.ToString(Encoding.UTF8)); |
| } |
| |
| [Test] |
| public void FromBase64_WithText() |
| { |
| byte[] data = new byte[] {0, 1, 2, 3, 4, 5, 6}; |
| string base64 = Convert.ToBase64String(data); |
| ByteString bs = ByteString.FromBase64(base64); |
| Assert.AreEqual(data, bs.ToByteArray()); |
| } |
| |
| [Test] |
| public void FromBase64_Empty() |
| { |
| // Optimization which also fixes issue 61. |
| Assert.AreSame(ByteString.Empty, ByteString.FromBase64("")); |
| } |
| |
| [Test] |
| public void ToBase64_Array() |
| { |
| ByteString bs = ByteString.CopyFrom(Encoding.UTF8.GetBytes("Hello world")); |
| |
| Assert.AreEqual("SGVsbG8gd29ybGQ=", bs.ToBase64()); |
| } |
| |
| [Test] |
| public void ToBase64_Stackalloc() |
| { |
| byte[] data = Encoding.UTF8.GetBytes("Hello world"); |
| Span<byte> s = stackalloc byte[data.Length]; |
| data.CopyTo(s); |
| |
| using var manager = new UnmanagedMemoryManager<byte>(s); |
| ByteString bs = ByteString.AttachBytes(manager.Memory); |
| Assert.AreEqual("SGVsbG8gd29ybGQ=", bs.ToBase64()); |
| } |
| |
| [Test] |
| public void FromStream_Seekable() |
| { |
| var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); |
| // Consume the first byte, just to test that it's "from current position" |
| stream.ReadByte(); |
| var actual = ByteString.FromStream(stream); |
| ByteString expected = ByteString.CopyFrom(2, 3, 4, 5); |
| Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}"); |
| } |
| |
| [Test] |
| public void FromStream_NotSeekable() |
| { |
| var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); |
| // Consume the first byte, just to test that it's "from current position" |
| stream.ReadByte(); |
| // Wrap the original stream in LimitedInputStream, which has CanSeek=false |
| var limitedStream = new LimitedInputStream(stream, 3); |
| var actual = ByteString.FromStream(limitedStream); |
| ByteString expected = ByteString.CopyFrom(2, 3, 4); |
| Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}"); |
| } |
| |
| [Test] |
| public async Task FromStreamAsync_Seekable() |
| { |
| var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); |
| // Consume the first byte, just to test that it's "from current position" |
| stream.ReadByte(); |
| var actual = await ByteString.FromStreamAsync(stream); |
| ByteString expected = ByteString.CopyFrom(2, 3, 4, 5); |
| Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}"); |
| } |
| |
| [Test] |
| public async Task FromStreamAsync_NotSeekable() |
| { |
| var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); |
| // Consume the first byte, just to test that it's "from current position" |
| stream.ReadByte(); |
| // Wrap the original stream in LimitedInputStream, which has CanSeek=false |
| var limitedStream = new LimitedInputStream(stream, 3); |
| var actual = await ByteString.FromStreamAsync(limitedStream); |
| ByteString expected = ByteString.CopyFrom(2, 3, 4); |
| Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}"); |
| } |
| |
| [Test] |
| public void GetHashCode_Regression() |
| { |
| // We used to have an awful hash algorithm where only the last four |
| // bytes were relevant. This is a regression test for |
| // https://github.com/protocolbuffers/protobuf/issues/2511 |
| |
| ByteString b1 = ByteString.CopyFrom(100, 1, 2, 3, 4); |
| ByteString b2 = ByteString.CopyFrom(200, 1, 2, 3, 4); |
| Assert.AreNotEqual(b1.GetHashCode(), b2.GetHashCode()); |
| } |
| |
| [Test] |
| public void GetContentsAsReadOnlySpan() |
| { |
| var byteString = ByteString.CopyFrom(1, 2, 3, 4, 5); |
| var copied = byteString.Span.ToArray(); |
| CollectionAssert.AreEqual(byteString, copied); |
| } |
| |
| [Test] |
| public void GetContentsAsReadOnlyMemory() |
| { |
| var byteString = ByteString.CopyFrom(1, 2, 3, 4, 5); |
| var copied = byteString.Memory.ToArray(); |
| CollectionAssert.AreEqual(byteString, copied); |
| } |
| |
| // Create Memory<byte> from non-array source. |
| // Use by ByteString tests that have optimized path for array backed Memory<byte>. |
| private sealed unsafe class UnmanagedMemoryManager<T> : MemoryManager<T> where T : unmanaged |
| { |
| private readonly T* _pointer; |
| private readonly int _length; |
| |
| public UnmanagedMemoryManager(Span<T> span) |
| { |
| fixed (T* ptr = &MemoryMarshal.GetReference(span)) |
| { |
| _pointer = ptr; |
| _length = span.Length; |
| } |
| } |
| |
| public override Span<T> GetSpan() => new Span<T>(_pointer, _length); |
| |
| public override MemoryHandle Pin(int elementIndex = 0) |
| { |
| if (elementIndex < 0 || elementIndex >= _length) |
| { |
| throw new ArgumentOutOfRangeException(nameof(elementIndex)); |
| } |
| |
| return new MemoryHandle(_pointer + elementIndex); |
| } |
| |
| public override void Unpin() { } |
| |
| protected override void Dispose(bool disposing) { } |
| } |
| } |
| } |