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

namespace XUnitTest.Net;

/// <summary>测试用显式端口分配。仅用于“同一端口同时监听 TCP/UDP”的用例</summary>
/// <remarks>
/// 单协议用例保持动态端口(Port=0 由系统分配,天然对该协议可用)。
/// 双协议用例若用 Port=0,先启动的协议选中的端口可能落在另一协议的保留段(WSAEACCES);
/// 本类在用例专属候选段内顺序探测第一个 TCP 与 UDP 均可绑定的端口,显式交给服务器,
/// 不经过系统分配与跨协议端口继承。宿主序号取自输出目录的“工程目录 + 目标框架”,
/// 四个测试工程共 10 个宿主(VS 并行多目标框架与本地并行跑测脚本均适用),各宿主候选段互不相交,多宿主并行不互抢。
/// </remarks>
internal static class TestPort
{
    /// <summary>宿主序号(工程目录 + TFM):UnitTest/net8.0=0 … UnitTestSG/net10.0=9,未登记目录兜底 10</summary>
    private static readonly Int32 _host = GetHostIndex();

    /// <summary>在用例专属候选段内取一个 TCP 与 UDP 均可绑定的显式端口</summary>
    /// <remarks>
    /// 候选段 = basePort + 宿主序号×1000,宽 50。各用例基址取 500 的倍数(26xxx 低段,
    /// 避开 Syncthing 22000 等常见服务与 Windows 高段动态/保留区);宿主步长 1000、段宽 50,
    /// 任意“用例 × 宿主”候选段两两不相交(起点均为 500 的倍数,两两相隔至少 500)。
    /// </remarks>
    /// <param name="basePort">用例专属基址</param>
    /// <returns>可同时用于 TCP 与 UDP 监听的端口</returns>
    public static Int32 PickDual(Int32 basePort)
    {
        var start = basePort + _host * 1000;
        for (var port = start; port < start + 50; port++)
        {
            if (CanBind(port, SocketType.Stream) && CanBind(port, SocketType.Dgram)) return port;
        }

        throw new InvalidOperationException($"从 {start} 起的 50 个候选端口均无法同时用于 TCP/UDP");
    }

    private static Boolean CanBind(Int32 port, SocketType type)
    {
        var netType = type == SocketType.Stream ? NetType.Tcp : NetType.Udp;

        // 与 NetServer.CreateServer 相同口径:已有监听者的端口一律跳过。
        // 对端可能设置了 SO_REUSEADDR(如 Syncthing 等 Go 程序),此时裸 bind 探测会误判“可绑”,
        // 而 NetServer 会按 CheckPort 拒绝该地址,导致“Failed to listen to all ports”
        if (IPAddress.Any.CheckPort(netType, port)) return false;

        var protocol = type == SocketType.Stream ? System.Net.Sockets.ProtocolType.Tcp : System.Net.Sockets.ProtocolType.Udp;
        using var socket = new Socket(AddressFamily.InterNetwork, type, protocol);
        try
        {
            socket.Bind(new IPEndPoint(IPAddress.Any, port));
            return true;
        }
        catch (SocketException)
        {
            return false;
        }
    }

    private static Int32 GetHostIndex()
    {
        var dir = new DirectoryInfo(AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
        var tfm = dir.Name;
        var project = dir.Parent?.Name;
        return (project, tfm) switch
        {
            ("UnitTest", "net8.0") => 0,
            ("UnitTest", "net8.0-windows") => 1,
            ("UnitTest", "net9.0") => 2,
            ("UnitTest", "net10.0") => 3,
            ("UnitTest", "net10.0-windows") => 4,
            ("UnitTest6", "net6.0") => 5,
            ("UnitTest6", "net6.0-windows") => 6,
            ("UnitTestCompat", "net462") => 7,
            ("UnitTestCompat", "net8.0") => 8,
            ("UnitTestSG", "net10.0") => 9,
            _ => 10,
        };
    }
}