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

namespace XUnitTest.Buffers;

/// <summary>SpanWriter/SpanReader WriteValue/ReadValue 实例方法全覆盖测试</summary>
/// <remarks>
/// 覆盖所有基础类型、Nullable、EncodeInt、FullTime 代码路径,
/// 以及 SpanWriter↔SpanReader、Binary↔SpanWriter/SpanReader 互操作字节级一致性验证。
/// </remarks>
public class SpanReaderWriterTests
{
    #region WriteValue/ReadValue 基础类型
    [Fact]
    [DisplayName("WriteValue/ReadValue: 所有基础类型往返一致")]
    public void WriteReadValue_AllPrimitives()
    {
        var buf = new Byte[512];
        var cases = new (Object? value, Type type)[]
        {
            (true,                typeof(Boolean)),
            (false,               typeof(Boolean)),
            ((Byte)0,             typeof(Byte)),
            ((Byte)0xFF,          typeof(Byte)),
            ((SByte)(-100),       typeof(SByte)),
            ((SByte)127,          typeof(SByte)),
            ((Char)'A',           typeof(Char)),
            ((Char)'\0',          typeof(Char)),
            ((Int16)(-1234),      typeof(Int16)),
            ((Int16)Int16.MaxValue, typeof(Int16)),
            ((UInt16)5678,        typeof(UInt16)),
            ((UInt16)UInt16.MaxValue, typeof(UInt16)),
            (0,                   typeof(Int32)),
            (12345,               typeof(Int32)),
            (Int32.MaxValue,      typeof(Int32)),
            (Int32.MinValue,      typeof(Int32)),
            ((UInt32)654321,      typeof(UInt32)),
            ((UInt32)UInt32.MaxValue, typeof(UInt32)),
            (0L,                  typeof(Int64)),
            (9876543210L,         typeof(Int64)),
            (Int64.MaxValue,      typeof(Int64)),
            (Int64.MinValue,      typeof(Int64)),
            ((UInt64)9876543210UL, typeof(UInt64)),
            ((UInt64)UInt64.MaxValue, typeof(UInt64)),
            (0f,                  typeof(Single)),
            (3.14f,               typeof(Single)),
            (Single.MaxValue,     typeof(Single)),
            (0.0,                 typeof(Double)),
            (2.718281828,         typeof(Double)),
            (Double.MaxValue,     typeof(Double)),
            (0m,                  typeof(Decimal)),
            (123456.789m,         typeof(Decimal)),
            (Decimal.MaxValue,    typeof(Decimal)),
            (new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc), typeof(DateTime)),
            (DateTime.MinValue,   typeof(DateTime)),
            ("hello world",       typeof(String)),
            ("",                  typeof(String)),
            (null,                typeof(String)),
        };

