File: Http1\Http1OutputProducerTests.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;
using System.Buffers;
using System.IO.Pipelines;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.InternalTesting;
using Moq;
using Xunit;
using Microsoft.AspNetCore.Connections.Features;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
 
public class Http1OutputProducerTests : IDisposable
{
    private readonly MemoryPool<byte> _memoryPool;
 
    public Http1OutputProducerTests()
    {
        _memoryPool = PinnedBlockMemoryPoolFactory.Create();
    }
 
    public void Dispose()
    {
        _memoryPool.Dispose();
    }
 
    [Fact]
    public async Task WritesNoopAfterConnectionCloses()
    {
        var pipeOptions = new PipeOptions
        (
            pool: _memoryPool,
            readerScheduler: Mock.Of<PipeScheduler>(),
            writerScheduler: PipeScheduler.Inline,
            useSynchronizationContext: false
        );
 
        using (var socketOutput = CreateOutputProducer(pipeOptions))
        {
            // Close
            socketOutput.Dispose();
 
            await socketOutput.WriteDataAsync(new byte[] { 1, 2, 3, 4 }, default);
 
            Assert.False(socketOutput.Pipe.Reader.TryRead(out var result));
 
            socketOutput.Pipe.Writer.Complete();
            socketOutput.Pipe.Reader.Complete();
        }
    }
 
    [Fact]
    public async Task FlushAsync_OnDisposedSocket_ReturnsResultWithIsCompletedTrue()
    {
        var pipeOptions = new PipeOptions
        (
            pool: _memoryPool,
            readerScheduler: Mock.Of<PipeScheduler>(),
            writerScheduler: PipeScheduler.Inline,
            useSynchronizationContext: false
        );
 
        var socketOutput = CreateOutputProducer(pipeOptions);
 
        await socketOutput.WriteDataAsync(new byte[] { 1, 2, 3, 4 }, default);
        var successResult = await socketOutput.FlushAsync();
        Assert.False(successResult.IsCompleted);
 
        // Close
        socketOutput.Dispose();
 
        await socketOutput.WriteDataAsync(new byte[] { 1, 2, 3, 4 }, default);
        var completedResult = await socketOutput.FlushAsync();
        Assert.True(completedResult.IsCompleted);
 
        socketOutput.Pipe.Writer.Complete();
        socketOutput.Pipe.Reader.Complete();
    }
 
    [Fact]
    public async Task FlushAsync_OnSocketWithCanceledPendingFlush_ReturnsResultWithIsCanceledTrue()
    {
        var pipeOptions = new PipeOptions
        (
            pool: _memoryPool,
            readerScheduler: Mock.Of<PipeScheduler>(),
            writerScheduler: PipeScheduler.Inline,
            useSynchronizationContext: false
        );
 
        var socketOutput = CreateOutputProducer(pipeOptions);
 
        await socketOutput.WriteDataAsync(new byte[] { 1, 2, 3, 4 }, default);
        var successResult = await socketOutput.FlushAsync();
        Assert.False(successResult.IsCanceled);
 
        // Close
        socketOutput.CancelPendingFlush();
 
        var cancelResult = await socketOutput.WriteDataToPipeAsync(new byte[] { 1, 2, 3, 4 }, default);
        Assert.True(cancelResult.IsCanceled);
 
        // only one flush should be cancelled
        var goodResult = await socketOutput.WriteDataToPipeAsync(new byte[] { 1, 2, 3, 4 }, default);
        Assert.False(goodResult.IsCanceled);
 
        socketOutput.Pipe.Writer.Complete();
        socketOutput.Pipe.Reader.Complete();
    }
 
    [Fact]
    public void AbortsTransportEvenAfterDispose()
    {
        var mockConnectionContext = new Mock<ConnectionContext>();
        var metricsContext = new ConnectionMetricsContext { ConnectionContext = mockConnectionContext.Object };
 
        var outputProducer = CreateOutputProducer(connectionContext: mockConnectionContext.Object, metricsContext: metricsContext);
 
        outputProducer.Dispose();
 
        mockConnectionContext.Verify(f => f.Abort(It.IsAny<ConnectionAbortedException>()), Times.Never());
 
        outputProducer.Abort(null, ConnectionEndReason.AbortedByApp);
 
        mockConnectionContext.Verify(f => f.Abort(null), Times.Once());
 
        outputProducer.Abort(null, ConnectionEndReason.AbortedByApp);
 
        mockConnectionContext.Verify(f => f.Abort(null), Times.Once());
 
        Assert.Equal(ConnectionEndReason.AbortedByApp, metricsContext.ConnectionEndReason);
    }
 
