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

namespace XUnitTest.Serialization;

public class BinaryTests
{
    [Fact]
    public void Normal()
    {
        var model = new MyModel { Code = 1234, Name = "Stone" };
        var bn = new Binary();
        Assert.Equal(5, bn.Handlers.Count);

        bn.Write(model);

        var pk = bn.GetPacket();
        Assert.Equal(10, pk.Length);
        Assert.Equal("000004D20553746F6E65", pk.ToHex());
        Assert.Equal(bn.Total, pk.Length);

        var bn2 = new Binary { Stream = pk.GetStream(false) };
        var model2 = bn2.Read<MyModel>();
        Assert.Equal(model.Code, model2.Code);
        Assert.Equal(model.Name, model2.Name);
        Assert.Equal(bn2.Total, pk.Length);

        //Assert.False(bn2.EndOfStream);
        Object value = null;
        Assert.False(bn2.TryRead(typeof(MyModel), ref value));
        Assert.True(bn2.EndOfStream);
    }

    [Fact]
    public void EncodeInt()
    {
        var model = new MyModel { Code = 1234, Name = "Stone" };
        var bn = new Binary { EncodeInt = true, };
        Assert.Equal(5, bn.Handlers.Count);

        bn.Write(model);

        var pk = bn.GetPacket();
        Assert.Equal(8, pk.Length);
        Assert.Equal("D2090553746F6E65", pk.ToHex());
        Assert.Equal(bn.Total, pk.Length);

        var bn2 = new Binary { Stream = pk.GetStream(false), EncodeInt = true };
        var model2 = bn2.Read<MyModel>();
        Assert.Equal(model.Code, model2.Code);
        Assert.Equal(model.Name, model2.Name);
        Assert.Equal(bn2.Total, pk.Length);
    }

    [Fact]
    public void IsLittleEndian()
    {
        var model = new MyModel { Code = 1234, Name = "Stone" };
        var bn = new Binary { IsLittleEndian = true, };
        Assert.Equal(5, bn.Handlers.Count);

        bn.Write(model);

        var pk = bn.GetPacket();
        Assert.Equal(10, pk.Length);
        Assert.Equal("D20400000553746F6E65", pk.ToHex());
        Assert.Equal(bn.Total, pk.Length);

        var bn2 = new Binary { Stream = pk.GetStream(false), IsLittleEndian = true };
        var model2 = bn2.Read<MyModel>();
        Assert.Equal(model.Code, model2.Code);
        Assert.Equal(model.Name, model2.Name);
        Assert.Equal(bn2.Total, pk.Length);
    }

    [Fact]
    public void UseFieldSize()
    {
        var model = new MyModelWithFieldSize { Name = "Stone" };
        var bn = new Binary { UseFieldSize = true, };
        Assert.Equal(5, bn.Handlers.Count);

        bn.Write(model);

        var pk = bn.GetPacket();
        Assert.Equal(6, pk.Length);
        Assert.Equal("0553746F6E65", pk.ToHex());
        Assert.Equal(bn.Total, pk.Length);

        var bn2 = new Binary { Stream = pk.GetStream(false), UseFieldSize = true };
        var model2 = bn2.Read<MyModelWithFieldSize>();
        Assert.Equal(model.Length, model2.Length);
        Assert.Equal(model.Name, model2.Name);
        Assert.Equal(bn2.Total, pk.Length);
    }

    [Fact]
    public void SizeWidth()
    {
        var model = new MyModel { Code = 1234, Name = "Stone" };
        var bn = new Binary { SizeWidth = 2, };
        Assert.Equal(5, bn.Handlers.Count);

        bn.Write(model);

        var pk = bn.GetPacket();
        Assert.Equal(11, pk.Length);
        Assert.Equal("000004D2000553746F6E65", pk.ToHex());
        Assert.Equal(bn.Total, pk.Length);

        var bn2 = new Binary { Stream = pk.GetStream(false), SizeWidth = 2 };
        var model2 = bn2.Read<MyModel>();
        Assert.Equal(model.Code, model2.Code);
        Assert.Equal(model.Name, model2.Name);
        Assert.Equal(bn2.Total, pk.Length);
    }

    [Fact]
    public void UseProperty()
    {
        var model = new MyModel { Code = 1234, Name = "Stone" };
        var bn = new Binary { UseProperty = false, };
        Assert.Equal(5, bn.Handlers.Count);

        bn.Write(model);

        var pk = bn.GetPacket();
        Assert.Equal(10, pk.Length);
        Assert.Equal("000004D20553746F6E65", pk.ToHex());
        Assert.Equal(bn.Total, pk.Length);

        var bn2 = new Binary { Stream = pk.GetStream(false), UseProperty = false };
        var model2 = bn2.Read<MyModel>();
        Assert.Equal(model.Code, model2.Code);
        Assert.Equal(model.Name, model2.Name);
        Assert.Equal(bn2.Total, pk.Length);
    }

