// 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.Buffers; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CommunityToolkit.HighPerformance.UnitTests.Streams; [TestClass] public partial class Test_ReadOnlySequenceStream { [TestMethod] public void Test_ReadOnlySequenceStream_Lifecycle() { ReadOnlySequence sequence = CreateReadOnlySequence(new byte[100]); Stream stream = sequence.AsStream(); Assert.IsTrue(stream.CanRead); Assert.IsTrue(stream.CanSeek); Assert.IsFalse(stream.CanWrite); Assert.AreEqual(stream.Length, sequence.Length); Assert.AreEqual(0, stream.Position); stream.Dispose(); Assert.IsFalse(stream.CanRead); Assert.IsFalse(stream.CanSeek); Assert.IsFalse(stream.CanWrite); _ = Assert.ThrowsExactly(() => stream.Length); _ = Assert.ThrowsExactly(() => stream.Position); } [TestMethod] public void Test_ReadOnlySequenceStream_Seek() { Stream stream = CreateReadOnlySequence(new byte[50], new byte[50]).AsStream(); Assert.AreEqual(0, stream.Position); stream.Position = 42; Assert.AreEqual(42, stream.Position); _ = Assert.ThrowsExactly(() => stream.Position = -1); _ = Assert.ThrowsExactly(() => stream.Position = 120); _ = stream.Seek(0, SeekOrigin.Begin); _ = Assert.ThrowsExactly(() => stream.Seek(-1, SeekOrigin.Begin)); _ = Assert.ThrowsExactly(() => stream.Seek(120, SeekOrigin.Begin)); Assert.AreEqual(0, stream.Position); _ = stream.Seek(-1, SeekOrigin.End); _ = Assert.ThrowsExactly(() => stream.Seek(20, SeekOrigin.End)); _ = Assert.ThrowsExactly(() => stream.Seek(-120, SeekOrigin.End)); Assert.AreEqual(stream.Position, stream.Length - 1); _ = stream.Seek(42, SeekOrigin.Begin); _ = stream.Seek(20, SeekOrigin.Current); _ = stream.Seek(-30, SeekOrigin.Current); _ = Assert.ThrowsExactly(() => stream.Seek(-64, SeekOrigin.Current)); _ = Assert.ThrowsExactly(() => stream.Seek(80, SeekOrigin.Current)); Assert.AreEqual(32, stream.Position); } [TestMethod] public void Test_ReadOnlySequenceStream_Read_Array() { Memory data = CreateRandomData(64); Stream stream = CreateReadOnlySequence(data).AsStream(); stream.Position = 0; byte[] result = new byte[data.Length]; int bytesRead = stream.Read(result, 0, result.Length); Assert.HasCount(bytesRead, result); Assert.AreEqual(stream.Position, data.Length); Assert.IsTrue(data.Span.SequenceEqual(result)); stream.Dispose(); _ = Assert.ThrowsExactly(() => stream.Read(result, 0, result.Length)); } [TestMethod] public void Test_ReadOnlySequenceStream_NotFromStart_Read_Array() { const int offset = 8; Memory data = CreateRandomData(64); Stream stream = CreateReadOnlySequence(data).AsStream(); stream.Position = offset; byte[] result = new byte[data.Length - offset]; int bytesRead = stream.Read(result, 0, result.Length); Assert.HasCount(bytesRead, result); Assert.AreEqual(stream.Position, result.Length + offset); Assert.IsTrue(data.Span.Slice(offset).SequenceEqual(result)); stream.Dispose(); _ = Assert.ThrowsExactly(() => stream.Read(result, 0, result.Length)); } [TestMethod] public void Test_ReadOnlySequenceStream_ReadByte() { Memory data = new byte[] { 1, 128, 255, 32 }; Stream stream = CreateReadOnlySequence(data.Slice(0,2), data.Slice(2, 2)).AsStream(); Span result = stackalloc byte[4]; foreach (ref byte value in result) { value = checked((byte)stream.ReadByte()); } Assert.AreEqual(stream.Position, data.Length); Assert.IsTrue(data.Span.SequenceEqual(result)); int exitCode = stream.ReadByte(); Assert.AreEqual(-1, exitCode); } [TestMethod] public void Test_ReadOnlySequenceStream_Read_Span() { Memory data = CreateRandomData(64); Stream stream = CreateReadOnlySequence(data).AsStream(); stream.Position = 0; Span result = new byte[data.Length]; int bytesRead = stream.Read(result); Assert.AreEqual(bytesRead, result.Length); Assert.AreEqual(stream.Position, data.Length); Assert.IsTrue(data.Span.SequenceEqual(result)); } [TestMethod] public async Task Test_ReadOnlySequenceStream_ReadAsync_Memory() { Memory data = CreateRandomData(64); Stream stream = CreateReadOnlySequence(data).AsStream(); Memory result = new byte[data.Length]; int bytesRead = await stream.ReadAsync(result); Assert.AreEqual(bytesRead, result.Length); Assert.AreEqual(stream.Position, data.Length); Assert.IsTrue(data.Span.SequenceEqual(result.Span)); } [TestMethod] public void Test_ReadOnlySequenceStream_SigleSegment_CopyTo() { Memory data = CreateRandomData(64); Stream source = CreateReadOnlySequence(data).AsStream(); Stream destination = new byte[100].AsMemory().AsStream(); source.CopyTo(destination); Assert.AreEqual(source.Position, destination.Position); destination.Position = 0; Memory result = new byte[data.Length]; int bytesRead = destination.Read(result.Span); Assert.AreEqual(bytesRead, result.Length); Assert.AreEqual(destination.Position, data.Length); Assert.IsTrue(data.Span.SequenceEqual(result.Span)); } [TestMethod] public void Test_ReadOnlySequenceStream_CopyTo() { Memory data = CreateRandomData(64); Stream source = CreateReadOnlySequence(data.Slice(0, 32), data.Slice(32)).AsStream(); Stream destination = new byte[100].AsMemory().AsStream(); source.CopyTo(destination); Assert.AreEqual(source.Position, destination.Position); destination.Position = 0; Memory result = new byte[data.Length]; int bytesRead = destination.Read(result.Span); Assert.AreEqual(bytesRead, result.Length); Assert.AreEqual(destination.Position, data.Length); Assert.IsTrue(data.Span.SequenceEqual(result.Span)); } [TestMethod] public async Task Test_ReadOnlySequenceStream_SigleSegment_CopyToAsync() { Memory data = CreateRandomData(64); Stream source = CreateReadOnlySequence(data).AsStream(); Stream destination = new byte[100].AsMemory().AsStream(); await source.CopyToAsync(destination); Assert.AreEqual(source.Position, destination.Position); destination.Position = 0; Memory result = new byte[data.Length]; int bytesRead = await destination.ReadAsync(result); Assert.AreEqual(bytesRead, result.Length); Assert.AreEqual(destination.Position, data.Length); Assert.IsTrue(data.Span.SequenceEqual(result.Span)); } [TestMethod] public async Task Test_ReadOnlySequenceStream_SigleSegment_NotFromStart_CopyToAsync() { const int offset = 8; Memory data = CreateRandomData(64); Stream source = CreateReadOnlySequence(data).AsStream(); source.Position = offset; Stream destination = new byte[100].AsMemory().AsStream(); await source.CopyToAsync(destination); Assert.AreEqual(source.Position, destination.Position + offset); destination.Position = 0; Memory result = new byte[data.Length - offset]; int bytesRead = await destination.ReadAsync(result); Assert.AreEqual(bytesRead, result.Length); Assert.AreEqual(destination.Position, data.Length - offset); Assert.IsTrue(data.Span.Slice(offset).SequenceEqual(result.Span)); } [TestMethod] public async Task Test_ReadOnlySequenceStream_MultipleSegments_CopyToAsync() { Memory data = CreateRandomData(64); Stream source = CreateReadOnlySequence(data.Slice(0, 16), data.Slice(16, 16), data.Slice(32, 16), data.Slice(48, 16)).AsStream(); Stream destination = new byte[100].AsMemory().AsStream(); await source.CopyToAsync(destination); Assert.AreEqual(source.Position, destination.Position); destination.Position = 0; Memory result = new byte[data.Length]; int bytesRead = await destination.ReadAsync(result); Assert.AreEqual(bytesRead, result.Length); Assert.AreEqual(destination.Position, data.Length); Assert.IsTrue(data.Span.SequenceEqual(result.Span)); } [TestMethod] public async Task Test_ReadOnlySequenceStream_MultipleSegments_NotFromStart_CopyToAsync() { const int offset = 8; Memory data = CreateRandomData(64); Stream source = CreateReadOnlySequence(data.Slice(0, 16), data.Slice(16, 16), data.Slice(32, 16), data.Slice(48, 16)).AsStream(); source.Position = offset; Stream destination = new byte[100].AsMemory().AsStream(); await source.CopyToAsync(destination); Assert.AreEqual(source.Position, destination.Position + offset); destination.Position = 0; Memory result = new byte[data.Length - offset]; int bytesRead = await destination.ReadAsync(result); Assert.AreEqual(bytesRead, result.Length); Assert.AreEqual(destination.Position, data.Length - offset); Assert.IsTrue(data.Span.Slice(offset).SequenceEqual(result.Span)); } /// /// Creates a random array filled with random data. /// /// The number of array items to create. /// The returned random array. private static byte[] CreateRandomData(int count) { Random? random = new(DateTime.Now.Ticks.GetHashCode()); byte[] data = new byte[count]; foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan())) { n = (byte)random.Next(0, byte.MaxValue); } return data; } /// /// Creates a value from the input segments. /// /// The input segments. /// The resulting value. private static ReadOnlySequence CreateReadOnlySequence(params ReadOnlyMemory[] segments) { if (segments is not { Length: > 0 }) { return ReadOnlySequence.Empty; } if (segments.Length == 1) { return new(segments[0]); } ReadOnlySequenceSegmentOfByte first = new(segments[0]); ReadOnlySequenceSegmentOfByte last = first; long length = first.Memory.Length; for (int i = 1; i < segments.Length; i++) { ReadOnlyMemory segment = segments[i]; length += segment.Length; last = last.Append(segment); } return new(first, 0, last, (int)(length - last.RunningIndex)); } /// /// A custom that supports appending new segments. /// private sealed class ReadOnlySequenceSegmentOfByte : ReadOnlySequenceSegment { public ReadOnlySequenceSegmentOfByte(ReadOnlyMemory memory) { Memory = memory; } public ReadOnlySequenceSegmentOfByte Append(ReadOnlyMemory memory) { ReadOnlySequenceSegmentOfByte nextSegment = new(memory) { RunningIndex = RunningIndex + Memory.Length }; Next = nextSegment; return nextSegment; } } }