File: TcpEndpointProbesServiceTests.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.Diagnostics.Probes.Tests\Microsoft.Extensions.Diagnostics.Probes.Tests.csproj (Microsoft.Extensions.Diagnostics.Probes.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.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Time.Testing;
using Xunit;
 
namespace Microsoft.Extensions.Diagnostics.Probes.Test;
 
[CollectionDefinition(nameof(TcpEndpointProbesServiceTests), DisableParallelization = true)]
public class TcpEndpointProbesServiceTests
{
    [Fact]
    public async Task ExecuteAsync_CheckListenerOpenAndCloseAfterHealthStatusEvents()
    {
        var port = GetFreeTcpPort();
 
        using var cts = new CancellationTokenSource();
 
        var healthCheckService = new MockHealthCheckService();
 
        var options = new TcpEndpointProbesOptions
        {
            TcpPort = port,
        };
        var timeProvider = new FakeTimeProvider();
        using var tcpEndpointProbesService = new TcpEndpointProbesService(
            new FakeLogger<TcpEndpointProbesService>(),
            healthCheckService,
            options)
        {
            TimeProvider = timeProvider
        };
 
        Assert.False(IsTcpOpened(port));
 
        await tcpEndpointProbesService.StartAsync(cts.Token);
        await tcpEndpointProbesService.UpdateHealthStatusAsync(cts.Token);
 
        Assert.True(IsTcpOpened(port));
 
        timeProvider.Advance(TimeSpan.FromMinutes(1));
 
        Assert.True(IsTcpOpened(port));
 
        healthCheckService.IsHealthy = false;
        await tcpEndpointProbesService.UpdateHealthStatusAsync(cts.Token);
 
        Assert.False(IsTcpOpened(port));
 
        timeProvider.Advance(TimeSpan.FromMinutes(1));
 
        Assert.False(IsTcpOpened(port));
 
        healthCheckService.IsHealthy = true;
        await tcpEndpointProbesService.UpdateHealthStatusAsync(cts.Token);
 
        Assert.True(IsTcpOpened(port));
 
        cts.Cancel();
    }
 
#if NET5_0_OR_GREATER
    [Fact]
    public async Task ExecuteAsync_Does_Nothing_On_Cancellation()
    {
        var port = GetFreeTcpPort();
 
        var healthCheckService = new MockHealthCheckService();
 
        var options = new TcpEndpointProbesOptions
        {
            TcpPort = port,
        };
        var timeProvider = new FakeTimeProvider();
        using var tcpEndpointProbesService = new TcpEndpointProbesService(
            new FakeLogger<TcpEndpointProbesService>(),
            healthCheckService,
            options)
        {
            TimeProvider = timeProvider
        };
 
        using var cts = new CancellationTokenSource();
 
        cts.Cancel();
        await tcpEndpointProbesService.StartAsync(cts.Token);
 
        Assert.False(IsTcpOpened(port));
    }
#endif
 
    private static bool IsTcpOpened(int port)
    {
        try
        {
            using TcpClient tcpClient = new TcpClient();
            tcpClient.Connect(new IPEndPoint(IPAddress.Loopback, port));
            tcpClient.Close();
            return true;
        }
        catch (SocketException e)
        {
            if (e.SocketErrorCode == SocketError.ConnectionRefused)
            {
                return false;
            }
            else
            {
                throw;
            }
        }
    }
 
    private static int GetFreeTcpPort()
    {
#pragma warning disable CA2000 // Dispose objects before losing scope
        var listener = new TcpListener(IPAddress.Loopback, 0);
#pragma warning restore CA2000 // Dispose objects before losing scope
        listener.Start();
        int port = ((IPEndPoint)listener.LocalEndpoint).Port;
        listener.Stop();
        return port;
    }
}