File: RefArrayBuilder_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.Collections;
using Shouldly;
using Xunit;
 
namespace Microsoft.Build.UnitTests.Collections;
 
public class RefArrayBuilder_Tests
{
    [Fact]
    public void Constructor_InitializesWithCapacity()
    {
        using RefArrayBuilder<int> builder = new(10);
        builder.Count.ShouldBe(0);
    }
 
    [Fact]
    public void Constructor_WithScratchBuffer_UsesScratchBuffer()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[10]);
 
        builder.Count.ShouldBe(0);
        builder.Add(42);
 
        builder.Count.ShouldBe(1);
        builder[0].ShouldBe(42);
    }
 
    [Fact]
    public void Constructor_WithScratchBuffer_CanGrowBeyondInitialCapacity()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[2]);
 
        builder.Add(1);
        builder.Add(2);
        builder.Add(3); // Should trigger growth
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
    }
 
    [Fact]
    public void Constructor_WithEmptyScratchBuffer_CanStillAdd()
    {
        using RefArrayBuilder<int> builder = new(scratchBuffer: []);
 
        builder.Count.ShouldBe(0);
        builder.Add(42); // Should trigger growth
 
        builder.Count.ShouldBe(1);
        builder[0].ShouldBe(42);
    }
 
    [Fact]
    public void Constructor_WithScratchBuffer_AddRangeWorks()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[10]);
 
        builder.AddRange([1, 2, 3]);
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
    }
 
    [Fact]
    public void Add_SingleItem_IncreasesLength()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(42);
 
        builder.Count.ShouldBe(1);
        builder[0].ShouldBe(42);
    }
 
    [Fact]
    public void Add_MultipleItems_MaintainsOrder()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3);
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
    }
 
    [Fact]
    public void Add_ExceedsCapacity_GrowsAutomatically()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3);
        builder.Add(4);
        builder.Add(5);
 
        builder.Count.ShouldBe(5);
        builder[0].ShouldBe(1);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void Add_ScratchBuffer_ExceedsCapacity_GrowsAutomatically()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[4]);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3);
        builder.Add(4);
        builder.Add(5);
 
        builder.Count.ShouldBe(5);
        builder[0].ShouldBe(1);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void AddRange_SingleElement_AddsCorrectly()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([42]);
 
        builder.Count.ShouldBe(1);
        builder[0].ShouldBe(42);
    }
 
    [Fact]
    public void AddRange_MultipleElements_AddsAllInOrder()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.AddRange([2, 3, 4]);
 
        builder.Count.ShouldBe(4);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
        builder[3].ShouldBe(4);
    }
 
    [Fact]
    public void AddRange_ExceedsCapacity_GrowsAutomatically()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.AddRange([2, 3, 4, 5]);
 
        builder.Count.ShouldBe(5);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void AddRange_ScratchBuffer_ExceedsCapacity_GrowsAutomatically()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[4]);
        builder.Add(1);
        builder.AddRange([2, 3, 4, 5]);
 
        builder.Count.ShouldBe(5);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void AddRange_EmptySpan_DoesNothing()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.AddRange([]);
 
        builder.Count.ShouldBe(1);
        builder[0].ShouldBe(1);
    }
 
    [Fact]
    public void Insert_AtBeginning_ShiftsExistingElements()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(2);
        builder.Add(3);
        builder.Insert(0, 1);
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
    }
 
    [Fact]
    public void Insert_InMiddle_ShiftsSubsequentElements()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(3);
        builder.Insert(1, 2);
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
    }
 
    [Fact]
    public void Insert_AtEnd_AppendsElement()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.Insert(2, 3);
 
        builder.Count.ShouldBe(3);
        builder[2].ShouldBe(3);
    }
 
    [Fact]
    public void Insert_ExceedsCapacity_GrowsAutomatically()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(3);
        builder.Add(4);
        builder.Add(5);
        builder.Insert(1, 2);
 
        builder.Count.ShouldBe(5);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
        builder[3].ShouldBe(4);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void Insert_ScratchBuffer_ExceedsCapacity_GrowsAutomatically()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[4]);
        builder.Add(2);
        builder.Add(3);
        builder.Add(4);
        builder.Add(5);
        builder.Insert(0, 1);
 
        builder.Count.ShouldBe(5);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
        builder[3].ShouldBe(4);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void InsertRange_AtBeginning_ShiftsExistingElements()
    {
        using RefArrayBuilder<int> builder = new(10);
        builder.Add(4);
        builder.Add(5);
        builder.InsertRange(0, [1, 2, 3]);
 
        builder.Count.ShouldBe(5);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
        builder[3].ShouldBe(4);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void InsertRange_InMiddle_ShiftsSubsequentElements()
    {
        using RefArrayBuilder<int> builder = new(10);
        builder.Add(1);
        builder.Add(5);
        builder.InsertRange(1, [2, 3, 4]);
 
        builder.Count.ShouldBe(5);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
        builder[3].ShouldBe(4);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void InsertRange_AtEnd_AppendsElements()
    {
        using RefArrayBuilder<int> builder = new(10);
        builder.Add(1);
        builder.Add(2);
        builder.InsertRange(2, [3, 4, 5]);
 
        builder.Count.ShouldBe(5);
        builder[2].ShouldBe(3);
        builder[3].ShouldBe(4);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void InsertRange_ExceedsCapacity_GrowsAutomatically()
    {
        using RefArrayBuilder<int> builder = new(2);
        builder.Add(1);
        builder.Add(5);
        builder.InsertRange(1, [2, 3, 4]);
 
        builder.Count.ShouldBe(5);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
        builder[3].ShouldBe(4);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void InsertRange_ScratchBuffer_ExceedsCapacity_GrowsAutomatically()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[2]);
        builder.Add(1);
        builder.Add(5);
        builder.InsertRange(1, [2, 3, 4]);
 
        builder.Count.ShouldBe(5);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(3);
        builder[3].ShouldBe(4);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void InsertRange_AtBeginning_NearCapacity_ShiftsCorrectly()
    {
        // Use a capacity that we know ArrayPool will give us, then fill most of it.
        // The bug is that InsertRange checks (index + source.Length) < capacity
        // instead of (count + source.Length) <= capacity.
        using RefArrayBuilder<int> builder = new(16);
        int capacity = builder.Capacity;
 
        // Fill all but 2 slots
        int fillCount = capacity - 2;
        for (int i = 1; i <= fillCount; i++)
        {
            builder.Add(i);
        }
 
        // Insert 5 at index 0: index + 5 could be < capacity, but count + 5 exceeds it.
        builder.InsertRange(0, [91, 92, 93, 94, 95]);
 
        builder.Count.ShouldBe(fillCount + 5);
        builder[0].ShouldBe(91);
        builder[1].ShouldBe(92);
        builder[2].ShouldBe(93);
        builder[3].ShouldBe(94);
        builder[4].ShouldBe(95);
        builder[5].ShouldBe(1);
        builder[fillCount + 4].ShouldBe(fillCount);
    }
 
    [Fact]
    public void InsertRange_ScratchBuffer_AtBeginning_NearCapacity_ShiftsCorrectly()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[12]);
        for (int i = 3; i <= 12; i++)
        {
            builder.Add(i);
        }
 
        builder.InsertRange(0, [22, 33, 44, 55, 66]);
 
        builder.Count.ShouldBe(15);
        builder[0].ShouldBe(22);
        builder[1].ShouldBe(33);
        builder[2].ShouldBe(44);
        builder[3].ShouldBe(55);
        builder[4].ShouldBe(66);
        builder[5].ShouldBe(3);
        builder[14].ShouldBe(12);
    }
 
    [Fact]
    public void InsertRange_EmptySpan_DoesNothing()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.InsertRange(1, []);
 
        builder.Count.ShouldBe(2);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
    }
 
    [Fact]
    public void Count_CanBeSet()
    {
        var builder = new RefArrayBuilder<int>(4);
        try
        {
            builder.Add(1);
            builder.Add(2);
            builder.Add(3);
            builder.Count = 2;
 
            builder.Count.ShouldBe(2);
            builder[0].ShouldBe(1);
            builder[1].ShouldBe(2);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void Indexer_ReturnsReference()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
 
        ref int value = ref builder[1];
        value = 42;
 
        builder[1].ShouldBe(42);
    }
 
    [Fact]
    public void IsEmpty_NewBuilder_ReturnsTrue()
    {
        using RefArrayBuilder<int> builder = new(4);
 
        builder.IsEmpty.ShouldBeTrue();
    }
 
    [Fact]
    public void IsEmpty_AfterAdd_ReturnsFalse()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
 
        builder.IsEmpty.ShouldBeFalse();
    }
 
    [Fact]
    public void IsEmpty_AfterRemovingAllElements_ReturnsTrue()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.RemoveAt(0);
 
        builder.IsEmpty.ShouldBeTrue();
    }
 
    [Fact]
    public void IsEmpty_AfterSettingCountToZero_ReturnsTrue()
    {
        var builder = new RefArrayBuilder<int>(4);
        try
        {
            builder.Add(1);
            builder.Add(2);
            builder.Count = 0;
 
            builder.IsEmpty.ShouldBeTrue();
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void AsSpan_ReturnsCorrectSlice()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3);
 
        ReadOnlySpan<int> span = builder.AsSpan();
 
        span.Length.ShouldBe(3);
        span[0].ShouldBe(1);
        span[1].ShouldBe(2); span[2].ShouldBe(3);
        span[2].ShouldBe(3);
    }
 
    [Fact]
    public void AsSpan_EmptyBuilder_ReturnsEmptySpan()
    {
        using RefArrayBuilder<int> builder = new(4);
 
        ReadOnlySpan<int> span = builder.AsSpan();
 
        span.Length.ShouldBe(0);
    }
 
    [Fact]
    public void AsSpan_ReflectsChanges()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
 
        ReadOnlySpan<int> span1 = builder.AsSpan();
        span1.Length.ShouldBe(2);
 
        builder.Add(3);
 
        ReadOnlySpan<int> span2 = builder.AsSpan();
        span2.Length.ShouldBe(3);
        span2[2].ShouldBe(3);
    }
 
    [Fact]
    public void WithReferenceTypes_WorksCorrectly()
    {
        using RefArrayBuilder<string> builder = new(4);
        builder.Add("hello");
        builder.Add("world");
 
        builder.Count.ShouldBe(2);
        builder[0].ShouldBe("hello");
        builder[1].ShouldBe("world");
    }
 
    [Fact]
    public void WithReferenceTypes_AddRange_WorksCorrectly()
    {
        using RefArrayBuilder<string> builder = new(4);
        builder.AddRange(["one", "two", "three"]);
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe("one");
        builder[1].ShouldBe("two");
        builder[2].ShouldBe("three");
    }
 
    [Fact]
    public void WithReferenceTypes_Insert_WorksCorrectly()
    {
        using RefArrayBuilder<string> builder = new(4);
        builder.Add("first");
        builder.Add("third");
        builder.Insert(1, "second");
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe("first");
        builder[1].ShouldBe("second");
        builder[2].ShouldBe("third");
    }
 
    [Fact]
    public void MultipleOperations_ComplexScenario()
    {
        using RefArrayBuilder<int> builder = new(2);
 
        // Start small and grow
        builder.Add(1);
        builder.Add(2);
        builder.AddRange([3, 4, 5]);
        builder.Insert(0, 0);
        builder.InsertRange(6, [6, 7, 8]);
 
        builder.Count.ShouldBe(9);
 
        // Verify sequence
        for (int i = 0; i < 9; i++)
        {
            builder[i].ShouldBe(i);
        }
    }
 
    [Fact]
    public void LargeCapacity_HandlesGrowthCorrectly()
    {
        using RefArrayBuilder<int> builder = new(2);
 
        // Add many items to trigger multiple growth operations
        for (int i = 0; i < 100; i++)
        {
            builder.Add(i);
        }
 
        builder.Count.ShouldBe(100);
        builder[0].ShouldBe(0);
        builder[50].ShouldBe(50);
        builder[99].ShouldBe(99);
    }
 
    [Fact]
    public void Dispose_CanBeCalledMultipleTimes()
    {
        RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
 
        builder.Dispose();
        builder.Dispose(); // Should not throw
    }
 
    [Fact]
    public void RemoveAt_FirstElement_ShiftsSubsequentElements()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3);
        builder.RemoveAt(0);
 
        builder.Count.ShouldBe(2);
        builder[0].ShouldBe(2);
        builder[1].ShouldBe(3);
    }
 
    [Fact]
    public void RemoveAt_MiddleElement_ShiftsSubsequentElements()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3);
        builder.Add(4);
        builder.RemoveAt(1);
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(3);
        builder[2].ShouldBe(4);
    }
 
    [Fact]
    public void RemoveAt_LastElement_RemovesCorrectly()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3);
        builder.RemoveAt(2);
 
        builder.Count.ShouldBe(2);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
    }
 
    [Fact]
    public void RemoveAt_SingleElement_BecomesEmpty()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(42);
        builder.RemoveAt(0);
 
        builder.Count.ShouldBe(0);
    }
 
    [Fact]
    public void RemoveAt_WithReferenceTypes_ClearsReference()
    {
        using RefArrayBuilder<string> builder = new(4);
        builder.Add("first");
        builder.Add("second");
        builder.Add("third");
        builder.RemoveAt(1);
 
        builder.Count.ShouldBe(2);
        builder[0].ShouldBe("first");
        builder[1].ShouldBe("third");
    }
 
    [Fact]
    public void RemoveAt_MultipleRemovals_WorksCorrectly()
    {
        using RefArrayBuilder<int> builder = new(10);
        builder.AddRange([1, 2, 3, 4, 5]);
        builder.RemoveAt(2); // Remove 3
        builder.RemoveAt(1); // Remove 2
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(4);
        builder[2].ShouldBe(5);
    }
 
    [Fact]
    public void RemoveAt_RemoveAllElements_LeavesEmpty()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3);
        builder.RemoveAt(2);
        builder.RemoveAt(1);
        builder.RemoveAt(0);
 
        builder.Count.ShouldBe(0);
    }
 
    [Fact]
    public void RemoveAt_AfterGrowth_WorksCorrectly()
    {
        using RefArrayBuilder<int> builder = new(2);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3); // Triggers growth
        builder.Add(4);
        builder.RemoveAt(1);
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(3);
        builder[2].ShouldBe(4);
    }
 
    [Fact]
    public void RemoveAt_CanAddAfterRemoval()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3);
        builder.RemoveAt(1);
        builder.Add(4);
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(3);
        builder[2].ShouldBe(4);
    }
 
    [Fact]
    public void RemoveAt_WithScratchBuffer_WorksCorrectly()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[10]);
        builder.AddRange([1, 2, 3, 4, 5]);
        builder.RemoveAt(2);
 
        builder.Count.ShouldBe(4);
        builder[0].ShouldBe(1);
        builder[1].ShouldBe(2);
        builder[2].ShouldBe(4);
        builder[3].ShouldBe(5);
    }
 
    [Fact]
    public void ToImmutable_EmptyBuilder_ReturnsEmptyImmutableArray()
    {
        using RefArrayBuilder<int> builder = new(4);
 
        var result = builder.ToImmutable();
 
        result.IsEmpty.ShouldBeTrue();
    }
 
    [Fact]
    public void ToImmutable_WithItems_ReturnsImmutableArrayWithCorrectElements()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
        builder.Add(3);
 
        var result = builder.ToImmutable();
 
        result.Length.ShouldBe(3);
        result[0].ShouldBe(1);
        result[1].ShouldBe(2);
        result[2].ShouldBe(3);
    }
 
    [Fact]
    public void ToImmutable_WithReferenceTypes_ReturnsImmutableArrayWithCorrectElements()
    {
        using RefArrayBuilder<string> builder = new(4);
        builder.Add("hello");
        builder.Add("world");
 
        var result = builder.ToImmutable();
 
        result.Length.ShouldBe(2);
        result[0].ShouldBe("hello");
        result[1].ShouldBe("world");
    }
 
    [Fact]
    public void ToImmutable_AfterMultipleOperations_ReturnsCorrectImmutableArray()
    {
        using RefArrayBuilder<int> builder = new(2);
        builder.Add(1);
        builder.AddRange([2, 3]);
        builder.Insert(0, 0);
        builder.InsertRange(4, [4, 5]);
 
        var result = builder.ToImmutable();
 
        result.Length.ShouldBe(6);
        for (int i = 0; i < 6; i++)
        {
            result[i].ShouldBe(i);
        }
    }
 
    [Fact]
    public void ToImmutable_DoesNotModifyBuilder()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
 
        var result1 = builder.ToImmutable();
        builder.Add(3);
        var result2 = builder.ToImmutable();
 
        result1.Length.ShouldBe(2);
        result1[0].ShouldBe(1);
        result1[1].ShouldBe(2);
 
        result2.Length.ShouldBe(3);
        result2[0].ShouldBe(1);
        result2[1].ShouldBe(2);
        result2[2].ShouldBe(3);
    }
 
    [Fact]
    public void ToImmutable_AfterCountSet_ReturnsCorrectImmutableArray()
    {
        var builder = new RefArrayBuilder<int>(4);
        try
        {
            builder.Add(1);
            builder.Add(2);
            builder.Add(3);
            builder.Count = 2;
 
            var result = builder.ToImmutable();
 
            result.Length.ShouldBe(2);
            result[0].ShouldBe(1);
            result[1].ShouldBe(2);
        }
        finally
        {
            builder.Dispose();
        }
    }
 
    [Fact]
    public void Any_EmptyBuilder_ReturnsFalse()
    {
        using RefArrayBuilder<int> builder = new(4);
 
        builder.Any().ShouldBeFalse();
    }
 
    [Fact]
    public void Any_WithElements_ReturnsTrue()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
 
        builder.Any().ShouldBeTrue();
    }
 
    [Fact]
    public void Any_WithPredicate_NoMatch_ReturnsFalse()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.Any(x => x > 5).ShouldBeFalse();
    }
 
    [Fact]
    public void Any_WithPredicate_HasMatch_ReturnsTrue()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.Any(x => x > 3).ShouldBeTrue();
    }
 
    [Fact]
    public void Any_WithPredicate_EmptyBuilder_ReturnsFalse()
    {
        using RefArrayBuilder<int> builder = new(4);
 
        builder.Any(x => x > 0).ShouldBeFalse();
    }
 
    [Fact]
    public void Any_WithPredicateAndArg_NoMatch_ReturnsFalse()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.Any(5, (x, threshold) => x > threshold).ShouldBeFalse();
    }
 
    [Fact]
    public void Any_WithPredicateAndArg_HasMatch_ReturnsTrue()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.Any(3, (x, threshold) => x > threshold).ShouldBeTrue();
    }
 
    [Fact]
    public void Any_WithNullPredicate_ThrowsArgumentNullException()
    {
        Should.Throw<ArgumentNullException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.Add(1);
 
            return builder.Any(null!);
        });
    }
 
    [Fact]
    public void Any_WithPredicateAndArg_NullPredicate_ThrowsArgumentNullException()
    {
        Should.Throw<ArgumentNullException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.Add(1);
 
            return builder.Any(5, null!);
        });
    }
 
    [Fact]
    public void All_EmptyBuilder_ReturnsTrue()
    {
        using RefArrayBuilder<int> builder = new(4);
 
        builder.All(x => x > 0).ShouldBeTrue();
    }
 
    [Fact]
    public void All_AllElementsMatch_ReturnsTrue()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([2, 4, 6, 8]);
 
        builder.All(x => x % 2 == 0).ShouldBeTrue();
    }
 
    [Fact]
    public void All_SomeElementsDontMatch_ReturnsFalse()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([2, 3, 4, 6]);
 
        builder.All(x => x % 2 == 0).ShouldBeFalse();
    }
 
    [Fact]
    public void All_NoElementsMatch_ReturnsFalse()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 3, 5, 7]);
 
        builder.All(x => x % 2 == 0).ShouldBeFalse();
    }
 
    [Fact]
    public void All_WithPredicateAndArg_AllMatch_ReturnsTrue()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([5, 10, 15, 20]);
 
        builder.All(5, (x, divisor) => x % divisor == 0).ShouldBeTrue();
    }
 
    [Fact]
    public void All_WithPredicateAndArg_SomeDontMatch_ReturnsFalse()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([5, 10, 12, 20]);
 
        builder.All(5, (x, divisor) => x % divisor == 0).ShouldBeFalse();
    }
 
    [Fact]
    public void All_WithNullPredicate_ThrowsArgumentNullException()
    {
        Should.Throw<ArgumentNullException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.Add(1);
 
            return builder.All(null!);
        });
    }
 
    [Fact]
    public void All_WithPredicateAndArg_NullPredicate_ThrowsArgumentNullException()
    {
        Should.Throw<ArgumentNullException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.Add(1);
 
            return builder.All(5, null!);
        });
    }
 
    [Fact]
    public void First_EmptyBuilder_ThrowsInvalidOperationException()
    {
        Should.Throw<InvalidOperationException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
 
            return builder.First();
        });
    }
 
    [Fact]
    public void First_WithElements_ReturnsFirstElement()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.First().ShouldBe(1);
    }
 
    [Fact]
    public void First_SingleElement_ReturnsThatElement()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(42);
 
        builder.First().ShouldBe(42);
    }
 
    [Fact]
    public void First_WithPredicate_NoMatch_ThrowsInvalidOperationException()
    {
        Should.Throw<InvalidOperationException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.AddRange([1, 2, 3]);
 
            return builder.First(x => x > 5);
        });
    }
 
    [Fact]
    public void First_WithPredicate_HasMatch_ReturnsFirstMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.First(x => x > 2).ShouldBe(3);
    }
 
    [Fact]
    public void First_WithPredicate_EmptyBuilder_ThrowsInvalidOperationException()
    {
        Should.Throw<InvalidOperationException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
 
            return builder.First(x => x > 0);
        });
    }
 
    [Fact]
    public void First_WithPredicateAndArg_NoMatch_ThrowsInvalidOperationException()
    {
        Should.Throw<InvalidOperationException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.AddRange([1, 2, 3]);
 
            return builder.First(5, (x, threshold) => x > threshold);
        });
    }
 
    [Fact]
    public void First_WithPredicateAndArg_HasMatch_ReturnsFirstMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.First(2, (x, threshold) => x > threshold).ShouldBe(3);
    }
 
    [Fact]
    public void First_WithNullPredicate_ThrowsArgumentNullException()
    {
        Should.Throw<ArgumentNullException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.Add(1);
 
            return builder.First(null!);
        });
    }
 
    [Fact]
    public void First_WithPredicateAndArg_NullPredicate_ThrowsArgumentNullException()
    {
        Should.Throw<ArgumentNullException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.Add(1);
 
            return builder.First(5, null!);
        });
    }
 
    [Fact]
    public void FirstOrDefault_EmptyBuilder_ReturnsDefault()
    {
        using RefArrayBuilder<int> builder = new(4);
 
        builder.FirstOrDefault().ShouldBe(0);
    }
 
    [Fact]
    public void FirstOrDefault_WithElements_ReturnsFirstElement()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.FirstOrDefault().ShouldBe(1);
    }
 
    [Fact]
    public void FirstOrDefault_WithDefaultValue_EmptyBuilder_ReturnsDefaultValue()
    {
        using RefArrayBuilder<int> builder = new(4);
 
        builder.FirstOrDefault(99).ShouldBe(99);
    }
 
    [Fact]
    public void FirstOrDefault_WithDefaultValue_WithElements_ReturnsFirstElement()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.FirstOrDefault(99).ShouldBe(1);
    }
 
    [Fact]
    public void FirstOrDefault_WithPredicate_NoMatch_ReturnsDefault()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.FirstOrDefault(x => x > 5).ShouldBe(0);
    }
 
    [Fact]
    public void FirstOrDefault_WithPredicate_HasMatch_ReturnsFirstMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.FirstOrDefault(x => x > 2).ShouldBe(3);
    }
 
    [Fact]
    public void FirstOrDefault_WithPredicateAndDefaultValue_NoMatch_ReturnsDefaultValue()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.FirstOrDefault(x => x > 5, 99).ShouldBe(99);
    }
 
    [Fact]
    public void FirstOrDefault_WithPredicateAndDefaultValue_HasMatch_ReturnsFirstMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.FirstOrDefault(x => x > 2, 99).ShouldBe(3);
    }
 
    [Fact]
    public void FirstOrDefault_WithPredicateAndArg_NoMatch_ReturnsDefault()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.FirstOrDefault(5, (x, threshold) => x > threshold).ShouldBe(0);
    }
 
    [Fact]
    public void FirstOrDefault_WithPredicateAndArg_HasMatch_ReturnsFirstMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.FirstOrDefault(2, (x, threshold) => x > threshold).ShouldBe(3);
    }
 
    [Fact]
    public void FirstOrDefault_WithPredicateArgAndDefaultValue_NoMatch_ReturnsDefaultValue()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.FirstOrDefault(5, (x, threshold) => x > threshold, 99).ShouldBe(99);
    }
 
    [Fact]
    public void FirstOrDefault_WithPredicateArgAndDefaultValue_HasMatch_ReturnsFirstMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.FirstOrDefault(2, (x, threshold) => x > threshold, 99).ShouldBe(3);
    }
 
    [Fact]
    public void FirstOrDefault_WithNullPredicate_ThrowsArgumentNullException()
    {
        Should.Throw<ArgumentNullException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.Add(1);
 
            return builder.FirstOrDefault(null!);
        });
    }
 
    [Fact]
    public void FirstOrDefault_WithReferenceType_EmptyBuilder_ReturnsNull()
    {
        using RefArrayBuilder<string> builder = new(4);
 
        builder.FirstOrDefault().ShouldBeNull();
    }
 
    [Fact]
    public void FirstOrDefault_WithReferenceType_WithElements_ReturnsFirstElement()
    {
        using RefArrayBuilder<string> builder = new(4);
        builder.AddRange(["hello", "world"]);
 
        builder.FirstOrDefault().ShouldBe("hello");
    }
 
    [Fact]
    public void Last_EmptyBuilder_ThrowsInvalidOperationException()
    {
        Should.Throw<InvalidOperationException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
 
            return builder.Last();
        });
    }
 
    [Fact]
    public void Last_WithElements_ReturnsLastElement()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.Last().ShouldBe(3);
    }
 
    [Fact]
    public void Last_SingleElement_ReturnsThatElement()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(42);
 
        builder.Last().ShouldBe(42);
    }
 
    [Fact]
    public void Last_WithPredicate_NoMatch_ThrowsInvalidOperationException()
    {
        Should.Throw<InvalidOperationException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.AddRange([1, 2, 3]);
 
            return builder.Last(x => x > 5);
        });
    }
 
    [Fact]
    public void Last_WithPredicate_HasMatch_ReturnsLastMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.Last(x => x < 4).ShouldBe(3);
    }
 
    [Fact]
    public void Last_WithPredicate_EmptyBuilder_ThrowsInvalidOperationException()
    {
        Should.Throw<InvalidOperationException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
 
            return builder.Last(x => x > 0);
        });
    }
 
    [Fact]
    public void Last_WithPredicateAndArg_NoMatch_ThrowsInvalidOperationException()
    {
        Should.Throw<InvalidOperationException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.AddRange([1, 2, 3]);
 
            return builder.Last(5, (x, threshold) => x > threshold);
        });
    }
 
    [Fact]
    public void Last_WithPredicateAndArg_HasMatch_ReturnsLastMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.Last(3, (x, threshold) => x < threshold).ShouldBe(2);
    }
 
    [Fact]
    public void Last_WithNullPredicate_ThrowsArgumentNullException()
    {
        Should.Throw<ArgumentNullException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.Add(1);
 
            return builder.Last(null!);
        });
    }
 
    [Fact]
    public void Last_WithPredicateAndArg_NullPredicate_ThrowsArgumentNullException()
    {
        Should.Throw<ArgumentNullException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.Add(1);
 
            return builder.Last(5, null!);
        });
    }
 
    [Fact]
    public void LastOrDefault_EmptyBuilder_ReturnsDefault()
    {
        using RefArrayBuilder<int> builder = new(4);
 
        builder.LastOrDefault().ShouldBe(0);
    }
 
    [Fact]
    public void LastOrDefault_WithElements_ReturnsLastElement()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.LastOrDefault().ShouldBe(3);
    }
 
    [Fact]
    public void LastOrDefault_WithDefaultValue_EmptyBuilder_ReturnsDefaultValue()
    {
        using RefArrayBuilder<int> builder = new(4);
 
        builder.LastOrDefault(99).ShouldBe(99);
    }
 
    [Fact]
    public void LastOrDefault_WithDefaultValue_WithElements_ReturnsLastElement()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.LastOrDefault(99).ShouldBe(3);
    }
 
    [Fact]
    public void LastOrDefault_WithPredicate_NoMatch_ReturnsDefault()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.LastOrDefault(x => x > 5).ShouldBe(0);
    }
 
    [Fact]
    public void LastOrDefault_WithPredicate_HasMatch_ReturnsLastMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.LastOrDefault(x => x < 4).ShouldBe(3);
    }
 
    [Fact]
    public void LastOrDefault_WithPredicateAndDefaultValue_NoMatch_ReturnsDefaultValue()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.LastOrDefault(x => x > 5, 99).ShouldBe(99);
    }
 
    [Fact]
    public void LastOrDefault_WithPredicateAndDefaultValue_HasMatch_ReturnsLastMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.LastOrDefault(x => x < 4, 99).ShouldBe(3);
    }
 
    [Fact]
    public void LastOrDefault_WithPredicateAndArg_NoMatch_ReturnsDefault()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.LastOrDefault(5, (x, threshold) => x > threshold).ShouldBe(0);
    }
 
    [Fact]
    public void LastOrDefault_WithPredicateAndArg_HasMatch_ReturnsLastMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.LastOrDefault(3, (x, threshold) => x < threshold).ShouldBe(2);
    }
 
    [Fact]
    public void LastOrDefault_WithPredicateArgAndDefaultValue_NoMatch_ReturnsDefaultValue()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3]);
 
        builder.LastOrDefault(5, (x, threshold) => x > threshold, 99).ShouldBe(99);
    }
 
    [Fact]
    public void LastOrDefault_WithPredicateArgAndDefaultValue_HasMatch_ReturnsLastMatch()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.AddRange([1, 2, 3, 4, 5]);
 
        builder.LastOrDefault(3, (x, threshold) => x < threshold, 99).ShouldBe(2);
    }
 
    [Fact]
    public void LastOrDefault_WithNullPredicate_ThrowsArgumentNullException()
    {
        Should.Throw<ArgumentNullException>(() =>
        {
            using RefArrayBuilder<int> builder = new(4);
            builder.Add(1);
 
            return builder.LastOrDefault(null!);
        });
    }
 
    [Fact]
    public void LastOrDefault_WithReferenceType_EmptyBuilder_ReturnsNull()
    {
        using RefArrayBuilder<string> builder = new(4);
 
        builder.LastOrDefault().ShouldBeNull();
    }
 
    [Fact]
    public void LastOrDefault_WithReferenceType_WithElements_ReturnsLastElement()
    {
        using RefArrayBuilder<string> builder = new(4);
        builder.AddRange(["hello", "world"]);
 
        builder.LastOrDefault().ShouldBe("world");
    }
 
    [Fact]
    public void AsRef_ModificationsViaRef_AreReflectedInOriginalBuilder()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[4]);
 
        ref RefArrayBuilder<int> builderRef = ref builder.AsRef();
        builderRef.Add(10);
        builderRef.Add(20);
        builderRef.Add(30);
 
        builder.Count.ShouldBe(3);
        builder[0].ShouldBe(10);
        builder[1].ShouldBe(20);
        builder[2].ShouldBe(30);
    }
 
    [Fact]
    public void AsRef_AllowsPassingUsingDeclaredBuilderToRefMethod()
    {
        using RefArrayBuilder<int> builder = new(4);
        builder.Add(1);
        builder.Add(2);
 
        AddItemsToBuilder(ref builder.AsRef(), [3, 4, 5]);
 
        builder.Count.ShouldBe(5);
        builder[2].ShouldBe(3);
        builder[3].ShouldBe(4);
        builder[4].ShouldBe(5);
    }
 
    [Fact]
    public void AsRef_AllowsGrowthThroughRef()
    {
        using RefArrayBuilder<int> builder = new(stackalloc int[2]);
        builder.Add(1);
        builder.Add(2);
 
        // Passing by ref allows the helper to grow the builder beyond the initial stack buffer.
        AddItemsToBuilder(ref builder.AsRef(), [3, 4, 5]);
 
        builder.Count.ShouldBe(5);
 
        for (int i = 0; i < 5; i++)
        {
            builder[i].ShouldBe(i + 1);
        }
    }
 
    private static void AddItemsToBuilder(ref RefArrayBuilder<int> builder, ReadOnlySpan<int> items)
        => builder.AddRange(items);
}