File: PipelineExtensionTests.cs
Web Access
Project: src\src\Servers\Kestrel\Core\test\Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj (Microsoft.AspNetCore.Server.Kestrel.Core.Tests)
// 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;
using System.Globalization;
using System.IO.Pipelines;
using System.Text;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
 
#pragma warning disable xUnit1031 // Do not use blocking task operations in test method
 
public class PipelineExtensionTests : IDisposable
{
    // ulong.MaxValue.ToString().Length
    private const int _ulongMaxValueLength = 20;
 
    private readonly Pipe _pipe;
    private readonly MemoryPool<byte> _memoryPool = PinnedBlockMemoryPoolFactory.Create();
 
    public PipelineExtensionTests()
    {
        _pipe = new Pipe(new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false));
    }
 
    public void Dispose()
    {
        _pipe.Reader.Complete();
        _pipe.Writer.Complete();
        _memoryPool.Dispose();
    }
 
    [Theory]
    [InlineData(ulong.MinValue)]
    [InlineData(ulong.MaxValue)]
    [InlineData(4_8_15_16_23_42)]
    public void WritesNumericToAscii(ulong number)
    {
        var writerBuffer = _pipe.Writer;
        var writer = new BufferWriter<PipeWriter>(writerBuffer);
        writer.WriteNumeric(number);
        writer.Commit();
        writerBuffer.FlushAsync().GetAwaiter().GetResult();
 
        var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
        var numAsStr = number.ToString(CultureInfo.InvariantCulture);
        var expected = Encoding.ASCII.GetBytes(numAsStr);
        AssertExtensions.Equal(expected, reader.Buffer.Slice(0, numAsStr.Length).ToArray());
    }
 
    [Fact]
    public void WritesNumericToAsciiFastPath()
    {
        for (ulong number = 0; number < 1000; number++)
        {
            var writerBuffer = _pipe.Writer;
            var writer = new BufferWriter<PipeWriter>(writerBuffer);
            writer.WriteNumeric(number);
            writer.Commit();
            writerBuffer.FlushAsync().GetAwaiter().GetResult();
 
            var readResult = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
            var numAsStr = number.ToString(CultureInfo.InvariantCulture);
            var expected = Encoding.ASCII.GetBytes(numAsStr);
            AssertExtensions.Equal(expected, readResult.Buffer.Slice(0, numAsStr.Length).ToArray());
            _pipe.Reader.AdvanceTo(readResult.Buffer.End);
        }
    }
 
    [Theory]
    [InlineData(1)]
    [InlineData(_ulongMaxValueLength / 2)]
    [InlineData(_ulongMaxValueLength - 1)]
    public void WritesNumericAcrossSpanBoundaries(int gapSize)
    {
        var writerBuffer = _pipe.Writer;
        var writer = new BufferWriter<PipeWriter>(writerBuffer);
        // almost fill up the first block
        var spacer = new byte[writer.Span.Length - gapSize];
        writer.Write(spacer);
 
        var bufferLength = writer.Span.Length;
        writer.WriteNumeric(ulong.MaxValue);
        Assert.NotEqual(bufferLength, writer.Span.Length);
        writer.Commit();
        writerBuffer.FlushAsync().GetAwaiter().GetResult();
 
        var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
        var numAsString = ulong.MaxValue.ToString(CultureInfo.InvariantCulture);
        var written = reader.Buffer.Slice(spacer.Length, numAsString.Length);
        Assert.False(written.IsSingleSegment, "The buffer should cross spans");
        AssertExtensions.Equal(Encoding.ASCII.GetBytes(numAsString), written.ToArray());
    }
 
    [Theory]
    [InlineData("\0abcxyz", new byte[] { 0, 97, 98, 99, 120, 121, 122 })]
    [InlineData("!#$%i", new byte[] { 33, 35, 36, 37, 105 })]
    [InlineData("!#$%", new byte[] { 33, 35, 36, 37 })]
    [InlineData("!#$", new byte[] { 33, 35, 36 })]
    [InlineData("!#", new byte[] { 33, 35 })]
    [InlineData("!", new byte[] { 33 })]
    // null or empty
    [InlineData("", new byte[0])]
    [InlineData(null, new byte[0])]
    public void EncodesAsAscii(string input, byte[] expected)
    {
        var pipeWriter = _pipe.Writer;
        var writer = new BufferWriter<PipeWriter>(pipeWriter);
        writer.WriteAscii(input);
        writer.Commit();
        pipeWriter.FlushAsync().GetAwaiter().GetResult();
        pipeWriter.Complete();
 
        var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
 
        if (expected.Length > 0)
        {
            AssertExtensions.Equal(
                expected,
                reader.Buffer.ToArray());
        }
        else
        {
            Assert.Equal(0, reader.Buffer.Length);
        }
    }
 
    [Theory]
    // non-ascii characters stored in 32 bits
    [InlineData("𤭢𐐝")]
    // non-ascii characters stored in 16 bits
    [InlineData("ñ٢⛄⛵")]
    public void WriteAsciiWritesOnlyOneBytePerChar(string input)
    {
        // WriteAscii doesn't validate if characters are in the ASCII range
        // but it shouldn't produce more than one byte per character
        var writerBuffer = _pipe.Writer;
        var writer = new BufferWriter<PipeWriter>(writerBuffer);
        writer.WriteAscii(input);
        writer.Commit();
        writerBuffer.FlushAsync().GetAwaiter().GetResult();
        var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
 
        Assert.Equal(input.Length, reader.Buffer.Length);
    }
 
    [Fact]
    public void WriteAscii()
    {
        const byte maxAscii = 0x7f;
        var writerBuffer = _pipe.Writer;
        var writer = new BufferWriter<PipeWriter>(writerBuffer);
        for (var i = 0; i < maxAscii; i++)
        {
            writer.WriteAscii(new string((char)i, 1));
        }
        writer.Commit();
        writerBuffer.FlushAsync().GetAwaiter().GetResult();
 
        var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
        var data = reader.Buffer.Slice(0, maxAscii).ToArray();
        for (var i = 0; i < maxAscii; i++)
        {
            Assert.Equal(i, data[i]);
        }
    }
 
    [Theory]
    [InlineData(3, 2)]
    [InlineData(4, 2)]
    [InlineData(5, 3)]
    [InlineData(7, 4)]
    [InlineData(8, 3)]
    [InlineData(8, 4)]
    [InlineData(8, 5)]
    [InlineData(100, 48)]
    public void WritesAsciiAcrossBlockBoundaries(int stringLength, int gapSize)
    {
        var testString = new string(' ', stringLength);
        var writerBuffer = _pipe.Writer;
        var writer = new BufferWriter<PipeWriter>(writerBuffer);
        // almost fill up the first block
        var spacer = new byte[writer.Span.Length - gapSize];
        writer.Write(spacer);
        Assert.Equal(gapSize, writer.Span.Length);
 
        var bufferLength = writer.Span.Length;
        writer.WriteAscii(testString);
        Assert.NotEqual(bufferLength, writer.Span.Length);
        writer.Commit();
        writerBuffer.FlushAsync().GetAwaiter().GetResult();
 
        var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
        var written = reader.Buffer.Slice(spacer.Length, stringLength);
        Assert.False(written.IsSingleSegment, "The buffer should cross spans");
        AssertExtensions.Equal(Encoding.ASCII.GetBytes(testString), written.ToArray());
    }
}