File: Buffers\PagedBufferedTextWriterTest.cs
Web Access
Project: src\src\Mvc\Mvc.ViewFeatures\test\Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj (Microsoft.AspNetCore.Mvc.ViewFeatures.Test)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
 
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers;
 
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<char>(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<char>(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<ArgumentNullException>("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<char>(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<char>(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<char>(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<char>(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<char>(Content, inner.ToString().ToCharArray());
    }
 
    private class TestArrayPool : ArrayPool<char>
    {
        public IList<char[]> Returned { get; } = new List<char[]>();
 
        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<char>
    {
        public IList<char[]> Returned { get; } = new List<char[]>();
 
        public override char[] Rent(int minimumLength)
        {
            return new char[2 * minimumLength];
        }
 
        public override void Return(char[] buffer, bool clearArray = false)
        {
            Returned.Add(buffer);
        }
    }
}