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

namespace XUnitTest.Messaging;

/// <summary>长度字段消息编解码器(LengthFieldCodec)测试</summary>
public class LengthFieldCodecTests
{
    #region 工具
    private static async Task<Byte[]> ReadBodyAsync(IMessage message)
    {
        var body = await message.Body!.ReadAllAsync();
        var data = body.AsReadOnlySequence().ToArray();
        body.TryDispose();

        return data;
    }
    #endregion

    [Fact]
    [DisplayName("长度字段编解码_变长负值_标记损坏帧")]
    public void TryParse_VarintNegative_ReturnsInvalid()
    {
        var codec = new LengthFieldCodec { Size = 0 };

        // 5 字节变长编码 FF FF FF FF 0F 表示 -1:负长度按损坏帧上报,
        // 既不能当作“数据不足”默默等待,也不能当作“帧已到齐”去切帧
        var frame = new Byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F };

        var rs = codec.TryParse(new ArrayPacket(frame).AsReadOnlySequence());
        Assert.True(rs!.Value.Invalid);
        Assert.Null(rs.Value.Message);
    }

    [Fact]
    [DisplayName("长度字段编解码_2字节小端_定界")]
    public void TryParse_Size2_LittleEndian()
    {
        var codec = new LengthFieldCodec();
        var frame = new Byte[] { 0x05, 0x00, (Byte)'h', (Byte)'e', (Byte)'l', (Byte)'l', (Byte)'o' };

        var rs = codec.TryParse(new ArrayPacket(frame).AsReadOnlySequence());
        Assert.NotNull(rs);
        Assert.NotNull(rs.Value.Message);
        Assert.Equal(2, rs.Value.HeaderSize);
        Assert.Equal(5L, rs.Value.BodyLength);

        // 头部不足:等待(不产生对象)
        Assert.Null(codec.TryParse(new ArrayPacket(new Byte[] { 0x05 }).AsReadOnlySequence()));
    }

    [Fact]
    [DisplayName("长度字段编解码_MQTT风格_头部偏移跳过")]
    public void TryParse_Offset2()
    {
        var codec = new LengthFieldCodec { Offset = 2 };
        var frame = new Byte[] { 0xA0, 0x01, 0x03, 0x00, (Byte)'a', (Byte)'b', (Byte)'c' };

        var rs = codec.TryParse(new ArrayPacket(frame).AsReadOnlySequence());
        Assert.NotNull(rs);
        Assert.NotNull(rs.Value.Message);
        Assert.Equal(4, rs.Value.HeaderSize);
        Assert.Equal(3L, rs.Value.BodyLength);

        // 仅有固定头部无长度字段:等待
        Assert.Null(codec.TryParse(new ArrayPacket(new Byte[] { 0xA0, 0x01 }).AsReadOnlySequence()));
    }

    [Fact]
    [DisplayName("长度字段编解码_大端与变长_定界")]
    public void TryParse_BigEndian_Varint()
    {
        // 大端 2 字节:0x00 0x05
        var codecBe = new LengthFieldCodec { Size = -2 };
        var rs1 = codecBe.TryParse(new ArrayPacket(new Byte[] { 0x00, 0x05 }).AsReadOnlySequence());
        Assert.NotNull(rs1);
        Assert.NotNull(rs1.Value.Message);
        Assert.Equal(2, rs1.Value.HeaderSize);
        Assert.Equal(5L, rs1.Value.BodyLength);

        var codecVar = new LengthFieldCodec { Size = 0 };

        // 变长:127 → 1 字节
        var rs2 = codecVar.TryParse(new ArrayPacket(new Byte[] { 0x7F }).AsReadOnlySequence());
        Assert.NotNull(rs2);
        Assert.NotNull(rs2.Value.Message);
        Assert.Equal(1, rs2.Value.HeaderSize);
        Assert.Equal(127L, rs2.Value.BodyLength);

        // 变长:128 → [0x80, 0x01] 两字节
        var rs3 = codecVar.TryParse(new ArrayPacket(new Byte[] { 0x80, 0x01 }).AsReadOnlySequence());
        Assert.NotNull(rs3);
        Assert.NotNull(rs3.Value.Message);
        Assert.Equal(2, rs3.Value.HeaderSize);
        Assert.Equal(128L, rs3.Value.BodyLength);

        // 变长未终止:等待
        Assert.Null(codecVar.TryParse(new ArrayPacket(new Byte[] { 0x80 }).AsReadOnlySequence()));
    }

    [Fact]
    [DisplayName("长度字段编解码_非法长度_标记损坏帧")]
    public void TryParse_Invalid()
    {
        // 4 字节长度为负(最高位 1):头部已完整但非法,应标记损坏帧(立即报错,而不是等到残余上限)
        var codec = new LengthFieldCodec { Size = 4 };
        var r1 = codec.TryParse(new ArrayPacket(new Byte[] { 0xFF, 0xFF, 0xFF, 0xFF }).AsReadOnlySequence());
        Assert.NotNull(r1);
        Assert.True(r1.Value.Invalid);

        // 大端 4 字节同样处理
        var codecBe = new LengthFieldCodec { Size = -4 };
        var r2 = codecBe.TryParse(new ArrayPacket(new Byte[] { 0xFF, 0xFF, 0xFF, 0xFF }).AsReadOnlySequence());
        Assert.NotNull(r2);
        Assert.True(r2.Value.Invalid);

        // 变长编码超过 32 位(5 字节仍未终止):非法
        var codecVar = new LengthFieldCodec { Size = 0 };
        var r3 = codecVar.TryParse(new ArrayPacket(new Byte[] { 0x80, 0x80, 0x80, 0x80, 0x80 }).AsReadOnlySequence());
        Assert.NotNull(r3);
        Assert.True(r3.Value.Invalid);

        // 变长数据不足(未终止但不足 5 字节):等待更多数据,不能当成损坏
        var r4 = codecVar.TryParse(new ArrayPacket(new Byte[] { 0x80, 0x80 }).AsReadOnlySequence());
        Assert.Null(r4);
    }

    [Fact]
    [DisplayName("长度字段编解码_构建与解析_整帧往返")]
    public async Task Build_RoundTrip()
    {
        var codec = new LengthFieldCodec();
        var msg = new Message();
        msg.SetBody(new ArrayPacket(Encoding.UTF8.GetBytes("hello")));

        var pk = codec.Build(msg);
        Assert.NotNull(pk);
        Assert.NotNull(msg.Payload);     // 构建不消费消息负载

        // 帧字节:[0x05, 0x00][hello]
        Assert.Equal(new Byte[] { 0x05, 0x00, (Byte)'h', (Byte)'e', (Byte)'l', (Byte)'l', (Byte)'o' }, pk!.AsReadOnlySequence().ToArray());

        // 帧泵解析 + 读体
        var pump = new MessagePump(codec);
        using var pipe = new Pipe();
        pipe.Writer.Append(pk);

        Assert.True(pump.TryRead(pipe.Reader, out var m2));
        Assert.Equal("hello", Encoding.UTF8.GetString(await ReadBodyAsync(m2!)));
        m2!.Dispose();
    }

    [Fact]
    [DisplayName("长度字段编解码_构建_头部偏移零填充")]
    public void Build_Offset2_FillZero()
    {
        var codec = new LengthFieldCodec { Offset = 2 };
        var msg = new Message();
        msg.SetBody(new ArrayPacket(Encoding.UTF8.GetBytes("abc")));

        var pk = codec.Build(msg);
        Assert.NotNull(pk);
        Assert.Equal(new Byte[] { 0x00, 0x00, 0x03, 0x00, (Byte)'a', (Byte)'b', (Byte)'c' }, pk!.AsReadOnlySequence().ToArray());
        pk.TryDispose();
    }

    [Fact]
    [DisplayName("长度字段编解码_大端与变长_构建")]
    public void Build_BigEndian_Varint()
    {
        // 大端 2 字节:长度 5 → [0x00, 0x05]
        var codecBe = new LengthFieldCodec { Size = -2 };
        var msg = new Message();
        msg.SetBody(new ArrayPacket(Encoding.UTF8.GetBytes("hello")));
        var pk = codecBe.Build(msg);
        Assert.NotNull(pk);
        Assert.Equal(new Byte[] { 0x00, 0x05, (Byte)'h', (Byte)'e', (Byte)'l', (Byte)'l', (Byte)'o' }, pk!.AsReadOnlySequence().ToArray());
        pk.TryDispose();

        // 变长:128 字节负载 → 头 [0x80, 0x01]
        var codecVar = new LengthFieldCodec { Size = 0 };
        var msg2 = new Message();
        msg2.SetBody(new ArrayPacket(new Byte[128]));
        var pk2 = codecVar.Build(msg2);
        Assert.NotNull(pk2);
        var data = pk2!.AsReadOnlySequence().ToArray();
        Assert.Equal(130, data.Length);
        Assert.Equal(0x80, data[0]);
        Assert.Equal(0x01, data[1]);
        pk2.TryDispose();
    }

    [Fact]
    [DisplayName("长度字段编解码_容量超限_构建抛出")]
    public void Build_Overflow_Throws()
    {
        var codec = new LengthFieldCodec { Size = 1 };
        var msg = new Message();
        msg.SetBody(new ArrayPacket(new Byte[300]));

        Assert.Throws<ArgumentOutOfRangeException>(() => codec.Build(msg));
    }

    [Fact]
    [DisplayName("长度字段编解码_头部构建_声明长度")]
    public void BuildHeader_DeclaresLength()
    {
        var codec = new LengthFieldCodec { Offset = 2 };
        var pk = codec.BuildHeader(new Message(), 100);
        Assert.Equal(new Byte[] { 0x00, 0x00, 0x64, 0x00 }, pk.AsReadOnlySequence().ToArray());
        pk.TryDispose();

        // 负数拒绝
        Assert.Throws<ArgumentOutOfRangeException>(() => codec.BuildHeader(new Message(), -1));
    }

    [Fact]
    [DisplayName("帧泵_长度字段跨段_等待后定界")]
    public async Task Pump_HeaderAcrossSegments()
    {
        var pump = new MessagePump(new LengthFieldCodec());
        using var pipe = new Pipe();

        // 长度字段只到了 1 字节
        pipe.Writer.Append(new ArrayPacket(new Byte[] { 0x03 }));
        Assert.False(pump.TryRead(pipe.Reader, out _));
        Assert.Equal(1, pipe.UnconsumedLength);

        // 第二段到达:0x00 + 负载
        pipe.Writer.Append(new ArrayPacket(new Byte[] { 0x00, (Byte)'a', (Byte)'b', (Byte)'c' }));
        Assert.True(pump.TryRead(pipe.Reader, out var m));
        Assert.Equal("abc", Encoding.UTF8.GetString(await ReadBodyAsync(m!)));
        m!.Dispose();
    }

    [Fact]
    [DisplayName("帧泵_头到齐体未齐_流式交付")]
    public async Task Pump_HeadReady_BodyStreaming()
    {
        var pump = new MessagePump(new LengthFieldCodec());
        using var pipe = new Pipe();

        // 头 + 部分体:3 字节体只到了 1 字节,头到齐即交付
        pipe.Writer.Append(new ArrayPacket(new Byte[] { 0x03, 0x00, (Byte)'a' }));
        Assert.True(pump.TryRead(pipe.Reader, out var m));
        Assert.NotNull(m);
        Assert.True(m!.Body!.IsStreaming);

        // 补体:读满
        var readTask = ReadBodyAsync(m);
        pipe.Writer.Append(new ArrayPacket(new Byte[] { (Byte)'b', (Byte)'c' }));
        Assert.Equal("abc", Encoding.UTF8.GetString(await readTask));
        m.Dispose();
    }
}