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

namespace XUnitTest.Data;

/// <summary>数据包序列桥接(AsReadOnlySequence)与序列定界测试</summary>
public class PacketSequenceTests
{
    #region 工具
    /// <summary>构建数据包链</summary>
    private static IPacket Chain(params Byte[][] parts)
    {
        IPacket pk = new ArrayPacket(parts[0]);
        for (var i = 1; i < parts.Length; i++)
        {
            pk.Append(new ArrayPacket(parts[i]));
        }

        return pk;
    }

    /// <summary>生成顺序字节序列</summary>
    private static Byte[] Range(Int32 start, Int32 count)
    {
        var buf = new Byte[count];
        for (var i = 0; i < count; i++) buf[i] = (Byte)(start + i);

        return buf;
    }

    /// <summary>快速构建字节数组</summary>
    private static Byte[] B(params Int32[] values)
    {
        var buf = new Byte[values.Length];
        for (var i = 0; i < values.Length; i++) buf[i] = (Byte)values[i];

        return buf;
    }

    /// <summary>协议解析原型。只取帧长(头部+体长),实例状态被丢弃</summary>
    private static readonly SrmpCodec _dmParser = new();
    private static readonly WebSocketCodec _wsParser = new();

    private static Int32 GetFrameLength(ParseResult? rs) => rs == null ? 0 : (Int32)(rs.Value.HeaderSize + rs.Value.BodyLength);

    private static Int32 GetLength(Byte[] data) => GetFrameLength(_dmParser.TryParse(new ArrayPacket(data).AsReadOnlySequence()));
    private static Int32 GetLength(IPacket pk) => GetFrameLength(_dmParser.TryParse(pk.AsReadOnlySequence()));
    private static Int32 GetLength(ReadOnlySequence<Byte> buffer) => GetFrameLength(_dmParser.TryParse(buffer));
    private static Int32 GetWsLength(Byte[] data) => GetFrameLength(_wsParser.TryParse(new ArrayPacket(data).AsReadOnlySequence()));
    private static Int32 GetWsLength(IPacket pk) => GetFrameLength(_wsParser.TryParse(pk.AsReadOnlySequence()));
    private static Int32 GetWsLength(ReadOnlySequence<Byte> buffer) => GetFrameLength(_wsParser.TryParse(buffer));
    #endregion

    #region 序列桥接
    [Fact]
    [DisplayName("AsReadOnlySequence_单段_与原始数据一致")]
    public void AsReadOnlySequence_SingleSegment()
    {
        var data = Range(0, 8);
        IPacket pk = new ArrayPacket(data);

        var seq = pk.AsReadOnlySequence();

        Assert.Equal(data.Length, seq.Length);
        Assert.True(seq.IsSingleSegment);
        Assert.Equal(data, seq.ToArray());
    }

    [Fact]
    [DisplayName("AsReadOnlySequence_多段链_按序拼接")]
    public void AsReadOnlySequence_MultiSegment()
    {
        var pk = Chain(B(1, 2, 3), B(4, 5), B(6));

        var seq = pk.AsReadOnlySequence();

        Assert.Equal(6, seq.Length);
        Assert.False(seq.IsSingleSegment);
        Assert.Equal(B(1, 2, 3, 4, 5, 6), seq.ToArray());
    }

    [Fact]
    [DisplayName("AsReadOnlySequence_跨段窗口_裁剪正确")]
    public void AsReadOnlySequence_WindowCrossSegment()
    {
        var pk = Chain(Range(0, 4), Range(4, 6), Range(10, 5)); // 0..14

        // 跨两个段边界
        var seq = pk.AsReadOnlySequence(2, 10);
        Assert.Equal(10, seq.Length);
        Assert.False(seq.IsSingleSegment);
        Assert.Equal(Range(2, 10), seq.ToArray());

        // 恰好覆盖中间整段
        seq = pk.AsReadOnlySequence(4, 6);
        Assert.Equal(Range(4, 6), seq.ToArray());

        // 直到链尾
        seq = pk.AsReadOnlySequence(2);
        Assert.Equal(Range(2, 13), seq.ToArray());

        // 单段窗口落在链中某一段内
        seq = pk.AsReadOnlySequence(0, 3);
        Assert.True(seq.IsSingleSegment);
        Assert.Equal(Range(0, 3), seq.ToArray());
    }

