优化环形缓冲区逻辑并增强单元测试覆盖率
石头 authored at 2025-09-27 00:23:52
17.75 KiB
X
using System;
using System.ComponentModel;
using System.Text;
using NewLife;
using NewLife.Data;
using Xunit;

namespace XUnitTest.Data;

public class RingBufferTests
{
    [Fact]
    [DisplayName("测试默认构造函数和指定容量构造函数")]
    public void Constructor_Test()
    {
        // 默认构造函数
        var rb = new RingBuffer();
        Assert.Equal(1024, rb.Capacity);
        Assert.Equal(0, rb.Length);
        Assert.Equal(0, rb.Head);
        Assert.Equal(0, rb.Tail);

        // 指定容量构造函数
        rb = new RingBuffer(512);
        Assert.Equal(512, rb.Capacity);
        Assert.Equal(0, rb.Length);
        Assert.Equal(0, rb.Head);
        Assert.Equal(0, rb.Tail);

        // 最小容量
        rb = new RingBuffer(1);
        Assert.Equal(1, rb.Capacity);
    }

    [Fact]
    [DisplayName("测试容量扩展")]
    public void EnsureCapacity()
    {
        var rb = new RingBuffer();
        Assert.Equal(1024, rb.Capacity);

        rb = new RingBuffer(333);
        Assert.Equal(333, rb.Capacity);

        rb.EnsureCapacity(2048);
        Assert.Equal(2048, rb.Capacity);
    }

    [Fact]
    [DisplayName("测试容量扩展时不缩小")]
    public void EnsureCapacity_DoesNotShrink()
    {
        var rb = new RingBuffer(1024);
        Assert.Equal(1024, rb.Capacity);

        // 尝试设置更小的容量,应该不改变
        rb.EnsureCapacity(512);
        Assert.Equal(1024, rb.Capacity);

        // 设置相同容量,应该不改变
        rb.EnsureCapacity(1024);
        Assert.Equal(1024, rb.Capacity);
    }

    [Fact]
    [DisplayName("测试空缓冲区的扩容")]
    public void EnsureCapacity_EmptyBuffer()
    {
        var rb = new RingBuffer(64);
        
        // 空缓冲区扩容
        rb.EnsureCapacity(128);
        Assert.Equal(128, rb.Capacity);
        Assert.Equal(0, rb.Length);
        Assert.Equal(0, rb.Head);
        Assert.Equal(0, rb.Tail);
    }

    [Fact]
    [DisplayName("测试有数据时的连续布局扩容")]
    public void EnsureCapacity_WithContinuousData()
    {
        var rb = new RingBuffer(8);
        var data = "Hello"u8.ToArray();
        
        rb.Write(data);
        Assert.Equal(5, rb.Length);
        Assert.Equal(5, rb.Head);
        Assert.Equal(0, rb.Tail);
        
        // 扩容
        rb.EnsureCapacity(16);
        Assert.Equal(16, rb.Capacity);
        Assert.Equal(5, rb.Length);
        Assert.Equal(5, rb.Head);
        Assert.Equal(0, rb.Tail);
        
        // 验证数据完整性
        var readBuffer = new Byte[5];
        var count = rb.Read(readBuffer);
        Assert.Equal(5, count);
        Assert.Equal("Hello"u8.ToArray(), readBuffer);
    }