    [Fact]
    public void AllocatesFakeMemorySmallerThanMaxBufferSize()
    {
        var pipeOptions = new PipeOptions
        (
            pool: _memoryPool,
            readerScheduler: Mock.Of<PipeScheduler>(),
            writerScheduler: PipeScheduler.Inline,
            useSynchronizationContext: false
        );
 
        using (var socketOutput = CreateOutputProducer(pipeOptions))
        {
            var bufferSize = 1;
            var fakeMemory = socketOutput.GetFakeMemory(bufferSize);
 
            Assert.True(fakeMemory.Length >= bufferSize);
        }
    }
 
    [Fact]
    public void AllocatesFakeMemoryBiggerThanMaxBufferSize()
    {
        var pipeOptions = new PipeOptions
        (
            pool: _memoryPool,
            readerScheduler: Mock.Of<PipeScheduler>(),
            writerScheduler: PipeScheduler.Inline,
            useSynchronizationContext: false
        );
 
        using (var socketOutput = CreateOutputProducer(pipeOptions))
        {
            var bufferSize = _memoryPool.MaxBufferSize * 2;
            var fakeMemory = socketOutput.GetFakeMemory(bufferSize);
 
            Assert.True(fakeMemory.Length >= bufferSize);
        }
    }
 
    [Fact]
    public void AllocatesIncreasingFakeMemory()
    {
        var pipeOptions = new PipeOptions
        (
            pool: _memoryPool,
            readerScheduler: Mock.Of<PipeScheduler>(),
            writerScheduler: PipeScheduler.Inline,
            useSynchronizationContext: false
        );
 
        using (var socketOutput = CreateOutputProducer(pipeOptions))
        {
            var bufferSize1 = 1024;
            var bufferSize2 = 2048;
            var fakeMemory = socketOutput.GetFakeMemory(bufferSize1);
            fakeMemory = socketOutput.GetFakeMemory(bufferSize2);
 
            Assert.True(fakeMemory.Length >= bufferSize2);
        }
    }
 
    [Fact]
    public void ReusesFakeMemory()
    {
        var pipeOptions = new PipeOptions
        (
            pool: _memoryPool,
            readerScheduler: Mock.Of<PipeScheduler>(),
            writerScheduler: PipeScheduler.Inline,
            useSynchronizationContext: false
        );
 
        using (var socketOutput = CreateOutputProducer(pipeOptions))
        {
            var bufferSize = 1024;
            var fakeMemory1 = socketOutput.GetFakeMemory(bufferSize);
            var fakeMemory2 = socketOutput.GetFakeMemory(bufferSize);
 
            Assert.Equal(fakeMemory1, fakeMemory2);
        }
    }
 
    private TestHttpOutputProducer CreateOutputProducer(
        PipeOptions pipeOptions = null,
        ConnectionContext connectionContext = null,
        ConnectionMetricsContext metricsContext = null)
    {
        pipeOptions = pipeOptions ?? new PipeOptions();
        connectionContext = connectionContext ?? Mock.Of<ConnectionContext>();
 
        var pipe = new Pipe(pipeOptions);
        var serviceContext = new TestServiceContext();
        var socketOutput = new TestHttpOutputProducer(
            pipe,
            "0",
            connectionContext,
            _memoryPool,
            serviceContext.Log,
            Mock.Of<ITimeoutControl>(),
            Mock.Of<IHttpMinResponseDataRateFeature>(),
            metricsContext ?? new ConnectionMetricsContext { ConnectionContext = connectionContext },
            Mock.Of<IHttpOutputAborter>());
 
        return socketOutput;
    }
 
    private sealed class TestConnectionMetricsContextFeature : IConnectionMetricsContextFeature
    {
        public ConnectionMetricsContext MetricsContext { get; }
    }
 
    private class TestHttpOutputProducer : Http1OutputProducer
    {
        public TestHttpOutputProducer(Pipe pipe, string connectionId, ConnectionContext connectionContext, MemoryPool<byte> memoryPool, KestrelTrace log, ITimeoutControl timeoutControl, IHttpMinResponseDataRateFeature minResponseDataRateFeature, ConnectionMetricsContext metricsContext, IHttpOutputAborter outputAborter)
            : base(pipe.Writer, connectionId, connectionContext, memoryPool, log, timeoutControl, minResponseDataRateFeature, metricsContext, outputAborter)
        {
            Pipe = pipe;
        }
 
        public Pipe Pipe { get; }
    }
}