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

namespace XUnitTest.Data;

public class IPacketTests
{
    [Fact(DisplayName = "索引器_负索引_各实现一致拒绝越窗访问")]
    public void Indexer_Negative_Throws()
    {
        // 负索引在“本段”分支会直接做数组下标运算(_offset + index),越窗读到本包之前的活数据;
        // 写操作更会踩坏共享/池化缓冲里的相邻数据,各实现必须口径一致地拒绝
        var bytes = new Byte[] { 1, 2, 3, 4, 5, 6 };

        var arr = new ArrayPacket(bytes, 2, 4);
        Assert.Throws<IndexOutOfRangeException>(() => { var _ = arr[-1]; });
        Assert.Throws<IndexOutOfRangeException>(() => { arr[-1] = 0; });

        var mem = new MemoryPacket(new Memory<Byte>(bytes, 2, 4), 4);
        Assert.Throws<IndexOutOfRangeException>(() => { var _ = mem[-1]; });
        Assert.Throws<IndexOutOfRangeException>(() => { mem[-1] = 0; });

        var owner = new OwnerPacket(bytes, 2, 4, false);
        Assert.Throws<IndexOutOfRangeException>(() => { var _ = owner[-1]; });

        var readOnly = new ReadOnlyPacket(bytes, 2, 4);
        Assert.Throws<IndexOutOfRangeException>(() => { var _ = readOnly[-1]; });
    }

    [Fact]
    public void OwnerPacketTest()
    {
        var pk = new OwnerPacket(123);

        Assert.NotNull(pk.Buffer);
        Assert.Equal(128, pk.Buffer.Length);
        Assert.Equal(0, pk.Offset);
        Assert.Equal(123, pk.Length);
        Assert.Equal(123, pk.Total);
        Assert.Null(pk.Next);
        Assert.NotNull(pk.GetValue("_owner"));

        pk[77] = (Byte)'A';
        Assert.Equal('A', (Char)pk[77]);

        var span = pk.GetSpan();
        Assert.Equal('A', (Char)span[77]);

        var memory = pk.GetMemory();
        Assert.Equal(123, memory.Length);
        Assert.Equal('A', (Char)memory.Span[77]);

        var gcmemory = GC.GetAllocatedBytesForCurrentThread();
        pk.Resize(127);

        var pk2 = pk.Slice(7, 70);
        Assert.Equal(gcmemory + 48, GC.GetAllocatedBytesForCurrentThread());
        Assert.NotNull(pk2);
        Assert.Equal(70, pk2.Length);
        Assert.Equal(7, pk2.Offset);
        Assert.Same(pk.GetValue("_owner"), pk2.GetValue("_owner"));
        Assert.NotNull(pk.GetValue("_owner"));
        Assert.Equal(2, pk.RefCount);

        var rs = pk2.TryGetArray(out var segment);
        Assert.True(rs);
        Assert.Equal(pk.Buffer, segment.Array);
        Assert.Equal(7, segment.Offset);
        Assert.Equal(70, segment.Count);

        // 扩展头部:共享借位(引用计数各自释放,源句柄不受影响)
        var pk3 = pk2.ExpandHeader(3) as OwnerPacket;
        Assert.NotNull(pk3);
        Assert.Equal(pk.Buffer, pk3.Buffer);
        Assert.Equal(7 - 3, pk3.Offset);
        Assert.Equal(70 + 3, pk3.Length);
        Assert.Equal(3, pk.RefCount);       // pk 切片、pk2 切片、借位头各持一份引用

        pk3.TryDispose();
        Assert.NotNull(pk.GetValue("_owner"));
        Assert.Equal(2, pk.RefCount);
        Assert.Equal(70, pk2.Length);       // 源切片仍然有效

        pk.TryDispose();
    }