    [Fact]
    [DisplayName("测试有数据时的分段布局扩容")]
    public void EnsureCapacity_WithWrappedData()
    {
        var rb = new RingBuffer(8);
        var data1 = "Hello"u8.ToArray(); // 5 bytes
        var data2 = "ABC"u8.ToArray();   // 3 bytes
        
        // 写入第一段数据
        rb.Write(data1);
        Assert.Equal(5, rb.Head);
        Assert.Equal(0, rb.Tail);
        
        // 读取部分数据,制造环形状态
        var tempBuffer = new Byte[3];
        rb.Read(tempBuffer);
        Assert.Equal(5, rb.Head);
        Assert.Equal(3, rb.Tail);
        Assert.Equal(2, rb.Length);
        
        // 写入更多数据,触发环形写入
        rb.Write(data2);
        Assert.Equal(0, rb.Head); // 回绕到开头
        Assert.Equal(3, rb.Tail);
        Assert.Equal(5, rb.Length);
        
        // 扩容,此时数据是分段的
        rb.EnsureCapacity(16);
        Assert.Equal(16, rb.Capacity);
        Assert.Equal(5, rb.Length);
        Assert.Equal(5, rb.Head);  // 重置为线性布局
        Assert.Equal(0, rb.Tail);  // 重置为线性布局
        
        // 验证数据完整性 - 应该是 "lo" + "ABC"
        var readBuffer = new Byte[5];
        var count = rb.Read(readBuffer);
        Assert.Equal(5, count);
        var expected = new Byte[5];
        "lo"u8.ToArray().CopyTo(expected, 0);
        "ABC"u8.ToArray().CopyTo(expected, 2);
        Assert.Equal(expected, readBuffer);
    }

    [Fact]
    [DisplayName("测试基本的写入和读取")]
    public void WriteReadRead()
    {
        var rb = new RingBuffer(128);

        Assert.Equal(128, rb.Capacity);
        Assert.Equal(0, rb.Length);
        Assert.Equal(0, rb.Head);
        Assert.Equal(0, rb.Tail);

        // 120个字符
        var sb = new StringBuilder();
        for (var i = 0; i < 10; i++)
        {
            sb.Append("HelloNewLife");
        }
        var buf = sb.ToString().GetBytes();

        // 写入数据
        rb.Write(buf);
        Assert.Equal(128, rb.Capacity);
        Assert.Equal(buf.Length, rb.Length);
        Assert.Equal(buf.Length, rb.Head);
        Assert.Equal(0, rb.Tail);

        // 读取
        var buf2 = new Byte[70];
        var count = rb.Read(buf2);
        Assert.Equal(buf2.Length, count);
        Assert.Equal(128, rb.Capacity);
        Assert.Equal(buf.Length - buf2.Length, rb.Length);
        Assert.Equal(buf.Length, rb.Head);
        Assert.Equal(buf2.Length, rb.Tail);
        Assert.Equal(sb.ToString()[..buf2.Length], buf2.ToStr());

        // 读取
        count = rb.Read(buf2);
        Assert.Equal(buf.Length - buf2.Length, count);
        Assert.Equal(128, rb.Capacity);
        Assert.Equal(0, rb.Length);
        Assert.Equal(buf.Length, rb.Head);
        Assert.Equal(buf.Length, rb.Tail);
    }

    [Fact]
    [DisplayName("测试连续写入触发扩容")]
    public void WriteWriteReadRead()
    {
        var rb = new RingBuffer(128);

        // 120个字符
        var sb = new StringBuilder();
        for (var i = 0; i < 10; i++)
        {
            sb.Append("HelloNewLife");
        }
        var buf = sb.ToString().GetBytes();

        // 写入数据
        rb.Write(buf);
        Assert.Equal(128, rb.Capacity);
        Assert.Equal(buf.Length, rb.Length);
        Assert.Equal(buf.Length, rb.Head);
        Assert.Equal(0, rb.Tail);

        // 再写,扩容
        rb.Write(buf);
        Assert.Equal(256, rb.Capacity);
        Assert.Equal(buf.Length * 2, rb.Length);
        Assert.Equal(buf.Length * 2, rb.Head);
        Assert.Equal(0, rb.Tail);

        // 读取
        var buf2 = new Byte[70];
        var count = rb.Read(buf2);
        Assert.Equal(buf2.Length, count);
        Assert.Equal(buf.Length * 2 - buf2.Length, rb.Length);
        Assert.Equal(buf2.Length, rb.Tail);
        Assert.Equal(sb.ToString()[..buf2.Length], buf2.ToStr());

        // 读取
        count = rb.Read(buf2);
        Assert.Equal(buf2.Length, count);
        Assert.Equal(buf.Length * 2 - buf2.Length * 2, rb.Length);
        Assert.Equal(buf2.Length * 2, rb.Tail);
    }