    [Fact]
    [DisplayName("AsReadOnlySequence_空包与空窗口_返回空序列")]
    public void AsReadOnlySequence_Empty()
    {
        IPacket pk = new ArrayPacket(Array.Empty<Byte>());
        Assert.Equal(0, pk.AsReadOnlySequence().Length);
        Assert.Equal(0, pk.AsReadOnlySequence(0).Length);
        Assert.Equal(0, pk.AsReadOnlySequence(0, 0).Length);

        // 偏移恰好等于总长且长度为0
        var chain = Chain(B(1, 2), B(3));
        Assert.Equal(0, chain.AsReadOnlySequence(3, 0).Length);
    }

    [Fact]
    [DisplayName("AsReadOnlySequence_越界_抛参数异常")]
    public void AsReadOnlySequence_OutOfRange()
    {
        var pk = Chain(B(1, 2, 3), B(4, 5));

        Assert.Throws<ArgumentOutOfRangeException>(() => pk.AsReadOnlySequence(-1));
        Assert.Throws<ArgumentOutOfRangeException>(() => pk.AsReadOnlySequence(6));
        Assert.Throws<ArgumentOutOfRangeException>(() => pk.AsReadOnlySequence(0, 6));
    }

    [Fact]
    [DisplayName("CopyPrefix_跨段_按时序复制前缀")]
    public void CopyPrefix_CrossSegment()
    {
        var pk = Chain(B(1, 2), B(3, 4, 5), B(6));
        var seq = pk.AsReadOnlySequence();

        Span<Byte> buf = stackalloc Byte[4];
        var n = PacketHelper.CopyPrefix(seq, buf);

        Assert.Equal(4, n);
        Assert.Equal(B(1, 2, 3, 4), buf.ToArray());

        // 目标比数据长:只复制可用部分
        Span<Byte> big = stackalloc Byte[8];
        n = PacketHelper.CopyPrefix(seq, big);

        Assert.Equal(6, n);
        Assert.Equal(B(1, 2, 3, 4, 5, 6), big[..n].ToArray());
    }

    [Fact]
    [DisplayName("GetPrefix_首段足够直引_跨段拼读_数据不足取可用")]
    public void GetPrefix_CrossSegment()
    {
        // 首段不足:跨段拼读
        var pk = Chain(B(1, 2), B(3, 4, 5), B(6));
        Span<Byte> buf = stackalloc Byte[4];
        var span = pk.GetPrefix(buf, 4);
        Assert.Equal(B(1, 2, 3, 4), span.ToArray());

        // 数据总量不足:返回可用部分
        Span<Byte> big = stackalloc Byte[8];
        span = pk.GetPrefix(big, 8);
        Assert.Equal(6, span.Length);
        Assert.Equal(B(1, 2, 3, 4, 5, 6), span.ToArray());

        // 首段足够:零拷贝直引整个首段(解析方按需取前缀)
        var pk2 = Chain(B(1, 2, 3, 4, 5), B(6));
        Span<Byte> small = stackalloc Byte[3];
        span = pk2.GetPrefix(small, 3);
        Assert.Equal(B(1, 2, 3, 4, 5), span.ToArray());

        // 单段且不足:直引短跨度
        var pk3 = new ArrayPacket(B(1, 2));
        Span<Byte> any = stackalloc Byte[8];
        span = pk3.GetPrefix(any, 8);
        Assert.Equal(2, span.Length);
    }

    [Fact]
    [DisplayName("头部拼读_IPacket重载_帧首不足跨节点定界")]
    public void HeaderSpan_PacketOverloads_CrossNode()
    {
        // DefaultMessage:扩展长度需要 8 字节头,帧首仅 3/5 字节
        var ext = B(0x01, 0x02, 0xFF, 0xFF, 70000 & 0xFF, (70000 >> 8) & 0xFF, (70000 >> 16) & 0xFF, (70000 >> 24) & 0xFF);
        Assert.Equal(70008, GetLength(Chain(ext[..3], ext[3..])));
        Assert.Equal(70008, GetLength(Chain(ext[..3], ext[3..5], ext[5..])));

        // WebSocket:127 扩展长度(8 字节大端)落在第二段
        var longBuf = B(0x82, 127, 0, 0, 0, 0, 0, 1, 0x11, 0x70);
        Assert.Equal(70010, GetWsLength(Chain(longBuf[..5], longBuf[5..])));

        // 数据不足:头部不完整时返回 0
        Assert.Equal(0, GetLength(Chain(ext[..3])));
        Assert.Equal(0, GetWsLength(Chain(longBuf[..9])));
    }

