File: MemoryBuilderTests.cs
Web Access
Project: src\src\Razor\src\Shared\Microsoft.AspNetCore.Razor.Utilities.Shared.UnitTests\Microsoft.AspNetCore.Razor.Utilities.Shared.UnitTests.csproj (Microsoft.AspNetCore.Razor.Utilities.Shared.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Linq;
using Xunit;
 
namespace Microsoft.AspNetCore.Razor.Utilities.Shared.Test;
 
public class MemoryBuilderTests
{
    [Fact]
    public void StartWithDefault()
    {
        using MemoryBuilder<int> builder = default;
 
        for (var i = 0; i < 1000; i++)
        {
            builder.Append(i);
        }
 
        var result = builder.AsMemory();
 
        for (var i = 0; i < 1000; i++)
        {
            Assert.Equal(i, result.Span[i]);
        }
    }
 
    [Fact]
    public void StartWithNew()
    {
        using MemoryBuilder<int> builder = new();
 
        for (var i = 0; i < 1000; i++)
        {
            builder.Append(i);
        }
 
        var result = builder.AsMemory();
 
        for (var i = 0; i < 1000; i++)
        {
            Assert.Equal(i, result.Span[i]);
        }
    }
 
    [Fact]
    public void StartWithInitialCapacity()
    {
        using MemoryBuilder<int> builder = new(1024);
 
        for (var i = 0; i < 1000; i++)
        {
            builder.Append(i);
        }
 
        var result = builder.AsMemory();
 
        for (var i = 0; i < 1000; i++)
        {
            Assert.Equal(i, result.Span[i]);
        }
    }
 
    [Fact]
    public void StartWithInitialArray()
    {
        using MemoryBuilder<int> builder = new(1024);
 
        for (var i = 0; i < 1000; i++)
        {
            builder.Append(i);
        }
 
        var result = builder.AsMemory();
 
        for (var i = 0; i < 1000; i++)
        {
            Assert.Equal(i, result.Span[i]);
        }
    }
 
    [Fact]
    public void AppendChunks()
    {
        using MemoryBuilder<int> builder = default;
 
        ReadOnlySpan<int> chunk = [1, 2, 3, 4, 5, 6, 7, 8];
 
        for (var i = 0; i < 1000; i++)
        {
            builder.Append(chunk);
        }
 
        var result = builder.AsMemory();
 
        for (var i = 0; i < 1000; i++)
        {
            for (var j = 0; j < chunk.Length; j++)
            {
                Assert.Equal(chunk[j], result.Span[(i * 8) + j]);
            }
        }
    }
 
    [Fact]
    public void Push_SingleItem_AddsToBuilder()
    {
        using var builder = new MemoryBuilder<int>();
 
        builder.Push(42);
 
        Assert.Equal(1, builder.Length);
        Assert.Equal(42, builder[0]);
    }
 
    [Fact]
    public void Push_MultipleItems_AddsInOrder()
    {
        using var builder = new MemoryBuilder<int>();
 
        builder.Push(10);
        builder.Push(20);
        builder.Push(30);
 
        Assert.Equal(3, builder.Length);
        Assert.Equal(10, builder[0]);
        Assert.Equal(20, builder[1]);
        Assert.Equal(30, builder[2]);
    }
 
    [Fact]
    public void Push_WithInitialCapacity_WorksCorrectly()
    {
        using var builder = new MemoryBuilder<string>(10);
 
        builder.Push("first");
        builder.Push("second");
 
        Assert.Equal(2, builder.Length);
        Assert.Equal("first", builder[0]);
        Assert.Equal("second", builder[1]);
    }
 
    [Fact]
    public void Push_CausesResize_WorksCorrectly()
    {
        using var builder = new MemoryBuilder<int>(2);
 
        // Fill initial capacity
        builder.Push(1);
        builder.Push(2);
        
        // This should cause a resize
        builder.Push(3);
        builder.Push(4);
 
        Assert.Equal(4, builder.Length);
        Assert.Equal(1, builder[0]);
        Assert.Equal(2, builder[1]);
        Assert.Equal(3, builder[2]);
        Assert.Equal(4, builder[3]);
    }
 
    [Fact]
    public void Peek_SingleItem_ReturnsLastItem()
    {
        using var builder = new MemoryBuilder<int>();
 
        builder.Push(42);
 
        var result = builder.Peek();
 
        Assert.Equal(42, result);
        Assert.Equal(1, builder.Length); // Length should remain unchanged
    }
 
    [Fact]
    public void Peek_MultipleItems_ReturnsLastItem()
    {
        using var builder = new MemoryBuilder<string>();
 
        builder.Push("first");
        builder.Push("second");
        builder.Push("third");
 
        var result = builder.Peek();
 
        Assert.Equal("third", result);
        Assert.Equal(3, builder.Length); // Length should remain unchanged
    }
 
    [Fact]
    public void Peek_AfterMultiplePushes_ReturnsCorrectItem()
    {
        using var builder = new MemoryBuilder<int>();
 
        for (var i = 0; i < 100; i++)
        {
            builder.Push(i);
            Assert.Equal(i, builder.Peek());
        }
 
        Assert.Equal(100, builder.Length);
        Assert.Equal(99, builder.Peek());
    }
 
    [Fact]
    public void Pop_SingleItem_ReturnsAndRemovesItem()
    {
        using var builder = new MemoryBuilder<int>();
 
        builder.Push(42);
 
        var result = builder.Pop();
 
        Assert.Equal(42, result);
        Assert.Equal(0, builder.Length);
        Assert.True(builder.IsEmpty);
    }
 
    [Fact]
    public void Pop_MultipleItems_ReturnsInReverseOrder()
    {
        using var builder = new MemoryBuilder<string>();
 
        builder.Push("first");
        builder.Push("second");
        builder.Push("third");
 
        Assert.Equal("third", builder.Pop());
        Assert.Equal(2, builder.Length);
 
        Assert.Equal("second", builder.Pop());
        Assert.Equal(1, builder.Length);
 
        Assert.Equal("first", builder.Pop());
        Assert.Equal(0, builder.Length);
        Assert.True(builder.IsEmpty);
    }
 
    [Fact]
    public void Pop_Order_WorksCorrectly()
    {
        using var builder = new MemoryBuilder<int>();
 
        // Push numbers 0-9
        for (var i = 0; i < 10; i++)
        {
            builder.Push(i);
        }
 
        // Pop should return in reverse order (LIFO)
        for (var i = 9; i >= 0; i--)
        {
            Assert.Equal(i, builder.Pop());
        }
 
        Assert.True(builder.IsEmpty);
    }
 
    [Fact]
    public void TryPop_EmptyBuilder_ReturnsFalse()
    {
        using var builder = new MemoryBuilder<int>();
 
        var result = builder.TryPop(out var item);
 
        Assert.False(result);
        Assert.Equal(default, item);
        Assert.True(builder.IsEmpty);
    }
 
    [Fact]
    public void TryPop_SingleItem_ReturnsTrueAndItem()
    {
        using var builder = new MemoryBuilder<string>();
 
        builder.Push("test");
 
        var result = builder.TryPop(out var item);
 
        Assert.True(result);
        Assert.Equal("test", item);
        Assert.Equal(0, builder.Length);
        Assert.True(builder.IsEmpty);
    }
 
    [Fact]
    public void TryPop_MultipleItems_ReturnsTrueAndLastItem()
    {
        using var builder = new MemoryBuilder<int>();
 
        builder.Push(10);
        builder.Push(20);
        builder.Push(30);
 
        var result = builder.TryPop(out var item);
 
        Assert.True(result);
        Assert.Equal(30, item);
        Assert.Equal(2, builder.Length);
    }
 
    [Fact]
    public void TryPop_UntilEmpty_WorksCorrectly()
    {
        using var builder = new MemoryBuilder<int>();
 
        builder.Push(1);
        builder.Push(2);
        builder.Push(3);
 
        // Pop all items
        Assert.True(builder.TryPop(out var item1));
        Assert.Equal(3, item1);
 
        Assert.True(builder.TryPop(out var item2));
        Assert.Equal(2, item2);
 
        Assert.True(builder.TryPop(out var item3));
        Assert.Equal(1, item3);
 
        // Try to pop from empty builder
        Assert.False(builder.TryPop(out var item4));
        Assert.Equal(default, item4);
    }
 
    [Fact]
    public void TryPop_WithNullableReferenceTypes_WorksCorrectly()
    {
        using var builder = new MemoryBuilder<string?>();
 
        builder.Push("test");
        builder.Push(null);
 
        // Pop null value
        Assert.True(builder.TryPop(out var item1));
        Assert.Null(item1);
 
        // Pop string value
        Assert.True(builder.TryPop(out var item2));
        Assert.Equal("test", item2);
 
        // Pop from empty
        Assert.False(builder.TryPop(out var item3));
        Assert.Null(item3);
    }
 
    [Fact]
    public void StackOperations_MixedUsage_WorksCorrectly()
    {
        using var builder = new MemoryBuilder<int>();
 
        // Push some items
        builder.Push(1);
        builder.Push(2);
        builder.Push(3);
 
        // Peek (should not modify)
        Assert.Equal(3, builder.Peek());
        Assert.Equal(3, builder.Length);
 
        // Pop one item
        Assert.Equal(3, builder.Pop());
        Assert.Equal(2, builder.Length);
 
        // Push another item
        builder.Push(4);
        Assert.Equal(3, builder.Length);
 
        // TryPop
        Assert.True(builder.TryPop(out var item));
        Assert.Equal(4, item);
        Assert.Equal(2, builder.Length);
 
        // Peek again
        Assert.Equal(2, builder.Peek());
    }
 
    [Fact]
    public void StackOperations_LargeNumberOfItems_WorksCorrectly()
    {
        using var builder = new MemoryBuilder<int>();
 
        // Push 1000 items
        for (var i = 0; i < 1000; i++)
        {
            builder.Push(i);
        }
 
        Assert.Equal(1000, builder.Length);
        Assert.Equal(999, builder.Peek());
 
        // Pop all items in reverse order
        for (var i = 999; i >= 0; i--)
        {
            Assert.True(builder.TryPop(out var item));
            Assert.Equal(i, item);
        }
 
        Assert.True(builder.IsEmpty);
        Assert.False(builder.TryPop(out _));
    }
 
    [Fact]
    public void CreateString_EmptyBuilder_ReturnsEmptyString()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            var result = builder.CreateString();
 
            Assert.Equal(string.Empty, result);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_SingleChunk_ReturnsCorrectString()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            var text = "Hello, World!";
 
            builder.Append(text.AsMemory());
 
            var result = builder.CreateString();
 
            Assert.Same(text, result);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_SingleChunk_FromSubstring_ReturnsCorrectString()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            var originalText = "Hello, World! This is a test.";
            var substring = originalText.AsMemory(7, 5); // "World"
 
            builder.Append(substring);
 
            var result = builder.CreateString();
 
            Assert.Equal("World", result);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_MultipleChunks_ConcatenatesCorrectly()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            builder.Append("Hello".AsMemory());
            builder.Append(", ".AsMemory());
            builder.Append("World".AsMemory());
            builder.Append("!".AsMemory());
 
            var result = builder.CreateString();
 
            Assert.Equal("Hello, World!", result);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_MixedChunkTypes_ConcatenatesCorrectly()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            var fullString = "Original text here";
 
            // Mix of full strings and substrings
            builder.Append("Start".AsMemory());
            builder.Append(" - ".AsMemory());
            builder.Append(fullString.AsMemory(0, 8)); // "Original"
            builder.Append(" + ".AsMemory());
            builder.Append(fullString.AsMemory(9, 4)); // "text"
            builder.Append(" - End".AsMemory());
 
            var result = builder.CreateString();
 
            Assert.Equal("Start - Original + text - End", result);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_EmptyChunks_HandlesCorrectly()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            builder.Append("Hello".AsMemory());
            builder.Append(ReadOnlyMemory<char>.Empty);
            builder.Append(", ".AsMemory());
            builder.Append(ReadOnlyMemory<char>.Empty);
            builder.Append("World".AsMemory());
 
            var result = builder.CreateString();
 
            Assert.Equal("Hello, World", result);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_OnlyEmptyChunks_ReturnsEmptyString()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            builder.Append(ReadOnlyMemory<char>.Empty);
            builder.Append(ReadOnlyMemory<char>.Empty);
            builder.Append(ReadOnlyMemory<char>.Empty);
 
            var result = builder.CreateString();
 
            Assert.Equal(string.Empty, result);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_LargeNumberOfChunks_ConcatenatesCorrectly()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            var expectedLength = 0;
 
            // Add 100 chunks of "X"
            for (var i = 0; i < 100; i++)
            {
                builder.Append("X".AsMemory());
                expectedLength++;
            }
 
            var result = builder.CreateString();
 
            Assert.Equal(expectedLength, result.Length);
            Assert.True(result.All(c => c == 'X'));
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_VaryingChunkSizes_ConcatenatesCorrectly()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            var chunks = new[]
            {
                "A",           // 1 char
                "BB",          // 2 chars
                "CCC",         // 3 chars
                "DDDD",        // 4 chars
                "EEEEE"        // 5 chars
            };
 
            foreach (var chunk in chunks)
            {
                builder.Append(chunk.AsMemory());
            }
 
            var result = builder.CreateString();
 
            Assert.Equal("ABBCCCDDDDEEEEE", result);
            Assert.Equal(15, result.Length); // 1+2+3+4+5
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_UnicodeCharacters_HandlesCorrectly()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            builder.Append("Hello ".AsMemory());
            builder.Append("🌍".AsMemory());          // Earth emoji
            builder.Append(" and ".AsMemory());
            builder.Append("🚀".AsMemory());          // Rocket emoji
            builder.Append("!".AsMemory());
 
            var result = builder.CreateString();
 
            Assert.Equal("Hello 🌍 and 🚀!", result);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_SpecialCharacters_HandlesCorrectly()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            builder.Append("Line1\n".AsMemory());
            builder.Append("Line2\r\n".AsMemory());
            builder.Append("Tab\t".AsMemory());
            builder.Append("Quote\"".AsMemory());
            builder.Append("Backslash\\".AsMemory());
 
            var result = builder.CreateString();
 
            Assert.Equal("Line1\nLine2\r\nTab\tQuote\"Backslash\\", result);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_SingleCharacterChunks_ConcatenatesCorrectly()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            var word = "HELLO";
 
            foreach (var c in word)
            {
                builder.Append(c.ToString().AsMemory());
            }
 
            var result = builder.CreateString();
 
            Assert.Equal(word, result);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void CreateString_AfterMultipleOperations_WorksCorrectly()
    {
        var builder = new MemoryBuilder<ReadOnlyMemory<char>>();
        try
        {
            // First, add some content
            builder.Append("Initial".AsMemory());
 
            // Get initial string
            var initial = builder.CreateString();
            Assert.Equal("Initial", initial);
 
            // Add more content
            builder.Append(" + More".AsMemory());
 
            // Get final string
            var final = builder.CreateString();
            Assert.Equal("Initial + More", final);
        }
        finally
        {
            builder.Dispose();
        }
    }
}