优化 OwnerPacket 内存管理与功能增强
石头 authored at 2025-09-25 07:53:43
10.55 KiB
X
using NewLife.Data;
using NewLife.Reflection;
using Xunit;

namespace XUnitTest.Data;

/// <summary>OwnerPacket 专门单元测试</summary>
public class OwnerPacketTests
{
    #region 构造函数测试

    [Fact(DisplayName = "构造函数:创建指定长度的内存包")]
    public void Constructor_WithLength_ShouldCreatePacketCorrectly()
    {
        using var packet = new OwnerPacket(100);

        Assert.NotNull(packet.Buffer);
        Assert.True(packet.Buffer.Length >= 100);
        Assert.Equal(0, packet.Offset);
        Assert.Equal(100, packet.Length);
        Assert.Equal(100, packet.Total);
        Assert.Null(packet.Next);
        Assert.True((Boolean)packet.GetValue("_hasOwner"));
    }

    [Fact(DisplayName = "构造函数:零长度包")]
    public void Constructor_WithZeroLength_ShouldCreateEmptyPacket()
    {
        using var packet = new OwnerPacket(0);

        Assert.NotNull(packet.Buffer);
        Assert.Equal(0, packet.Length);
        Assert.Equal(0, packet.Total);
    }

    [Fact(DisplayName = "构造函数:使用现有缓冲区")]
    public void Constructor_WithExistingBuffer_ShouldCreatePacketCorrectly()
    {
        var buffer = new Byte[200];
        // 注意:使用 hasOwner = false 避免 ArrayPool 返回错误
        var packet = new OwnerPacket(buffer, 10, 50, false);
        try
        {
            Assert.Same(buffer, packet.Buffer);
            Assert.Equal(10, packet.Offset);
            Assert.Equal(50, packet.Length);
            Assert.Equal(50, packet.Total);
            Assert.False((Boolean)packet.GetValue("_hasOwner"));
        }
        finally
        {
            packet.Free(); // 不会尝试返回到 ArrayPool
        }
    }

    [Fact(DisplayName = "构造函数:头部扩展构造")]
    public void Constructor_WithHeaderExpansion_ShouldTransferOwnership()
    {
        // 创建一个从偏移位置开始的包,这样就有前置空间可以扩展
        var buffer = new Byte[200];
        var originalPacket = new OwnerPacket(buffer, 30, 50, false); // offset=30,这样前面有30字节可以扩展
        originalPacket.GetSpan().Fill(0x42);
        var expandSize = 20;

        try
        {
            var expandedPacket = new OwnerPacket(originalPacket, expandSize);
            
            try
            {
                Assert.Same(originalPacket.Buffer, expandedPacket.Buffer);
                Assert.Equal(originalPacket.Offset - expandSize, expandedPacket.Offset);
                Assert.Equal(originalPacket.Length + expandSize, expandedPacket.Length);

                // 验证所有权转移
                Assert.False((Boolean)originalPacket.GetValue("_hasOwner"));
                Assert.False((Boolean)expandedPacket.GetValue("_hasOwner"));
            }
            finally
            {
                expandedPacket.Free();
            }
        }
        finally
        {
            originalPacket.Free();
        }
    }

    #endregion

    #region 索引器测试

    [Fact(DisplayName = "索引器:正常读写操作")]
    public void Indexer_NormalAccess_ShouldWorkCorrectly()
    {
        using var packet = new OwnerPacket(100);
        
        packet[50] = (Byte)'X';
        Assert.Equal((Byte)'X', packet[50]);
    }

    [Fact(DisplayName = "索引器:链式包跨段访问")]
    public void Indexer_ChainedPackets_ShouldAccessAcrossSegments()
    {
        using var packet1 = new OwnerPacket(50);
        using var packet2 = new OwnerPacket(50);
        packet1.Next = packet2;

        packet1[25] = 0x11;
        packet1[75] = 0x22;

        Assert.Equal(0x11, packet1[25]);
        Assert.Equal(0x22, packet1[75]);
        Assert.Equal(0x22, packet2[25]);
    }