    [Fact]
    [DisplayName("头部拼读_DefaultMessage哨兵边界_65534/65535/65536")]
    public void DefaultMessage_TryParse_SentinelBoundary()
    {
        // 0xFFFF 哨兵边界:< 0xFFFF 用 4 字节头;= 0xFFFF 与 > 0xFFFF 用 8 字节头(扩展长度小端)
        // 65534:长度字段为普通值,4 + 65534
        var h16 = B(0x01, 0x02, 0xFE, 0xFF);
        Assert.Equal(4 + 65534, GetLength(h16));
        Assert.Equal(4 + 65534, GetLength(new ArrayPacket(h16).AsReadOnlySequence()));

        // 65535:哨兵 + 扩展长度 0x0000FFFF → 8 + 65535
        var h32Mid = B(0x01, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00);
        Assert.Equal(8 + 65535, GetLength(h32Mid));
        Assert.Equal(8 + 65535, GetLength(Chain(h32Mid[..3], h32Mid[3..])));

        // 65536:哨兵 + 扩展长度 0x00010000 → 8 + 65536
        var h32Big = B(0x01, 0x02, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00);
        Assert.Equal(8 + 65536, GetLength(h32Big));
        Assert.Equal(8 + 65536, GetLength(Chain(h32Big[..5], h32Big[5..])));
    }
    #endregion

    #region 序列定界
    [Fact]
    [DisplayName("DefaultMessage_序列定界_普通包与链式跨段")]
    public void DefaultMessage_TryParse_Sequence()
    {
        // 负载10字节:Flag+Seq+Len(4字节头)
        var buf = B(0x01, 0x02, 10, 0).Concat(Range(100, 10)).ToArray();

        Assert.Equal(14, GetLength(buf)); // 完整帧(4字节头 + 10负载)
        Assert.Equal(14, GetLength(new ArrayPacket(buf).AsReadOnlySequence()));

        // 链式:头部横跨节点
        Assert.Equal(14, GetLength(Chain(buf[..2], buf[2..]).AsReadOnlySequence()));
        Assert.Equal(14, GetLength(Chain(buf[..3], buf[3..]).AsReadOnlySequence()));
        Assert.Equal(14, GetLength(Chain(buf[..1], buf[1..3], buf[3..]).AsReadOnlySequence()));
    }

    [Fact]
    [DisplayName("DefaultMessage_序列定界_扩展长度与数据不足")]
    public void DefaultMessage_TryParse_Extended()
    {
        // 扩展长度:0xFFFF + LE int32 70000
        var ext = B(0x01, 0x02, 0xFF, 0xFF, 70000 & 0xFF, (70000 >> 8) & 0xFF, (70000 >> 16) & 0xFF, (70000 >> 24) & 0xFF);

        Assert.Equal(70008, GetLength(ext));
        Assert.Equal(70008, GetLength(new ArrayPacket(ext).AsReadOnlySequence()));
        Assert.Equal(70008, GetLength(Chain(ext[..4], ext[4..]).AsReadOnlySequence()));
        Assert.Equal(70008, GetLength(Chain(ext[..7], ext[7..]).AsReadOnlySequence()));

        // 数据不足:扩展头不完整 / 基础头不完整
        Assert.Equal(0, GetLength(Chain(ext[..7]).AsReadOnlySequence()));
        Assert.Equal(0, GetLength(Chain(ext[..3]).AsReadOnlySequence()));
        Assert.Equal(0, GetLength(new ArrayPacket(Array.Empty<Byte>()).AsReadOnlySequence()));
    }

    [Fact]
    [DisplayName("WebSocket_序列定界_三种长度形态与掩码")]
    public void WebSocketMessage_TryParse_Length()
    {
        // 短长度:2 + 125
        var shortBuf = B(0x82, 125);
        Assert.Equal(127, GetWsLength(shortBuf));
        Assert.Equal(127, GetWsLength(new ArrayPacket(shortBuf).AsReadOnlySequence()));
        Assert.Equal(127, GetWsLength(Chain(shortBuf[..1], shortBuf[1..]).AsReadOnlySequence()));

        // 126:2字节大端 300 → 4 + 300
        var midBuf = B(0x82, 126, 0x01, 0x2C);
        Assert.Equal(304, GetWsLength(new ArrayPacket(midBuf).AsReadOnlySequence()));
        Assert.Equal(304, GetWsLength(Chain(midBuf[..2], midBuf[2..]).AsReadOnlySequence()));
        Assert.Equal(304, GetWsLength(Chain(midBuf[..3], midBuf[3..]).AsReadOnlySequence()));

        // 127:8字节大端 70000 → 10 + 70000
        var longBuf = B(0x82, 127, 0, 0, 0, 0, 0, 1, 0x11, 0x70);
        Assert.Equal(70010, GetWsLength(longBuf));
        Assert.Equal(70010, GetWsLength(new ArrayPacket(longBuf).AsReadOnlySequence()));
        Assert.Equal(70010, GetWsLength(Chain(longBuf[..5], longBuf[5..]).AsReadOnlySequence()));

        // 掩码:短帧 5 字节负载,头部完整(含掩码)→ 2 + 4 + 5,总长 11 已大于现有字节数(部分到达即可定界)
        var masked = B(0x82, 0x80 | 5, 1, 2, 3, 4);
        Assert.Equal(11, GetWsLength(masked));

        // 数据不足
        Assert.Equal(0, GetWsLength(new ArrayPacket(B(0x82)).AsReadOnlySequence()));
        Assert.Equal(0, GetWsLength(Chain(midBuf[..3]).AsReadOnlySequence()));
        Assert.Equal(0, GetWsLength(Chain(longBuf[..9]).AsReadOnlySequence()));
    }