    [Fact]
    public void MemoryPacketTest()
    {
        var buf = Rand.NextBytes(125);
        var gcmemory = GC.GetAllocatedBytesForCurrentThread();
        var pk = new MemoryPacket(buf, 123);
        Assert.Equal(gcmemory, GC.GetAllocatedBytesForCurrentThread());

        Assert.Equal(125, pk.Memory.Length);
        Assert.Equal(123, pk.Length);
        Assert.Equal(123, pk.Total);
        Assert.Null(pk.Next);

        pk[77] = (Byte)'A';
        Assert.Equal('A', (Char)pk[77]);

        var span = pk.GetSpan();
        Assert.Equal('A', (Char)span[77]);

        var memory = pk.GetMemory();
        Assert.Equal(123, memory.Length);
        Assert.Equal('A', (Char)memory.Span[77]);

        var pk2 = (MemoryPacket)pk.Slice(7, 70);
        Assert.Equal(70, pk2.Length);

        var rs = (pk2 as IPacket).TryGetArray(out var segment);
        Assert.True(rs);
        Assert.Equal(buf, segment.Array);
        Assert.Equal(7, segment.Offset);
        Assert.Equal(70, segment.Count);

        pk2.TryDispose();

        // 头部准备:MemoryPacket 为视图,新头节点挂接负载(零拷贝单段头 + 负载链)
        var pk3 = (OwnerPacket)pk2.PrepareHeader(3);
        Assert.Equal(3, pk3.Length);
        Assert.NotNull(pk3.Next);
        Assert.Equal(70 + 3, pk3.Total);
        pk3.TryDispose();
    }

    [Fact]
    public void ArrayPacketTest()
    {
        var buf = Rand.NextBytes(125);
        var pk = new ArrayPacket(buf, 2, 123);

        Assert.NotNull(pk.Buffer);
        Assert.Equal(125, pk.Buffer.Length);
        Assert.Equal(2, pk.Offset);
        Assert.Equal(123, pk.Length);
        Assert.Equal(123, pk.Total);
        Assert.Null(pk.Next);

        pk[77] = (Byte)'A';
        Assert.Equal('A', (Char)pk[77]);

        var span = pk.GetSpan();
        Assert.Equal('A', (Char)span[77]);

        var memory = pk.GetMemory();
        Assert.Equal(123, memory.Length);
        Assert.Equal('A', (Char)memory.Span[77]);

        var pk2 = pk.Slice(7, 70);
        Assert.Equal(70, pk2.Length);
        Assert.Equal(2 + 7, pk2.Offset);

        var rs = (pk2 as IPacket).TryGetArray(out var segment);
        Assert.True(rs);
        Assert.Equal(pk.Buffer, segment.Array);
        Assert.Equal(2 + 7, segment.Offset);
        Assert.Equal(70, segment.Count);

        pk2.TryDispose();

        // 扩展头部
        var pk3 = (ArrayPacket)pk2.ExpandHeader(3);
        Assert.Equal(pk.Buffer, pk3.Buffer);
        Assert.Equal(2 + 7 - 3, pk3.Offset);
        Assert.Equal(70 + 3, pk3.Length);
    }

    [Fact(DisplayName = "ExpandHeader:未预留头部空间时抛异常")]
    public void ExpandHeader_NoReserve_Throws()
    {
        using var op = new OwnerPacket(64);
        op.GetSpan().Fill(0x51);

        var ex = Assert.Throws<InvalidOperationException>(() => op.ExpandHeader(8));
        Assert.Contains("预留", ex.Message);
    }

