File: SocketConnectionListenerTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Native.Core.Tests\Aspire.Hosting.Native.Core.Tests.csproj (Aspire.Hosting.Native.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.Net.Sockets;
using System.Threading.Channels;
using Aspire.Hosting.Native.Core.Tests.TestServices;
using Aspire.Hosting.Native.Transport;
 
namespace Aspire.Hosting.Native.Core.Tests;
 
public class SocketConnectionListenerTests
{
    [Theory]
    [InlineData(0)]
    [InlineData(-1)]
    public async Task RejectsInvalidCapacityBeforeAccepting(int maximumConnections)
    {
        using var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
            SocketConnectionListener.RunAsync(listener, maximumConnections, (_, _) => Task.CompletedTask,
                TestContext.Current.CancellationToken));
    }
 
    [Fact]
    public async Task ConnectionsRunIndependentlyAndShutdownDrainsAllHandlers()
    {
        using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
        var connected = Channel.CreateUnbounded<bool>();
        var drained = 0;
        await using (var host = new NativeSocketTestHost(async (stream, cancellationToken) =>
        {
            connected.Writer.TryWrite(true);
            try
            {
                var buffer = new byte[1];
                if (await stream.ReadAsync(buffer, cancellationToken) == 1)
                {
                    await stream.WriteAsync(buffer, cancellationToken);
                }
                await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
            }
            finally
            {
                Interlocked.Increment(ref drained);
            }
        }, 2))
        {
            using var idle = new NativeSocketTestClient(host.EndPoint);
            await connected.Reader.ReadAsync(timeout.Token);
            using var active = new NativeSocketTestClient(host.EndPoint);
            await connected.Reader.ReadAsync(timeout.Token);
            await active.SendRawAsync("x", timeout.Token);
            var response = new byte[1];
            Assert.Equal(1, await active.Socket.ReceiveAsync(response, SocketFlags.None, timeout.Token));
            Assert.Equal((byte)'x', response[0]);
            Assert.False(host.Completion.IsCompleted);
        }
        Assert.Equal(2, drained);
    }
 
    [Fact]
    public async Task CapacityRejectsExcessConnectionsWithoutStoppingTheListener()
    {
        using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
        var connected = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        await using var host = new NativeSocketTestHost((_, cancellationToken) =>
        {
            connected.TrySetResult();
 
            return Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
        }, 1);
        using var first = new NativeSocketTestClient(host.EndPoint);
        await connected.Task.WaitAsync(timeout.Token);
        using var excess = new NativeSocketTestClient(host.EndPoint);
        Assert.Equal(0, await excess.Socket.ReceiveAsync(new byte[1], SocketFlags.None, timeout.Token));
        Assert.False(host.Completion.IsCompleted);
    }
 
    [Fact]
    public async Task HandlerFailureCancelsAndDrainsOtherConnectionsWithoutWaitingForAnotherClient()
    {
        using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
        var connected = Channel.CreateUnbounded<bool>();
        var failure = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var accepted = 0;
        var drained = 0;
        var host = new NativeSocketTestHost(async (_, cancellationToken) =>
        {
            var index = Interlocked.Increment(ref accepted);
            connected.Writer.TryWrite(true);
            try
            {
                if (index == 1)
                {
                    await failure.Task.WaitAsync(cancellationToken);
                }
                else
                {
                    await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
                }
            }
            finally
            {
                Interlocked.Increment(ref drained);
            }
        }, 2);
        try
        {
            using var first = new NativeSocketTestClient(host.EndPoint);
            await connected.Reader.ReadAsync(timeout.Token);
            using var second = new NativeSocketTestClient(host.EndPoint);
            await connected.Reader.ReadAsync(timeout.Token);
            failure.SetException(new InvalidOperationException("test-handler-failure"));
            var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => host.Completion.WaitAsync(timeout.Token));
            Assert.Equal("test-handler-failure", exception.Message);
            Assert.Equal(2, drained);
        }
        finally
        {
            if (host.Completion.IsFaulted)
            {
                await Assert.ThrowsAsync<InvalidOperationException>(() => host.DisposeAsync().AsTask());
            }
            else
            {
                await host.DisposeAsync();
            }
        }
    }
}