    [Fact]
    [DisplayName("长度字段_序列定界_各字段规格与链式跨段")]
    public void MessageCodec_GetLength_Sequence()
    {
        // 2字节小端:300 → 帧 302
        var le = B(0x2C, 0x01);
        Assert.Equal(302, GetLen(le, 0, 2));
        Assert.Equal(302, GetLen(new ArrayPacket(le).AsReadOnlySequence(), 0, 2));
        Assert.Equal(302, GetLen(Chain(le[..1], le[1..]).AsReadOnlySequence(), 0, 2));

        // 偏移2(MQTT风格):[前缀2][长度2],帧长含前缀 = 2 + 300 + 2
        var mqtt = B(0x30, 0x00, 0x2C, 0x01);
        Assert.Equal(304, GetLen(mqtt, 2, 2));
        Assert.Equal(304, GetLen(new ArrayPacket(mqtt).AsReadOnlySequence(), 2, 2));
        Assert.Equal(304, GetLen(Chain(mqtt[..3], mqtt[3..]).AsReadOnlySequence(), 2, 2));

        // 2字节大端 / 4字节小端 / 1字节
        Assert.Equal(302, GetLen(new ArrayPacket(B(0x01, 0x2C)).AsReadOnlySequence(), 0, -2));
        Assert.Equal(304, GetLen(new ArrayPacket(B(0x2C, 0x01, 0, 0)).AsReadOnlySequence(), 0, 4));
        Assert.Equal(11, GetLen(new ArrayPacket(B(10)).AsReadOnlySequence(), 0, 1));

        // 变长编码:10 → 10+1;300 → 300+2;跨段
        Assert.Equal(11, GetLen(new ArrayPacket(B(0x0A)).AsReadOnlySequence(), 0, 0));
        Assert.Equal(302, GetLen(B(0xAC, 0x02), 0, 0));
        Assert.Equal(302, GetLen(new ArrayPacket(B(0xAC, 0x02)).AsReadOnlySequence(), 0, 0));
        Assert.Equal(302, GetLen(Chain(B(0xAC), B(0x02)).AsReadOnlySequence(), 0, 0));

        // 数据不足
        Assert.Equal(0, GetLen(new ArrayPacket(B(0x2C)).AsReadOnlySequence(), 0, 2));
        Assert.Equal(0, GetLen(new ArrayPacket(B(0x30, 0x00)).AsReadOnlySequence(), 2, 2));
        Assert.Equal(0, GetLen(new ArrayPacket(Array.Empty<Byte>()).AsReadOnlySequence(), 0, 2));

        // 变长字段跨轮截断(首字节高位为 1 表示继续):按数据不足返回 0,不抛异常
        Assert.Equal(0, GetLen(new ArrayPacket(B(0xAC)).AsReadOnlySequence(), 0, 0));
        Assert.Equal(0, GetLen(B(0xAC), 0, 0));

        // 长度字段读出负值(最高位为 1):视为无法定界返回 0,不得当作正帧长切帧
        Assert.Equal(0, GetLen(new ArrayPacket(B(0xFF, 0xFF, 0xFF, 0xFF)).AsReadOnlySequence(), 0, 4));

        // 帧长超出 Int32 上限:BodyLength 以 Int64 表示不溢出(旧 Int32 路径会溢出为负)
        var big = new LengthFieldCodec { Size = 4 }.TryParse(new ArrayPacket(B(0xFE, 0xFF, 0xFF, 0x7F)).AsReadOnlySequence());
        Assert.NotNull(big);
        Assert.Equal(4, big.Value.HeaderSize);
        Assert.Equal(0x7FFFFFFEL, big.Value.BodyLength);
    }

    /// <summary>长度字段定界便捷入口:按偏移与字段规格解析帧长(头部+体长);无法定界返回 0</summary>
    private static Int32 GetLen(Byte[] data, Int32 offset, Int32 size) => GetLen(new ArrayPacket(data).AsReadOnlySequence(), offset, size);

