mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-01 19:27:44 +00:00
653aafacd8
- Implemented Prime method in Vel, Ao, Apo, Frama, Adl, Adosc, Aobv, Cmf, Efi, Eom, Iii, Kvo, Mfi, Nvi, Obv, Pvd, Pvi, Pvo, Pvr, Pvt, Tvi, Twap, Va, Vf, Vo, Vroc, Vwad, Vwap, and Vwma classes. - The Prime method resets the indicator state and processes the provided historical bar data to initialize the indicator. - Added warmup period property to Adl and Wad classes to define the minimum number of data points required for validity. - Updated benchmark tests to use Batch methods for performance evaluation.
795 lines
21 KiB
C#
795 lines
21 KiB
C#
|
|
namespace QuanTAlib.Tests;
|
|
|
|
public class TSeriesTests
|
|
{
|
|
[Fact]
|
|
public void Constructor_Default_CreatesEmptySeries()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
Assert.Empty(series);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithCapacity_CreatesEmptySeries()
|
|
{
|
|
var series = new TSeries(100);
|
|
|
|
Assert.Empty(series);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithLists_WrapsExistingData()
|
|
{
|
|
var times = new List<long> { 100, 200, 300 };
|
|
var values = new List<double> { 1.0, 2.0, 3.0 };
|
|
|
|
var series = new TSeries(times, values);
|
|
|
|
Assert.Equal(3, series.Count);
|
|
Assert.Equal(1.0, series[0].Value);
|
|
Assert.Equal(2.0, series[1].Value);
|
|
Assert.Equal(3.0, series[2].Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Name_DefaultValue_IsData()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
Assert.Equal("Data", series.Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void Name_CanBeSet()
|
|
{
|
|
var series = new TSeries { Name = "TestSeries" };
|
|
|
|
Assert.Equal("TestSeries", series.Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_NewValue_IncreasesCount()
|
|
{
|
|
var series = new TSeries();
|
|
long time = DateTime.UtcNow.Ticks;
|
|
|
|
series.Add(time, 10.0, isNew: true);
|
|
|
|
Assert.Single(series);
|
|
Assert.Equal(10.0, series.Last.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_UpdateValue_DoesNotIncreaseCount()
|
|
{
|
|
var series = new TSeries();
|
|
long time = DateTime.UtcNow.Ticks;
|
|
|
|
series.Add(time, 10.0, isNew: true);
|
|
series.Add(time, 11.0, isNew: false);
|
|
|
|
Assert.Single(series);
|
|
Assert.Equal(11.0, series.Last.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_MultipleValues_MaintainsOrder()
|
|
{
|
|
var series = new TSeries();
|
|
long t0 = DateTime.UtcNow.Ticks;
|
|
long t1 = t0 + TimeSpan.TicksPerMinute;
|
|
|
|
series.Add(t0, 10.0, isNew: true);
|
|
series.Add(t1, 20.0, isNew: true);
|
|
|
|
Assert.Equal(2, series.Count);
|
|
Assert.Equal(10.0, series[0].Value);
|
|
Assert.Equal(20.0, series[1].Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_TValue_AddsNewItem()
|
|
{
|
|
var series = new TSeries();
|
|
var tv = new TValue(DateTime.UtcNow.Ticks, 42.0);
|
|
|
|
series.Add(tv);
|
|
|
|
Assert.Single(series);
|
|
Assert.Equal(42.0, series.Last.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_TValueWithIsNew_AddsOrUpdates()
|
|
{
|
|
var series = new TSeries();
|
|
var tv1 = new TValue(DateTime.UtcNow.Ticks, 42.0);
|
|
var tv2 = new TValue(DateTime.UtcNow.Ticks, 43.0);
|
|
|
|
series.Add(tv1, isNew: true);
|
|
series.Add(tv2, isNew: false);
|
|
|
|
Assert.Single(series);
|
|
Assert.Equal(43.0, series.Last.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_WithDateTime_AddsValue()
|
|
{
|
|
var series = new TSeries();
|
|
var dt = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Utc);
|
|
|
|
series.Add(dt, 100.0, isNew: true);
|
|
|
|
Assert.Single(series);
|
|
Assert.Equal(dt.Ticks, series.Last.Time);
|
|
Assert.Equal(100.0, series.Last.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_EnumerableDoubles_AddsAllValues()
|
|
{
|
|
var series = new TSeries();
|
|
var values = new[] { 1.0, 2.0, 3.0, 4.0, 5.0 };
|
|
|
|
series.Add(values);
|
|
|
|
Assert.Equal(5, series.Count);
|
|
Assert.Equal(1.0, series[0].Value);
|
|
Assert.Equal(5.0, series[4].Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_UpdateOnEmptySeries_AddsNewItem()
|
|
{
|
|
var series = new TSeries();
|
|
var tv = new TValue(DateTime.UtcNow.Ticks, 42.0);
|
|
|
|
series.Add(tv, isNew: false);
|
|
|
|
Assert.Single(series);
|
|
Assert.Equal(42.0, series.Last.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Last_EmptySeries_ReturnsDefault()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
var last = series.Last;
|
|
|
|
Assert.Equal(0, last.Time);
|
|
Assert.Equal(0.0, last.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Last_NonEmptySeries_ReturnsLastValue()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 10.0);
|
|
series.Add(200, 20.0);
|
|
|
|
var last = series.Last;
|
|
|
|
Assert.Equal(200, last.Time);
|
|
Assert.Equal(20.0, last.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void LastValue_EmptySeries_ReturnsNaN()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
Assert.True(double.IsNaN(series.LastValue));
|
|
}
|
|
|
|
[Fact]
|
|
public void LastValue_NonEmptySeries_ReturnsLastValue()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 10.0);
|
|
series.Add(200, 20.0);
|
|
|
|
Assert.Equal(20.0, series.LastValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void LastTime_EmptySeries_ReturnsZero()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
Assert.Equal(0, series.LastTime);
|
|
}
|
|
|
|
[Fact]
|
|
public void LastTime_NonEmptySeries_ReturnsLastTime()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 10.0);
|
|
series.Add(200, 20.0);
|
|
|
|
Assert.Equal(200, series.LastTime);
|
|
}
|
|
|
|
[Fact]
|
|
public void Values_ReturnsReadOnlySpanOfValues()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
series.Add(200, 2.0);
|
|
series.Add(300, 3.0);
|
|
|
|
ReadOnlySpan<double> values = series.Values;
|
|
|
|
Assert.Equal(3, values.Length);
|
|
Assert.Equal(1.0, values[0]);
|
|
Assert.Equal(2.0, values[1]);
|
|
Assert.Equal(3.0, values[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Times_ReturnsReadOnlySpanOfTimes()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
series.Add(200, 2.0);
|
|
series.Add(300, 3.0);
|
|
|
|
ReadOnlySpan<long> times = series.Times;
|
|
|
|
Assert.Equal(3, times.Length);
|
|
Assert.Equal(100, times[0]);
|
|
Assert.Equal(200, times[1]);
|
|
Assert.Equal(300, times[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Indexer_ReturnsCorrectTValue()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 10.0);
|
|
series.Add(200, 20.0);
|
|
series.Add(300, 30.0);
|
|
|
|
Assert.Equal(100, series[0].Time);
|
|
Assert.Equal(10.0, series[0].Value);
|
|
Assert.Equal(200, series[1].Time);
|
|
Assert.Equal(20.0, series[1].Value);
|
|
Assert.Equal(300, series[2].Time);
|
|
Assert.Equal(30.0, series[2].Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetEnumerator_IteratesAllValues()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
series.Add(200, 2.0);
|
|
series.Add(300, 3.0);
|
|
|
|
var list = series.ToList();
|
|
|
|
Assert.Equal(3, list.Count);
|
|
Assert.Equal(1.0, list[0].Value);
|
|
Assert.Equal(2.0, list[1].Value);
|
|
Assert.Equal(3.0, list[2].Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetEnumerator_NonGeneric_Works()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
series.Add(200, 2.0);
|
|
|
|
var list = new List<object>();
|
|
IEnumerable enumerable = series;
|
|
#pragma warning disable S4158
|
|
foreach (var item in enumerable)
|
|
{
|
|
list.Add(item);
|
|
}
|
|
|
|
Assert.Equal(2, series.Count);
|
|
Assert.Equal(2, list.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Pub_Event_IsRaisedOnAdd()
|
|
{
|
|
var series = new TSeries();
|
|
TValue? received = null;
|
|
series.Pub += (object? sender, in TValueEventArgs args) => received = args.Value;
|
|
|
|
series.Add(100, 42.0);
|
|
|
|
Assert.NotNull(received);
|
|
Assert.Equal(100, received.Value.Time);
|
|
Assert.Equal(42.0, received.Value.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Pub_Event_IsRaisedOnUpdate()
|
|
{
|
|
var series = new TSeries();
|
|
TValue? received = null;
|
|
series.Add(100, 42.0);
|
|
series.Pub += (object? sender, in TValueEventArgs args) => received = args.Value;
|
|
|
|
series.Add(100, 43.0, isNew: false);
|
|
|
|
Assert.NotNull(received);
|
|
Assert.Equal(43.0, received.Value.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Count_ReturnsCorrectValue()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
series.Add(100, 1.0);
|
|
Assert.Single(series);
|
|
|
|
series.Add(200, 2.0);
|
|
Assert.Equal(2, series.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithMismatchedLists_WrapsData()
|
|
{
|
|
// TSeries wraps the lists directly if they're List<T>, no length validation
|
|
var times = new List<long> { 100, 200, 300 };
|
|
var values = new List<double> { 1.0, 2.0 }; // Different length
|
|
|
|
var series = new TSeries(times, values);
|
|
|
|
// Count is based on values list
|
|
Assert.Equal(2, series.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Indexer_OutOfBounds_ThrowsException()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => _ = series[0]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Indexer_NegativeIndex_ThrowsException()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
|
|
#pragma warning disable DS003 // Invalid index - intentional for testing exception
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => _ = series[-1]);
|
|
#pragma warning restore DS003
|
|
}
|
|
|
|
[Fact]
|
|
public void Indexer_BeyondCount_ThrowsException()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => _ = series[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Values_EmptySeries_ReturnsEmptySpan()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
ReadOnlySpan<double> values = series.Values;
|
|
|
|
Assert.Equal(0, values.Length);
|
|
}
|
|
|
|
[Fact]
|
|
public void Times_EmptySeries_ReturnsEmptySpan()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
ReadOnlySpan<long> times = series.Times;
|
|
|
|
Assert.Equal(0, times.Length);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_WithNaN_PreservesNaN()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
series.Add(100, double.NaN);
|
|
|
|
Assert.True(double.IsNaN(series.Last.Value));
|
|
Assert.True(double.IsNaN(series.LastValue));
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_WithInfinity_PreservesInfinity()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
series.Add(100, double.PositiveInfinity);
|
|
|
|
Assert.True(double.IsPositiveInfinity(series.Last.Value));
|
|
Assert.True(double.IsPositiveInfinity(series.LastValue));
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_WithNegativeInfinity_PreservesNegativeInfinity()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
series.Add(100, double.NegativeInfinity);
|
|
|
|
Assert.True(double.IsNegativeInfinity(series.Last.Value));
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_EnumerableDoubles_GeneratesIncreasingTimes()
|
|
{
|
|
var series = new TSeries();
|
|
var values = new[] { 1.0, 2.0, 3.0 };
|
|
|
|
series.Add(values);
|
|
|
|
Assert.Equal(3, series.Count);
|
|
// Times should be increasing by TicksPerMinute
|
|
Assert.True(series[1].Time > series[0].Time);
|
|
Assert.True(series[2].Time > series[1].Time);
|
|
Assert.Equal(TimeSpan.TicksPerMinute, series[1].Time - series[0].Time);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_EnumerableDoubles_EmptyArray_AddsNothing()
|
|
{
|
|
var series = new TSeries();
|
|
|
|
series.Add(Array.Empty<double>());
|
|
|
|
Assert.Empty(series);
|
|
}
|
|
|
|
[Fact]
|
|
public void Pub_EventArgs_ContainsIsNewFlag()
|
|
{
|
|
var series = new TSeries();
|
|
bool? receivedIsNew = null;
|
|
series.Pub += (object? sender, in TValueEventArgs args) => receivedIsNew = args.IsNew;
|
|
|
|
series.Add(new TValue(100, 42.0), isNew: true);
|
|
|
|
Assert.True(receivedIsNew);
|
|
|
|
series.Add(new TValue(100, 43.0), isNew: false);
|
|
|
|
Assert.False(receivedIsNew);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithCapacity_DoesNotAffectCount()
|
|
{
|
|
var series = new TSeries(1000);
|
|
|
|
Assert.Empty(series);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_WithDateTimeLocal_ConvertsToUtc()
|
|
{
|
|
var series = new TSeries();
|
|
var localTime = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Local);
|
|
|
|
series.Add(localTime, 100.0);
|
|
|
|
// The stored time should be UTC
|
|
var storedTime = new DateTime(series.Last.Time, DateTimeKind.Utc);
|
|
Assert.Equal(DateTimeKind.Utc, storedTime.Kind);
|
|
}
|
|
|
|
[Fact]
|
|
public void Add_WithDateTimeUnspecified_TreatsAsLocal()
|
|
{
|
|
var series = new TSeries();
|
|
var unspecifiedTime = new DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Unspecified);
|
|
|
|
series.Add(unspecifiedTime, 100.0);
|
|
|
|
Assert.Single(series);
|
|
}
|
|
|
|
[Fact]
|
|
public void Values_ModifyingUnderlyingList_ReflectsInSpan()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
series.Add(200, 2.0);
|
|
|
|
// Get the span
|
|
ReadOnlySpan<double> values1 = series.Values;
|
|
Assert.Equal(2, values1.Length);
|
|
|
|
// Add more data
|
|
series.Add(300, 3.0);
|
|
|
|
// Get new span - should reflect the change
|
|
ReadOnlySpan<double> values2 = series.Values;
|
|
Assert.Equal(3, values2.Length);
|
|
Assert.Equal(3.0, values2[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithReadOnlyLists_CopiesData()
|
|
{
|
|
// Using arrays which implement IReadOnlyList but aren't List<T>
|
|
long[] times = [100, 200, 300];
|
|
IReadOnlyList<double> values = [1.0, 2.0, 3.0];
|
|
|
|
var series = new TSeries(times, values);
|
|
|
|
Assert.Equal(3, series.Count);
|
|
Assert.Equal(1.0, series[0].Value);
|
|
Assert.Equal(3.0, series[2].Value);
|
|
}
|
|
|
|
|
|
[Fact]
|
|
public void GetEnumerator_ExplicitGenericInterface_Works()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
series.Add(200, 2.0);
|
|
series.Add(300, 3.0);
|
|
|
|
// Explicitly call IEnumerable<TValue>.GetEnumerator() through interface cast
|
|
IEnumerable<TValue> genericEnumerable = series;
|
|
using var enumerator = genericEnumerable.GetEnumerator();
|
|
|
|
var values = new List<double>();
|
|
while (enumerator.MoveNext())
|
|
{
|
|
values.Add(enumerator.Current.Value);
|
|
}
|
|
|
|
Assert.Equal(3, values.Count);
|
|
Assert.Equal(1.0, values[0]);
|
|
Assert.Equal(2.0, values[1]);
|
|
Assert.Equal(3.0, values[2]);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetEnumerator_ExplicitNonGenericInterface_Works()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
series.Add(200, 2.0);
|
|
|
|
// Explicitly call IEnumerable.GetEnumerator() through interface cast
|
|
IEnumerable nonGenericEnumerable = series;
|
|
var enumerator = nonGenericEnumerable.GetEnumerator();
|
|
|
|
var values = new List<double>();
|
|
while (enumerator.MoveNext())
|
|
{
|
|
var tv = (TValue)enumerator.Current;
|
|
values.Add(tv.Value);
|
|
}
|
|
|
|
Assert.Equal(2, values.Count);
|
|
Assert.Equal(1.0, values[0]);
|
|
Assert.Equal(2.0, values[1]);
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
// NEW TESTS: ShareStorageTag struct
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void ShareStorageTag_Instance_IsDefault()
|
|
{
|
|
var tag = ShareStorageTag.Instance;
|
|
|
|
// It's a readonly struct with no fields — default should work
|
|
Assert.Equal(default(ShareStorageTag), tag);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShareStorageTag_TwoInstances_AreEqual()
|
|
{
|
|
var a = ShareStorageTag.Instance;
|
|
var b = ShareStorageTag.Instance;
|
|
|
|
Assert.Equal(a, b);
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
// NEW TESTS: TSeriesEnumerator struct
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void TSeriesEnumerator_Reset_AllowsReIteration()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
series.Add(200, 2.0);
|
|
|
|
var enumerator = series.GetEnumerator();
|
|
|
|
// First pass
|
|
int count1 = 0;
|
|
while (enumerator.MoveNext()) { count1++; }
|
|
Assert.Equal(2, count1);
|
|
|
|
// Reset and re-iterate
|
|
enumerator.Reset();
|
|
int count2 = 0;
|
|
while (enumerator.MoveNext()) { count2++; }
|
|
Assert.Equal(2, count2);
|
|
}
|
|
|
|
[Fact]
|
|
public void TSeriesEnumerator_Dispose_DoesNotThrow()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
|
|
var enumerator = series.GetEnumerator();
|
|
enumerator.MoveNext();
|
|
enumerator.Dispose(); // Should be no-op
|
|
|
|
Assert.Equal(1.0, enumerator.Current.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void TSeriesEnumerator_Equals_SameState_ReturnsTrue()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
|
|
var a = series.GetEnumerator();
|
|
var b = series.GetEnumerator();
|
|
|
|
// Both at initial state (_index = -1)
|
|
Assert.True(a.Equals(b));
|
|
Assert.True(a == b);
|
|
Assert.False(a != b);
|
|
}
|
|
|
|
[Fact]
|
|
public void TSeriesEnumerator_Equals_DifferentState_ReturnsFalse()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
series.Add(200, 2.0);
|
|
|
|
var a = series.GetEnumerator();
|
|
var b = series.GetEnumerator();
|
|
|
|
a.MoveNext(); // a is at index 0, b is at -1
|
|
|
|
Assert.False(a.Equals(b));
|
|
Assert.False(a == b);
|
|
Assert.True(a != b);
|
|
}
|
|
|
|
[Fact]
|
|
public void TSeriesEnumerator_Equals_Object_SameState_ReturnsTrue()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
|
|
var a = series.GetEnumerator();
|
|
object b = series.GetEnumerator();
|
|
|
|
Assert.True(a.Equals(b));
|
|
}
|
|
|
|
[Fact]
|
|
public void TSeriesEnumerator_Equals_Object_DifferentType_ReturnsFalse()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
|
|
var a = series.GetEnumerator();
|
|
|
|
Assert.False(a.Equals("not an enumerator"));
|
|
Assert.False(a.Equals(null));
|
|
}
|
|
|
|
[Fact]
|
|
public void TSeriesEnumerator_GetHashCode_SameState_SameHash()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
|
|
var a = series.GetEnumerator();
|
|
var b = series.GetEnumerator();
|
|
|
|
Assert.Equal(a.GetHashCode(), b.GetHashCode());
|
|
}
|
|
|
|
[Fact]
|
|
public void TSeriesEnumerator_GetHashCode_DifferentState_LikelyDifferentHash()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 1.0);
|
|
series.Add(200, 2.0);
|
|
|
|
var a = series.GetEnumerator();
|
|
var b = series.GetEnumerator();
|
|
a.MoveNext();
|
|
|
|
Assert.NotEqual(a.GetHashCode(), b.GetHashCode());
|
|
}
|
|
|
|
[Fact]
|
|
public void TSeriesEnumerator_Current_ViaIEnumerator_ReturnsBoxedTValue()
|
|
{
|
|
var series = new TSeries();
|
|
series.Add(100, 42.0);
|
|
|
|
IEnumerator enumerator = series.GetEnumerator();
|
|
enumerator.MoveNext();
|
|
|
|
object current = enumerator.Current;
|
|
Assert.IsType<TValue>(current);
|
|
Assert.Equal(42.0, ((TValue)current).Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void TSeriesEnumerator_DifferentSeries_NotEqual()
|
|
{
|
|
var series1 = new TSeries();
|
|
series1.Add(100, 1.0);
|
|
|
|
var series2 = new TSeries();
|
|
series2.Add(100, 1.0);
|
|
|
|
var a = series1.GetEnumerator();
|
|
var b = series2.GetEnumerator();
|
|
|
|
// Different underlying list references -> not equal
|
|
Assert.False(a.Equals(b));
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
// NEW TESTS: Constructor defensive copy verification
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Constructor_WithReadOnlyLists_MakesDefensiveCopy()
|
|
{
|
|
var times = new List<long> { 100, 200 };
|
|
var values = new List<double> { 1.0, 2.0 };
|
|
|
|
// Use IReadOnlyList<T> constructor which makes defensive copies
|
|
var series = new TSeries((IReadOnlyList<long>)times, (IReadOnlyList<double>)values);
|
|
|
|
// Mutate original lists
|
|
times.Add(300);
|
|
values.Add(3.0);
|
|
|
|
// Series should NOT reflect the mutation (defensive copy was made)
|
|
Assert.Equal(2, series.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_WithReadOnlyLists_OriginalMutationDoesNotAffectSeries()
|
|
{
|
|
long[] originalTimes = [100, 200, 300];
|
|
double[] originalValues = [1.0, 2.0, 3.0];
|
|
|
|
var series = new TSeries(originalTimes, originalValues);
|
|
|
|
// Mutate original arrays
|
|
originalValues[0] = 999.0;
|
|
|
|
// Series should NOT reflect the mutation (defensive copy was made)
|
|
Assert.Equal(1.0, series[0].Value);
|
|
}
|
|
}
|