namespace QuanTAlib.Tests; public class RingBufferTests { [Fact] public void Constructor_ValidCapacity_CreatesBuffer() { var buffer = new RingBuffer(10); Assert.Equal(10, buffer.Capacity); Assert.Equal(0, buffer.Count); Assert.False(buffer.IsFull); Assert.Equal(0, buffer.Sum); Assert.True(double.IsNaN(buffer.Average)); } [Fact] public void Constructor_ZeroCapacity_ThrowsArgumentException() { Assert.Throws(() => new RingBuffer(0)); } [Fact] public void Constructor_NegativeCapacity_ThrowsArgumentException() { Assert.Throws(() => new RingBuffer(-1)); } [Fact] public void Add_SingleValue_UpdatesState() { var buffer = new RingBuffer(5); buffer.Add(10.0); Assert.Equal(1, buffer.Count); Assert.Equal(10.0, buffer.Sum); Assert.Equal(10.0, buffer.Average); Assert.Equal(10.0, buffer.Newest); Assert.Equal(10.0, buffer.Oldest); Assert.False(buffer.IsFull); } [Fact] public void Add_MultipleValues_UpdatesState() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); Assert.Equal(3, buffer.Count); Assert.Equal(60.0, buffer.Sum); Assert.Equal(20.0, buffer.Average); Assert.Equal(30.0, buffer.Newest); Assert.Equal(10.0, buffer.Oldest); Assert.False(buffer.IsFull); } [Fact] public void Add_FillBuffer_BecomesFullAndWraps() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); Assert.Equal(3, buffer.Count); Assert.True(buffer.IsFull); Assert.Equal(60.0, buffer.Sum); Assert.Equal(20.0, buffer.Average); // Add one more - should remove 10.0 double removed = buffer.Add(40.0); Assert.Equal(10.0, removed); Assert.Equal(3, buffer.Count); Assert.True(buffer.IsFull); Assert.Equal(90.0, buffer.Sum); // 20 + 30 + 40 Assert.Equal(30.0, buffer.Average); Assert.Equal(40.0, buffer.Newest); Assert.Equal(20.0, buffer.Oldest); } [Fact] public void Add_MultipleWraps_MaintainsCorrectState() { var buffer = new RingBuffer(3); // Fill and wrap multiple times for (int i = 1; i <= 10; i++) { buffer.Add(i * 10.0); } // Should contain: 80, 90, 100 Assert.Equal(3, buffer.Count); Assert.True(buffer.IsFull); Assert.Equal(270.0, buffer.Sum); // 80 + 90 + 100 Assert.Equal(90.0, buffer.Average); Assert.Equal(100.0, buffer.Newest); Assert.Equal(80.0, buffer.Oldest); } [Fact] public void UpdateNewest_ModifiesLastValue() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); Assert.Equal(60.0, buffer.Sum); buffer.UpdateNewest(35.0); Assert.Equal(65.0, buffer.Sum); // 10 + 20 + 35 Assert.Equal(35.0, buffer.Newest); Assert.Equal(3, buffer.Count); } [Fact] public void UpdateNewest_EmptyBuffer_DoesNothing() { var buffer = new RingBuffer(5); buffer.UpdateNewest(100.0); // Should not throw Assert.Equal(0, buffer.Count); Assert.Equal(0, buffer.Sum); } [Fact] public void Indexer_AccessesCorrectValues() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); // Index 0 = oldest, Index 2 = newest Assert.Equal(10.0, buffer[0]); Assert.Equal(20.0, buffer[1]); Assert.Equal(30.0, buffer[2]); } [Fact] public void Indexer_AfterWrap_AccessesCorrectValues() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Add(40.0); // Wraps, removes 10 // Should contain: 20, 30, 40 Assert.Equal(20.0, buffer[0]); Assert.Equal(30.0, buffer[1]); Assert.Equal(40.0, buffer[2]); } [Fact] public void Indexer_OutOfRange_ThrowsException() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); // Valid indices are 0 and 1 (2 elements) // Index 2 should throw ArgumentOutOfRangeException Assert.Throws(() => _ = buffer[2]); Assert.Throws(() => _ = buffer[10]); } [Fact] public void Clear_ResetsState() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Clear(); Assert.Equal(0, buffer.Count); Assert.Equal(0, buffer.Sum); Assert.True(double.IsNaN(buffer.Average)); Assert.False(buffer.IsFull); } [Fact] public void Clear_AllowsReuse() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Clear(); buffer.Add(100.0); Assert.Equal(1, buffer.Count); Assert.Equal(100.0, buffer.Sum); Assert.Equal(100.0, buffer.Newest); } [Fact] public void Clone_CreatesIndependentCopy() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); var clone = buffer.Clone(); // Verify clone has same state Assert.Equal(buffer.Count, clone.Count); Assert.Equal(buffer.Sum, clone.Sum); Assert.Equal(buffer.Newest, clone.Newest); Assert.Equal(buffer.Oldest, clone.Oldest); // Modify original - clone should be unaffected buffer.Add(40.0); Assert.Equal(4, buffer.Count); Assert.Equal(3, clone.Count); Assert.Equal(100.0, buffer.Sum); Assert.Equal(60.0, clone.Sum); } [Fact] public void CopyFrom_CopiesState() { var source = new RingBuffer(5); var target = new RingBuffer(5); source.Add(10.0); source.Add(20.0); source.Add(30.0); target.Add(100.0); // Different initial state target.CopyFrom(source); Assert.Equal(source.Count, target.Count); Assert.Equal(source.Sum, target.Sum); Assert.Equal(source.Newest, target.Newest); Assert.Equal(source.Oldest, target.Oldest); // Verify independence after copy source.Add(40.0); Assert.NotEqual(source.Sum, target.Sum); } [Fact] public void CopyFrom_DifferentCapacity_ThrowsException() { var source = new RingBuffer(5); var target = new RingBuffer(10); Assert.Throws(() => target.CopyFrom(source)); } [Fact] public void GetSpan_ReturnsChronologicalOrder() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); var span = buffer.GetSpan(); Assert.Equal(3, span.Length); Assert.Equal(10.0, span[0]); Assert.Equal(20.0, span[1]); Assert.Equal(30.0, span[2]); } [Fact] public void GetSpan_AfterWrap_ReturnsChronologicalOrder() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Add(40.0); buffer.Add(50.0); var span = buffer.GetSpan(); // Should be: 30, 40, 50 Assert.Equal(3, span.Length); Assert.Equal(30.0, span[0]); Assert.Equal(40.0, span[1]); Assert.Equal(50.0, span[2]); } [Fact] public void GetSpan_EmptyBuffer_ReturnsEmpty() { var buffer = new RingBuffer(5); var span = buffer.GetSpan(); Assert.True(span.IsEmpty); } [Fact] public void Min_ReturnsMinimumValue() { var buffer = new RingBuffer(5); buffer.Add(30.0); buffer.Add(10.0); buffer.Add(50.0); buffer.Add(20.0); buffer.Add(40.0); Assert.Equal(10.0, buffer.Min()); } [Fact] public void Max_ReturnsMaximumValue() { var buffer = new RingBuffer(5); buffer.Add(30.0); buffer.Add(10.0); buffer.Add(50.0); buffer.Add(20.0); buffer.Add(40.0); Assert.Equal(50.0, buffer.Max()); } [Fact] public void Min_EmptyBuffer_ReturnsNaN() { var buffer = new RingBuffer(5); Assert.True(double.IsNaN(buffer.Min())); } [Fact] public void Max_EmptyBuffer_ReturnsNaN() { var buffer = new RingBuffer(5); Assert.True(double.IsNaN(buffer.Max())); } [Fact] public void Enumerator_IteratesInChronologicalOrder() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); List values = []; foreach (var v in buffer) { values.Add(v); } Assert.Equal(3, values.Count); Assert.Equal(10.0, values[0]); Assert.Equal(20.0, values[1]); Assert.Equal(30.0, values[2]); } [Fact] public void Enumerator_AfterWrap_IteratesInChronologicalOrder() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Add(40.0); buffer.Add(50.0); List values = []; foreach (var v in buffer) { values.Add(v); } // Should be: 30, 40, 50 Assert.Equal(3, values.Count); Assert.Equal(30.0, values[0]); Assert.Equal(40.0, values[1]); Assert.Equal(50.0, values[2]); } [Fact] public void Add_WithIsNew_WorksCorrectly() { var buffer = new RingBuffer(5); buffer.Add(10.0, isNew: true); buffer.Add(20.0, isNew: true); buffer.Add(25.0, isNew: false); // Should update 20.0 to 25.0 Assert.Equal(2, buffer.Count); Assert.Equal(25.0, buffer.Newest); Assert.Equal(35.0, buffer.Sum); // 10 + 25 } [Fact] public void Indexer_WithIndexType_SupportsFromEnd() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); Assert.Equal(30.0, buffer[^1]); // Newest Assert.Equal(20.0, buffer[^2]); Assert.Equal(10.0, buffer[^3]); // Oldest } [Fact] public void Newest_EmptyBuffer_ReturnsNaN() { var buffer = new RingBuffer(5); Assert.True(double.IsNaN(buffer.Newest)); } [Fact] public void Oldest_EmptyBuffer_ReturnsNaN() { var buffer = new RingBuffer(5); Assert.True(double.IsNaN(buffer.Oldest)); } [Fact] public void Average_EmptyBuffer_ReturnsNaN() { var buffer = new RingBuffer(5); Assert.True(double.IsNaN(buffer.Average)); } [Fact] public void GetInternalSpan_ReturnsFullBuffer() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); var span = buffer.GetInternalSpan(); Assert.Equal(5, span.Length); // Full capacity, not count } [Fact] public void ToArray_AfterWrap_ReturnsChronologicalOrder() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Add(40.0); // Wraps var arr = buffer.ToArray(); Assert.Equal(3, arr.Length); Assert.Equal(20.0, arr[0]); Assert.Equal(30.0, arr[1]); Assert.Equal(40.0, arr[2]); } [Fact] public void CopyTo_AfterWrap_CopiesInChronologicalOrder() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Add(40.0); // Wraps var dest = new double[5]; buffer.CopyTo(dest, 1); Assert.Equal(0, dest[0]); // Untouched Assert.Equal(20.0, dest[1]); Assert.Equal(30.0, dest[2]); Assert.Equal(40.0, dest[3]); Assert.Equal(0, dest[4]); // Untouched } [Fact] public void Indexer_Set_UpdatesValueAndSum() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); Assert.Equal(60.0, buffer.Sum); buffer[1] = 25.0; // Change 20.0 to 25.0 Assert.Equal(65.0, buffer.Sum); Assert.Equal(25.0, buffer[1]); } [Fact] public void Indexer_SetFromEnd_UpdatesValueAndSum() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer[^1] = 35.0; // Change newest (30.0) to 35.0 Assert.Equal(65.0, buffer.Sum); Assert.Equal(35.0, buffer[^1]); } [Fact] public void Min_LargeBuffer_UsesSimd() { var buffer = new RingBuffer(100); for (int i = 0; i < 100; i++) { buffer.Add(i + 1); // 1 to 100 } Assert.Equal(1.0, buffer.Min()); } [Fact] public void Max_LargeBuffer_UsesSimd() { var buffer = new RingBuffer(100); for (int i = 0; i < 100; i++) { buffer.Add(i + 1); // 1 to 100 } Assert.Equal(100.0, buffer.Max()); } [Fact] public void ToArray_EmptyBuffer_ReturnsEmpty() { var buffer = new RingBuffer(5); var arr = buffer.ToArray(); Assert.Empty(arr); } [Fact] public void CopyTo_EmptyBuffer_DoesNothing() { var buffer = new RingBuffer(5); double[] dest = [1.0, 2.0, 3.0]; buffer.CopyTo(dest, 0); Assert.Equal(1.0, dest[0]); Assert.Equal(2.0, dest[1]); Assert.Equal(3.0, dest[2]); } [Fact] public void InternalBuffer_ReturnsSpan() { var buffer = new RingBuffer(5); buffer.Add(10.0); var span = buffer.InternalBuffer; Assert.Equal(5, span.Length); Assert.Equal(10.0, span[0]); } [Fact] public void Enumerator_Reset_AllowsReIteration() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); var enumerator = buffer.GetEnumerator(); // First iteration Assert.True(enumerator.MoveNext()); Assert.Equal(10.0, enumerator.Current); Assert.True(enumerator.MoveNext()); Assert.Equal(20.0, enumerator.Current); Assert.False(enumerator.MoveNext()); // Reset and iterate again enumerator.Reset(); Assert.True(enumerator.MoveNext()); Assert.Equal(10.0, enumerator.Current); enumerator.Dispose(); // Coverage for Dispose } [Fact] public void IEnumerable_GetEnumerator_Works() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); IEnumerable enumerable = buffer; List values = []; foreach (var v in enumerable) { values.Add(v); } Assert.Equal(2, values.Count); Assert.Equal(10.0, values[0]); Assert.Equal(20.0, values[1]); } [Fact] public void IEnumerable_NonGeneric_GetEnumerator_Works() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); IEnumerable enumerable = buffer; List values = []; foreach (var v in enumerable) { values.Add((double)v); } Assert.Equal(2, values.Count); } [Fact] public void Indexer_Set_AfterWrap_UpdatesCorrectly() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Add(40.0); // Wraps - now has 20, 30, 40 buffer[0] = 25.0; // Change oldest (20.0) to 25.0 Assert.Equal(95.0, buffer.Sum); // 25 + 30 + 40 Assert.Equal(25.0, buffer[0]); } [Fact] public void Add_WithIsNew_EmptyBuffer_AddsValue() { var buffer = new RingBuffer(5); buffer.Add(10.0, isNew: false); // isNew=false but buffer empty, should still add Assert.Equal(1, buffer.Count); Assert.Equal(10.0, buffer.Newest); } [Fact] public void BarCorrection_Workflow() { // Simulate bar correction (isNew=false) workflow var buffer = new RingBuffer(3); var backup = new RingBuffer(3); // Add values as new bars buffer.Add(10.0); backup.CopyFrom(buffer); buffer.Add(20.0); backup.CopyFrom(buffer); buffer.Add(30.0); backup.CopyFrom(buffer); double avgBeforeCorrection = buffer.Average; // Simulate correction (isNew=false) buffer.CopyFrom(backup); // Restore previous state buffer.UpdateNewest(35.0); // Update with corrected value // Average should reflect the correction Assert.Equal(21.666666666666668, buffer.Average, 1e-10); Assert.NotEqual(avgBeforeCorrection, buffer.Average); } [Fact] public void Snapshot_CapturesCurrentState() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Snapshot(); // Modify buffer after snapshot buffer.Add(40.0); Assert.Equal(4, buffer.Count); Assert.Equal(100.0, buffer.Sum); // 10 + 20 + 30 + 40 } [Fact] public void Restore_ReturnsToSnapshotState() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Snapshot(); double sumBeforeModification = buffer.Sum; int countBeforeModification = buffer.Count; // Modify buffer after snapshot buffer.Add(40.0); Assert.Equal(4, buffer.Count); // Restore to snapshot state buffer.Restore(); Assert.Equal(countBeforeModification, buffer.Count); Assert.Equal(sumBeforeModification, buffer.Sum); } [Fact] public void Snapshot_Restore_WithWrapping() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Snapshot(); // Add value that causes wrap buffer.Add(40.0); Assert.Equal(90.0, buffer.Sum); // 20 + 30 + 40 buffer.Restore(); Assert.Equal(60.0, buffer.Sum); // 10 + 20 + 30 Assert.Equal(30.0, buffer.Newest); } [Fact] public void RecalculateSum_CorrectsDrift() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); double recalculated = buffer.RecalculateSum(); Assert.Equal(60.0, recalculated); Assert.Equal(60.0, buffer.Sum); } [Fact] public void RecalculateSum_AfterMultipleOperations() { var buffer = new RingBuffer(3); // Simulate many operations that could accumulate floating-point drift for (int i = 0; i < 100; i++) { buffer.Add(i * 0.1); } double recalculated = buffer.RecalculateSum(); // Should be equal (or very close) since we're using exact values Assert.Equal(recalculated, buffer.Sum); } [Fact] public void StartIndex_EmptyBuffer_ReturnsZero() { var buffer = new RingBuffer(5); Assert.Equal(0, buffer.StartIndex); } [Fact] public void StartIndex_PartiallyFilled_ReturnsZero() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); Assert.Equal(0, buffer.StartIndex); } [Fact] public void StartIndex_FullBuffer_ReturnsHead() { var buffer = new RingBuffer(3); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); buffer.Add(40.0); // Wraps // StartIndex should point to oldest element Assert.True(buffer.StartIndex >= 0 && buffer.StartIndex < buffer.Capacity); Assert.Equal(20.0, buffer.Oldest); } [Fact] public void Indexer_NegativeIndexViaFromEnd_ThrowsWhenOutOfBounds() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); // ^4 when count=3 should throw Assert.Throws(() => _ = buffer[^4]); } [Fact] public void CopyTo_InsufficientDestinationBuffer_Behavior() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); buffer.Add(30.0); var dest = new double[2]; // Too small // This will throw IndexOutOfRangeException since we're copying 3 elements to size-2 array Assert.Throws(() => buffer.CopyTo(dest, 0)); } [Fact] public void CopyTo_StartIndexOutOfRange_Behavior() { var buffer = new RingBuffer(5); buffer.Add(10.0); buffer.Add(20.0); var dest = new double[5]; // Starting at index 4 with 2 elements should fail Assert.Throws(() => buffer.CopyTo(dest, 4)); } }