    private static Int32 GetLen(ReadOnlySequence<Byte> buffer, Int32 offset, Int32 size)
    {
        var rs = new LengthFieldCodec { Offset = offset, Size = size }.TryParse(buffer);
        return rs == null ? 0 : (Int32)(rs.Value.HeaderSize + rs.Value.BodyLength);
    }
    #endregion

    #region 流式头解析(TryParse)
    [Fact]
    [DisplayName("DefaultMessage_流式头_跨段解析与状态位")]
    public void DefaultMessage_TryParse_Header()
    {
        var codec = new SrmpCodec();

        // 0x81:mode=2(响应)、Flag=1、Seq=7、负载10
        var head = B(0x81, 0x07, 10, 0);

        var rs = codec.TryParse(new ArrayPacket(head).AsReadOnlySequence());
        Assert.NotNull(rs);
        Assert.Equal(4 + 10L, rs.Value.HeaderSize + rs.Value.BodyLength);
        Assert.Equal(4, rs.Value.HeaderSize);

        var msg = Assert.IsType<DefaultMessage>(rs.Value.Message);
        Assert.Equal(MessageKinds.Response, msg.Kind);
        Assert.Equal((Byte)1, msg.Flag);
        Assert.Equal((Byte)7, msg.Sequence);

        // 跨段
        var rs2 = codec.TryParse(Chain(head[..2], head[2..]).AsReadOnlySequence());
        Assert.NotNull(rs2);
        Assert.Equal(4 + 10L, rs2.Value.HeaderSize + rs2.Value.BodyLength);
        Assert.Equal(4, rs2.Value.HeaderSize);

        // 数据不足:不消费、不产生对象
        Assert.Null(codec.TryParse(Chain(head[..3]).AsReadOnlySequence()));

        // 扩展长度:0xFFFF + LE int32 70000 → 8 字节头
        var ext = B(0x01, 0x02, 0xFF, 0xFF, 0x70, 0x11, 0x01, 0x00);
        var rs4 = codec.TryParse(Chain(ext[..5], ext[5..]).AsReadOnlySequence());
        Assert.NotNull(rs4);
        Assert.Equal(8 + 70000L, rs4.Value.HeaderSize + rs4.Value.BodyLength);
        Assert.Equal(8, rs4.Value.HeaderSize);

        // 扩展头不足
        Assert.Null(codec.TryParse(Chain(ext[..7]).AsReadOnlySequence()));
    }

    [Fact]
    [DisplayName("WebSocket_流式头_跨段解析FIN类型与掩码")]
    public void WebSocketMessage_TryParse_Header()
    {
        var codec = new WebSocketCodec();

        var masked = B(0x81, 0x80 | 5, 0x11, 0x22, 0x33, 0x44);

        var rs = codec.TryParse(new ArrayPacket(masked).AsReadOnlySequence());
        Assert.NotNull(rs);
        Assert.Equal(2 + 4 + 5L, rs.Value.HeaderSize + rs.Value.BodyLength);
        Assert.Equal(6, rs.Value.HeaderSize);

        var msg = (WsMessage)rs.Value.Message!;
        Assert.True(msg.Fin);
        Assert.Equal(WebSocketMessageType.Text, msg.Type);
        Assert.Equal(new Byte[] { 0x11, 0x22, 0x33, 0x44 }, msg.MaskKey);

        // 跨段(掩码跨段)
        var rs2 = codec.TryParse(Chain(masked[..1], masked[1..3], masked[3..]).AsReadOnlySequence());
        Assert.NotNull(rs2);
        Assert.Equal(2 + 4 + 5L, rs2.Value.HeaderSize + rs2.Value.BodyLength);
        Assert.Equal(6, rs2.Value.HeaderSize);
        Assert.Equal(new Byte[] { 0x11, 0x22, 0x33, 0x44 }, ((WsMessage)rs2.Value.Message!).MaskKey);

        // 掩码字节未到齐:头部未完整,返回 null
        Assert.Null(codec.TryParse(Chain(masked[..5]).AsReadOnlySequence()));

        // 126 扩展长度 + 掩码跨段
        var mid = B(0x82, 0x80 | 126, 0x01, 0x2C, 1, 2, 3, 4);
        var rs4 = codec.TryParse(Chain(mid[..4], mid[4..]).AsReadOnlySequence());
        Assert.NotNull(rs4);
        Assert.Equal(4 + 4 + 300L, rs4.Value.HeaderSize + rs4.Value.BodyLength);
        Assert.Equal(8, rs4.Value.HeaderSize);
    }
    #endregion
}