        foreach (var (value, type) in cases)
        {
            var writer = new SpanWriter(buf);
            writer.WriteValue(value, type);
            var written = writer.WrittenCount;
            Assert.True(written > 0 || type == typeof(String) && value == null, $"Type={type.Name} value={value}");

            var reader = new SpanReader(buf.AsSpan(0, written));
            var result = reader.ReadValue(type);
            Assert.Equal(reader.Position, written);

            if (type == typeof(DateTime))
            {
                // DateTime精度到秒(Unix秒数模式)
                var expected = ((DateTime)value!).Ticks / TimeSpan.TicksPerSecond;
                var actual = ((DateTime)result!).Ticks / TimeSpan.TicksPerSecond;
                Assert.Equal(expected, actual);
            }
            else if (type == typeof(String) && value == null)
            {
                // null字符串序列化后读回为空字符串(长度前缀=0),符合SpanWriter设计
                Assert.Equal("", result);
            }
            else
            {
                Assert.Equal(value, result);
            }
        }
    }

    [Fact]
    [DisplayName("WriteValue/ReadValue: Byte[] 和 Guid")]
    public void WriteReadValue_ComplexTypes()
    {
        var buf = new Byte[256];

        // Byte[] 有数据
        var data = new Byte[] { 0xDE, 0xAD, 0xBE, 0xEF };
        var writer = new SpanWriter(buf);
        writer.WriteValue(data, typeof(Byte[]));
        var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(data, (Byte[]?)reader.ReadValue(typeof(Byte[])));

        // Byte[] 空数组
        writer = new SpanWriter(buf);
        writer.WriteValue(Array.Empty<Byte>(), typeof(Byte[]));
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Empty((Byte[]?)reader.ReadValue(typeof(Byte[])) ?? []);

        // Byte[] null → 将值视为 Byte[] null,长度前缀为0
        writer = new SpanWriter(buf);
        // null 在 Byte[] 非可空情况下 WriteValue 通过Code=Object走到value is Byte[] 失败
        // 实际上 null/Byte[] 应用可空包装,非可空情况下 null 值不写标志位
        // 直接传空数组
        writer.WriteValue(Array.Empty<Byte>(), typeof(Byte[]));
        Assert.Equal(1, writer.WrittenCount); // 仅1字节:EncodedInt(0)

        // Guid
        var guid = Guid.NewGuid();
        writer = new SpanWriter(buf);
        writer.WriteValue(guid, typeof(Guid));
        Assert.Equal(16, writer.WrittenCount); // Guid 固定16字节,无长度前缀
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(guid, (Guid)reader.ReadValue(typeof(Guid))!);
    }

    [Fact]
    [DisplayName("WriteUInt24/ReadUInt24: 小端和大端往返")]
    public void WriteReadUInt24_LittleAndBigEndian()
    {
        var buf = new Byte[256];

        // 测试值:0x123456
        var testValues = new UInt32[] { 0x000000, 0x123456, 0xFFFFFF };

        foreach (var value in testValues)
        {
            // 小端测试
            var writer = new SpanWriter(buf) { IsLittleEndian = true };
            writer.WriteUInt24(value);
            var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount)) { IsLittleEndian = true };
            var result = reader.ReadUInt24();
            Assert.Equal(value, result);

            // 大端测试
            writer = new SpanWriter(buf) { IsLittleEndian = false };
            writer.WriteUInt24(value);
            reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount)) { IsLittleEndian = false };
            result = reader.ReadUInt24();
            Assert.Equal(value, result);
        }
    }

    [Fact]
    [DisplayName("WriteUInt24/ReadUInt24: 小端和大端字节序验证")]
    public void WriteReadUInt24_EndianessVerification()
    {
        var buf = new Byte[256];
        var value = 0x123456u;

        // 小端:低字节在前 (0x56, 0x34, 0x12)
        var writer = new SpanWriter(buf) { IsLittleEndian = true };
        writer.WriteUInt24(value);
        Assert.Equal(3, writer.WrittenCount);
        Assert.Equal(0x56, buf[0]);
        Assert.Equal(0x34, buf[1]);
        Assert.Equal(0x12, buf[2]);

        // 大端:高字节在前 (0x12, 0x34, 0x56)
        writer = new SpanWriter(buf) { IsLittleEndian = false };
        writer.WriteUInt24(value);
        Assert.Equal(3, writer.WrittenCount);
        Assert.Equal(0x12, buf[0]);
        Assert.Equal(0x34, buf[1]);
        Assert.Equal(0x56, buf[2]);
    }

    [Fact]
    [DisplayName("WriteValue: null值写入类型默认值(非可空类型)")]
    public void WriteValue_NullForNonNullableTypes()
    {
        var buf = new Byte[64];

        // null Boolean → 写0x00
        var writer = new SpanWriter(buf);
        writer.WriteValue(null, typeof(Boolean));
        var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.False((Boolean)reader.ReadValue(typeof(Boolean))!);

        // null Int32 → 写0
        writer = new SpanWriter(buf);
        writer.WriteValue(null, typeof(Int32));
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(0, (Int32)reader.ReadValue(typeof(Int32))!);

        // null Int64 → 写0
        writer = new SpanWriter(buf);
        writer.WriteValue(null, typeof(Int64));
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(0L, (Int64)reader.ReadValue(typeof(Int64))!);

        // null DateTime → 写0(等价MinValue)
        writer = new SpanWriter(buf);
        writer.WriteValue(null, typeof(DateTime));
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(DateTime.MinValue, (DateTime)reader.ReadValue(typeof(DateTime))!);

        // DBNull 等价 null
        writer = new SpanWriter(buf);
        writer.WriteValue(DBNull.Value, typeof(Int32));
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(0, (Int32)reader.ReadValue(typeof(Int32))!);
    }
    #endregion

    #region DateTime FullTime 路径
    [Fact]
    [DisplayName("FullTime=false: DateTime写4字节Unix秒(默认模式)")]
    public void DateTime_FullTimeFalse_UnixSeconds()
    {
        var buf = new Byte[32];
        var dt1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        var dtSample = new DateTime(2025, 3, 15, 10, 20, 30, DateTimeKind.Utc);

        var writer = new SpanWriter(buf) { FullTime = false };
        writer.WriteValue(dtSample, typeof(DateTime));
        Assert.Equal(4, writer.WrittenCount);

        // 字节内容是Unix秒数(小端)
        var expectedSeconds = (UInt32)(dtSample - dt1970).TotalSeconds;
        var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(expectedSeconds, reader.ReadUInt32());

        // 往返正确(精度到秒)
        writer = new SpanWriter(buf) { FullTime = false };
        writer.WriteValue(dtSample, typeof(DateTime));
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount)) { FullTime = false };
        var dtBack = (DateTime)reader.ReadValue(typeof(DateTime))!;
        Assert.Equal(dtSample.Ticks / TimeSpan.TicksPerSecond, dtBack.Ticks / TimeSpan.TicksPerSecond);

        // DateTime.MinValue → 写0
        writer = new SpanWriter(buf) { FullTime = false };
        writer.WriteValue(DateTime.MinValue, typeof(DateTime));
        Assert.Equal(4, writer.WrittenCount);
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount)) { FullTime = false };
        Assert.Equal(DateTime.MinValue, (DateTime)reader.ReadValue(typeof(DateTime))!);
    }

    [Fact]
    [DisplayName("FullTime=true: DateTime写8字节ToBinary,精度到Ticks")]
    public void DateTime_FullTimeTrue_ToBinary()
    {
        var buf = new Byte[32];
        var dt = new DateTime(2025, 6, 15, 14, 30, 50, 123, DateTimeKind.Utc);

        var writer = new SpanWriter(buf) { FullTime = true };
        writer.WriteValue(dt, typeof(DateTime));
        Assert.Equal(8, writer.WrittenCount); // 始终8字节(无EncodeInt时)

        var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount)) { FullTime = true };
        var dtBack = (DateTime)reader.ReadValue(typeof(DateTime))!;
        Assert.Equal(dt, dtBack); // Ticks级精度

        // FullTime=true 不影响写入字节数(写ToBinary Int64固定8字节)
        writer = new SpanWriter(buf) { FullTime = true };
        writer.WriteValue(DateTime.MinValue, typeof(DateTime));
        Assert.Equal(8, writer.WrittenCount);
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount)) { FullTime = true };
        Assert.Equal(DateTime.MinValue, (DateTime)reader.ReadValue(typeof(DateTime))!);
    }

    [Fact]
    [DisplayName("FullTime=true与Binary(FullTime=true)字节级一致")]
    public void DateTime_FullTimeTrue_CompatibleWithBinary()
    {
        var buf = new Byte[64];
        var datetimes = new DateTime[]
        {
            new(2025, 6, 30, 23, 59, 59, 999, DateTimeKind.Utc),
            new(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            DateTime.MinValue,
        };

        foreach (var dt in datetimes)
        {
            var bn = new Binary { IsLittleEndian = true, FullTime = true };
            bn.Write(dt, typeof(DateTime));
            var binaryBytes = bn.GetPacket().ToHex(-1);

            var writer = new SpanWriter(buf) { IsLittleEndian = true, FullTime = true };
            writer.WriteValue(dt, typeof(DateTime));
            var spanBytes = buf.ToHex(0, writer.WrittenCount);

            Assert.True(binaryBytes == spanBytes, $"dt={dt:O}: Binary={binaryBytes} vs SpanWriter={spanBytes}");
        }
    }
    #endregion

    #region EncodeInt 路径
    [Fact]
    [DisplayName("EncodeInt=true: 整数使用7位压缩编码,小值占更少字节")]
    public void WriteValue_EncodeInt_ByteCount()
    {
        var buf = new Byte[64];

        // Int32 小值
        var writer = new SpanWriter(buf) { EncodeInt = true };
        writer.WriteValue(42, typeof(Int32));
        Assert.Equal(1, writer.WrittenCount);   // 42 < 0x80 → 1字节

        writer = new SpanWriter(buf) { EncodeInt = false };
        writer.WriteValue(42, typeof(Int32));
        Assert.Equal(4, writer.WrittenCount);   // 固定4字节

        // Int32 大值
        writer = new SpanWriter(buf) { EncodeInt = true };
        writer.WriteValue(1_000_000, typeof(Int32));
        Assert.True(writer.WrittenCount == 3, $"1M encoded should be 3 bytes, got {writer.WrittenCount}");

        // Int16
        writer = new SpanWriter(buf) { EncodeInt = true };
        writer.WriteValue((Int16)1000, typeof(Int16));
        Assert.Equal(2, writer.WrittenCount);   // 1000 ∈ [0x80,0x4000) → 2字节

        // Int64
        writer = new SpanWriter(buf) { EncodeInt = true };
        writer.WriteValue(200L, typeof(Int64));
        Assert.Equal(2, writer.WrittenCount);   // 200 ∈ [0x80,0x4000) → 2字节

        writer = new SpanWriter(buf) { EncodeInt = false };
        writer.WriteValue(200L, typeof(Int64));
        Assert.Equal(8, writer.WrittenCount);   // 固定8字节
    }

    [Fact]
    [DisplayName("EncodeInt=true: 所有整数类型往返一致(含边界值)")]
    public void WriteValue_EncodeInt_RoundTrip()
    {
        var buf = new Byte[128];

        // Int16
        var values16 = new Int16[] { 0, 1, 127, 128, -1, -128, Int16.MaxValue, Int16.MinValue };
        foreach (var v in values16)
        {
            var writer = new SpanWriter(buf) { EncodeInt = true };
            writer.WriteValue(v, typeof(Int16));
            var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount)) { EncodeInt = true };
            Assert.Equal(v, (Int16)reader.ReadValue(typeof(Int16))!);
        }

        // Int32
        var values32 = new Int32[] { 0, 1, 127, 128, 16383, 16384, -1, Int32.MaxValue, Int32.MinValue };
        foreach (var v in values32)
        {
            var writer = new SpanWriter(buf) { EncodeInt = true };
            writer.WriteValue(v, typeof(Int32));
            var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount)) { EncodeInt = true };
            Assert.Equal(v, (Int32)reader.ReadValue(typeof(Int32))!);
        }

        // Int64
        var values64 = new Int64[] { 0, 1, 127L, 16383L, 2_097_151L, (Int64)Int32.MaxValue + 1, -1L, Int64.MaxValue, Int64.MinValue };
        foreach (var v in values64)
        {
            var writer = new SpanWriter(buf) { EncodeInt = true };
            writer.WriteValue(v, typeof(Int64));
            var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount)) { EncodeInt = true };
            Assert.Equal(v, (Int64)reader.ReadValue(typeof(Int64))!);
        }

        // UInt16 / UInt32 / UInt64
        var writer2 = new SpanWriter(buf) { EncodeInt = true };
        writer2.WriteValue((UInt16)60000, typeof(UInt16));
        writer2.WriteValue((UInt32)2_000_000_000, typeof(UInt32));
        writer2.WriteValue((UInt64)9876543210, typeof(UInt64));
        var reader2 = new SpanReader(buf.AsSpan(0, writer2.WrittenCount)) { EncodeInt = true };
        Assert.Equal((UInt16)60000, (UInt16)reader2.ReadValue(typeof(UInt16))!);
        Assert.Equal((UInt32)2_000_000_000, (UInt32)reader2.ReadValue(typeof(UInt32))!);
        Assert.Equal((UInt64)9876543210, (UInt64)reader2.ReadValue(typeof(UInt64))!);
    }

    [Fact]
    [DisplayName("EncodeInt=true WriteValue 与 Binary(EncodeInt=true) 字节级一致")]
    public void WriteValue_EncodeInt_CompatibleWithBinary()
    {
        var cases = new (Object value, Type type, String label)[]
        {
            (0,             typeof(Int32),  "Int32 0"),
            (42,            typeof(Int32),  "Int32 42"),
            (16383,         typeof(Int32),  "Int32 16383"),
            (1_000_000,     typeof(Int32),  "Int32 1M"),
            (-1,            typeof(Int32),  "Int32 -1"),
            ((Int16)100,    typeof(Int16),  "Int16 100"),
            ((Int16)(-500), typeof(Int16),  "Int16 -500"),
            (9876543210L,   typeof(Int64),  "Int64 large"),
            (-1L,           typeof(Int64),  "Int64 -1"),
            ((UInt16)60000, typeof(UInt16), "UInt16 60000"),
            ((UInt32)3_000_000_000u, typeof(UInt32), "UInt32 3G"),
            ((UInt64)9876543210UL,   typeof(UInt64), "UInt64 big"),
        };

        var buf = new Byte[256];
        foreach (var (value, type, label) in cases)
        {
            var bn = new Binary { IsLittleEndian = true, EncodeInt = true };
            bn.Write(value, type);
            var binaryBytes = bn.GetPacket().ToHex(-1);

            var writer = new SpanWriter(buf) { IsLittleEndian = true, EncodeInt = true };
            writer.WriteValue(value, type);
            var spanBytes = buf.ToHex(0, writer.WrittenCount);

            Assert.True(binaryBytes == spanBytes, $"{label}: Binary={binaryBytes} vs SpanWriter={spanBytes}");
        }
    }

    [Fact]
    [DisplayName("EncodeInt+FullTime 组合:与 Binary(EncodeInt=true,FullTime=true) 字节级一致")]
    public void WriteValue_EncodeInt_FullTime_CompatibleWithBinary()
    {
        var dt = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        var cases = new (Object value, Type type, String label)[]
        {
            (42,        typeof(Int32),    "Int32 42"),
            (-100,      typeof(Int32),    "Int32 -100"),
            (1_000_000, typeof(Int32),    "Int32 1M"),
            (dt,        typeof(DateTime), "DateTime full"),
            (9876543210L, typeof(Int64),  "Int64"),
        };

        var buf = new Byte[256];
        foreach (var (value, type, label) in cases)
        {
            var bn = new Binary { IsLittleEndian = true, EncodeInt = true, FullTime = true };
            bn.Write(value, type);
            var binaryBytes = bn.GetPacket().ToHex(-1);

            var writer = new SpanWriter(buf) { IsLittleEndian = true, EncodeInt = true, FullTime = true };
            writer.WriteValue(value, type);
            var spanBytes = buf.ToHex(0, writer.WrittenCount);

            Assert.True(binaryBytes == spanBytes, $"{label}: Binary={binaryBytes} vs SpanWriter={spanBytes}");

            var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount)) { IsLittleEndian = true, EncodeInt = true, FullTime = true };
            var readBack = reader.ReadValue(type);
            if (type == typeof(DateTime))
                Assert.Equal((DateTime)value, (DateTime)readBack!);
            else
                Assert.Equal(value, readBack);
        }
    }

    [Fact]
    [DisplayName("Single/Double 不受 EncodeInt 影响,始终固定字节数")]
    public void SingleDouble_NotAffectedByEncodeInt()
    {
        var buf = new Byte[64];

        // Single 固定4字节
        var writer = new SpanWriter(buf) { EncodeInt = true };
        writer.WriteValue(3.14f, typeof(Single));
        Assert.Equal(4, writer.WrittenCount);

        writer = new SpanWriter(buf) { EncodeInt = false };
        writer.WriteValue(3.14f, typeof(Single));
        Assert.Equal(4, writer.WrittenCount);

        // Double 固定8字节
        writer = new SpanWriter(buf) { EncodeInt = true };
        writer.WriteValue(2.718281828, typeof(Double));
        Assert.Equal(8, writer.WrittenCount);

        // 与 Binary(EncodeInt=true) 字节级一致
        var bnS = new Binary { IsLittleEndian = true, EncodeInt = true };
        bnS.Write(3.14f, typeof(Single));
        writer = new SpanWriter(buf) { IsLittleEndian = true, EncodeInt = true };
        writer.WriteValue(3.14f, typeof(Single));
        Assert.Equal(bnS.GetPacket().ToHex(-1), buf.ToHex(0, writer.WrittenCount));

        var bnD = new Binary { IsLittleEndian = true, EncodeInt = true };
        bnD.Write(2.718281828, typeof(Double));
        writer = new SpanWriter(buf) { IsLittleEndian = true, EncodeInt = true };
        writer.WriteValue(2.718281828, typeof(Double));
        Assert.Equal(bnD.GetPacket().ToHex(-1), buf.ToHex(0, writer.WrittenCount));
    }
    #endregion

    #region Nullable<T> 读写
    [Fact]
    [DisplayName("Nullable<T> 有值: 写 0x01 + 值")]
    public void Nullable_WithValue_WritesFlagPlusData()
    {
        var buf = new Byte[64];

        // Nullable<Int32> 有值 → 1字节标志 + 4字节Int32 = 5字节
        var writer = new SpanWriter(buf);
        writer.WriteValue(42, typeof(Int32?));
        Assert.Equal(5, writer.WrittenCount);
        Assert.Equal(0x01, buf[0]); // 标志位

        var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(42, (Int32)reader.ReadValue(typeof(Int32?))!);

        // Nullable<Boolean> 有值 → 1字节标志 + 1字节 = 2字节
        writer = new SpanWriter(buf);
        writer.WriteValue(true, typeof(Boolean?));
        Assert.Equal(2, writer.WrittenCount);
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.True((Boolean)reader.ReadValue(typeof(Boolean?))!);

        // Nullable<Int64> 有值 → 1字节标志 + 8字节Int64 = 9字节
        writer = new SpanWriter(buf);
        writer.WriteValue(9876543210L, typeof(Int64?));
        Assert.Equal(9, writer.WrittenCount);
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(9876543210L, (Int64)reader.ReadValue(typeof(Int64?))!);

        // Nullable<Decimal> 有值 → 1字节标志 + 16字节Decimal = 17字节
        writer = new SpanWriter(buf);
        writer.WriteValue(123.45m, typeof(Decimal?));
        Assert.Equal(17, writer.WrittenCount);
        reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(123.45m, (Decimal)reader.ReadValue(typeof(Decimal?))!);
    }

    [Fact]
    [DisplayName("Nullable<T> null: 仅写 0x00(1字节)")]
    public void Nullable_Null_WritesSingleZeroByte()
    {
        var buf = new Byte[64];
        var nullTypes = new Type[]
        {
            typeof(Boolean?),
            typeof(Int32?),
            typeof(Int64?),
            typeof(DateTime?),
            typeof(Decimal?),
            typeof(Double?),
            typeof(Single?),
        };

        foreach (var type in nullTypes)
        {
            var writer = new SpanWriter(buf);
            writer.WriteValue(null, type);
            Assert.True(writer.WrittenCount == 1, $"Type={type.Name}: should write exactly 1 byte for null");
            Assert.True(buf[0] == 0x00, $"Type={type.Name}: null flag byte should be 0x00");

            var reader = new SpanReader(buf.AsSpan(0, 1));
            Assert.Null(reader.ReadValue(type));
        }
    }

    [Fact]
    [DisplayName("Nullable<T> 与 Binary 字节级一致")]
    public void Nullable_CompatibleWithBinary()
    {
        var dt2025 = new DateTime(2025, 6, 15, 8, 30, 0, DateTimeKind.Utc);
        var cases = new (Object? value, Type type, String label)[]
        {
            (42,          typeof(Int32?),   "Int32? 有值"),
            (null,        typeof(Int32?),   "Int32? null"),
            (true,        typeof(Boolean?), "Boolean? true"),
            (null,        typeof(Boolean?), "Boolean? null"),
            (123.45m,     typeof(Decimal?), "Decimal? 有值"),
            (null,        typeof(Decimal?), "Decimal? null"),
            (9876543210L, typeof(Int64?),   "Int64? 有值"),
            (null,        typeof(Int64?),   "Int64? null"),
            (dt2025,      typeof(DateTime?), "DateTime? 有值"),
            (null,        typeof(DateTime?), "DateTime? null"),
        };

        var buf = new Byte[64];
        foreach (var (value, type, label) in cases)
        {
            var bn = new Binary { IsLittleEndian = true };
            bn.Write(value, type);
            var binaryBytes = bn.GetPacket().ToHex(-1);

            var writer = new SpanWriter(buf) { IsLittleEndian = true };
            writer.WriteValue(value, type);
            var spanBytes = buf.ToHex(0, writer.WrittenCount);

            Assert.True(binaryBytes == spanBytes, $"{label}: Binary={binaryBytes} vs SpanWriter={spanBytes}");
        }
    }
    #endregion

    #region 与 Binary 互操作(跨框架读写)
    [Fact]
    [DisplayName("SpanWriter 写入 → Binary 读取(小端,无EncodeInt)")]
    public void SpanWriter_To_Binary_Read_LittleEndian()
    {
        var buf = new Byte[256];
        var dt = new DateTime(2025, 6, 15, 0, 0, 0, DateTimeKind.Utc);

        // 写入所有基础类型
        var writer = new SpanWriter(buf) { IsLittleEndian = true };
        writer.WriteValue(true, typeof(Boolean));
        writer.WriteValue((Byte)0xAB, typeof(Byte));
        writer.WriteValue((SByte)(-50), typeof(SByte));
        writer.WriteValue((Char)'Z', typeof(Char));
        writer.WriteValue((Int16)1234, typeof(Int16));
        writer.WriteValue((UInt16)5678, typeof(UInt16));
        writer.WriteValue(987654, typeof(Int32));
        writer.WriteValue((UInt32)123456, typeof(UInt32));
        writer.WriteValue(9876543210L, typeof(Int64));
        writer.WriteValue((UInt64)9876543210UL, typeof(UInt64));
        writer.WriteValue(3.14f, typeof(Single));
        writer.WriteValue(2.718281828, typeof(Double));
        writer.WriteValue(12345.678m, typeof(Decimal));
        writer.WriteValue(dt, typeof(DateTime));
        writer.WriteValue("hello", typeof(String));

        // Binary 从同一内存读取
        using var ms = new MemoryStream(buf, 0, writer.WrittenCount);
        var bn = new Binary(ms) { IsLittleEndian = true };

        Assert.True(bn.Read<Boolean>());
        Assert.Equal((Byte)0xAB, bn.Read<Byte>());
        // Binary框架不支持SByte负值,读为Byte后比较位模式
        Assert.Equal(unchecked((Byte)(SByte)(-50)), bn.Read<Byte>());
        Assert.Equal((Byte)'Z', bn.Read<Byte>()); // Char → 1字节
        Assert.Equal((Int16)1234, bn.Read<Int16>());
        Assert.Equal((UInt16)5678, bn.Read<UInt16>());
        Assert.Equal(987654, bn.Read<Int32>());
        Assert.Equal((UInt32)123456, bn.Read<UInt32>());
        Assert.Equal(9876543210L, bn.Read<Int64>());
        Assert.Equal((UInt64)9876543210UL, bn.Read<UInt64>());
        Assert.Equal(3.14f, bn.Read<Single>());
        Assert.Equal(2.718281828, bn.Read<Double>());
        // Decimal: Binary.Read<Decimal> 与 SpanWriter 格式一致
        Assert.Equal(12345.678m, bn.Read<Decimal>());

        // DateTime: Binary读Unix秒数 → 与SpanWriter写入一致
        var dtRead = bn.Read<DateTime>();
        Assert.Equal(dt.Ticks / TimeSpan.TicksPerSecond, dtRead.Ticks / TimeSpan.TicksPerSecond);

        Assert.Equal("hello", bn.Read<String>());
    }

    [Fact]
    [DisplayName("Binary 写入 → SpanReader 读取(小端,无EncodeInt)")]
    public void Binary_Write_To_SpanReader_Read_LittleEndian()
    {
        var dt = new DateTime(2025, 6, 15, 0, 0, 0, DateTimeKind.Utc);

        // Binary 写入
        var bn = new Binary { IsLittleEndian = true };
        bn.Write(true, typeof(Boolean));
        bn.Write((Byte)0xAB, typeof(Byte));
        // Binary框架不支持SByte负值,改用Byte位表示
        bn.Write(unchecked((Byte)(SByte)(-50)), typeof(Byte));
        bn.Write((Char)'Z', typeof(Char));
        bn.Write((Int16)1234, typeof(Int16));
        bn.Write((UInt16)5678, typeof(UInt16));
        bn.Write(987654, typeof(Int32));
        bn.Write((UInt32)123456, typeof(UInt32));
        bn.Write(9876543210L, typeof(Int64));
        bn.Write((UInt64)9876543210UL, typeof(UInt64));
        bn.Write(3.14f, typeof(Single));
        bn.Write(2.718281828, typeof(Double));
        bn.Write(12345.678m, typeof(Decimal));
        bn.Write(dt, typeof(DateTime));
        bn.Write("hello", typeof(String));

        var data = bn.GetPacket().ToArray();

        // SpanReader 读取
        var reader = new SpanReader(data) { IsLittleEndian = true };
        Assert.True((Boolean)reader.ReadValue(typeof(Boolean))!);
        Assert.Equal((Byte)0xAB, (Byte)reader.ReadValue(typeof(Byte))!);
        // Binary写入的是Byte位表示,SpanReader以Byte读取并比较位模式
        Assert.Equal(unchecked((Byte)(SByte)(-50)), (Byte)reader.ReadValue(typeof(Byte))!);
        Assert.Equal((Char)'Z', (Char)reader.ReadValue(typeof(Char))!);
        Assert.Equal((Int16)1234, (Int16)reader.ReadValue(typeof(Int16))!);
        Assert.Equal((UInt16)5678, (UInt16)reader.ReadValue(typeof(UInt16))!);
        Assert.Equal(987654, (Int32)reader.ReadValue(typeof(Int32))!);
        Assert.Equal((UInt32)123456, (UInt32)reader.ReadValue(typeof(UInt32))!);
        Assert.Equal(9876543210L, (Int64)reader.ReadValue(typeof(Int64))!);
        Assert.Equal((UInt64)9876543210UL, (UInt64)reader.ReadValue(typeof(UInt64))!);
        Assert.Equal(3.14f, (Single)reader.ReadValue(typeof(Single))!);
        Assert.Equal(2.718281828, (Double)reader.ReadValue(typeof(Double))!);
        Assert.Equal(12345.678m, (Decimal)reader.ReadValue(typeof(Decimal))!);

        var dtBack = (DateTime)reader.ReadValue(typeof(DateTime))!;
        Assert.Equal(dt.Ticks / TimeSpan.TicksPerSecond, dtBack.Ticks / TimeSpan.TicksPerSecond);

        Assert.Equal("hello", (String?)reader.ReadValue(typeof(String)));
    }

    [Fact]
    [DisplayName("SpanWriter(EncodeInt=true) → Binary(EncodeInt=true) 字节级一致并互读")]
    public void SpanWriter_To_Binary_EncodeInt_Interop()
    {
        var buf = new Byte[256];
        var cases = new (Object value, Type type)[]
        {
            (0, typeof(Int32)),
            (42, typeof(Int32)),
            (16383, typeof(Int32)),
            (1_000_000, typeof(Int32)),
            (-1, typeof(Int32)),
            (9876543210L, typeof(Int64)),
            ((UInt32)3_000_000_000u, typeof(UInt32)),
        };

        foreach (var (value, type) in cases)
        {
            // SpanWriter写 → Binary读
            var writer = new SpanWriter(buf) { IsLittleEndian = true, EncodeInt = true };
            writer.WriteValue(value, type);

            using var ms = new MemoryStream(buf, 0, writer.WrittenCount);
            var bn = new Binary(ms) { IsLittleEndian = true, EncodeInt = true };
            var readBack = bn.Read(type);
            Assert.Equal(value, readBack);

            // Binary写 → SpanReader读
            var bn2 = new Binary { IsLittleEndian = true, EncodeInt = true };
            bn2.Write(value, type);
            var data = bn2.GetPacket().ToArray();
            var reader = new SpanReader(data) { IsLittleEndian = true, EncodeInt = true };
            var readBack2 = reader.ReadValue(type);
            Assert.Equal(value, readBack2);
        }
    }

    [Fact]
    [DisplayName("SpanWriter(FullTime=true) → Binary(FullTime=true) 字节级一致并互读")]
    public void SpanWriter_To_Binary_FullTime_Interop()
    {
        var buf = new Byte[64];
        var datetimes = new DateTime[]
        {
            new(2025, 12, 31, 23, 59, 59, 999, DateTimeKind.Utc),
            new(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc),
        };

        foreach (var dt in datetimes)
        {
            // SpanWriter写 → Binary读
            var writer = new SpanWriter(buf) { IsLittleEndian = true, FullTime = true };
            writer.WriteValue(dt, typeof(DateTime));

            using var ms = new MemoryStream(buf, 0, writer.WrittenCount);
            var bn = new Binary(ms) { IsLittleEndian = true, FullTime = true };
            var dtBack = bn.Read<DateTime>();
            Assert.Equal(dt, dtBack);

            // Binary写 → SpanReader读
            var bn2 = new Binary { IsLittleEndian = true, FullTime = true };
            bn2.Write(dt, typeof(DateTime));
            var data = bn2.GetPacket().ToArray();
            var reader = new SpanReader(data) { IsLittleEndian = true, FullTime = true };
            var dtBack2 = (DateTime)reader.ReadValue(typeof(DateTime))!;
            Assert.Equal(dt, dtBack2);
        }
    }

    [Fact]
    [DisplayName("SpanWriter 写、SpanReader 读:字节精确往返(全类型)")]
    public void SpanWriter_SpanReader_RoundTrip_Exact()
    {
        var buf = new Byte[512];
        var guid = new Guid("12345678-1234-5678-9abc-def012345678");
        var byteArr = new Byte[] { 1, 2, 3, 4, 5 };
        var dt = new DateTime(2025, 3, 15, 10, 20, 30, DateTimeKind.Utc);

        var writer = new SpanWriter(buf) { IsLittleEndian = true, EncodeInt = true, FullTime = true };
        writer.WriteValue(true, typeof(Boolean));
        writer.WriteValue((Byte)0x42, typeof(Byte));
        writer.WriteValue((SByte)(-99), typeof(SByte));
        writer.WriteValue((Char)'X', typeof(Char));
        writer.WriteValue((Int16)(-300), typeof(Int16));
        writer.WriteValue((UInt16)50000, typeof(UInt16));
        writer.WriteValue(1_000_000, typeof(Int32));
        writer.WriteValue((UInt32)4_000_000_000u, typeof(UInt32));
        writer.WriteValue(-9876543210L, typeof(Int64));
        writer.WriteValue((UInt64)9876543210UL, typeof(UInt64));
        writer.WriteValue(1.5f, typeof(Single));
        writer.WriteValue(3.14159265358979, typeof(Double));
        writer.WriteValue(99999.9m, typeof(Decimal));
        writer.WriteValue(dt, typeof(DateTime));
        writer.WriteValue("测试字符串", typeof(String));
        writer.WriteValue(byteArr, typeof(Byte[]));
        writer.WriteValue(guid, typeof(Guid));

        var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount))
        {
            IsLittleEndian = true,
            EncodeInt = true,
            FullTime = true,
        };
        Assert.True((Boolean)reader.ReadValue(typeof(Boolean))!);
        Assert.Equal((Byte)0x42, (Byte)reader.ReadValue(typeof(Byte))!);
        Assert.Equal((SByte)(-99), (SByte)reader.ReadValue(typeof(SByte))!);
        Assert.Equal((Char)'X', (Char)reader.ReadValue(typeof(Char))!);
        Assert.Equal((Int16)(-300), (Int16)reader.ReadValue(typeof(Int16))!);
        Assert.Equal((UInt16)50000, (UInt16)reader.ReadValue(typeof(UInt16))!);
        Assert.Equal(1_000_000, (Int32)reader.ReadValue(typeof(Int32))!);
        Assert.Equal((UInt32)4_000_000_000u, (UInt32)reader.ReadValue(typeof(UInt32))!);
        Assert.Equal(-9876543210L, (Int64)reader.ReadValue(typeof(Int64))!);
        Assert.Equal((UInt64)9876543210UL, (UInt64)reader.ReadValue(typeof(UInt64))!);
        Assert.Equal(1.5f, (Single)reader.ReadValue(typeof(Single))!);
        Assert.Equal(3.14159265358979, (Double)reader.ReadValue(typeof(Double))!);
        Assert.Equal(99999.9m, (Decimal)reader.ReadValue(typeof(Decimal))!);
        Assert.Equal(dt, (DateTime)reader.ReadValue(typeof(DateTime))!); // FullTime=true → Ticks级精度
        Assert.Equal("测试字符串", (String?)reader.ReadValue(typeof(String)));
        Assert.Equal(byteArr, (Byte[]?)reader.ReadValue(typeof(Byte[])));
        Assert.Equal(guid, (Guid)reader.ReadValue(typeof(Guid))!);
        Assert.Equal(writer.WrittenCount, reader.Position); // 全部消耗完
    }

    [Fact]
    [DisplayName("WriteValue<T> 泛型便捷方法正确(读)")]
    public void ReadValue_Generic_Helper()
    {
        var buf = new Byte[64];
        var writer = new SpanWriter(buf);
        writer.WriteValue(42, typeof(Int32));
        writer.WriteValue("world", typeof(String));

        var reader = new SpanReader(buf.AsSpan(0, writer.WrittenCount));
        Assert.Equal(42, reader.ReadValue<Int32>());
        Assert.Equal("world", reader.ReadValue<String>());
    }
    #endregion

    #region 字节级一致性验证(大端/小端)
    [Fact]
    [DisplayName("大端模式: WriteValue 与 Binary(大端) 字节级一致(基础类型)")]
    public void WriteValue_BigEndian_CompatibleWithBinary()
    {
        var dt2025 = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        var cases = new (Object value, Type type, String label)[]
        {
            (true,           typeof(Boolean),  "Boolean true"),
            (false,          typeof(Boolean),  "Boolean false"),
            ((Byte)0xAB,     typeof(Byte),     "Byte"),
            (12345,          typeof(Int32),    "Int32"),
            (9876543210L,    typeof(Int64),    "Int64"),
            (3.14f,          typeof(Single),   "Single"),
            (2.71828,        typeof(Double),   "Double"),
            ("hello",        typeof(String),   "String"),
            (dt2025,         typeof(DateTime), "DateTime"),
            (DateTime.MinValue, typeof(DateTime), "DateTime.MinValue"),
        };

        var buf = new Byte[256];
        foreach (var (value, type, label) in cases)
        {
            var bn = new Binary { IsLittleEndian = false };
            bn.Write(value, type);
            var binaryBytes = bn.GetPacket().ToHex(-1);

            var writer = new SpanWriter(buf) { IsLittleEndian = false };
            writer.WriteValue(value, type);
            var spanBytes = buf.ToHex(0, writer.WrittenCount);

            Assert.True(binaryBytes == spanBytes, $"{label}: Binary={binaryBytes} vs SpanWriter={spanBytes}");
        }
    }

    [Fact]
    [DisplayName("小端模式: WriteValue 与 Binary(小端) 字节级一致(基础类型)")]
    public void WriteValue_LittleEndian_CompatibleWithBinary()
    {
        var cases = new (Object value, Type type, String label)[]
        {
            ((Int16)1234,   typeof(Int16),  "Int16"),
            ((UInt16)65535, typeof(UInt16), "UInt16"),
            (Int32.MaxValue, typeof(Int32), "Int32.MaxValue"),
            (Int64.MinValue, typeof(Int64), "Int64.MinValue"),
            (3.14f,          typeof(Single), "Single"),
            (2.71828,        typeof(Double), "Double"),
        };

        var buf = new Byte[256];
        foreach (var (value, type, label) in cases)
        {
            var bn = new Binary { IsLittleEndian = true };
            bn.Write(value, type);
            var binaryBytes = bn.GetPacket().ToHex(-1);

            var writer = new SpanWriter(buf) { IsLittleEndian = true };
            writer.WriteValue(value, type);
            var spanBytes = buf.ToHex(0, writer.WrittenCount);

            Assert.True(binaryBytes == spanBytes, $"{label}: Binary={binaryBytes} vs SpanWriter={spanBytes}");
        }
    }
    #endregion
}