DataPoint
This commit is contained in:
+307
-272
@@ -11,9 +11,9 @@ uses
|
||||
type
|
||||
|
||||
[TestFixture]
|
||||
TTestTDataSeries = class(TObject)
|
||||
TTestDataArray = class(TObject)
|
||||
private
|
||||
FSeries: TDataSeries<TAskBidItem>;
|
||||
FArray: IDataArray<TAskBidItem>;
|
||||
procedure SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
||||
public
|
||||
[Setup]
|
||||
@@ -21,6 +21,7 @@ type
|
||||
[Teardown]
|
||||
procedure Teardown;
|
||||
|
||||
// Tests for Setup and basic Add/Count
|
||||
[Test]
|
||||
procedure TestSetupSeriesWithDataVerification;
|
||||
[Test]
|
||||
@@ -30,22 +31,6 @@ type
|
||||
procedure TestAddOrderAssertion;
|
||||
[Test]
|
||||
procedure TestGetItemsIndexing;
|
||||
[Test]
|
||||
procedure TestIndexOfExistingTimeStamp;
|
||||
[Test]
|
||||
procedure TestIndexOfNonExistingBetween;
|
||||
[Test]
|
||||
procedure TestIndexOfBeforeFirstItem;
|
||||
[Test]
|
||||
procedure TestIndexOfAfterLastItem;
|
||||
[Test]
|
||||
procedure TestIndexOfExactOldestItem;
|
||||
[Test]
|
||||
procedure TestIndexOfExactNewestItem;
|
||||
[Test]
|
||||
procedure TestIndexOfEmptySeries;
|
||||
[Test]
|
||||
procedure TestIndexOfSingleItemSeries;
|
||||
|
||||
// Tests for MaxLookback
|
||||
[Test]
|
||||
@@ -55,8 +40,6 @@ type
|
||||
[Test]
|
||||
[IgnoreMemoryLeaks]
|
||||
procedure TestLoopIsSafeWithMaxLookback;
|
||||
[Test]
|
||||
procedure TestIndexOfRespectsMaxLookback;
|
||||
|
||||
// Tests for bulk Add
|
||||
[Test]
|
||||
@@ -73,48 +56,79 @@ type
|
||||
procedure TestBulkAddWithMaxLookback;
|
||||
|
||||
// Tests for TotalCount
|
||||
[Test]
|
||||
procedure TestTotalCountIncrementsCorrectly;
|
||||
[Test]
|
||||
procedure TestTotalCountIgnoresTrimming;
|
||||
[Test]
|
||||
[Test]
|
||||
procedure TestTotalCountIncrementsCorrectly;
|
||||
[Test]
|
||||
procedure TestTotalCountIgnoresTrimming;
|
||||
[Test]
|
||||
procedure TestTotalCountResetsOnClear;
|
||||
end;
|
||||
|
||||
[TestFixture]
|
||||
TTestDataSeries = class(TObject)
|
||||
private
|
||||
FArray: IDataArray<TAskBidItem>;
|
||||
FSeries: TDataSeries<TAskBidItem>;
|
||||
procedure SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
||||
public
|
||||
[Setup]
|
||||
procedure Setup;
|
||||
[Teardown]
|
||||
procedure Teardown;
|
||||
|
||||
// Tests for IndexOf
|
||||
[Test]
|
||||
procedure TestIndexOfExistingTimeStamp;
|
||||
[Test]
|
||||
procedure TestIndexOfNonExistingBetween;
|
||||
[Test]
|
||||
procedure TestIndexOfBeforeFirstItem;
|
||||
[Test]
|
||||
procedure TestIndexOfAfterLastItem;
|
||||
[Test]
|
||||
procedure TestIndexOfExactOldestItem;
|
||||
[Test]
|
||||
procedure TestIndexOfExactNewestItem;
|
||||
[Test]
|
||||
procedure TestIndexOfEmptySeries;
|
||||
[Test]
|
||||
procedure TestIndexOfSingleItemSeries;
|
||||
[Test]
|
||||
procedure TestIndexOfRespectsMaxLookback;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TTestTDataSeries }
|
||||
{ TTestDataArray }
|
||||
|
||||
procedure TTestTDataSeries.Setup;
|
||||
procedure TTestDataArray.Setup;
|
||||
begin
|
||||
// Managed record is implicitly initialized.
|
||||
FArray := TDataSeries<TAskBidItem>.CreateArray(0);
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.Teardown;
|
||||
procedure TTestDataArray.Teardown;
|
||||
begin
|
||||
// Ensure managed record is finalized to prevent false leak reports.
|
||||
FSeries := Default(TDataSeries<TAskBidItem>);
|
||||
FArray := nil;
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
||||
procedure TTestDataArray.SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
||||
var
|
||||
i: Integer;
|
||||
DataPoint: TDataPoint<TAskBidItem>;
|
||||
baseTime: TDateTime;
|
||||
begin
|
||||
FSeries := TDataSeries<TAskBidItem>.Create( MaxLookBack );
|
||||
FArray := TDataSeries<TAskBidItem>.CreateArray(MaxLookBack);
|
||||
baseTime := EncodeDate(2020, 7, 7);
|
||||
|
||||
// Add data points with increasing timestamps.
|
||||
for i := 0 to ItemCount - 1 do
|
||||
begin
|
||||
DataPoint := TDataPoint<TAskBidItem>.Create(baseTime + i, TAskBidItem.Create(i * 1.0, i * 1.0 + 0.1));
|
||||
FSeries.Add(DataPoint);
|
||||
FArray.Add(DataPoint);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Verifies that the test data setup helper works as expected.
|
||||
procedure TTestTDataSeries.TestSetupSeriesWithDataVerification;
|
||||
procedure TTestDataArray.TestSetupSeriesWithDataVerification;
|
||||
var
|
||||
i: Integer;
|
||||
baseTime, expectedTime: TDateTime;
|
||||
@@ -123,20 +137,19 @@ begin
|
||||
SetupSeriesWithData;
|
||||
baseTime := EncodeDate(2020, 7, 7);
|
||||
|
||||
Assert.AreEqual(Int64(10), FSeries.Count, 'Setup should create exactly 10 items');
|
||||
Assert.AreEqual(Int64(10), FArray.Count, 'Setup should create exactly 10 items');
|
||||
|
||||
// Check all items to ensure correct reverse chronological order.
|
||||
for i := 0 to FSeries.Count - 1 do
|
||||
for i := 0 to FArray.Count - 1 do
|
||||
begin
|
||||
expectedTime := baseTime + (9 - i);
|
||||
expectedAsk := Single(9 - i);
|
||||
Assert.AreEqual(expectedTime, FSeries[i].Time, 'Item at logical index should have correct reversed timestamp');
|
||||
Assert.AreEqual(expectedAsk, FSeries[i].Data.Ask, 'Item at logical index should have correct reversed data');
|
||||
Assert.AreEqual(expectedTime, FArray[i].Time, 'Item at logical index should have correct reversed timestamp');
|
||||
Assert.AreEqual(expectedAsk, FArray[i].Data.Ask, 'Item at logical index should have correct reversed data');
|
||||
end;
|
||||
end;
|
||||
|
||||
// Tests adding new items and the resulting count.
|
||||
procedure TTestTDataSeries.TestAddAndCount;
|
||||
procedure TTestDataArray.TestAddAndCount;
|
||||
var
|
||||
DataPoint: TDataPoint<TAskBidItem>;
|
||||
newTime: TDateTime;
|
||||
@@ -144,17 +157,16 @@ begin
|
||||
SetupSeriesWithData;
|
||||
newTime := EncodeDate(2020, 7, 17);
|
||||
|
||||
Assert.AreEqual(Int64(10), FSeries.Count, 'Initial count should be 10');
|
||||
Assert.AreEqual(Int64(10), FArray.Count, 'Initial count should be 10');
|
||||
|
||||
DataPoint := TDataPoint<TAskBidItem>.Create(newTime, TAskBidItem.Create(100.0, 100.1));
|
||||
FSeries.Add(DataPoint);
|
||||
FArray.Add(DataPoint);
|
||||
|
||||
Assert.AreEqual(Int64(11), FSeries.Count, 'Count should be 11 after adding one more');
|
||||
Assert.AreEqual(newTime, FSeries.Items[0].Time, 'Newest item should be at index 0');
|
||||
Assert.AreEqual(Int64(11), FArray.Count, 'Count should be 11 after adding one more');
|
||||
Assert.AreEqual(newTime, FArray.Items[0].Time, 'Newest item should be at index 0');
|
||||
end;
|
||||
|
||||
// Ensures that adding an item with an older timestamp raises an assertion.
|
||||
procedure TTestTDataSeries.TestAddOrderAssertion;
|
||||
procedure TTestDataArray.TestAddOrderAssertion;
|
||||
var
|
||||
olderTime: TDateTime;
|
||||
begin
|
||||
@@ -165,15 +177,14 @@ begin
|
||||
procedure
|
||||
begin
|
||||
var DataPoint := TDataPoint<TAskBidItem>.Create(olderTime, TAskBidItem.Create(0.0, 0.0));
|
||||
FSeries.Add(DataPoint);
|
||||
FArray.Add(DataPoint);
|
||||
end,
|
||||
EAssertionFailed,
|
||||
'Adding item with older timestamp should raise an assert error'
|
||||
);
|
||||
end;
|
||||
|
||||
// Verifies that the logical-to-physical index mapping is correct.
|
||||
procedure TTestTDataSeries.TestGetItemsIndexing;
|
||||
procedure TTestDataArray.TestGetItemsIndexing;
|
||||
var
|
||||
i: Integer;
|
||||
expectedTime: TDateTime;
|
||||
@@ -185,13 +196,241 @@ begin
|
||||
for i := 0 to 9 do
|
||||
begin
|
||||
expectedTime := baseTime + (9 - i);
|
||||
Assert.AreEqual(expectedTime, FSeries.Items[i].Time, 'Item at logical index should have reversed chronological time');
|
||||
Assert.AreEqual(Single(9 - i), FSeries.Items[i].Data.Ask, 'Item data at logical index should match reversed insertion order');
|
||||
Assert.AreEqual(expectedTime, FArray.Items[i].Time, 'Item at logical index should have reversed chronological time');
|
||||
Assert.AreEqual(Single(9 - i), FArray.Items[i].Data.Ask, 'Item data at logical index should match reversed insertion order');
|
||||
end;
|
||||
end;
|
||||
|
||||
// Tests finding an existing item (in this case, the oldest).
|
||||
procedure TTestTDataSeries.TestIndexOfExistingTimeStamp;
|
||||
procedure TTestDataArray.TestCountIsCappedByMaxLookback_NoTrim;
|
||||
begin
|
||||
SetupSeriesWithData(500, 400); // Less than one chunk
|
||||
|
||||
// The public Count must now be the MaxLookback value, even though
|
||||
// no chunk was freed and FCount is still 500 internally.
|
||||
Assert.AreEqual(Int64(400), FArray.Count, 'Public count should be capped by MaxLookback even if no chunk is freed');
|
||||
end;
|
||||
|
||||
procedure TTestDataArray.TestCountIsCappedByMaxLookback_WithTrim;
|
||||
const
|
||||
ITEMS_TO_ADD = 2000;
|
||||
LOOKBACK_LIMIT = 900;
|
||||
begin
|
||||
SetupSeriesWithData(ITEMS_TO_ADD, LOOKBACK_LIMIT);
|
||||
|
||||
// Internally, FCount will be 976 after one chunk is freed.
|
||||
// The public Count should still be capped at the lookback limit.
|
||||
Assert.AreEqual(Int64(LOOKBACK_LIMIT), FArray.Count, 'Public count should be capped by MaxLookback after trimming');
|
||||
end;
|
||||
|
||||
procedure TTestDataArray.TestLoopIsSafeWithMaxLookback;
|
||||
var
|
||||
dummy: TDataPoint<TAskBidItem>;
|
||||
begin
|
||||
SetupSeriesWithData(500, 400);
|
||||
Assert.AreEqual(Int64(400), FArray.Count, 'Pre-condition: Count must be capped at 400');
|
||||
|
||||
// This test proves that a standard loop based on the public Count is safe
|
||||
// and will not access an invalid index.
|
||||
Assert.WillNotRaise(
|
||||
procedure
|
||||
begin
|
||||
for var i := 0 to FArray.Count - 1 do
|
||||
begin
|
||||
dummy := FArray[i];
|
||||
end;
|
||||
end,
|
||||
nil,
|
||||
'Looping up to the public Count should not raise an exception'
|
||||
);
|
||||
|
||||
// Also test the assert when going one item beyond the public count
|
||||
Assert.WillRaise(
|
||||
procedure begin dummy := FArray[400]; end,
|
||||
EAssertionFailed,
|
||||
'Accessing index at the limit of MaxLookback should raise an exception'
|
||||
);
|
||||
end;
|
||||
|
||||
procedure TTestDataArray.TestBulkAddIncreasesCount;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
baseTime, newTime: TDateTime;
|
||||
i: Integer;
|
||||
begin
|
||||
SetupSeriesWithData(10);
|
||||
baseTime := EncodeDate(2020, 7, 7);
|
||||
|
||||
SetLength(newData, 5);
|
||||
for i := 0 to High(newData) do
|
||||
begin
|
||||
newTime := baseTime + 10 + i;
|
||||
newData[i] := TDataPoint<TAskBidItem>.Create(newTime, TAskBidItem.Create(i, i));
|
||||
end;
|
||||
|
||||
FArray.Add(newData);
|
||||
|
||||
Assert.AreEqual(Int64(15), FArray.Count, 'Count should be increased by the number of items in the bulk add');
|
||||
Assert.AreEqual(baseTime + 10 + High(newData), FArray[0].Time, 'Newest item should be the last item from the added array');
|
||||
end;
|
||||
|
||||
procedure TTestDataArray.TestBulkAddChronologicalAssertion_Internal;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
begin
|
||||
SetupSeriesWithData(10);
|
||||
SetLength(newData, 2);
|
||||
newData[0] := TDataPoint<TAskBidItem>.Create(Now + 1, TAskBidItem.Create(1, 1));
|
||||
newData[1] := TDataPoint<TAskBidItem>.Create(Now, TAskBidItem.Create(2, 2)); // Not sorted
|
||||
|
||||
Assert.WillRaise(
|
||||
procedure begin FArray.Add(newData); end,
|
||||
EAssertionFailed,
|
||||
'Bulk Add should fail if the input array is not chronologically sorted'
|
||||
);
|
||||
end;
|
||||
|
||||
procedure TTestDataArray.TestBulkAddChronologicalAssertion_External;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
olderTime: TDateTime;
|
||||
begin
|
||||
SetupSeriesWithData(10); // Newest is baseTime + 9
|
||||
olderTime := EncodeDate(2020, 7, 7) + 8;
|
||||
|
||||
SetLength(newData, 1);
|
||||
newData[0] := TDataPoint<TAskBidItem>.Create(olderTime, TAskBidItem.Create(1, 1));
|
||||
|
||||
Assert.WillRaise(
|
||||
procedure begin FArray.Add(newData); end,
|
||||
EAssertionFailed,
|
||||
'Bulk Add should fail if its first item is older than the series last item'
|
||||
);
|
||||
end;
|
||||
|
||||
procedure TTestDataArray.TestBulkAddSpanningMultipleChunks;
|
||||
const
|
||||
CHUNK_SIZE = 1024;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
lastTimeBeforeAdd, firstNewTime, lastNewTime: TDateTime;
|
||||
i: Integer;
|
||||
begin
|
||||
SetupSeriesWithData(CHUNK_SIZE - 4); // Almost fill the first chunk
|
||||
lastTimeBeforeAdd := FArray[0].Time;
|
||||
|
||||
SetLength(newData, 8); // Add 8 items, which will span the chunk boundary
|
||||
for i := 0 to High(newData) do
|
||||
begin
|
||||
newData[i] := TDataPoint<TAskBidItem>.Create(lastTimeBeforeAdd + 1 + i, TAskBidItem.Create(i, i));
|
||||
end;
|
||||
|
||||
firstNewTime := newData[0].Time;
|
||||
lastNewTime := newData[High(newData)].Time;
|
||||
|
||||
FArray.Add(newData);
|
||||
Assert.AreEqual(Int64(CHUNK_SIZE + 4), FArray.Count, 'Count should be correct after spanning a chunk');
|
||||
Assert.AreEqual(lastNewTime, FArray[0].Time, 'Newest item should be correct');
|
||||
Assert.AreEqual(firstNewTime, FArray[7].Time, 'Oldest of the new items should be at the correct logical index');
|
||||
end;
|
||||
|
||||
procedure TTestDataArray.TestBulkAddWithMaxLookback;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
i: Integer;
|
||||
baseTime: TDateTime;
|
||||
begin
|
||||
SetupSeriesWithData(400, 500);
|
||||
baseTime := FArray[0].Time;
|
||||
|
||||
SetLength(newData, 200);
|
||||
for i := 0 to High(newData) do
|
||||
begin
|
||||
newData[i] := TDataPoint<TAskBidItem>.Create(baseTime + 1 + i, TAskBidItem.Create(i, i));
|
||||
end;
|
||||
|
||||
FArray.Add(newData);
|
||||
|
||||
// Total items would be 600, but MaxLookback is 500.
|
||||
// Trim does not free chunks (100 to remove < 1024).
|
||||
// The public Count must be capped at 500.
|
||||
Assert.AreEqual(Int64(500), FArray.Count, 'Count should be capped by MaxLookback after bulk add');
|
||||
end;
|
||||
|
||||
procedure TTestDataArray.TestTotalCountIncrementsCorrectly;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
i: Integer;
|
||||
begin
|
||||
Assert.AreEqual(Int64(0), FArray.TotalCount, 'TotalCount should be 0 on creation');
|
||||
|
||||
// Test single add
|
||||
FArray.Add(TDataPoint<TAskBidItem>.Create(Now, TAskBidItem.Create(1, 1)));
|
||||
Assert.AreEqual(Int64(1), FArray.TotalCount, 'TotalCount should be 1 after single add');
|
||||
|
||||
// Test bulk add
|
||||
SetLength(newData, 10);
|
||||
for i := 0 to High(newData) do
|
||||
newData[i] := TDataPoint<TAskBidItem>.Create(Now + 1 + i, TAskBidItem.Create(i, i));
|
||||
|
||||
FArray.Add(newData);
|
||||
Assert.AreEqual(Int64(11), FArray.TotalCount, 'TotalCount should be 11 after bulk add');
|
||||
end;
|
||||
|
||||
procedure TTestDataArray.TestTotalCountIgnoresTrimming;
|
||||
begin
|
||||
// Create series with a lookback that will cause trimming
|
||||
SetupSeriesWithData(20, 10);
|
||||
|
||||
// The visible count is capped by MaxLookback
|
||||
Assert.AreEqual(Int64(10), FArray.Count, 'Count should be capped by MaxLookback');
|
||||
// The total count should reflect all items that were ever added
|
||||
Assert.AreEqual(Int64(20), FArray.TotalCount, 'TotalCount must not be affected by trimming');
|
||||
end;
|
||||
|
||||
procedure TTestDataArray.TestTotalCountResetsOnClear;
|
||||
begin
|
||||
SetupSeriesWithData(15);
|
||||
Assert.IsTrue(FArray.TotalCount > 0, 'Pre-condition: TotalCount should be greater than 0');
|
||||
|
||||
FArray.Clear;
|
||||
|
||||
Assert.AreEqual(Int64(0), FArray.TotalCount, 'TotalCount should be 0 after Clear');
|
||||
Assert.AreEqual(Int64(0), FArray.Count, 'Count should be 0 after Clear');
|
||||
end;
|
||||
|
||||
{ TTestDataSeries }
|
||||
|
||||
procedure TTestDataSeries.Setup;
|
||||
begin
|
||||
FArray := TDataSeries<TAskBidItem>.CreateArray(0);
|
||||
FSeries.Create(FArray);
|
||||
end;
|
||||
|
||||
procedure TTestDataSeries.Teardown;
|
||||
begin
|
||||
FSeries := Default(TDataSeries<TAskBidItem>);
|
||||
FArray := nil;
|
||||
end;
|
||||
|
||||
procedure TTestDataSeries.SetupSeriesWithData(ItemCount: Integer = 10; MaxLookBack: Int64 = 0);
|
||||
var
|
||||
i: Integer;
|
||||
DataPoint: TDataPoint<TAskBidItem>;
|
||||
baseTime: TDateTime;
|
||||
begin
|
||||
FArray := TDataSeries<TAskBidItem>.CreateArray(MaxLookBack);
|
||||
FSeries.Create(FArray);
|
||||
baseTime := EncodeDate(2020, 7, 7);
|
||||
|
||||
// Add data points with increasing timestamps.
|
||||
for i := 0 to ItemCount - 1 do
|
||||
begin
|
||||
DataPoint := TDataPoint<TAskBidItem>.Create(baseTime + i, TAskBidItem.Create(i * 1.0, i * 1.0 + 0.1));
|
||||
FArray.Add(DataPoint);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TTestDataSeries.TestIndexOfExistingTimeStamp;
|
||||
var
|
||||
ATimeStamp: TDateTime;
|
||||
expectedIndex: Int64;
|
||||
@@ -207,8 +446,7 @@ begin
|
||||
);
|
||||
end;
|
||||
|
||||
// Tests finding the index for a timestamp that falls between two existing items.
|
||||
procedure TTestTDataSeries.TestIndexOfNonExistingBetween;
|
||||
procedure TTestDataSeries.TestIndexOfNonExistingBetween;
|
||||
var
|
||||
ATimeStamp: TDateTime;
|
||||
expectedIndex: Int64;
|
||||
@@ -224,8 +462,7 @@ begin
|
||||
);
|
||||
end;
|
||||
|
||||
// Tests finding a timestamp that is older than any item in the series.
|
||||
procedure TTestTDataSeries.TestIndexOfBeforeFirstItem;
|
||||
procedure TTestDataSeries.TestIndexOfBeforeFirstItem;
|
||||
var
|
||||
ATimeStamp: TDateTime;
|
||||
baseTime: TDateTime;
|
||||
@@ -237,8 +474,7 @@ begin
|
||||
Assert.AreEqual(Int64(-1), FSeries.IndexOf(ATimeStamp), 'IndexOf for a timestamp before the oldest item should return -1');
|
||||
end;
|
||||
|
||||
// Tests finding a timestamp that is newer than any item in the series.
|
||||
procedure TTestTDataSeries.TestIndexOfAfterLastItem;
|
||||
procedure TTestDataSeries.TestIndexOfAfterLastItem;
|
||||
var
|
||||
ATimeStamp: TDateTime;
|
||||
baseTime: TDateTime;
|
||||
@@ -254,8 +490,7 @@ begin
|
||||
);
|
||||
end;
|
||||
|
||||
// Specifically tests finding the exact oldest item.
|
||||
procedure TTestTDataSeries.TestIndexOfExactOldestItem;
|
||||
procedure TTestDataSeries.TestIndexOfExactOldestItem;
|
||||
var
|
||||
ATimeStamp: TDateTime;
|
||||
begin
|
||||
@@ -265,8 +500,7 @@ begin
|
||||
Assert.AreEqual(Int64(9), FSeries.IndexOf(ATimeStamp), 'IndexOf for the exact oldest timestamp should return the last index');
|
||||
end;
|
||||
|
||||
// Specifically tests finding the exact newest item.
|
||||
procedure TTestTDataSeries.TestIndexOfExactNewestItem;
|
||||
procedure TTestDataSeries.TestIndexOfExactNewestItem;
|
||||
var
|
||||
ATimeStamp: TDateTime;
|
||||
begin
|
||||
@@ -276,24 +510,22 @@ begin
|
||||
Assert.AreEqual(Int64(0), FSeries.IndexOf(ATimeStamp), 'IndexOf for the exact newest timestamp should return index 0');
|
||||
end;
|
||||
|
||||
// Tests IndexOf on an empty series.
|
||||
procedure TTestTDataSeries.TestIndexOfEmptySeries;
|
||||
procedure TTestDataSeries.TestIndexOfEmptySeries;
|
||||
begin
|
||||
FSeries := Default(TDataSeries<TAskBidItem>);
|
||||
// Use default setup with no data
|
||||
Assert.AreEqual(Int64(0), FSeries.Count);
|
||||
Assert.AreEqual(Int64(-1), FSeries.IndexOf(Now), 'IndexOf on empty series should return -1');
|
||||
end;
|
||||
|
||||
// Tests IndexOf on a series with only one item.
|
||||
procedure TTestTDataSeries.TestIndexOfSingleItemSeries;
|
||||
procedure TTestDataSeries.TestIndexOfSingleItemSeries;
|
||||
var
|
||||
DataPoint: TDataPoint<TAskBidItem>;
|
||||
testTime: TDateTime;
|
||||
begin
|
||||
FSeries := Default(TDataSeries<TAskBidItem>);
|
||||
// Use default setup, but add one item
|
||||
testTime := EncodeDate(2025, 1, 1);
|
||||
DataPoint := TDataPoint<TAskBidItem>.Create(testTime, TAskBidItem.Create(1.0, 2.0));
|
||||
FSeries.Add(DataPoint);
|
||||
FArray.Add(DataPoint);
|
||||
|
||||
Assert.AreEqual(Int64(1), FSeries.Count);
|
||||
Assert.AreEqual(Int64(0), FSeries.IndexOf(testTime), 'IndexOf for exact single item should be 0');
|
||||
@@ -301,57 +533,7 @@ begin
|
||||
Assert.AreEqual(Int64(-1), FSeries.IndexOf(testTime - 1), 'IndexOf for time before single item should be -1');
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestCountIsCappedByMaxLookback_NoTrim;
|
||||
begin
|
||||
SetupSeriesWithData(500, 400); // Less than one chunk
|
||||
|
||||
// The public Count must now be the MaxLookback value, even though
|
||||
// no chunk was freed and FCount is still 500 internally.
|
||||
Assert.AreEqual(Int64(400), FSeries.Count, 'Public count should be capped by MaxLookback even if no chunk is freed');
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestCountIsCappedByMaxLookback_WithTrim;
|
||||
const
|
||||
ITEMS_TO_ADD = 2000;
|
||||
LOOKBACK_LIMIT = 900;
|
||||
begin
|
||||
SetupSeriesWithData(ITEMS_TO_ADD, LOOKBACK_LIMIT);
|
||||
|
||||
// Internally, FCount will be 976 after one chunk is freed.
|
||||
// The public Count should still be capped at the lookback limit.
|
||||
Assert.AreEqual(Int64(LOOKBACK_LIMIT), FSeries.Count, 'Public count should be capped by MaxLookback after trimming');
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestLoopIsSafeWithMaxLookback;
|
||||
var
|
||||
dummy: TDataPoint<TAskBidItem>;
|
||||
begin
|
||||
SetupSeriesWithData(500, 400);
|
||||
Assert.AreEqual(Int64(400), FSeries.Count, 'Pre-condition: Count must be capped at 400');
|
||||
|
||||
// This test proves that a standard loop based on the public Count is safe
|
||||
// and will not access an invalid index.
|
||||
Assert.WillNotRaise(
|
||||
procedure
|
||||
begin
|
||||
for var i := 0 to FSeries.Count - 1 do
|
||||
begin
|
||||
dummy := FSeries[i];
|
||||
end;
|
||||
end,
|
||||
nil,
|
||||
'Looping up to the public Count should not raise an exception'
|
||||
);
|
||||
|
||||
// Also test the assert when going one item beyond the public count
|
||||
Assert.WillRaise(
|
||||
procedure begin dummy := FSeries[400]; end,
|
||||
EAssertionFailed,
|
||||
'Accessing index at the limit of MaxLookback should raise an exception'
|
||||
);
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestIndexOfRespectsMaxLookback;
|
||||
procedure TTestDataSeries.TestIndexOfRespectsMaxLookback;
|
||||
var
|
||||
baseTime, searchTime: TDateTime;
|
||||
begin
|
||||
@@ -373,154 +555,7 @@ begin
|
||||
Assert.AreEqual(Int64(399), FSeries.IndexOf(searchTime), 'IndexOf should find the oldest valid item at the edge of MaxLookback');
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestBulkAddIncreasesCount;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
baseTime, newTime: TDateTime;
|
||||
i: Integer;
|
||||
begin
|
||||
SetupSeriesWithData(10);
|
||||
baseTime := EncodeDate(2020, 7, 7);
|
||||
|
||||
SetLength(newData, 5);
|
||||
for i := 0 to High(newData) do
|
||||
begin
|
||||
newTime := baseTime + 10 + i;
|
||||
newData[i] := TDataPoint<TAskBidItem>.Create(newTime, TAskBidItem.Create(i, i));
|
||||
end;
|
||||
|
||||
FSeries.Add(newData);
|
||||
|
||||
Assert.AreEqual(Int64(15), FSeries.Count, 'Count should be increased by the number of items in the bulk add');
|
||||
Assert.AreEqual(baseTime + 10 + High(newData), FSeries.Time[0], 'Newest item should be the last item from the added array');
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestBulkAddChronologicalAssertion_Internal;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
begin
|
||||
SetupSeriesWithData(10);
|
||||
SetLength(newData, 2);
|
||||
newData[0] := TDataPoint<TAskBidItem>.Create(Now + 1, TAskBidItem.Create(1, 1));
|
||||
newData[1] := TDataPoint<TAskBidItem>.Create(Now, TAskBidItem.Create(2, 2)); // Not sorted
|
||||
|
||||
Assert.WillRaise(
|
||||
procedure begin FSeries.Add(newData); end,
|
||||
EAssertionFailed,
|
||||
'Bulk Add should fail if the input array is not chronologically sorted'
|
||||
);
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestBulkAddChronologicalAssertion_External;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
olderTime: TDateTime;
|
||||
begin
|
||||
SetupSeriesWithData(10); // Newest is baseTime + 9
|
||||
olderTime := EncodeDate(2020, 7, 7) + 8;
|
||||
|
||||
SetLength(newData, 1);
|
||||
newData[0] := TDataPoint<TAskBidItem>.Create(olderTime, TAskBidItem.Create(1, 1));
|
||||
|
||||
Assert.WillRaise(
|
||||
procedure begin FSeries.Add(newData); end,
|
||||
EAssertionFailed,
|
||||
'Bulk Add should fail if its first item is older than the series last item'
|
||||
);
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestBulkAddSpanningMultipleChunks;
|
||||
const
|
||||
CHUNK_SIZE = 1024;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
lastTimeBeforeAdd, firstNewTime, lastNewTime: TDateTime;
|
||||
i: Integer;
|
||||
begin
|
||||
SetupSeriesWithData(CHUNK_SIZE - 4); // Almost fill the first chunk
|
||||
lastTimeBeforeAdd := FSeries.Time[0];
|
||||
|
||||
SetLength(newData, 8); // Add 8 items, which will span the chunk boundary
|
||||
for i := 0 to High(newData) do
|
||||
begin
|
||||
newData[i] := TDataPoint<TAskBidItem>.Create(lastTimeBeforeAdd + 1 + i, TAskBidItem.Create(i, i));
|
||||
end;
|
||||
|
||||
firstNewTime := newData[0].Time;
|
||||
lastNewTime := newData[High(newData)].Time;
|
||||
|
||||
FSeries.Add(newData);
|
||||
Assert.AreEqual(Int64(CHUNK_SIZE + 4), FSeries.Count, 'Count should be correct after spanning a chunk');
|
||||
Assert.AreEqual(lastNewTime, FSeries.Time[0], 'Newest item should be correct');
|
||||
Assert.AreEqual(firstNewTime, FSeries.Time[7], 'Oldest of the new items should be at the correct logical index');
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestBulkAddWithMaxLookback;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
i: Integer;
|
||||
baseTime: TDateTime;
|
||||
begin
|
||||
SetupSeriesWithData(400, 500);
|
||||
baseTime := FSeries.Time[0];
|
||||
|
||||
SetLength(newData, 200);
|
||||
for i := 0 to High(newData) do
|
||||
begin
|
||||
newData[i] := TDataPoint<TAskBidItem>.Create(baseTime + 1 + i, TAskBidItem.Create(i, i));
|
||||
end;
|
||||
|
||||
FSeries.Add(newData);
|
||||
|
||||
// Total items would be 600, but MaxLookback is 500.
|
||||
// Trim does not free chunks (100 to remove < 1024).
|
||||
// The public Count must be capped at 500.
|
||||
Assert.AreEqual(Int64(500), FSeries.Count, 'Count should be capped by MaxLookback after bulk add');
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestTotalCountIncrementsCorrectly;
|
||||
var
|
||||
newData: TArray<TDataPoint<TAskBidItem>>;
|
||||
i: Integer;
|
||||
begin
|
||||
FSeries := TDataSeries<TAskBidItem>.Create( 0 );
|
||||
Assert.AreEqual(Int64(0), FSeries.TotalCount, 'TotalCount should be 0 on creation');
|
||||
|
||||
// Test single add
|
||||
FSeries.Add(TDataPoint<TAskBidItem>.Create(Now, TAskBidItem.Create(1,1)));
|
||||
Assert.AreEqual(Int64(1), FSeries.TotalCount, 'TotalCount should be 1 after single add');
|
||||
|
||||
// Test bulk add
|
||||
SetLength(newData, 10);
|
||||
for i := 0 to High(newData) do
|
||||
newData[i] := TDataPoint<TAskBidItem>.Create(Now + 1 + i, TAskBidItem.Create(i,i));
|
||||
|
||||
FSeries.Add(newData);
|
||||
Assert.AreEqual(Int64(11), FSeries.TotalCount, 'TotalCount should be 11 after bulk add');
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestTotalCountIgnoresTrimming;
|
||||
begin
|
||||
// Create series with a lookback that will cause trimming
|
||||
SetupSeriesWithData(20, 10);
|
||||
|
||||
// The visible count is capped by MaxLookback
|
||||
Assert.AreEqual(Int64(10), FSeries.Count, 'Count should be capped by MaxLookback');
|
||||
// The total count should reflect all items that were ever added
|
||||
Assert.AreEqual(Int64(20), FSeries.TotalCount, 'TotalCount must not be affected by trimming');
|
||||
end;
|
||||
|
||||
procedure TTestTDataSeries.TestTotalCountResetsOnClear;
|
||||
begin
|
||||
SetupSeriesWithData(15);
|
||||
Assert.IsTrue(FSeries.TotalCount > 0, 'Pre-condition: TotalCount should be greater than 0');
|
||||
|
||||
FSeries.Clear;
|
||||
|
||||
Assert.AreEqual(Int64(0), FSeries.TotalCount, 'TotalCount should be 0 after Clear');
|
||||
Assert.AreEqual(Int64(0), FSeries.Count, 'Count should be 0 after Clear');
|
||||
end;
|
||||
|
||||
initialization
|
||||
TDUnitX.RegisterTestFixture(TTestTDataSeries);
|
||||
TDUnitX.RegisterTestFixture(TTestDataArray);
|
||||
TDUnitX.RegisterTestFixture(TTestDataSeries);
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user