    [Fact]
    [DisplayName("测试读取后写入的环形处理")]
    public void WriteRead3()
    {
        var rb = new RingBuffer(128);

        Assert.Equal(128, rb.Capacity);
        Assert.Equal(0, rb.Length);
        Assert.Equal(0, rb.Head);
        Assert.Equal(0, rb.Tail);

        // 120个字符
        var sb = new StringBuilder();
        for (var i = 0; i < 10; i++)
        {
            sb.Append("HelloNewLife");
        }
        var buf = sb.ToString().GetBytes();

        // 写入数据
        rb.Write(buf);
        Assert.Equal(128, rb.Capacity);
        Assert.Equal(buf.Length, rb.Length);
        Assert.Equal(buf.Length, rb.Head);
        Assert.Equal(0, rb.Tail);

        // 读取
        var buf2 = new Byte[115];
        var count = rb.Read(buf2);
        Assert.Equal(buf2.Length, count);
        Assert.Equal(buf.Length - buf2.Length, rb.Length);
        Assert.Equal(buf2.Length, rb.Tail);
        Assert.Equal(sb.ToString()[..buf2.Length], buf2.ToStr());

        // 再写,扩容
        rb.Write(buf);
        Assert.Equal(128, rb.Capacity);
        Assert.Equal(buf.Length * 2 - buf2.Length, rb.Length);
        Assert.Equal(buf.Length * 2 - rb.Capacity, rb.Head);
        Assert.Equal(buf2.Length, rb.Tail);
    }

    [Fact]
    [DisplayName("测试跨边界写入")]
    public void Write_CrossBoundary()
    {
        var rb = new RingBuffer(10);
        
        // 写入8字节数据
        var data1 = "12345678"u8.ToArray();
        rb.Write(data1);
        Assert.Equal(8, rb.Head);
        Assert.Equal(8, rb.Length);
        
        // 读取5字节,为后续跨边界写入腾出空间
        var readBuffer = new Byte[5];
        rb.Read(readBuffer);
        Assert.Equal(8, rb.Head);
        Assert.Equal(5, rb.Tail);
        Assert.Equal(3, rb.Length);
        
        // 写入5字节数据,应该跨边界:2字节到末尾,3字节到开头
        var data2 = "ABCDE"u8.ToArray();
        rb.Write(data2);
        Assert.Equal(3, rb.Head); // 回绕到开头位置3
        Assert.Equal(5, rb.Tail);
        Assert.Equal(8, rb.Length);
        
        // 验证数据完整性
        var allData = new Byte[8];
        var count = rb.Read(allData);
        Assert.Equal(8, count);
        Assert.Equal("678ABCDE"u8.ToArray(), allData);
    }

    [Fact]
    [DisplayName("测试跨边界读取")]
    public void Read_CrossBoundary()
    {
        var rb = new RingBuffer(8);
        
        // 先写入一些数据并读取一部分,制造环形状态
        var data1 = "123456"u8.ToArray();
        rb.Write(data1);
        
        var tempBuffer = new Byte[3];
        rb.Read(tempBuffer);
        Assert.Equal("123", tempBuffer.ToStr());
        
        // 再写入数据,触发跨边界
        var data2 = "ABCDE"u8.ToArray();
        rb.Write(data2);
        
        // 此时数据布局:[CDE][456AB],Tail=3, Head=3
        Assert.Equal(3, rb.Head);
        Assert.Equal(3, rb.Tail);
        Assert.Equal(8, rb.Length);
        
        // 跨边界读取
        var readBuffer = new Byte[8];
        var count = rb.Read(readBuffer);
        Assert.Equal(8, count);
        Assert.Equal("456ABCDE"u8.ToArray(), readBuffer);
    }

    [Fact]
    [DisplayName("测试空缓冲区读取")]
    public void Read_EmptyBuffer()
    {
        var rb = new RingBuffer(10);
        
        var buffer = new Byte[5];
        var count = rb.Read(buffer);
        
        Assert.Equal(0, count);
        Assert.Equal(0, rb.Length);
        Assert.Equal(0, rb.Head);
        Assert.Equal(0, rb.Tail);
    }