    [Fact(DisplayName = "ExpandHeader:带链句柄切片后前移链头,源句柄与源链均不受影响")]
    public void ExpandHeader_Chained_SharedSlice()
    {
        var part1 = new OwnerPacket(8, 8);
        var part2 = new OwnerPacket(8);
        part1.Next = part2;
        part1.GetSpan().Fill(0x11);
        part2.GetSpan().Fill(0x22);
        var buffer1 = part1.GetValue("_buffer");
        var buffer2 = part2.GetValue("_buffer");

        // 带链:切片得到独占的共享节点链后前移链头
        var pk = part1.ExpandHeader(4);
        Assert.Same(buffer1, pk.Buffer);
        Assert.Equal(8 - 4, pk.Offset);
        Assert.Equal(4 + 8, pk.Length);
        Assert.NotNull(pk.Next);
        Assert.Same(buffer2, ((OwnerPacket)pk.Next!).Buffer);
        Assert.Equal(0x11, pk[4]);
        Assert.Equal(0x22, pk[12]);

        // 源链完整可用,仅多出一份共享引用
        Assert.Equal(0x11, part1[0]);
        Assert.Equal(0x22, part2[0]);
        Assert.Equal(16, part1.Total);
        Assert.Equal(2, part1.RefCount);
        Assert.Equal(2, part2.RefCount);

        pk.TryDispose();
        Assert.Equal(1, part1.RefCount);
        Assert.Equal(1, part2.RefCount);
        Assert.Equal(0x11, part1[0]);
    }

    [Fact(DisplayName = "ExpandHeader:空首节点带链时保留预留区前移,不产生负偏移")]
    public void ExpandHeader_EmptyHeadChain_KeepsReserve()
    {
        // 首节点为空但持有预留区:切片会跳过长度 0 的节点,预留区随之丢失,必须改为保留首节点自身前移窗口
        var part1 = new OwnerPacket(0, 8);
        var part2 = new OwnerPacket(8);
        part1.Next = part2;
        part2.GetSpan().Fill(0x33);
        var buffer1 = part1.GetValue("_buffer");
        var buffer2 = part2.GetValue("_buffer");

        var pk = part1.ExpandHeader(4);
        Assert.Same(buffer1, pk.Buffer);
        Assert.Equal(8 - 4, pk.Offset);
        Assert.Equal(4, pk.Length);
        Assert.NotNull(pk.Next);
        Assert.Same(buffer2, ((OwnerPacket)pk.Next!).Buffer);

        // 新头窗口落在原预留区(与负载同缓冲),负载从第 4 字节起可读
        pk.GetSpan().Fill(0x44);
        Assert.Equal(0x44, pk[0]);
        Assert.Equal(0x33, pk[4]);

        // 源句柄与源链保持有效,仅多出共享引用
        Assert.Equal(0, part1.Length);
        Assert.Equal(8, part1.Offset);
        Assert.Equal(2, part1.RefCount);
        Assert.Equal(2, part2.RefCount);

        pk.TryDispose();
        Assert.Equal(1, part1.RefCount);
        Assert.Equal(1, part2.RefCount);
        Assert.Equal(0x33, part2[0]);
    }

    [Fact(DisplayName = "PrepareHeader:预留借位共享零拷贝,未预留新头节点挂接负载链")]
    public void PrepareHeader_BorrowOrChain()
    {
        // 预留 8 字节:借位共享零拷贝,共用同一缓冲(源句柄保持有效)
        var op = new OwnerPacket(16, 8);
        op.GetSpan().Fill(0x42);
        var buffer = op.GetValue("_buffer");

        var pk = op.PrepareHeader(4);
        var head = Assert.IsType<OwnerPacket>(pk);
        Assert.Same(buffer, head.GetValue("_buffer"));
        Assert.Equal(4, head.Offset);
        Assert.Equal(4 + 16, head.Length);
        Assert.Null(head.Next);
        Assert.Equal(0x42, head[4]);
        Assert.Equal(2, op.RefCount);       // 源句柄与帧头各持一份

        head.TryDispose();
        Assert.Equal(1, op.RefCount);
        Assert.Equal(0x42, op[0]);          // 源句柄始终可用
        op.TryDispose();

        // 未预留:新头节点挂接共享负载链(零拷贝),源句柄保持有效
        using var raw = new OwnerPacket(16);
        raw.GetSpan().Fill(0x42);

        var pk2 = raw.PrepareHeader(4);
        var head2 = Assert.IsType<OwnerPacket>(pk2);
        Assert.Equal(0, head2.FreeHeader);                          // 新头节点无前置空间
        Assert.Equal(4, head2.Length);
        Assert.Same(raw.Buffer, ((OwnerPacket)head2.Next!).Buffer);  // 负载共享同一缓冲
        Assert.Equal(4 + 16, head2.Total);
        Assert.Equal(0x42, head2[4]);
        Assert.Equal(0x42, head2[19]);
        Assert.NotNull(raw.GetValue("_owner"));                      // 源句柄仍有效
        Assert.Equal(2, raw.RefCount);

        head2.TryDispose();
        Assert.Equal(1, raw.RefCount);
        Assert.Equal(0x42, raw[0]);
    }

