mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-20 19:48:05 +00:00
docs: remove C# Implementation Considerations sections, clean up temp scripts, reorganize test files
- Remove 'C# Implementation Considerations' sections from 34 indicator .md files - Delete 29 temp PowerShell scripts (_fix_mojibake.ps1, _hex_scan.ps1, etc.) - Move test files into tests/ subdirectories for consistent project structure - Add trader-focused bullet points to indicator documentation
This commit is contained in:
@@ -0,0 +1,541 @@
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class TValueEventArgsTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithValueAndIsNew_SetsPropertiesCorrectly()
|
||||
{
|
||||
var tValue = new TValue(DateTime.UtcNow.Ticks, 123.45);
|
||||
const bool isNew = true;
|
||||
|
||||
var eventArgs = new TValueEventArgs { Value = tValue, IsNew = isNew };
|
||||
|
||||
Assert.Equal(tValue, eventArgs.Value);
|
||||
Assert.True(eventArgs.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_Default_SetsDefaultValues()
|
||||
{
|
||||
var eventArgs = new TValueEventArgs();
|
||||
|
||||
Assert.Equal(default(TValue), eventArgs.Value);
|
||||
Assert.False(eventArgs.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNaNValue_PreservesNaN()
|
||||
{
|
||||
var tValue = new TValue(DateTime.UtcNow.Ticks, double.NaN);
|
||||
|
||||
var eventArgs = new TValueEventArgs { Value = tValue, IsNew = false };
|
||||
|
||||
Assert.True(double.IsNaN(eventArgs.Value.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithInfinityValue_PreservesInfinity()
|
||||
{
|
||||
var tValue = new TValue(DateTime.UtcNow.Ticks, double.PositiveInfinity);
|
||||
|
||||
var eventArgs = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
|
||||
Assert.True(double.IsPositiveInfinity(eventArgs.Value.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_SameValues_ReturnsTrue()
|
||||
{
|
||||
var tValue = new TValue(12345, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
|
||||
Assert.True(args1.Equals(args2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_DifferentValue_ReturnsFalse()
|
||||
{
|
||||
var tValue1 = new TValue(12345, 100.0);
|
||||
var tValue2 = new TValue(12345, 101.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue1, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue2, IsNew = true };
|
||||
|
||||
Assert.False(args1.Equals(args2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_DifferentTime_ReturnsFalse()
|
||||
{
|
||||
var tValue1 = new TValue(12345, 100.0);
|
||||
var tValue2 = new TValue(12346, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue1, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue2, IsNew = true };
|
||||
|
||||
Assert.False(args1.Equals(args2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_DifferentIsNew_ReturnsFalse()
|
||||
{
|
||||
var tValue = new TValue(12345, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue, IsNew = false };
|
||||
|
||||
Assert.False(args1.Equals(args2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_Object_SameTValueEventArgs_ReturnsTrue()
|
||||
{
|
||||
var tValue = new TValue(12345, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
object args2 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
|
||||
Assert.True(args1.Equals(args2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_Object_DifferentType_ReturnsFalse()
|
||||
{
|
||||
var args = new TValueEventArgs { Value = new TValue(12345, 100.0), IsNew = true };
|
||||
object other = "not a TValueEventArgs";
|
||||
|
||||
Assert.False(args.Equals(other));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_Object_Null_ReturnsFalse()
|
||||
{
|
||||
var args = new TValueEventArgs { Value = new TValue(12345, 100.0), IsNew = true };
|
||||
|
||||
Assert.False(args.Equals(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_SameValues_ReturnsSameHashCode()
|
||||
{
|
||||
var tValue = new TValue(12345, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
|
||||
Assert.Equal(args1.GetHashCode(), args2.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_DifferentValues_ReturnsDifferentHashCode()
|
||||
{
|
||||
var tValue1 = new TValue(12345, 100.0);
|
||||
var tValue2 = new TValue(12346, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue1, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue2, IsNew = true };
|
||||
|
||||
Assert.NotEqual(args1.GetHashCode(), args2.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_DifferentIsNew_ReturnsDifferentHashCode()
|
||||
{
|
||||
var tValue = new TValue(12345, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue, IsNew = false };
|
||||
|
||||
Assert.NotEqual(args1.GetHashCode(), args2.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_SameValues_ReturnsTrue()
|
||||
{
|
||||
var tValue = new TValue(12345, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
|
||||
Assert.True(args1 == args2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_DifferentValues_ReturnsFalse()
|
||||
{
|
||||
var tValue1 = new TValue(12345, 100.0);
|
||||
var tValue2 = new TValue(12346, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue1, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue2, IsNew = true };
|
||||
|
||||
Assert.False(args1 == args2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_SameValues_ReturnsFalse()
|
||||
{
|
||||
var tValue = new TValue(12345, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
|
||||
Assert.False(args1 != args2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_DifferentValues_ReturnsTrue()
|
||||
{
|
||||
var tValue1 = new TValue(12345, 100.0);
|
||||
var tValue2 = new TValue(12346, 100.0);
|
||||
var args1 = new TValueEventArgs { Value = tValue1, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue2, IsNew = true };
|
||||
|
||||
Assert.True(args1 != args2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithNaN_BothNaN_ReturnsTrue()
|
||||
{
|
||||
var tValue1 = new TValue(12345, double.NaN);
|
||||
var tValue2 = new TValue(12345, double.NaN);
|
||||
var args1 = new TValueEventArgs { Value = tValue1, IsNew = true };
|
||||
var args2 = new TValueEventArgs { Value = tValue2, IsNew = true };
|
||||
|
||||
Assert.True(args1.Equals(args2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_WithNaN_DoesNotThrow()
|
||||
{
|
||||
var tValue = new TValue(12345, double.NaN);
|
||||
var args = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
|
||||
var hash = args.GetHashCode();
|
||||
|
||||
Assert.True(hash != 0 || hash == 0); // Just verify it doesn't throw
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithZeroTime_Allowed()
|
||||
{
|
||||
var tValue = new TValue(0, 100.0);
|
||||
var args = new TValueEventArgs { Value = tValue, IsNew = false };
|
||||
|
||||
Assert.Equal(0, args.Value.Time);
|
||||
Assert.Equal(100.0, args.Value.Value);
|
||||
Assert.False(args.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNegativeTime_Allowed()
|
||||
{
|
||||
var tValue = new TValue(-12345, 100.0);
|
||||
var args = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
|
||||
Assert.Equal(-12345, args.Value.Time);
|
||||
Assert.Equal(100.0, args.Value.Value);
|
||||
Assert.True(args.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithMaxLongTime_Allowed()
|
||||
{
|
||||
var tValue = new TValue(long.MaxValue, 100.0);
|
||||
var args = new TValueEventArgs { Value = tValue, IsNew = false };
|
||||
|
||||
Assert.Equal(long.MaxValue, args.Value.Time);
|
||||
Assert.Equal(100.0, args.Value.Value);
|
||||
Assert.False(args.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithMaxDoubleValue_Allowed()
|
||||
{
|
||||
var tValue = new TValue(DateTime.UtcNow.Ticks, double.MaxValue);
|
||||
var args = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
|
||||
Assert.Equal(double.MaxValue, args.Value.Value);
|
||||
Assert.True(args.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithMinDoubleValue_Allowed()
|
||||
{
|
||||
var tValue = new TValue(DateTime.UtcNow.Ticks, double.MinValue);
|
||||
var args = new TValueEventArgs { Value = tValue, IsNew = false };
|
||||
|
||||
Assert.Equal(double.MinValue, args.Value.Value);
|
||||
Assert.False(args.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithEpsilonValue_Allowed()
|
||||
{
|
||||
var tValue = new TValue(DateTime.UtcNow.Ticks, double.Epsilon);
|
||||
var args = new TValueEventArgs { Value = tValue, IsNew = true };
|
||||
|
||||
Assert.Equal(double.Epsilon, args.Value.Value);
|
||||
Assert.True(args.IsNew);
|
||||
}
|
||||
}
|
||||
|
||||
public class TValuePublishedHandlerTests
|
||||
{
|
||||
private class MockPublisher : ITValuePublisher
|
||||
{
|
||||
#pragma warning disable CS0067 // Event is never used - intentional for delegate testing
|
||||
public event TValuePublishedHandler? Pub;
|
||||
#pragma warning restore CS0067
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delegate_CanBeAssigned()
|
||||
{
|
||||
TValuePublishedHandler handler = (_, in _) => { };
|
||||
|
||||
Assert.NotNull(handler);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delegate_CanBeInvoked()
|
||||
{
|
||||
bool wasCalled = false;
|
||||
TValuePublishedHandler handler = (object? sender, in TValueEventArgs args) => wasCalled = true;
|
||||
|
||||
var args = new TValueEventArgs { Value = new TValue(12345, 100.0), IsNew = true };
|
||||
handler(null, args);
|
||||
|
||||
Assert.True(wasCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delegate_ReceivesCorrectArguments()
|
||||
{
|
||||
object? receivedSender = null;
|
||||
TValueEventArgs receivedArgs = default;
|
||||
|
||||
TValuePublishedHandler handler = (object? sender, in TValueEventArgs args) =>
|
||||
{
|
||||
receivedSender = sender;
|
||||
receivedArgs = args;
|
||||
};
|
||||
|
||||
var publisher = new MockPublisher();
|
||||
var expectedArgs = new TValueEventArgs { Value = new TValue(12345, 100.0), IsNew = true };
|
||||
|
||||
handler(publisher, expectedArgs);
|
||||
|
||||
Assert.Equal(publisher, receivedSender);
|
||||
Assert.Equal(expectedArgs, receivedArgs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delegate_CanBeNull()
|
||||
{
|
||||
TValuePublishedHandler? handler = null;
|
||||
|
||||
var exception = Record.Exception(() => handler?.Invoke(null, new TValueEventArgs()));
|
||||
Assert.Null(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public class ITValuePublisherTests
|
||||
{
|
||||
private class MockPublisher : ITValuePublisher
|
||||
{
|
||||
public event TValuePublishedHandler? Pub;
|
||||
|
||||
public void RaiseEvent(TValue value, bool isNew = true)
|
||||
{
|
||||
Pub?.Invoke(this, new TValueEventArgs { Value = value, IsNew = isNew });
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interface_CanBeImplemented()
|
||||
{
|
||||
ITValuePublisher publisher = new MockPublisher();
|
||||
|
||||
Assert.NotNull(publisher);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_CanBeSubscribed()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
bool eventRaised = false;
|
||||
|
||||
publisher.Pub += (object? sender, in TValueEventArgs args) => eventRaised = true;
|
||||
|
||||
publisher.RaiseEvent(new TValue(12345, 100.0));
|
||||
|
||||
Assert.True(eventRaised);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_CanBeUnsubscribed()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
bool eventRaised = false;
|
||||
|
||||
TValuePublishedHandler handler = (object? sender, in TValueEventArgs args) => eventRaised = true;
|
||||
publisher.Pub += handler;
|
||||
|
||||
publisher.RaiseEvent(new TValue(12345, 100.0));
|
||||
Assert.True(eventRaised);
|
||||
|
||||
eventRaised = false;
|
||||
publisher.Pub -= handler;
|
||||
|
||||
publisher.RaiseEvent(new TValue(12346, 101.0));
|
||||
Assert.False(eventRaised);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_MultipleSubscribers_AllReceiveEvent()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
bool event1Raised = false;
|
||||
bool event2Raised = false;
|
||||
|
||||
publisher.Pub += (object? sender, in TValueEventArgs args) => event1Raised = true;
|
||||
publisher.Pub += (object? sender, in TValueEventArgs args) => event2Raised = true;
|
||||
|
||||
publisher.RaiseEvent(new TValue(12345, 100.0));
|
||||
|
||||
Assert.True(event1Raised);
|
||||
Assert.True(event2Raised);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_NoSubscribers_DoesNotThrow()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
|
||||
var exception = Record.Exception(() => publisher.RaiseEvent(new TValue(12345, 100.0)));
|
||||
Assert.Null(exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_ReceivesCorrectSender()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
object? receivedSender = null;
|
||||
|
||||
publisher.Pub += (object? sender, in TValueEventArgs args) => receivedSender = sender;
|
||||
|
||||
publisher.RaiseEvent(new TValue(12345, 100.0));
|
||||
|
||||
Assert.Equal(publisher, receivedSender);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_ReceivesCorrectEventArgs()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
TValueEventArgs receivedArgs = default;
|
||||
|
||||
publisher.Pub += (object? sender, in TValueEventArgs args) => receivedArgs = args;
|
||||
|
||||
var expectedValue = new TValue(12345, 100.0);
|
||||
publisher.RaiseEvent(expectedValue, isNew: true);
|
||||
|
||||
Assert.Equal(expectedValue, receivedArgs.Value);
|
||||
Assert.True(receivedArgs.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_IsNew_False_ReceivedCorrectly()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
TValueEventArgs receivedArgs = default;
|
||||
|
||||
publisher.Pub += (object? sender, in TValueEventArgs args) => receivedArgs = args;
|
||||
|
||||
var expectedValue = new TValue(12345, 100.0);
|
||||
publisher.RaiseEvent(expectedValue, isNew: false);
|
||||
|
||||
Assert.Equal(expectedValue, receivedArgs.Value);
|
||||
Assert.False(receivedArgs.IsNew);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_HandlerThrows_ExceptionPropagates()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
|
||||
publisher.Pub += (object? sender, in TValueEventArgs args) =>
|
||||
{
|
||||
throw new InvalidOperationException("Test exception");
|
||||
};
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
publisher.RaiseEvent(new TValue(12345, 100.0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_HandlerWithNaNValue_ReceivesNaN()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
double receivedValue = 0;
|
||||
|
||||
publisher.Pub += (object? sender, in TValueEventArgs args) => receivedValue = args.Value.Value;
|
||||
|
||||
publisher.RaiseEvent(new TValue(12345, double.NaN));
|
||||
|
||||
Assert.True(double.IsNaN(receivedValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_HandlerWithInfinityValue_ReceivesInfinity()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
double receivedValue = 0;
|
||||
|
||||
publisher.Pub += (object? sender, in TValueEventArgs args) => receivedValue = args.Value.Value;
|
||||
|
||||
publisher.RaiseEvent(new TValue(12345, double.PositiveInfinity));
|
||||
|
||||
Assert.True(double.IsPositiveInfinity(receivedValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_MultipleEvents_AllReceived()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
var receivedValues = new List<double>();
|
||||
|
||||
publisher.Pub += (object? sender, in TValueEventArgs args) => receivedValues.Add(args.Value.Value);
|
||||
|
||||
publisher.RaiseEvent(new TValue(12345, 100.0));
|
||||
publisher.RaiseEvent(new TValue(12346, 200.0));
|
||||
publisher.RaiseEvent(new TValue(12347, 300.0));
|
||||
|
||||
Assert.Equal(3, receivedValues.Count);
|
||||
Assert.Equal(100.0, receivedValues[0]);
|
||||
Assert.Equal(200.0, receivedValues[1]);
|
||||
Assert.Equal(300.0, receivedValues[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pub_Event_SubscribeUnsubscribeMultipleTimes_Works()
|
||||
{
|
||||
var publisher = new MockPublisher();
|
||||
int callCount = 0;
|
||||
|
||||
TValuePublishedHandler handler = (object? sender, in TValueEventArgs args) => callCount++;
|
||||
|
||||
// Subscribe multiple times
|
||||
publisher.Pub += handler;
|
||||
publisher.Pub += handler;
|
||||
|
||||
publisher.RaiseEvent(new TValue(12345, 100.0));
|
||||
Assert.Equal(2, callCount); // Called twice
|
||||
|
||||
callCount = 0;
|
||||
// Unsubscribe once
|
||||
publisher.Pub -= handler;
|
||||
|
||||
publisher.RaiseEvent(new TValue(12346, 200.0));
|
||||
Assert.Equal(1, callCount); // Called once
|
||||
|
||||
callCount = 0;
|
||||
// Unsubscribe remaining
|
||||
publisher.Pub -= handler;
|
||||
|
||||
publisher.RaiseEvent(new TValue(12347, 300.0));
|
||||
Assert.Equal(0, callCount); // Not called
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,794 @@
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user