// 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; using System.Buffers; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Xunit; namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal { public class PagedBufferedTextWriterTest { private static readonly char[] Content; static PagedBufferedTextWriterTest() { Content = new char[4 * PagedCharBuffer.PageSize]; for (var i = 0; i < Content.Length; i++) { Content[i] = (char)((i % 26) + 'A'); } } [Fact] public async Task Write_Char() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); // Act for (var i = 0; i < Content.Length; i++) { writer.Write(Content[i]); } await writer.FlushAsync(); // Assert Assert.Equal(Content, inner.ToString().ToCharArray()); } [Fact] public async Task Write_CharArray_Null() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); // Act writer.Write((char[])null); await writer.FlushAsync(); // Assert Assert.Empty(inner.ToString()); } [Fact] public async Task Write_CharArray() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); // These numbers chosen to hit boundary conditions in buffer lengths Assert.Equal(4096, Content.Length); // Update these numbers if this changes. var chunkSizes = new int[] { 3, 1021, 1023, 1023, 1, 1, 1024 }; // Act var offset = 0; foreach (var chunkSize in chunkSizes) { var chunk = new char[chunkSize]; for (var j = 0; j < chunkSize; j++) { chunk[j] = Content[offset + j]; } writer.Write(chunk); offset += chunkSize; } await writer.FlushAsync(); // Assert var array = inner.ToString().ToCharArray(); for (var i = 0; i < Content.Length; i++) { Assert.Equal(Content[i], array[i]); } Assert.Equal(Content, inner.ToString().ToCharArray()); } [Fact] public void Write_CharArray_Bounded_Null() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); // Act & Assert Assert.Throws("buffer", () => writer.Write(null, 0, 0)); } [Fact] public async Task Write_CharArray_Bounded() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); // These numbers chosen to hit boundary conditions in buffer lengths Assert.Equal(4096, Content.Length); // Update these numbers if this changes. var chunkSizes = new int[] { 3, 1021, 1023, 1023, 1, 1, 1024 }; // Act var offset = 0; foreach (var chunkSize in chunkSizes) { writer.Write(Content, offset, chunkSize); offset += chunkSize; } await writer.FlushAsync(); // Assert Assert.Equal(Content, inner.ToString().ToCharArray()); } [Fact] public async Task Write_String_Null() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); // Act writer.Write((string)null); await writer.FlushAsync(); // Assert Assert.Empty(inner.ToString()); } [Fact] public async Task Write_String() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); // These numbers chosen to hit boundary conditions in buffer lengths Assert.Equal(4096, Content.Length); // Update these numbers if this changes. var chunkSizes = new int[] { 3, 1021, 1023, 1023, 1, 1, 1024 }; // Act var offset = 0; foreach (var chunkSize in chunkSizes) { var chunk = new string(Content, offset, chunkSize); writer.Write(chunk); offset += chunkSize; } await writer.FlushAsync(); // Assert Assert.Equal(Content, inner.ToString().ToCharArray()); } [Fact] public async Task SynchronousWrites_FollowedByAsyncWriteString_WritesAllContent() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(new TestArrayPool(), inner); // Act writer.Write('a'); writer.Write(new[] { 'b', 'c', 'd' }); writer.Write("ef"); await writer.WriteAsync("ghi"); // Assert Assert.Equal("abcdefghi", inner.ToString()); } [Fact] public async Task SynchronousWrites_FollowedByAsyncWriteChar_WritesAllContent() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(new TestArrayPool(), inner); // Act writer.Write('a'); writer.Write(new[] { 'b', 'c', 'd' }); writer.Write("ef"); await writer.WriteAsync('g'); // Assert Assert.Equal("abcdefg", inner.ToString()); } [Fact] public async Task SynchronousWrites_FollowedByAsyncWriteCharArray_WritesAllContent() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(new TestArrayPool(), inner); // Act writer.Write('a'); writer.Write(new[] { 'b', 'c', 'd' }); writer.Write("ef"); await writer.WriteAsync(new[] { 'g', 'h', 'i' }); // Assert Assert.Equal("abcdefghi", inner.ToString()); } [Fact] public async Task FlushAsync_ReturnsPages() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); for (var i = 0; i < Content.Length; i++) { writer.Write(Content[i]); } // Act await writer.FlushAsync(); // Assert Assert.Equal(3, pool.Returned.Count); } [Fact] public async Task FlushAsync_FlushesContent() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); for (var i = 0; i < Content.Length; i++) { writer.Write(Content[i]); } // Act await writer.FlushAsync(); // Assert Assert.Equal(Content, inner.ToString().ToCharArray()); } [Fact] public async Task FlushAsync_WritesContentToInner() { // Arrange var pool = new TestArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); for (var i = 0; i < Content.Length; i++) { writer.Write(Content[i]); } // Act await writer.FlushAsync(); // Assert Assert.Equal(Content, inner.ToString().ToCharArray()); } [Fact] public async Task FlushAsync_WritesContentToInner_WithLargeArrays() { // Arrange var pool = new RentMoreArrayPool(); var inner = new StringWriter(); var writer = new PagedBufferedTextWriter(pool, inner); for (var i = 0; i < Content.Length; i++) { writer.Write(Content[i]); } // Act await writer.FlushAsync(); // Assert Assert.Equal(Content, inner.ToString().ToCharArray()); } private class TestArrayPool : ArrayPool { public IList Returned { get; } = new List(); public override char[] Rent(int minimumLength) { return new char[minimumLength]; } public override void Return(char[] buffer, bool clearArray = false) { Returned.Add(buffer); } } private class RentMoreArrayPool : ArrayPool { public IList Returned { get; } = new List(); public override char[] Rent(int minimumLength) { return new char[2 * minimumLength]; } public override void Return(char[] buffer, bool clearArray = false) { Returned.Add(buffer); } } } }