    [Fact]
    [DisplayName("测试零长度写入")]
    public void Write_ZeroLength()
    {
        var rb = new RingBuffer(10);
        var data = "Hello"u8.ToArray();
        
        // 零长度写入
        rb.Write(data, 0, 0);
        Assert.Equal(0, rb.Length);
        Assert.Equal(0, rb.Head);
        Assert.Equal(0, rb.Tail);
        
        // 空数组写入
        rb.Write(Array.Empty<Byte>());
        Assert.Equal(0, rb.Length);
    }

    [Fact]
    [DisplayName("测试零长度读取")]
    public void Read_ZeroLength()
    {
        var rb = new RingBuffer(10);
        var data = "Hello"u8.ToArray();
        rb.Write(data);
        
        var buffer = new Byte[10];
        var count = rb.Read(buffer, 0, 0);
        
        Assert.Equal(0, count);
        Assert.Equal(5, rb.Length); // 数据不应该被消耗
    }

    [Fact]
    [DisplayName("测试写入参数校验")]
    public void Write_ParameterValidation()
    {
        var rb = new RingBuffer(10);
        
        // null数组
        Assert.Throws<ArgumentNullException>(() => rb.Write(null));
        
        var data = "Hello"u8.ToArray();
        
        // 负偏移量
        Assert.Throws<ArgumentOutOfRangeException>(() => rb.Write(data, -1));
        
        // 偏移量越界
        Assert.Throws<ArgumentOutOfRangeException>(() => rb.Write(data, data.Length));
        
        // count越界
        Assert.Throws<ArgumentOutOfRangeException>(() => rb.Write(data, 0, data.Length + 1));
        
        // offset + count 越界
        Assert.Throws<ArgumentOutOfRangeException>(() => rb.Write(data, 3, 4));
    }

    [Fact]
    [DisplayName("测试读取参数校验")]
    public void Read_ParameterValidation()
    {
        var rb = new RingBuffer(10);
        
        // null数组
        Assert.Throws<ArgumentNullException>(() => rb.Read(null));
        
        var buffer = new Byte[5];
        
        // 负偏移量
        Assert.Throws<ArgumentOutOfRangeException>(() => rb.Read(buffer, -1));
        
        // 偏移量越界
        Assert.Throws<ArgumentOutOfRangeException>(() => rb.Read(buffer, buffer.Length));
        
        // count越界
        Assert.Throws<ArgumentOutOfRangeException>(() => rb.Read(buffer, 0, buffer.Length + 1));
        
        // offset + count 越界
        Assert.Throws<ArgumentOutOfRangeException>(() => rb.Read(buffer, 3, 4));
    }

    [Fact]
    [DisplayName("测试带偏移量的写入")]
    public void Write_WithOffset()
    {
        var rb = new RingBuffer(10);
        var data = "HelloWorld"u8.ToArray();
        
        // 从偏移量5开始写入5个字节
        rb.Write(data, 5, 5);
        Assert.Equal(5, rb.Length);
        
        var buffer = new Byte[5];
        var count = rb.Read(buffer);
        Assert.Equal(5, count);
        Assert.Equal("World"u8.ToArray(), buffer);
    }

    [Fact]
    [DisplayName("测试带偏移量的读取")]
    public void Read_WithOffset()
    {
        var rb = new RingBuffer(10);
        var data = "Hello"u8.ToArray();
        rb.Write(data);
        
        var buffer = new Byte[10];
        var count = rb.Read(buffer, 3, 5);
        
        Assert.Equal(5, count);
        Assert.Equal(0, rb.Length);
        
        // 验证数据写入到正确位置
        var expected = new Byte[10];
        "Hello"u8.ToArray().CopyTo(expected, 3);
        Assert.Equal(expected, buffer);
    }

    [Fact]
    [DisplayName("测试读取部分数据")]
    public void Read_PartialData()
    {
        var rb = new RingBuffer(10);
        var data = "HelloWorld"u8.ToArray();
        rb.Write(data);
        
        // 只读取一部分数据
        var buffer = new Byte[5];
        var count = rb.Read(buffer);
        
        Assert.Equal(5, count);
        Assert.Equal(5, rb.Length); // 还剩5字节
        Assert.Equal("Hello"u8.ToArray(), buffer);
        
        // 读取剩余数据
        count = rb.Read(buffer);
        Assert.Equal(5, count);
        Assert.Equal(0, rb.Length);
        Assert.Equal("World"u8.ToArray(), buffer);
    }