    [Fact]
    public void IgnoreMembers()
    {
        var model = new MyModel { Code = 1234, Name = "Stone" };
        var bn = new Binary();
        bn.IgnoreMembers.Add("Code");
        Assert.Equal(5, bn.Handlers.Count);

        bn.Write(model);

        var pk = bn.GetPacket();
        Assert.Equal(6, pk.Length);
        Assert.Equal("0553746F6E65", pk.ToHex());
        Assert.Equal(bn.Total, pk.Length);

        var bn2 = new Binary { Stream = pk.GetStream(false) };
        bn2.IgnoreMembers.Add("Code");
        var model2 = bn2.Read<MyModel>();
        Assert.Equal(0, model2.Code);
        Assert.Equal(model.Name, model2.Name);
        Assert.Equal(bn2.Total, pk.Length);
    }

    [Fact]
    public void Fast()
    {
        var model = new MyModel { Code = 1234, Name = "Stone" };
        var pk = Binary.FastWrite(model);
        Assert.Equal(8, pk.Length);
        Assert.Equal("D2090553746F6E65", pk.ToHex());
        Assert.Equal("0gkFU3RvbmU=", pk.ToArray().ToBase64());

        var model2 = Binary.FastRead<MyModel>(pk.GetStream(false));
        Assert.Equal(model.Code, model2.Code);
        Assert.Equal(model.Name, model2.Name);

        var ms = new MemoryStream();
        var total = Binary.FastWrite(model, ms);
        Assert.Equal("D2090553746F6E65", ms.ToArray().ToHex());
        Assert.Equal(total, ms.Length);
    }

    private class MyModel
    {
        public Int32 Code { get; set; }

        public String Name { get; set; }
    }

    [Fact]
    public void Accessor()
    {
        var model = new MyModelWithAccessor { Code = 1234, Name = "Stone" };
        var pk = Binary.FastWrite(model);
        Assert.Equal(10, pk.Length);
        Assert.Equal("D20400000553746F6E65", pk.ToHex());
        Assert.Equal("0gQAAAVTdG9uZQ==", pk.ToArray().ToBase64());

        var model2 = Binary.FastRead<MyModelWithAccessor>(pk.GetStream(false));
        Assert.Equal(model.Code, model2.Code);
        Assert.Equal(model.Name, model2.Name);

        var ms = new MemoryStream();
        var total = Binary.FastWrite(model, ms);
        Assert.Equal("D20400000553746F6E65", ms.ToArray().ToHex());
        Assert.Equal(total, ms.Length);
    }

    private class MyModelWithAccessor : IAccessor
    {
        public Int32 Code { get; set; }

        public String Name { get; set; }

        public Boolean Read(Stream stream, Object context)
        {
            var reader = new BinaryReader(stream);
            Code = reader.ReadInt32();
            Name = reader.ReadString();

            if (context is Binary bn)
            {
                bn.Total += 4 + 1 + Name.GetBytes(bn.Encoding).Length;
            }

            return true;
        }

        public Boolean Write(Stream stream, Object context)
        {
            var writer = new BinaryWriter(stream);
            writer.Write(Code);
            writer.Write(Name);

            if (context is Binary bn)
            {
                bn.Total += 4 + 1 + Name.GetBytes(bn.Encoding).Length;
            }

            return true;
        }
    }

    [Fact]
    public void MemberAccessor()
    {
        var model = new MyModelWithMemberAccessor { Code = 1234, Name = "Stone" };
        var pk = Binary.FastWrite(model);
        Assert.Equal(8, pk.Length);
        Assert.Equal("D2090553746F6E65", pk.ToHex());
        Assert.Equal("0gkFU3RvbmU=", pk.ToArray().ToBase64());

        var model2 = Binary.FastRead<MyModelWithMemberAccessor>(pk.GetStream(false));
        Assert.Equal(model.Code, model2.Code);
        Assert.Equal(model.Name, model2.Name);

        var ms = new MemoryStream();
        var total = Binary.FastWrite(model, ms);
        Assert.Equal("D2090553746F6E65", ms.ToArray().ToHex());
        Assert.Equal(total, ms.Length);
    }

    private class MyModelWithMemberAccessor : IMemberAccessor
    {
        public Int32 Code { get; set; }

