// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xunit; namespace System.IO.Pipelines.Tests { public class BufferWriterTests : IDisposable { protected Pipe Pipe; public BufferWriterTests() { Pipe = new Pipe(new PipeOptions(useSynchronizationContext: false, pauseWriterThreshold: 0, resumeWriterThreshold: 0)); } public void Dispose() { Pipe.Writer.Complete(); Pipe.Reader.Complete(); } private byte[] Read() { Pipe.Writer.FlushAsync().GetAwaiter().GetResult(); Pipe.Writer.Complete(); ReadResult readResult = Pipe.Reader.ReadAsync().GetAwaiter().GetResult(); byte[] data = readResult.Buffer.ToArray(); Pipe.Reader.AdvanceTo(readResult.Buffer.End); return data; } [Theory] [InlineData(3, -1, 0)] [InlineData(3, 0, -1)] [InlineData(3, 0, 4)] [InlineData(3, 4, 0)] [InlineData(3, -1, -1)] [InlineData(3, 4, 4)] public void ThrowsForInvalidParameters(int arrayLength, int offset, int length) { BufferWriter writer = new BufferWriter(Pipe.Writer); var array = new byte[arrayLength]; for (var i = 0; i < array.Length; i++) { array[i] = (byte)(i + 1); } writer.Write(new Span(array, 0, 0)); writer.Write(new Span(array, array.Length, 0)); try { writer.Write(new Span(array, offset, length)); Assert.True(false); } catch (Exception ex) { Assert.True(ex is ArgumentOutOfRangeException); } writer.Write(new Span(array, 0, array.Length)); writer.Commit(); Assert.Equal(array, Read()); } [Theory] [InlineData(0, 3)] [InlineData(1, 2)] [InlineData(2, 1)] [InlineData(1, 1)] public void CanWriteWithOffsetAndLength(int offset, int length) { BufferWriter writer = new BufferWriter(Pipe.Writer); var array = new byte[] { 1, 2, 3 }; writer.Write(new Span(array, offset, length)); Assert.Equal(0, writer.BytesCommitted); writer.Commit(); Assert.Equal(length, writer.BytesCommitted); Assert.Equal(array.Skip(offset).Take(length).ToArray(), Read()); Assert.Equal(length, writer.BytesCommitted); } [Fact] public void CanWriteEmpty() { BufferWriter writer = new BufferWriter(Pipe.Writer); var array = new byte[] { }; writer.Write(array); writer.Write(new Span(array, 0, array.Length)); writer.Commit(); Assert.Equal(0, writer.BytesCommitted); Assert.Equal(array, Read()); } [Fact] public void CanWriteIntoHeadlessBuffer() { BufferWriter writer = new BufferWriter(Pipe.Writer); writer.Write(new byte[] { 1, 2, 3 }); writer.Commit(); Assert.Equal(3, writer.BytesCommitted); Assert.Equal(new byte[] { 1, 2, 3 }, Read()); } [Fact] public void CanWriteMultipleTimes() { BufferWriter writer = new BufferWriter(Pipe.Writer); writer.Write(new byte[] { 1 }); writer.Write(new byte[] { 2 }); writer.Write(new byte[] { 3 }); writer.Commit(); Assert.Equal(3, writer.BytesCommitted); Assert.Equal(new byte[] { 1, 2, 3 }, Read()); } [Fact] public void CanWriteOverTheBlockLength() { Memory memory = Pipe.Writer.GetMemory(); BufferWriter writer = new BufferWriter(Pipe.Writer); IEnumerable source = Enumerable.Range(0, memory.Length).Select(i => (byte)i); byte[] expectedBytes = source.Concat(source).Concat(source).ToArray(); writer.Write(expectedBytes); writer.Commit(); Assert.Equal(expectedBytes.LongLength, writer.BytesCommitted); Assert.Equal(expectedBytes, Read()); } [Fact] public void EnsureAllocatesSpan() { BufferWriter writer = new BufferWriter(Pipe.Writer); writer.Ensure(10); Assert.True(writer.Span.Length > 10); Assert.Equal(0, writer.BytesCommitted); Assert.Equal(new byte[] { }, Read()); } [Fact] public void ExposesSpan() { int initialLength = Pipe.Writer.GetMemory().Length; BufferWriter writer = new BufferWriter(Pipe.Writer); Assert.Equal(initialLength, writer.Span.Length); Assert.Equal(new byte[] { }, Read()); } [Fact] public void SlicesSpanAndAdvancesAfterWrite() { int initialLength = Pipe.Writer.GetMemory().Length; BufferWriter writer = new BufferWriter(Pipe.Writer); writer.Write(new byte[] { 1, 2, 3 }); writer.Commit(); Assert.Equal(3, writer.BytesCommitted); Assert.Equal(initialLength - 3, writer.Span.Length); Assert.Equal(Pipe.Writer.GetMemory().Length, writer.Span.Length); Assert.Equal(new byte[] { 1, 2, 3 }, Read()); } [Fact] public void BufferWriterCountsBytesCommitted() { BufferWriter writer = new BufferWriter(Pipe.Writer); writer.Write(new byte[] { 1, 2, 3 }); Assert.Equal(0, writer.BytesCommitted); writer.Commit(); Assert.Equal(3, writer.BytesCommitted); writer.Ensure(10); writer.Advance(10); Assert.Equal(3, writer.BytesCommitted); writer.Commit(); Assert.Equal(13, writer.BytesCommitted); Pipe.Writer.FlushAsync().GetAwaiter().GetResult(); var readResult = Pipe.Reader.ReadAsync().GetAwaiter().GetResult(); // Consuming the buffer does not change BytesCommitted Assert.Equal(13, readResult.Buffer.Length); Assert.Equal(13, writer.BytesCommitted); } [Theory] [InlineData(5)] [InlineData(50)] [InlineData(500)] [InlineData(5000)] [InlineData(50000)] public void WriteLargeDataBinary(int length) { var data = new byte[length]; new Random(length).NextBytes(data); BufferWriter writer = new BufferWriter(Pipe.Writer); writer.Write(data); writer.Commit(); Assert.Equal(length, writer.BytesCommitted); Assert.Equal(data, Read()); } } }