    #endregion

    #region 内存访问测试

    [Fact(DisplayName = "GetSpan:应返回正确的内存片段")]
    public void GetSpan_ShouldReturnCorrectSpan()
    {
        using var packet = new OwnerPacket(100);
        packet.GetSpan().Fill(0x42);

        var span = packet.GetSpan();

        Assert.Equal(100, span.Length);
        Assert.True(span.ToArray().All(b => b == 0x42));
    }

    [Fact(DisplayName = "GetMemory:应返回正确的内存块")]
    public void GetMemory_ShouldReturnCorrectMemory()
    {
        using var packet = new OwnerPacket(100);
        packet.GetSpan().Fill(0x55);

        var memory = packet.GetMemory();

        Assert.Equal(100, memory.Length);
        Assert.True(memory.Span.ToArray().All(b => b == 0x55));
    }

    [Fact(DisplayName = "TryGetArray:应成功获取数组段")]
    public void TryGetArray_ShouldReturnArraySegment()
    {
        using var packet = new OwnerPacket(100);

        var success = ((IPacket)packet).TryGetArray(out var segment);

        Assert.True(success);
        Assert.Same(packet.Buffer, segment.Array);
        Assert.Equal(packet.Offset, segment.Offset);
        Assert.Equal(packet.Length, segment.Count);
    }

    #endregion

    #region 大小调整测试

    [Fact(DisplayName = "Resize:调整到更小大小")]
    public void Resize_ToSmallerSize_ShouldAdjustLength()
    {
        using var packet = new OwnerPacket(100);

        var result = packet.Resize(50);

        Assert.Same(packet, result);
        Assert.Equal(50, packet.Length);
    }

    [Fact(DisplayName = "Resize:保持相同大小")]
    public void Resize_ToSameSize_ShouldKeepLength()
    {
        using var packet = new OwnerPacket(100);

        var result = packet.Resize(100);

        Assert.Same(packet, result);
        Assert.Equal(100, packet.Length);
    }

    #endregion

    #region 切片操作测试

    [Fact(DisplayName = "Slice:基本切片操作")]
    public void Slice_BasicOperation_ShouldCreateNewPacket()
    {
        var packet = new OwnerPacket(100);
        packet.GetSpan().Fill(0x42);

        using var sliced = packet.Slice(20, 30) as OwnerPacket;

        Assert.NotNull(sliced);
        Assert.Same(packet.Buffer, sliced.Buffer);
        Assert.Equal(20, sliced.Offset);
        Assert.Equal(30, sliced.Length);

        // 验证所有权转移
        Assert.False((Boolean)packet.GetValue("_hasOwner"));
        Assert.True((Boolean)sliced!.GetValue("_hasOwner"));
    }

    [Fact(DisplayName = "Slice:不转移所有权")]
    public void Slice_WithoutOwnershipTransfer_ShouldRetainOwnership()
    {
        using var packet = new OwnerPacket(100);

        var sliced = packet.Slice(20, 30, transferOwner: false) as OwnerPacket;

        Assert.NotNull(sliced);
        Assert.True((Boolean)packet.GetValue("_hasOwner"));
        Assert.False((Boolean)sliced!.GetValue("_hasOwner"));
    }

    [Fact(DisplayName = "Slice:切片到末尾")]
    public void Slice_ToEnd_ShouldSliceToEndOfPacket()
    {
        var packet = new OwnerPacket(100);

        using var sliced = packet.Slice(30, -1) as OwnerPacket;

        Assert.NotNull(sliced);
        Assert.Equal(30, sliced.Offset);
        Assert.Equal(70, sliced.Length);
    }

    #endregion

    #region 属性测试