        public String Name { get; set; }

        public Boolean Read(IFormatterX formatter, AccessorContext context)
        {
            var bn = formatter as Binary;

            switch (context.Member.Name)
            {
                case "Code": Code = bn.Read<Int32>(); break;
                case "Name": Name = bn.Read<String>(); break;
            }
            //Code = bn.Read<Int32>();
            //Name = bn.Read<String>();

            return true;
        }
        public Boolean Write(IFormatterX formatter, AccessorContext context)
        {
            var bn = formatter as Binary;

            switch (context.Member.Name)
            {
                case "Code": bn.Write(Code); break;
                case "Name": bn.Write(Name); break;
            }

            return true;
        }
    }

    [Fact]
    public void FieldSize()
    {
        var model = new MyModelWithFieldSize { Name = "Stone" };
        Assert.Equal(0, model.Length);

        var bn = new Binary { EncodeInt = true, UseFieldSize = true };
        bn.Write(model);
        var pk = new ArrayPacket(bn.GetBytes());
        Assert.Equal(5, model.Length);
        Assert.Equal(6, pk.Total);
        Assert.Equal("0553746F6E65", pk.ToHex());
        Assert.Equal(bn.Total, pk.Total);

        var bn2 = new Binary { Stream = pk.GetStream(false), EncodeInt = true, UseFieldSize = true };
        var model2 = bn2.Read<MyModelWithFieldSize>();
        Assert.Equal(model.Length, model2.Length);
        Assert.Equal(model.Name, model2.Name);
        Assert.Equal(bn2.Total, pk.Total);
    }

    private class MyModelWithFieldSize
    {
        public Byte Length { get; set; }

        [FieldSize(nameof(Length))]
        public String Name { get; set; }
    }

    [Fact]
    public void MemberAccessorAtt()
    {
        var model = new MyModelWithMemberAccessorAtt { Code = 1234, Name = "Stone" };
        var pk = Binary.FastWrite(model);
        Assert.Equal(8, pk.Length);
        Assert.Equal("D2090553746F6E65", pk.ToHex());

        var model2 = Binary.FastRead<MyModelWithMemberAccessorAtt>(pk.GetStream(false));
        Assert.Equal(model.Code, model2.Code);
        Assert.Equal(model.Name, model2.Name);
    }

    private class MyModelWithMemberAccessorAtt
    {
        public Int32 Code { get; set; }

        [MyAccessor]
        public String Name { get; set; }
    }

    private class MyAccessorAttribute : AccessorAttribute
    {
        public override Boolean Read(IFormatterX formatter, AccessorContext context)
        {
            Assert.Equal("Name", context.Member.Name);

            var v = context.Value as MyModelWithMemberAccessorAtt;

            var bn = formatter as Binary;
            v.Name = bn.Read<String>();

            return true;
        }

        public override Boolean Write(IFormatterX formatter, AccessorContext context)
        {
            Assert.Equal("Name", context.Member.Name);

            var v = context.Value as MyModelWithMemberAccessorAtt;

            var bn = formatter as Binary;
            bn.Write(v.Name);

            return true;
        }
    }

    [Fact]
    public void FixedString()
    {
        var model = new MyModelWithFixed { Code = 1234, Name = "Stone" };

        var bn = new Binary { EncodeInt = true };
        bn.Write(model);
        var pk = bn.GetPacket();
        Assert.Equal(10, pk.Length);
        Assert.Equal("D20953746F6E65000000", pk.ToHex());
        Assert.Equal(bn.Total, pk.Total);

        var bn2 = new Binary { Stream = pk.GetStream(false), EncodeInt = true };
        var model2 = bn2.Read<MyModelWithFixed>();
        Assert.Equal(model.Code, model2.Code);
        Assert.Equal(model.Name, model2.Name);
        Assert.Equal(bn2.Total, pk.Total);
    }

    private class MyModelWithFixed
    {
        public Int32 Code { get; set; }

        [FixedString(8)]
        public String Name { get; set; }
    }

    [Fact]
    public void ReadDateTime()
    {
        var dt = DateTime.Now;
        var n1 = dt.ToInt();

        // 默认压缩整数,反而用了5字节
        var pk = Binary.FastWrite(dt);
        Assert.Equal(5, pk.Length);

        var dt2 = Binary.FastRead<DateTime>(pk.GetStream(false));
        var n2 = dt2.ToInt();

        Assert.Equal(dt.Trim(), dt2);
        Assert.Equal(n1, n2);

        // 不用压缩整数,只要4字节
        var bn = new Binary { EncodeInt = false };
        bn.Write(dt);
        pk = bn.GetPacket();
        Assert.Equal(4, pk.Length);
        Assert.Equal(bn.Total, pk.Total);

        bn = new Binary { EncodeInt = false, Stream = pk.GetStream(false) };
        dt2 = bn.Read<DateTime>();
        n2 = dt2.ToInt();

        Assert.Equal(dt.Trim(), dt2);
        Assert.Equal(n1, n2);
        Assert.Equal(bn.Total, pk.Total);
    }

