1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using System; 11 using System.Text; 12 using NUnit.Framework; 13 using System.IO; 14 using System.Collections.Generic; 15 using System.Collections; 16 using System.Linq; 17 using System.Buffers; 18 using System.Runtime.InteropServices; 19 using System.Threading; 20 using System.Runtime.CompilerServices; 21 using System.Threading.Tasks; 22 23 namespace Google.Protobuf 24 { 25 public class ByteStringTest 26 { 27 [Test] Equality()28 public void Equality() 29 { 30 ByteString b1 = ByteString.CopyFrom(1, 2, 3); 31 ByteString b2 = ByteString.CopyFrom(1, 2, 3); 32 ByteString b3 = ByteString.CopyFrom(1, 2, 4); 33 ByteString b4 = ByteString.CopyFrom(1, 2, 3, 4); 34 EqualityTester.AssertEquality(b1, b1); 35 EqualityTester.AssertEquality(b1, b2); 36 EqualityTester.AssertInequality(b1, b3); 37 EqualityTester.AssertInequality(b1, b4); 38 EqualityTester.AssertInequality(b1, null); 39 EqualityTester.AssertEquality(ByteString.Empty, ByteString.Empty); 40 #pragma warning disable 1718 // Deliberately calling ==(b1, b1) and !=(b1, b1) 41 Assert.IsTrue(b1 == b1); 42 Assert.IsTrue(b1 == b2); 43 Assert.IsFalse(b1 == b3); 44 Assert.IsFalse(b1 == b4); 45 Assert.IsFalse(b1 == null); 46 Assert.IsTrue((ByteString) null == null); 47 Assert.IsFalse(b1 != b1); 48 Assert.IsFalse(b1 != b2); 49 Assert.IsTrue(ByteString.Empty == ByteString.Empty); 50 #pragma warning restore 1718 51 Assert.IsTrue(b1 != b3); 52 Assert.IsTrue(b1 != b4); 53 Assert.IsTrue(b1 != null); 54 Assert.IsFalse((ByteString) null != null); 55 } 56 57 [Test] EmptyByteStringHasZeroSize()58 public void EmptyByteStringHasZeroSize() 59 { 60 Assert.AreEqual(0, ByteString.Empty.Length); 61 } 62 63 [Test] CopyFromStringWithExplicitEncoding()64 public void CopyFromStringWithExplicitEncoding() 65 { 66 ByteString bs = ByteString.CopyFrom("AB", Encoding.Unicode); 67 Assert.AreEqual(4, bs.Length); 68 Assert.AreEqual(65, bs[0]); 69 Assert.AreEqual(0, bs[1]); 70 Assert.AreEqual(66, bs[2]); 71 Assert.AreEqual(0, bs[3]); 72 } 73 74 [Test] IsEmptyWhenEmpty()75 public void IsEmptyWhenEmpty() 76 { 77 Assert.IsTrue(ByteString.CopyFromUtf8("").IsEmpty); 78 } 79 80 [Test] IsEmptyWhenNotEmpty()81 public void IsEmptyWhenNotEmpty() 82 { 83 Assert.IsFalse(ByteString.CopyFromUtf8("X").IsEmpty); 84 } 85 86 [Test] CopyFromByteArrayCopiesContents()87 public void CopyFromByteArrayCopiesContents() 88 { 89 byte[] data = new byte[1]; 90 data[0] = 10; 91 ByteString bs = ByteString.CopyFrom(data); 92 Assert.AreEqual(10, bs[0]); 93 data[0] = 5; 94 Assert.AreEqual(10, bs[0]); 95 } 96 97 [Test] CopyFromReadOnlySpanCopiesContents()98 public void CopyFromReadOnlySpanCopiesContents() 99 { 100 byte[] data = new byte[1]; 101 data[0] = 10; 102 ReadOnlySpan<byte> byteSpan = data; 103 var bs = ByteString.CopyFrom(byteSpan); 104 Assert.AreEqual(10, bs[0]); 105 data[0] = 5; 106 Assert.AreEqual(10, bs[0]); 107 } 108 109 [Test] ToByteArrayCopiesContents()110 public void ToByteArrayCopiesContents() 111 { 112 ByteString bs = ByteString.CopyFromUtf8("Hello"); 113 byte[] data = bs.ToByteArray(); 114 Assert.AreEqual((byte)'H', data[0]); 115 Assert.AreEqual((byte)'H', bs[0]); 116 data[0] = 0; 117 Assert.AreEqual(0, data[0]); 118 Assert.AreEqual((byte)'H', bs[0]); 119 } 120 121 [Test] CopyFromUtf8UsesUtf8()122 public void CopyFromUtf8UsesUtf8() 123 { 124 ByteString bs = ByteString.CopyFromUtf8("\u20ac"); 125 Assert.AreEqual(3, bs.Length); 126 Assert.AreEqual(0xe2, bs[0]); 127 Assert.AreEqual(0x82, bs[1]); 128 Assert.AreEqual(0xac, bs[2]); 129 } 130 131 [Test] CopyFromPortion()132 public void CopyFromPortion() 133 { 134 byte[] data = new byte[] {0, 1, 2, 3, 4, 5, 6}; 135 ByteString bs = ByteString.CopyFrom(data, 2, 3); 136 Assert.AreEqual(3, bs.Length); 137 Assert.AreEqual(2, bs[0]); 138 Assert.AreEqual(3, bs[1]); 139 } 140 141 [Test] CopyTo()142 public void CopyTo() 143 { 144 byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; 145 ByteString bs = ByteString.CopyFrom(data); 146 147 byte[] dest = new byte[data.Length]; 148 bs.CopyTo(dest, 0); 149 150 CollectionAssert.AreEqual(data, dest); 151 } 152 153 [Test] GetEnumerator()154 public void GetEnumerator() 155 { 156 byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; 157 ByteString bs = ByteString.CopyFrom(data); 158 159 IEnumerator<byte> genericEnumerator = bs.GetEnumerator(); 160 Assert.IsTrue(genericEnumerator.MoveNext()); 161 Assert.AreEqual(0, genericEnumerator.Current); 162 163 IEnumerator enumerator = ((IEnumerable)bs).GetEnumerator(); 164 Assert.IsTrue(enumerator.MoveNext()); 165 Assert.AreEqual(0, enumerator.Current); 166 167 // Call via LINQ 168 CollectionAssert.AreEqual(bs.Span.ToArray(), bs.ToArray()); 169 } 170 171 [Test] UnsafeWrap()172 public void UnsafeWrap() 173 { 174 byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; 175 ByteString bs = UnsafeByteOperations.UnsafeWrap(data.AsMemory(2, 3)); 176 ReadOnlySpan<byte> s = bs.Span; 177 178 Assert.AreEqual(3, s.Length); 179 Assert.AreEqual(2, s[0]); 180 Assert.AreEqual(3, s[1]); 181 Assert.AreEqual(4, s[2]); 182 183 // Check that the value is not a copy 184 data[2] = byte.MaxValue; 185 Assert.AreEqual(byte.MaxValue, s[0]); 186 } 187 188 [Test] CreateCodedInput_FromArraySegment()189 public void CreateCodedInput_FromArraySegment() 190 { 191 byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; 192 ByteString bs = UnsafeByteOperations.UnsafeWrap(data.AsMemory(2, 3)); 193 CodedInputStream codedInputStream = bs.CreateCodedInput(); 194 195 byte[] bytes = codedInputStream.ReadRawBytes(3); 196 197 Assert.AreEqual(3, bytes.Length); 198 Assert.AreEqual(2, bytes[0]); 199 Assert.AreEqual(3, bytes[1]); 200 Assert.AreEqual(4, bytes[2]); 201 Assert.IsTrue(codedInputStream.IsAtEnd); 202 } 203 204 [Test] WriteToStream()205 public void WriteToStream() 206 { 207 byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; 208 ByteString bs = ByteString.CopyFrom(data); 209 210 MemoryStream ms = new MemoryStream(); 211 bs.WriteTo(ms); 212 213 CollectionAssert.AreEqual(data, ms.ToArray()); 214 } 215 216 [Test] WriteToStream_Stackalloc()217 public void WriteToStream_Stackalloc() 218 { 219 byte[] data = Encoding.UTF8.GetBytes("Hello world"); 220 Span<byte> s = stackalloc byte[data.Length]; 221 data.CopyTo(s); 222 223 MemoryStream ms = new MemoryStream(); 224 225 using (UnmanagedMemoryManager<byte> manager = new UnmanagedMemoryManager<byte>(s)) 226 { 227 ByteString bs = ByteString.AttachBytes(manager.Memory); 228 229 bs.WriteTo(ms); 230 } 231 232 CollectionAssert.AreEqual(data, ms.ToArray()); 233 } 234 235 [Test] ToStringUtf8()236 public void ToStringUtf8() 237 { 238 ByteString bs = ByteString.CopyFromUtf8("\u20ac"); 239 Assert.AreEqual("\u20ac", bs.ToStringUtf8()); 240 } 241 242 [Test] ToStringWithExplicitEncoding()243 public void ToStringWithExplicitEncoding() 244 { 245 ByteString bs = ByteString.CopyFrom("\u20ac", Encoding.Unicode); 246 Assert.AreEqual("\u20ac", bs.ToString(Encoding.Unicode)); 247 } 248 249 [Test] ToString_Stackalloc()250 public void ToString_Stackalloc() 251 { 252 byte[] data = Encoding.UTF8.GetBytes("Hello world"); 253 Span<byte> s = stackalloc byte[data.Length]; 254 data.CopyTo(s); 255 256 using var manager = new UnmanagedMemoryManager<byte>(s); 257 ByteString bs = ByteString.AttachBytes(manager.Memory); 258 Assert.AreEqual("Hello world", bs.ToString(Encoding.UTF8)); 259 } 260 261 [Test] FromBase64_WithText()262 public void FromBase64_WithText() 263 { 264 byte[] data = new byte[] {0, 1, 2, 3, 4, 5, 6}; 265 string base64 = Convert.ToBase64String(data); 266 ByteString bs = ByteString.FromBase64(base64); 267 Assert.AreEqual(data, bs.ToByteArray()); 268 } 269 270 [Test] FromBase64_Empty()271 public void FromBase64_Empty() 272 { 273 // Optimization which also fixes issue 61. 274 Assert.AreSame(ByteString.Empty, ByteString.FromBase64("")); 275 } 276 277 [Test] ToBase64_Array()278 public void ToBase64_Array() 279 { 280 ByteString bs = ByteString.CopyFrom(Encoding.UTF8.GetBytes("Hello world")); 281 282 Assert.AreEqual("SGVsbG8gd29ybGQ=", bs.ToBase64()); 283 } 284 285 [Test] ToBase64_Stackalloc()286 public void ToBase64_Stackalloc() 287 { 288 byte[] data = Encoding.UTF8.GetBytes("Hello world"); 289 Span<byte> s = stackalloc byte[data.Length]; 290 data.CopyTo(s); 291 292 using var manager = new UnmanagedMemoryManager<byte>(s); 293 ByteString bs = ByteString.AttachBytes(manager.Memory); 294 Assert.AreEqual("SGVsbG8gd29ybGQ=", bs.ToBase64()); 295 } 296 297 [Test] FromStream_Seekable()298 public void FromStream_Seekable() 299 { 300 var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); 301 // Consume the first byte, just to test that it's "from current position" 302 stream.ReadByte(); 303 var actual = ByteString.FromStream(stream); 304 ByteString expected = ByteString.CopyFrom(2, 3, 4, 5); 305 Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}"); 306 } 307 308 [Test] FromStream_NotSeekable()309 public void FromStream_NotSeekable() 310 { 311 var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); 312 // Consume the first byte, just to test that it's "from current position" 313 stream.ReadByte(); 314 // Wrap the original stream in LimitedInputStream, which has CanSeek=false 315 var limitedStream = new LimitedInputStream(stream, 3); 316 var actual = ByteString.FromStream(limitedStream); 317 ByteString expected = ByteString.CopyFrom(2, 3, 4); 318 Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}"); 319 } 320 321 [Test] FromStreamAsync_Seekable()322 public async Task FromStreamAsync_Seekable() 323 { 324 var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); 325 // Consume the first byte, just to test that it's "from current position" 326 stream.ReadByte(); 327 var actual = await ByteString.FromStreamAsync(stream); 328 ByteString expected = ByteString.CopyFrom(2, 3, 4, 5); 329 Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}"); 330 } 331 332 [Test] FromStreamAsync_NotSeekable()333 public async Task FromStreamAsync_NotSeekable() 334 { 335 var stream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 }); 336 // Consume the first byte, just to test that it's "from current position" 337 stream.ReadByte(); 338 // Wrap the original stream in LimitedInputStream, which has CanSeek=false 339 var limitedStream = new LimitedInputStream(stream, 3); 340 var actual = await ByteString.FromStreamAsync(limitedStream); 341 ByteString expected = ByteString.CopyFrom(2, 3, 4); 342 Assert.AreEqual(expected, actual, $"{expected.ToBase64()} != {actual.ToBase64()}"); 343 } 344 345 [Test] GetHashCode_Regression()346 public void GetHashCode_Regression() 347 { 348 // We used to have an awful hash algorithm where only the last four 349 // bytes were relevant. This is a regression test for 350 // https://github.com/protocolbuffers/protobuf/issues/2511 351 352 ByteString b1 = ByteString.CopyFrom(100, 1, 2, 3, 4); 353 ByteString b2 = ByteString.CopyFrom(200, 1, 2, 3, 4); 354 Assert.AreNotEqual(b1.GetHashCode(), b2.GetHashCode()); 355 } 356 357 [Test] GetContentsAsReadOnlySpan()358 public void GetContentsAsReadOnlySpan() 359 { 360 var byteString = ByteString.CopyFrom(1, 2, 3, 4, 5); 361 var copied = byteString.Span.ToArray(); 362 CollectionAssert.AreEqual(byteString, copied); 363 } 364 365 [Test] GetContentsAsReadOnlyMemory()366 public void GetContentsAsReadOnlyMemory() 367 { 368 var byteString = ByteString.CopyFrom(1, 2, 3, 4, 5); 369 var copied = byteString.Memory.ToArray(); 370 CollectionAssert.AreEqual(byteString, copied); 371 } 372 373 // Create Memory<byte> from non-array source. 374 // Use by ByteString tests that have optimized path for array backed Memory<byte>. 375 private sealed unsafe class UnmanagedMemoryManager<T> : MemoryManager<T> where T : unmanaged 376 { 377 private readonly T* _pointer; 378 private readonly int _length; 379 UnmanagedMemoryManager(Span<T> span)380 public UnmanagedMemoryManager(Span<T> span) 381 { 382 fixed (T* ptr = &MemoryMarshal.GetReference(span)) 383 { 384 _pointer = ptr; 385 _length = span.Length; 386 } 387 } 388 GetSpan()389 public override Span<T> GetSpan() => new Span<T>(_pointer, _length); 390 Pin(int elementIndex = 0)391 public override MemoryHandle Pin(int elementIndex = 0) 392 { 393 if (elementIndex < 0 || elementIndex >= _length) 394 { 395 throw new ArgumentOutOfRangeException(nameof(elementIndex)); 396 } 397 398 return new MemoryHandle(_pointer + elementIndex); 399 } 400 Unpin()401 public override void Unpin() { } 402 Dispose(bool disposing)403 protected override void Dispose(bool disposing) { } 404 } 405 } 406 }