    [Fact(DisplayName = "Total:单个包应返回Length")]
    public void Total_SinglePacket_ShouldReturnLength()
    {
        using var packet = new OwnerPacket(100);

        Assert.Equal(100, packet.Total);
        Assert.Equal(packet.Length, packet.Total);
    }

    [Fact(DisplayName = "Total:链式包应返回所有段的总长度")]
    public void Total_ChainedPackets_ShouldReturnTotalLength()
    {
        using var packet1 = new OwnerPacket(50);
        using var packet2 = new OwnerPacket(30);
        using var packet3 = new OwnerPacket(20);
        packet1.Next = packet2;
        packet2.Next = packet3;

        Assert.Equal(100, packet1.Total);
    }

    #endregion

    #region 字符串表示测试

    [Fact(DisplayName = "ToString:应返回格式化的字符串表示")]
    public void ToString_ShouldReturnFormattedString()
    {
        using var packet = new OwnerPacket(100);

        var result = packet.ToString();

        Assert.Matches(@"\[\d+\]\(0, 100\)<100>", result);
    }

    #endregion

    #region 性能测试

    [Fact(DisplayName = "性能:切片操作应该零拷贝")]
    public void Performance_Slice_ShouldBeZeroCopy()
    {
        using var packet = new OwnerPacket(1000);
        packet.GetSpan().Fill(0x42);

        var slice1 = packet.Slice(100, 200, transferOwner: false);
        var slice2 = slice1.Slice(50, 100, transferOwner: false);

        // 验证数据一致性(间接验证零拷贝)
        Assert.Equal(0x42, slice2[0]);
        Assert.Equal(0x42, slice2[99]);
    }

    #endregion

    #region 边界条件测试

    [Fact(DisplayName = "边界条件:空包处理")]
    public void EdgeCase_EmptyPacket_ShouldHandleCorrectly()
    {
        using var packet = new OwnerPacket(0);

        Assert.Equal(0, packet.Length);
        Assert.Equal(0, packet.Total);
        Assert.Empty(packet.GetSpan().ToArray());
        Assert.Empty(packet.GetMemory().ToArray());
    }

    [Fact(DisplayName = "边界条件:大内存包处理")]
    public void EdgeCase_LargePacket_ShouldHandleCorrectly()
    {
        using var packet = new OwnerPacket(1024 * 1024); // 1MB

        Assert.Equal(1024 * 1024, packet.Length);
        Assert.True(packet.Buffer.Length >= 1024 * 1024);

        // 测试边界访问
        packet[0] = 0x11;
        packet[packet.Length - 1] = 0x22;
        Assert.Equal(0x11, packet[0]);
        Assert.Equal(0x22, packet[packet.Length - 1]);
    }

    #endregion

    #region 内存管理测试

    [Fact(DisplayName = "Free:应放弃所有权")]
    public void Free_ShouldAbandonOwnership()
    {
        var packet = new OwnerPacket(100);

        packet.Free();

        Assert.False((Boolean)packet.GetValue("_hasOwner"));
        Assert.Null(packet.Next);
    }

    [Fact(DisplayName = "内存管理:ArrayPool复用验证")]
    public void MemoryManagement_ArrayPoolReuse_ShouldReuseBuffers()
    {
        var packets = new List<OwnerPacket>();

        // 创建多个相同大小的包
        for (var i = 0; i < 10; i++)
        {
            var packet = new OwnerPacket(1024);
            packets.Add(packet);
        }

        // 释放前几个(使用 using 语句会自动释放)
        foreach (var p in packets.Take(5))
        {
            p.Free(); // 使用 Free 而不是 Dispose
        }

        // 创建新的包,应该能复用缓冲区
        using var newPacket = new OwnerPacket(1024);

        Assert.NotNull(newPacket.Buffer);
        Assert.True(newPacket.Buffer.Length >= 1024);

        // 清理剩余的包(使用 using 语句会自动释放)
        foreach (var p in packets.Skip(5))
        {
            p.Free(); // 使用 Free 而不是 Dispose
        }
    }

    #endregion
}