    [Fact]
    public void ReadDateTime2()
    {
        // 刚好超过Int32.MaxValue,最高可达UInt32.MaxValue
        var dt = new DateTime(2038, 12, 31);
        var n1 = dt.ToLong();

        var pk = Binary.FastWrite(dt);
        Assert.Equal(5, pk.Length);

        var dt2 = Binary.FastRead<DateTime>(pk.GetStream(false));
        var n2 = dt2.ToLong();

        Assert.Equal(dt, dt2);
        Assert.Equal(n1, n2);
    }

    [Fact]
    public void WriteMinTime()
    {
        var dt = DateTime.MinValue;
        var n1 = dt.ToLong();

        var pk = Binary.FastWrite(dt);
        Assert.Equal(1, pk.Length);

        var dt2 = Binary.FastRead<DateTime>(pk.GetStream(false));
        var n2 = dt2.ToLong();

        Assert.Equal(dt, dt2);
        Assert.Equal(n1, n2);
    }

    [Fact]
    public void WriteMaxTime()
    {
        // 刚好超过UInt32.MaxValue
        var dt = new DateTime(2106, 12, 31);
        var n1 = dt.ToLong();

        var ex = Assert.Throws<InvalidDataException>(() => Binary.FastWrite(dt));
    }

    [Theory]
    [InlineData("0001-01-01 00:00:00")]
    [InlineData("9999-12-31 23:59:59")]
    [InlineData("9999-12-31 23:59:59.9999999")]
    [InlineData("2024-07-01")]
    [InlineData("1970-01-01")]
    [InlineData("2038-12-31")]
    [InlineData("2106-12-31")]
    public void WriteFullTime(String str)
    {
        var dt = str.ToDateTime();
        var n1 = dt.ToLong();

        // 使用完整时间序列化
        var bn = new Binary { FullTime = true };
        bn.Write(dt);
        var pk = bn.GetPacket();
        Assert.Equal(8, pk.Length);
        Assert.Equal(bn.Total, pk.Total);

        // 反序列化
        bn = new Binary { FullTime = true, Stream = pk.GetStream(false) };
        var dt2 = bn.Read<DateTime>();
        var n2 = dt2.ToLong();

        Assert.Equal(dt, dt2);
        Assert.Equal(n1, n2);
        Assert.Equal(bn.Total, pk.Total);
    }

    private static void WriteFullTimeByKind(DateTime dt)
    {
        var n1 = dt.ToLong();

        // 使用完整时间序列化
        var bn = new Binary { FullTime = true };
        bn.Write(dt);
        var pk = bn.GetPacket();
        Assert.Equal(8, pk.Length);
        Assert.Equal(bn.Total, pk.Total);

        // 反序列化
        bn = new Binary { FullTime = true, Stream = pk.GetStream(false) };
        var dt2 = bn.Read<DateTime>();
        var n2 = dt2.ToLong();

        Assert.Equal(dt.Kind, dt2.Kind);
        Assert.Equal(dt, dt2);
        Assert.Equal(n1, n2);
        Assert.Equal(bn.Total, pk.Total);
    }

    [Fact]
    public void WriteFullTimeByLocal()
    {
        var dt = DateTime.Now;
        WriteFullTimeByKind(dt);

        dt = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Local);
        WriteFullTimeByKind(dt);

