File: ValueStringBuilder_Tests.cs
Web Access
Project: ..\..\..\src\Framework.UnitTests\Microsoft.Build.Framework.UnitTests.csproj (Microsoft.Build.Framework.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 Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
 
namespace Microsoft.Build.UnitTests
{
    /// <summary>
    /// Tests for <see cref="ValueStringBuilder"/>.
    /// </summary>
    public class ValueStringBuilder_Tests
    {
        [Fact]
        public void Append_String_AppendsContent()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("hello");
            builder.ToString().ShouldBe("hello");
            builder.Length.ShouldBe(5);
        }
 
        [Fact]
        public void Append_NullString_DoesNothing()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            string? nullString = null;
            builder.Append(nullString);
            builder.Length.ShouldBe(0);
            builder.ToString().ShouldBe(string.Empty);
        }
 
        [Fact]
        public void Append_SingleChar_TakesSingleCharFastPath()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append('a');
            builder.Append('b');
            builder.Append('c');
            builder.ToString().ShouldBe("abc");
        }
 
        [Fact]
        public void Append_CharCount_AppendsRepeatedChar()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append('-', 5);
            builder.ToString().ShouldBe("-----");
        }
 
        [Fact]
        public void Append_ReadOnlySpan_AppendsContent()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("abc".AsSpan());
            builder.Append("def".AsSpan());
            builder.ToString().ShouldBe("abcdef");
        }
 
        [Fact]
        public void Append_GrowsBeyondInitialBuffer()
        {
            using ValueStringBuilder builder = new(stackalloc char[4]);
            builder.Append("0123456789");
            builder.ToString().ShouldBe("0123456789");
            builder.Capacity.ShouldBeGreaterThanOrEqualTo(10);
        }
 
        [Fact]
        public unsafe void Append_CharPointer_AppendsContent()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            ReadOnlySpan<char> source = "abcdef";
            fixed (char* p = source)
            {
                builder.Append(p, 6);
            }
            builder.ToString().ShouldBe("abcdef");
        }
 
        [Fact]
        public void Insert_Char_InsertsAtIndex()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("abef");
            builder.Insert(2, 'c', 2);
            builder.ToString().ShouldBe("abccef");
        }
 
        [Fact]
        public void Insert_String_InsertsAtIndex()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("abef");
            builder.Insert(2, "cd");
            builder.ToString().ShouldBe("abcdef");
        }
 
        [Fact]
        public void Insert_NullString_DoesNothing()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("abc");
            string? nullString = null;
            builder.Insert(1, nullString);
            builder.ToString().ShouldBe("abc");
        }
 
        [Fact]
        public void Replace_ReplacesAllOccurrences()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("mississippi");
            builder.Replace('i', 'x');
            builder.ToString().ShouldBe("mxssxssxppx");
        }
 
        [Fact]
        public void Replace_SameChar_ReturnsImmediately()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("hello");
            builder.Replace('e', 'e');
            builder.ToString().ShouldBe("hello");
        }
 
        [Fact]
        public void Replace_EmptyBuilder_DoesNothing()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Replace('a', 'b');
            builder.Length.ShouldBe(0);
        }
 
        [Fact]
        public void Indexer_GetSet()
        {
            ValueStringBuilder builder = new(stackalloc char[16]);
            try
            {
                builder.Append("abc");
                builder[1].ShouldBe('b');
                builder[1] = 'B';
                builder.ToString().ShouldBe("aBc");
            }
            finally
            {
                builder.Dispose();
            }
        }
 
        [Fact]
        public void Length_Set_TruncatesContent()
        {
            ValueStringBuilder builder = new(stackalloc char[16]);
            try
            {
                builder.Append("abcdef");
                builder.Length = 3;
                builder.ToString().ShouldBe("abc");
            }
            finally
            {
                builder.Dispose();
            }
        }
 
        [Fact]
        public void Clear_ResetsLengthButKeepsCapacity()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("abcdef");
            int capacity = builder.Capacity;
            builder.Clear();
            builder.Length.ShouldBe(0);
            builder.Capacity.ShouldBe(capacity);
            builder.ToString().ShouldBe(string.Empty);
        }
 
        [Fact]
        public void EnsureCapacity_GrowsWhenNeeded()
        {
            using ValueStringBuilder builder = new(stackalloc char[4]);
            builder.EnsureCapacity(64);
            builder.Capacity.ShouldBeGreaterThanOrEqualTo(64);
        }
 
        [Fact]
        public void AsSpan_Terminate_AppendsNullChar()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("abc");
            ReadOnlySpan<char> span = builder.AsSpan(terminate: true);
            span.ToString().ShouldBe("abc");
            builder.Capacity.ShouldBeGreaterThanOrEqualTo(4);
            // The terminating '\0' is past Length, but writable from the underlying span.
        }
 
        [Fact]
        public void TryCopyTo_SufficientDestination_ReturnsTrue()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("hello");
            Span<char> destination = stackalloc char[16];
            bool ok = builder.TryCopyTo(destination, out int written);
            ok.ShouldBeTrue();
            written.ShouldBe(5);
            destination.Slice(0, written).ToString().ShouldBe("hello");
        }
 
        [Fact]
        public void TryCopyTo_TooSmall_ReturnsFalse()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("hello");
            Span<char> destination = stackalloc char[2];
            bool ok = builder.TryCopyTo(destination, out int written);
            ok.ShouldBeFalse();
            written.ShouldBe(0);
        }
 
        [Fact]
        public void AppendSpan_ReturnsWritableRegion()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("ab");
            Span<char> region = builder.AppendSpan(3);
            region.Length.ShouldBe(3);
            region[0] = 'c';
            region[1] = 'd';
            region[2] = 'e';
            builder.ToString().ShouldBe("abcde");
        }
 
        [Fact]
        public void Slice_ReturnsExpectedRange()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("abcdef");
            builder.Slice(2, 3).ToString().ShouldBe("cde");
            builder.Slice(2).ToString().ShouldBe("cdef");
        }
 
        [Fact]
        public unsafe void GetPinnableReference_NullTerminates()
        {
            ValueStringBuilder builder = new(stackalloc char[8]);
            try
            {
                builder.Append("abc");
                fixed (char* p = builder)
                {
                    p[0].ShouldBe('a');
                    p[1].ShouldBe('b');
                    p[2].ShouldBe('c');
                    p[3].ShouldBe('\0');
                }
            }
            finally
            {
                builder.Dispose();
            }
        }
 
        [Fact]
        public void ToStringAndDispose_ReturnsContentAndDisposesBuilder()
        {
#pragma warning disable CA2000 // ToStringAndDispose is the explicit dispose path under test.
            ValueStringBuilder builder = new(stackalloc char[16]);
#pragma warning restore CA2000
            builder.Append("hello");
            string result = builder.ToStringAndDispose();
            result.ShouldBe("hello");
            // After disposal the builder is in default state — Length is 0 again.
            builder.Length.ShouldBe(0);
        }
 
        [Fact]
        public void ImplicitOperator_ReadOnlySpan_ReturnsContent()
        {
            using ValueStringBuilder builder = new(stackalloc char[16]);
            builder.Append("hello");
            ReadOnlySpan<char> span = builder;
            span.ToString().ShouldBe("hello");
        }
    }
}