    [Fact]
    [DisplayName("测试自动扩容策略")]
    public void Write_AutoExpansion()
    {
        var rb = new RingBuffer(4);
        
        // 写入超过初始容量的数据,测试两倍扩容
        var data = "Hello"u8.ToArray(); // 5 bytes
        rb.Write(data);
        
        Assert.Equal(8, rb.Capacity); // 4 -> 8
        Assert.Equal(5, rb.Length);
        
        // 再写入更多数据
        var data2 = "World!!!"u8.ToArray(); // 8 bytes
        rb.Write(data2);
        
        Assert.Equal(16, rb.Capacity); // 8 -> 16 (因为需要13字节空间)
        Assert.Equal(13, rb.Length);
        
        // 验证数据完整性
        var buffer = new Byte[13];
        var count = rb.Read(buffer);
        Assert.Equal(13, count);
        Assert.Equal("HelloWorld!!!"u8.ToArray(), buffer);
    }

    [Fact]
    [DisplayName("测试复杂的环形操作序列")]
    public void ComplexRingOperations()
    {
        var rb = new RingBuffer(8);
        
        // 写入 "ABCDEF" (6字节)
        rb.Write("ABCDEF"u8.ToArray());
        Assert.Equal(6, rb.Length);
        
        // 读取 "ABC" (3字节)
        var buffer = new Byte[3];
        rb.Read(buffer);
        Assert.Equal("ABC", buffer.ToStr());
        Assert.Equal(3, rb.Length);
        Assert.Equal(6, rb.Head);
        Assert.Equal(3, rb.Tail);
        
        // 写入 "12345" (5字节),会触发跨边界:2字节到末尾,3字节到开头
        rb.Write("12345"u8.ToArray());
        Assert.Equal(8, rb.Length);
        Assert.Equal(3, rb.Head); // 回绕到位置3
        Assert.Equal(3, rb.Tail);
        
        // 读取所有数据 "DEF12" + "345"
        var allBuffer = new Byte[8];
        var count = rb.Read(allBuffer);
        Assert.Equal(8, count);
        Assert.Equal("DEF12345"u8.ToArray(), allBuffer);
        Assert.Equal(0, rb.Length);
    }

    [Fact]
    [DisplayName("测试大数据量操作")]
    public void LargeDataOperations()
    {
        var rb = new RingBuffer(16);
        
        // 准备大数据
        var largeData = new Byte[1024];
        for (var i = 0; i < largeData.Length; i++)
        {
            largeData[i] = (Byte)(i % 256);
        }
        
        // 写入大数据,会触发多次扩容
        rb.Write(largeData);
        Assert.True(rb.Capacity >= 1024);
        Assert.Equal(1024, rb.Length);
        
        // 分批读取
        var readData = new Byte[1024];
        var totalRead = 0;
        var batchSize = 100;
        
        while (totalRead < 1024)
        {
            var toRead = Math.Min(batchSize, 1024 - totalRead);
            var count = rb.Read(readData, totalRead, toRead);
            totalRead += count;
            if (count == 0) break; // 防止无限循环
        }
        
        Assert.Equal(1024, totalRead);
        Assert.Equal(0, rb.Length);
        Assert.Equal(largeData, readData);
    }

    [Fact]
    [DisplayName("测试边界条件:满缓冲区")]
    public void FullBufferScenario()
    {
        var rb = new RingBuffer(5);
        
        // 写满缓冲区
        rb.Write("12345"u8.ToArray());
        Assert.Equal(5, rb.Length);
        Assert.Equal(5, rb.Capacity);
        
        // 再写入数据,应该触发扩容
        rb.Write("A"u8.ToArray());
        Assert.Equal(6, rb.Length);
        Assert.Equal(10, rb.Capacity); // 扩容到10
        
        // 验证数据完整性
        var buffer = new Byte[6];
        var count = rb.Read(buffer);
        Assert.Equal(6, count);
        Assert.Equal("12345A"u8.ToArray(), buffer);
    }
}