    [Fact]
    public void ReadBytesToSpan()
    {
        // 单节点:直接拷贝,返回实读长度
        var pk = new ArrayPacket(Rand.NextBytes(16), 3, 10);
        var buf = new Byte[16];
        var n = pk.ReadBytes(buf);
        Assert.Equal(10, n);
        Assert.Equal(pk.ToArray(), buf[..n]);

        // 链式:跨节点续接(ArrayPacket 是结构体,自后向前组装链)
        var f3 = new ArrayPacket(Rand.NextBytes(8), 0, 4);
        var f2 = new ArrayPacket(Rand.NextBytes(8)) { Next = f3 };
        var f1 = new ArrayPacket(Rand.NextBytes(8)) { Next = f2 };

        var all = f1.ToArray();
        Assert.Equal(20, all.Length);

        var buf2 = new Byte[64];
        var n2 = f1.ReadBytes(buf2);
        Assert.Equal(20, n2);
        Assert.Equal(all, buf2[..n2]);

        // 缓冲不足:只读缓冲长度
        var buf3 = new Byte[6];
        var n3 = f1.ReadBytes(buf3);
        Assert.Equal(6, n3);
        Assert.Equal(all[..6], buf3);

        // 空缓冲
        Assert.Equal(0, f1.ReadBytes(Span<Byte>.Empty));
    }

    [Fact(DisplayName = "IndexOf:单段、跨段、跨多段与空段查找均返回最早匹配的全局偏移")]
    public void IndexOfTest()
    {
        // 单段
        var pk = new ArrayPacket("hello\r\nworld"u8.ToArray());
        Assert.Equal(5, pk.IndexOf("\r\n"u8));
        Assert.Equal(-1, pk.IndexOf("xyz"u8));

        // 链式:目标跨两段(结构体链自后向前组装)
        IPacket f2 = new ArrayPacket("hello\r"u8.ToArray()) { Next = new ArrayPacket("\nworld"u8.ToArray()) };
        Assert.Equal(5, f2.IndexOf("\r\n"u8));

        // 链式:目标跨三段(连续短段)
        IPacket f3 = new ArrayPacket("xa"u8.ToArray())
        {
            Next = new ArrayPacket("b"u8.ToArray())
            {
                Next = new ArrayPacket("c"u8.ToArray())
            }
        };
        Assert.Equal(1, f3.IndexOf("abc"u8));

        // 链式:中间夹空段
        IPacket f4 = new ArrayPacket("x"u8.ToArray())
        {
            Next = new ArrayPacket([])
            {
                Next = new ArrayPacket("yz"u8.ToArray())
            }
        };
        Assert.Equal(0, f4.IndexOf("xyz"u8));

        // 链式:跨段匹配与段内匹配同时存在时,返回更早的跨段偏移
        IPacket f5 = new ArrayPacket("A"u8.ToArray())
        {
            Next = new ArrayPacket("BxAB"u8.ToArray())
        };
        Assert.Equal(0, f5.IndexOf("AB"u8));

        // 未命中、超过总长、空目标
        Assert.Equal(-1, f4.IndexOf("xyz!"u8));
        Assert.Equal(-1, f4.IndexOf(ReadOnlySpan<Byte>.Empty));
    }
}