解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
大石头 authored at 2018-05-15 21:21:05
3.39 KiB
X
using System.Net;
using System.Net.Sockets;

namespace XUnitTest;

/// <summary>本机连接黑洞:监听但从不 Accept,accept 队列打满后新连接停在半开状态</summary>
/// <remarks>
/// <para>用于验证"建连阶段必须受超时预算约束"的用例:没有预算约束时,建连要等操作系统默认 TCP 超时
/// (Windows 约 21 秒),用例无从收敛。黑洞完全在本机构造,不依赖任何外部网络。</para>
/// <para>构造自带校验:只有确实观察到探测连接在窗口内不完成时才视为可用;本机 backlog 语义不支持
/// 时返回 null,用例跳过(属本机环境差异,与外部依赖无关)。</para>
/// </remarks>
internal sealed class TestBlackhole : IDisposable
{
    private readonly Socket _listener;
    private readonly List<TcpClient> _prefill = [];

    private TestBlackhole(Socket listener, Int32 port)
    {
        _listener = listener;
        Port = port;
    }

    /// <summary>黑洞端口。连接该端口会停在半开状态,既不通也不立刻失败</summary>
    public Int32 Port { get; }

    /// <summary>尝试构造黑洞。无法构造(本机 backlog 语义不支持)时返回 null</summary>
    /// <returns>黑洞实例;不可用时返回 null</returns>
    public static TestBlackhole? TryCreate()
    {
        var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
            listener.Listen(1);
        }
        catch
        {
            listener.Dispose();

            return null;
        }

        var blackhole = new TestBlackhole(listener, ((IPEndPoint)listener.LocalEndPoint!).Port);
        try
        {
            // 逐批连接压满 accept 队列(不等待完成,握手完成的连接驻留队列),再用探测连接确认"半开"成立。
            // 调用方给出的超时预算必须明显小于探测窗口,确保探测通过后建连仍处在半开窗口内
            for (var round = 0; round < 8; round++)
            {
                for (var i = 0; i < 32; i++) blackhole.Connect();

                Thread.Sleep(150);

                var probe = new TcpClient();
                blackhole._prefill.Add(probe);
                var task = Observe(probe.ConnectAsync(IPAddress.Loopback, blackhole.Port));

                var pending = false;
                try { pending = !task.Wait(1000); }
                catch { }

                if (pending) return blackhole;
            }
        }
        catch { }

        blackhole.Dispose();

        return null;
    }

    private void Connect()
    {
        var client = new TcpClient();
        _prefill.Add(client);
        Observe(client.ConnectAsync(IPAddress.Loopback, Port));
    }

    /// <summary>观察连接任务异常,避免未观察异常在用例结束后抛到全局</summary>
    private static Task Observe(Task task)
    {
        _ = task.ContinueWith(static t => { _ = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);

        return task;
    }

    /// <summary>释放全部预占连接与监听器</summary>
    public void Dispose()
    {
        foreach (var item in _prefill)
        {
            try { item.Dispose(); } catch { }
        }
        _prefill.Clear();

        try { _listener.Dispose(); } catch { }
    }
}