        dt = new DateTime(9999, 12, 31, 23, 59, 59, DateTimeKind.Local);
        WriteFullTimeByKind(dt);
    }

    [Fact]
    public void WriteFullTimeByUTC()
    {
        var dt = DateTime.UtcNow;
        WriteFullTimeByKind(dt);

        dt = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        WriteFullTimeByKind(dt);

        dt = new DateTime(9999, 12, 31, 23, 59, 59, DateTimeKind.Utc);
        WriteFullTimeByKind(dt);
    }

    [Fact]
    public void WriteFullTimeByUnspecified()
    {
        var dt = new DateTime(2106, 12, 31);
        WriteFullTimeByKind(dt);

        dt = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
        WriteFullTimeByKind(dt);

        dt = new DateTime(9999, 12, 31, 23, 59, 59, DateTimeKind.Unspecified);
        WriteFullTimeByKind(dt);

        dt = DateTime.MinValue;
        WriteFullTimeByKind(dt);

        dt = DateTime.MaxValue;
        WriteFullTimeByKind(dt);
    }

    /// <summary>测试 ISpanSerializable 序列化</summary>
    [Fact]
    public void SpanSerializable()
    {
        // 测试 DbTable 实现 ISpanSerializable
        var dt = new DbTable
        {
            Columns = new[] { "Id", "Name", "Age" },
            Types = new[] { typeof(Int32), typeof(String), typeof(Int32) },
            Rows = new List<Object?[]>
            {
                new Object?[] { 1, "Alice", 25 },
                new Object?[] { 2, "Bob", 30 },
            }
        };

        // 使用 Binary 序列化
        var bn = new Binary();
        bn.Write(dt);
        var pk = bn.GetPacket();

        // 反序列化
        bn = new Binary { Stream = pk.GetStream(false) };
        var dt2 = bn.Read<DbTable>();
        Assert.NotNull(dt2);
        Assert.Equal(dt.Columns.Length, dt2.Columns.Length);
        Assert.Equal(2, dt2.Total);
        Assert.Equal(2, dt2.Rows.Count);
    }

    /// <summary>测试 DbTable Span 路径序列化(EncodeInt+FullTime,与Binary格式兼容)</summary>
    [Fact]
    public void SpanSerializablePerformance()
    {
        var dt = new DbTable
        {
            Columns = new[] { "Id", "Name" },
            Types = new[] { typeof(Int32), typeof(String) },
            Rows = new List<Object?[]>
            {
                new Object?[] { 1, "Test1" },
                new Object?[] { 2, "Test2" },
            }
        };

        // DbTable 的 Span 路径不再通过 ISpanSerializable 接口,直接调用公共方法
        var buffer = new Byte[8192];
        var writer = new SpanWriter(buffer);
        dt.Write(ref writer);
        Assert.True(writer.WrittenCount > 0);

        // 反序列化
        var reader = new SpanReader(writer.WrittenSpan);
        var dt2 = new DbTable();
        dt2.Read(ref reader);
        Assert.Equal(2, dt2.Total);
    }

    /// <summary>测试 ECKey 的 ISpanSerializable</summary>
    [Fact]
    public void ECKeySpanSerializable()
    {
        var key = new ECKey
        {
            X = new Byte[] { 1, 2, 3, 4 },
            Y = new Byte[] { 5, 6, 7, 8 },
            D = new Byte[] { 9, 10, 11, 12 }
        };

        // Direct Span 序列化
        var buffer = new Byte[1024];
        var writer = new SpanWriter(buffer);
        ((ISpanSerializable)key).Write(ref writer);
        Assert.True(writer.WrittenCount > 0);

        // 反序列化
        var reader = new SpanReader(writer.WrittenSpan);
        var key2 = new ECKey();
        ((ISpanSerializable)key2).Read(ref reader);
        Assert.Equal(key.X, key2.X);
        Assert.Equal(key.Y, key2.Y);
        Assert.Equal(key.D, key2.D);
    }

    /// <summary>测试 HttpResponse.SetResult 对 ISpanSerializable 的支持</summary>
    [Fact]
    public void HttpResponseSetResultSpanSerializable()
    {
        var response = new HttpResponse();

        // 设置 DbTable 作为结果
        var dt = new DbTable
        {
            Columns = new[] { "Id", "Name" },
            Types = new[] { typeof(Int32), typeof(String) },
            Rows = new List<Object?[]>
            {
                new Object?[] { 1, "Alice" },
            }
        };

        response.SetResult(dt);
        Assert.NotNull(response.Body);
        Assert.Equal("application/octet-stream", response.ContentType);
    }

    /// <summary>测试编码器对 ISpanSerializable 的支持</summary>
    [Fact]
    public void PacketEncoderSpanSerializable()
    {
        var encoder = new DefaultPacketEncoder();
        
        // 测试 Encode
        var dt = new DbTable
        {
            Columns = new[] { "Id" },
            Types = new[] { typeof(Int32) },
            Rows = new List<Object?[]> { new Object?[] { 1 } }
        };

        var pk = encoder.Encode(dt);
        Assert.NotNull(pk);
        Assert.True(pk.Length > 0);

        // 测试 Decode
        var dt2 = encoder.Decode(pk, typeof(DbTable)) as DbTable;
        Assert.NotNull(dt2);
        Assert.Equal(1, dt2.Total);
    }
}