Units renamed & Chart refactored

This commit is contained in:
Michael Schimmel
2025-07-14 15:07:36 +02:00
parent 661faba75c
commit d0ad547aa3
20 changed files with 340 additions and 1050 deletions
+1 -1
View File
@@ -164,7 +164,7 @@ uses
System.Generics.Collections,
Myc.Signals,
Myc.Futures,
Myc.Lazy,
Myc.Mutable,
Myc.Trade.DataPoint,
Myc.Aura.Parameter;
@@ -1,4 +1,4 @@
unit Myc.Core.Futures;
unit Myc.Core.Future;
interface
@@ -1,4 +1,4 @@
unit Myc.Core.Lazy;
unit Myc.Core.Mutable;
interface
@@ -7,7 +7,7 @@ uses
System.SysUtils,
System.SyncObjs,
Myc.Signals,
Myc.Lazy;
Myc.Mutable;
type
TMycNullMutable<T> = class(TInterfacedObject, TMutable<T>.IMutable)
@@ -47,7 +47,6 @@ type
protected
function GetChanged: TSignal;
function GetValue: T;
function Exchange(const Value: T): T;
public
constructor Create(const AValue: T);
procedure SetValue(const Value: T);
@@ -58,7 +57,6 @@ type
FWriteable: TWriteable<T>.IWriteable;
FLock: TLightweightMREW;
protected
function Exchange(const Value: T): T;
function GetChanged: TSignal;
function GetValue: T;
public
@@ -118,12 +116,6 @@ begin
FChanged := TEvent.CreateEvent;
end;
function TMycWriteableMutable<T>.Exchange(const Value: T): T;
begin
Result := FValue;
FValue := Value;
end;
function TMycWriteableMutable<T>.GetChanged: TSignal;
begin
Result := FChanged.Signal;
@@ -162,16 +154,6 @@ begin
FWriteable := AWriteable;
end;
function TMycProtectedMutable<T>.Exchange(const Value: T): T;
begin
FLock.BeginWrite;
try
Result := FWriteable.Exchange(Value);
finally
FLock.EndWrite;
end;
end;
function TMycProtectedMutable<T>.GetChanged: TSignal;
begin
Result := FWriteable.Changed;
+1 -1
View File
@@ -8,7 +8,7 @@ uses
System.UITypes,
FMX.Graphics,
Myc.Signals,
Myc.Lazy,
Myc.Mutable,
Myc.Trade.Types,
Myc.Trade.DataArray,
Myc.Trade.DataPoint,
+53 -82
View File
@@ -18,7 +18,7 @@ uses
Myc.Trade.Types,
Myc.Trade.DataPoint,
Myc.Signals,
Myc.Lazy;
Myc.Mutable;
type
TMycChart = class(TStyledControl)
@@ -59,18 +59,14 @@ type
constructor Create(AParent: TPanel);
end;
TAxisLayer = class(TLayer)
TAxisLayer = class abstract(TLayer)
private
FOwner: TMycChart;
protected
function GetOwner: TMycChart; override; final;
// Paint crosshair and other axis related overlays
procedure Paint(
const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
const MousePos: TPointF
); virtual;
procedure Paint(const Canvas: TCanvas; const Viewport: TRectF; ViewStartIndex, ViewCount: Int64; IdxAtMousePos: Integer); virtual;
abstract;
public
constructor Create(AOwner: TMycChart);
end;
@@ -78,12 +74,7 @@ type
TXAxisLayer = class(TAxisLayer)
protected
// Paint crosshair and time caption
procedure Paint(
const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
const MousePos: TPointF
); override;
procedure Paint(const Canvas: TCanvas; const Viewport: TRectF; ViewStartIndex, ViewCount: Int64; IdxAtMousePos: Integer); override;
// This function delivers the text for the caption.
function GetCaption(Idx: Int64): String; virtual; abstract;
end;
@@ -119,6 +110,11 @@ type
property Weight: Single read FWeight write SetWeight;
end;
TDragPoint = record
Point: TPointF;
Idx: Int64;
end;
private
FPanelList: TObjectList<TPanel>;
FXAxisSeries: TMycChart.TXAxisLayer;
@@ -127,8 +123,8 @@ type
FViewStartIndex: Int64;
FViewCount: Int64;
FIsDragging: Boolean;
FDragStartPoint: TPointF;
FMousePos: TPointF;
FMousePos: TDragPoint;
FDragStartPoint: TDragPoint;
FIsMouseInControl: Boolean;
FJumpButtonRect: TRectF;
FJumpButtonHot: Boolean;
@@ -139,6 +135,7 @@ type
FHotResizePanelIndex: Integer;
function GetPanel(Index: Integer): TPanel;
function GetPanelCount: Integer;
function CreateDragPoint(X, Y: Single): TDragPoint;
protected
procedure Paint; override;
procedure DoIdle;
@@ -221,6 +218,15 @@ begin
Repaint;
end;
function TMycChart.CreateDragPoint(X, Y: Single): TDragPoint;
begin
Result.Point.X := X;
Result.Point.Y := Y;
Result.Idx := Round((LocalRect.Right - X) * (FViewCount - 1) / LocalRect.Width);
if (Result.Idx < 0) or (Result.Idx >= FXAxisSeries.Series.Count) then
Result.Idx := -1;
end;
function TMycChart.GetPanel(Index: Integer): TPanel;
begin
Result := FPanelList[Index];
@@ -333,8 +339,12 @@ begin
end;
// Draw crosshair if mouse is in control
if FIsMouseInControl and Assigned(FXAxisSeries) and not FIsDragging and not FIsResizing then
FXAxisSeries.Paint(Self.Canvas, rect, FViewStartIndex, FViewCount, FMousePos);
if FIsMouseInControl and Assigned(FXAxisSeries) and (FMousePos.Idx >= 0 ) then
begin
// Do not draw crosshair if mouse is over the jump button
if not FJumpButtonRect.Contains(FMousePos.Point) then
FXAxisSeries.Paint(Self.Canvas, rect, FViewStartIndex, FViewCount, FViewStartIndex + FMousePos.Idx );
end;
// --- Draw the "jump to latest" button (code unchanged) ---
isButtonVisible := (FViewStartIndex > 0);
@@ -380,6 +390,8 @@ end;
procedure TMycChart.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
inherited MouseDown(Button, Shift, X, Y);
// Check for button press first
if (Button = TMouseButton.mbLeft) and (FViewStartIndex > 0) and FJumpButtonRect.Contains(PointF(X, Y)) then
begin
@@ -393,16 +405,15 @@ begin
begin
FIsResizing := true;
FResizePanelIndex := FHotResizePanelIndex;
FDragStartPoint := TPointF.Create(X, Y);
FDragStartPoint := CreateDragPoint(X, Y);
Capture;
exit;
end;
inherited MouseDown(Button, Shift, X, Y);
if (Button = TMouseButton.mbLeft) then
begin
FIsDragging := true;
FDragStartPoint := TPointF.Create(X, Y);
FDragStartPoint := CreateDragPoint(X, Y);
Capture;
end;
end;
@@ -427,10 +438,7 @@ begin
end;
// Stop panning
if FIsDragging then
begin
FIsDragging := false;
end;
FIsDragging := false;
// Stop button press
if FJumpButtonPressed then
@@ -459,12 +467,14 @@ begin
if not FIsMouseInControl then
FIsMouseInControl := true;
FMousePos := TPointF.Create(X, Y);
FMousePos := CreateDragPoint(X, Y);
// --- Panel Resizing Logic ---
if FIsResizing then
begin
dy := Y - FDragStartPoint.Y;
dy := Y - FDragStartPoint.Point.Y;
if Abs(dx) < 2 then // Threshold to avoid jitter
exit;
var panel1 := Panels[FResizePanelIndex];
var panel2 := Panels[FResizePanelIndex + 1];
@@ -502,7 +512,7 @@ begin
panel2.Weight := (newH2 / (newH1 + newH2)) * twoPanelWeight;
end;
FDragStartPoint.Y := Y; // Update start point for next move
FDragStartPoint := CreateDragPoint( FDragStartPoint.Point.X, Y ); // Update start point for next move
Repaint;
exit; // Do not continue with other mouse move logic
end;
@@ -510,17 +520,17 @@ begin
// --- Chart Panning Logic ---
if FIsDragging then
begin
dx := X - FDragStartPoint.X;
dx := X - FDragStartPoint.Point.X;
if Abs(dx) < 2 then // Threshold to avoid jitter
begin
exit;
end;
if not Assigned(FXAxisSeries) or (FViewCount <= 0) then
exit;
indexDelta := Round(dx / (Self.Width / FViewCount));
FViewStartIndex := FViewStartIndex + indexDelta;
indexDelta := FMousePos.Idx - FDragStartPoint.Idx;
FDragStartPoint.Idx := FDragStartPoint.Idx + indexDelta;
FViewStartIndex := FViewStartIndex - indexDelta;
// Clamp values
maxIndex := FXAxisSeries.Series.Count - FViewCount;
@@ -531,8 +541,6 @@ begin
if (FViewStartIndex > maxIndex) then
FViewStartIndex := maxIndex;
FDragStartPoint.X := X;
FDragStartPoint.Y := Y;
Repaint;
exit;
end;
@@ -574,12 +582,11 @@ end;
procedure TMycChart.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean);
var
mousePos: TPointF;
anchorIndex: Double;
zoomFactor: Double;
newViewCount: Int64;
ratio: Double;
begin
inherited MouseWheel(Shift, WheelDelta, Handled);
Handled := true;
// An X-axis series is required for zooming
@@ -590,11 +597,6 @@ begin
if (xAxisCount = 0) or (Width <= 0) then
exit;
mousePos := ScreenToLocal(Screen.MousePos);
// Calculate the anchor index for a right-to-left axis
anchorIndex := FViewStartIndex + ((Self.Width - mousePos.X) / Self.Width) * FViewCount;
if (WheelDelta > 0) then
zoomFactor := 0.8 // Zoom In
else
@@ -610,15 +612,12 @@ begin
if (newViewCount = FViewCount) then
exit;
// Adjust start index to keep anchor point stable
if (FViewCount > 0) then
ratio := (anchorIndex - FViewStartIndex) / FViewCount
else
ratio := 0;
FViewStartIndex := Round(anchorIndex - (ratio * newViewCount));
FViewCount := newViewCount;
var oldIdx := FViewStartIndex + FMousePos.Idx;
FMousePos := CreateDragPoint( FMousePos.Point.X, FMousePos.Point.Y );
FViewStartIndex := oldIdx - FMousePos.Idx;
// Clamp start index
if (FViewStartIndex < 0) then
FViewStartIndex := 0;
@@ -818,51 +817,23 @@ begin
Result := FOwner;
end;
procedure TMycChart.TAxisLayer.Paint(
const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
const MousePos: TPointF
);
begin
// Do nothing in base class
end;
{ TMycChart.TXAxisLayer }
procedure TMycChart.TXAxisLayer.Paint(
const Canvas: TCanvas;
const Viewport: TRectF;
ViewStartIndex, ViewCount: Int64;
const MousePos: TPointF
);
procedure TMycChart.TXAxisLayer.Paint(const Canvas: TCanvas; const Viewport: TRectF; ViewStartIndex, ViewCount: Int64; IdxAtMousePos:
Integer);
var
idx: Int64;
caption: string;
captionRect: TRectF;
padding: Single;
indexFromMouse: Double;
candleX: Single;
textSize: TRectF;
begin
// Do not draw crosshair if mouse is over the jump button
if Owner.JumpButtonRect.Contains(MousePos) then
exit;
// Do not draw if no data or view is invalid
if (not Assigned(Series)) or (Series.Count = 0) or (ViewCount <= 1) then
exit;
// 1. Calculate the data index from the mouse X position
indexFromMouse := ViewStartIndex + (Viewport.Right - MousePos.X) * (ViewCount - 1) / Viewport.Width;
idx := Round(indexFromMouse);
// Check if the calculated index is valid for the series
if (idx < 0) or (idx >= Series.Count) then
exit;
// Snap X to the candle's center
candleX := Viewport.Right - ((idx - ViewStartIndex) * (ViewCount - 1)) / (ViewCount - 1) * (Viewport.Width / (ViewCount - 1));
candleX := Viewport.Right - ((IdxAtMousePos - ViewStartIndex) * (ViewCount - 1)) / (ViewCount - 1) * (Viewport.Width / (ViewCount - 1));
// 2. Draw the vertical line at the snapped position
Canvas.Stroke.Kind := TBrushKind.Solid;
@@ -871,7 +842,7 @@ begin
Canvas.DrawLine(PointF(candleX, Viewport.Top), PointF(candleX, Viewport.Bottom), 1.0);
// 3. Get the caption for the index
caption := GetCaption(idx);
caption := GetCaption(IdxAtMousePos);
if caption.IsEmpty then
exit;
+1 -1
View File
@@ -86,7 +86,7 @@ type
implementation
uses
Myc.Core.Futures;
Myc.Core.Future;
constructor TFuture<T>.Create(const AFuture: IFuture);
begin
+2 -5
View File
@@ -1,4 +1,4 @@
unit Myc.Lazy;
unit Myc.Mutable;
interface
@@ -48,7 +48,6 @@ type
type
IWriteable = interface(TMutable<T>.IMutable)
procedure SetValue(const Value: T);
function Exchange(const Value: T): T;
end;
{$REGION 'private'}
@@ -67,7 +66,6 @@ type
class function CreateWriteable(const Init: T): TWriteable<T>; overload; static;
function Protect: TWriteable<T>;
function AsMutable: TMutable<T>;
property Value: T read GetValue write SetValue;
@@ -89,7 +87,7 @@ type
implementation
uses
Myc.Core.Lazy;
Myc.Core.Mutable;
{ TMutable<T> }
@@ -201,7 +199,6 @@ constructor TLazy<T>.Create(const AMutable: TMutable<T>);
begin
FMutable := AMutable;
FChanged := TFlag.CreateFlag;
FChanged.Notify;
FChangeState := AMutable.Changed.Subscribe(FChanged);
end;
-439
View File
@@ -1,439 +0,0 @@
unit Myc.Test.Core.Lazy;
interface
uses
System.SysUtils,
DUnitX.TestFramework,
Myc.Signals, // For IMycState, TState, IMycDirty
Myc.Lazy, // For IMycLazy<T>
Myc.Core.Lazy; // Unit to be tested
type
[TestFixture]
TTestMycCoreLazy = class(TObject)
private
procedure Helper_ConsumeInitialPop(const ALazy: TLazy<Integer>.ILazy; InitialExpectedValue: Integer); overload;
public
[Setup]
procedure Setup;
[TearDown]
procedure TearDown;
// Tests for TMycNullLazy<T>
[Test]
procedure TestNullLazy_GetChanged_IsAlwaysNullState;
[Test]
procedure TestNullLazy_Pop_ReturnsDefaultIntegerAndTrue;
[Test]
procedure TestNullLazy_Pop_ReturnsDefaultStringAndTrue;
[Test]
procedure TestNullLazy_Pop_ReturnsDefaultInterfaceAndTrue;
// Tests for TMycFuncLazy<T> reflecting "IsSet is true by design after creation"
[Test]
procedure TestFuncLazy_GetChanged_IsAlwaysTrueAfterCreation;
[Test]
procedure TestFuncLazy_FirstPop_AlwaysEvaluatesAndResetsChanged;
// Tests for behavior after the initial "changed" state is consumed
[Test]
procedure TestFuncLazy_AfterFirstPop_IfNoSourceChange_GetChangedIsFalse;
[Test]
procedure TestFuncLazy_AfterFirstPop_IfNoSourceChange_PopReturnsFalse_ValueUndefined;
[Test]
procedure TestFuncLazy_AfterSourceChange_GetChanged_IsSet;
[Test]
procedure TestFuncLazy_AfterSourceChange_Pop_ReturnsTrueAndProcResult_ResetsChanged;
[Test]
procedure TestFuncLazy_Pop_Twice_SourceUnchanged_SecondPopReturnsFalse_ValueUndefined;
// This test is now significantly changed to reflect TMycDirty's notification behavior
[Test]
procedure TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy;
[Test]
procedure TestFuncLazy_Destroy_UnsubscribesFromSource;
[Test]
procedure TestFuncLazy_SourceIsInitiallySet_PopBehavesCorrectly;
end;
implementation
uses
System.Rtti,
Myc.Core.Signals;
{ TTestMycCoreLazy Helper Methods }
procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: TLazy<Integer>.ILazy; InitialExpectedValue: Integer);
var
tempValue: Integer;
popResult: Boolean;
begin
Assert.IsTrue(ALazy.GetChanged.IsSet, 'Changed.IsSet should be true before consuming initial pop');
popResult := ALazy.Pop(tempValue);
Assert.IsTrue(popResult, 'Consuming initial Pop should return true');
Assert.AreEqual(InitialExpectedValue, tempValue, 'Value from initial Pop is unexpected');
Assert.IsFalse(ALazy.GetChanged.IsSet, 'Changed.IsSet should be false after consuming initial pop');
end;
{ TTestMycCoreLazy Test Methods }
procedure TTestMycCoreLazy.Setup;
begin
end;
procedure TTestMycCoreLazy.TearDown;
begin
end;
// == Tests for TMycNullLazy<T> ==
procedure TTestMycCoreLazy.TestNullLazy_GetChanged_IsAlwaysNullState;
var
nullLazy: TLazy<Integer>.ILazy;
changedState: TState.IState;
begin
nullLazy := TMycNullLazy<Integer>.Create as TLazy<Integer>.ILazy;
Assert.IsNotNull(nullLazy, 'TMycNullLazy<Integer> instance should not be nil');
changedState := nullLazy.GetChanged;
Assert.AreSame(TState.Null, changedState, 'TMycNullLazy.GetChanged should return TState.Null');
Assert.IsTrue(changedState.IsSet, 'TState.Null should always be set');
end;
procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultIntegerAndTrue;
var
nullLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
begin
nullLazy := TMycNullLazy<Integer>.Create as TLazy<Integer>.ILazy;
Assert.IsNotNull(nullLazy, 'TMycNullLazy<Integer> instance should not be nil');
value := 123;
result := nullLazy.Pop(value);
Assert.IsTrue(result, 'TMycNullLazy.Pop should return true');
Assert.AreEqual(Default(Integer), value, 'Value should be Default(Integer) after Pop');
end;
procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultStringAndTrue;
var
nullLazy: TLazy<String>.ILazy;
value: string;
result: Boolean;
begin
nullLazy := TMycNullLazy<string>.Create as TLazy<String>.ILazy;
Assert.IsNotNull(nullLazy, 'TMycNullLazy<string> instance should not be nil');
value := 'test';
result := nullLazy.Pop(value);
Assert.IsTrue(result, 'TMycNullLazy.Pop should return true');
Assert.AreEqual(Default(string), value, 'Value should be Default(string) after Pop');
end;
procedure TTestMycCoreLazy.TestNullLazy_Pop_ReturnsDefaultInterfaceAndTrue;
var
nullLazy: TLazy<TState.IState>.ILazy;
value: TState.IState;
result: Boolean;
begin
nullLazy := TMycNullLazy<TState.IState>.Create as TLazy<TState.IState>.ILazy;
Assert.IsNotNull(nullLazy, 'TMycNullLazy<TState.IState> instance should not be nil');
value := TState.Null;
result := nullLazy.Pop(value);
Assert.IsTrue(result, 'TMycNullLazy.Pop should return true');
Assert.IsNull(value, 'Value should be nil for an interface type after Pop from NullLazy');
end;
// == Tests for TMycFuncLazy<T> - Initial State by Design ==
procedure TTestMycCoreLazy.TestFuncLazy_GetChanged_IsAlwaysTrueAfterCreation;
var
funcLazy: TLazy<Integer>.ILazy;
changedState: TState.IState;
sourceDirty: TFlag.IFlag;
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State.Signal, function: Integer begin Result := 10; end);
Assert.IsNotNull(funcLazy, 'TMycFuncLazy<Integer> instance should not be nil');
changedState := funcLazy.GetChanged;
Assert.IsNotNull(changedState, 'funcLazy.GetChanged should return a valid TState.IState');
Assert.IsTrue(changedState.IsSet, 'By design, funcLazy.GetChanged.IsSet should be true immediately after creation');
end;
procedure TTestMycCoreLazy.TestFuncLazy_FirstPop_AlwaysEvaluatesAndResetsChanged;
var
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
sourceDirty: TFlag.IFlag;
procExecuted: Boolean;
expectedValue: Integer;
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
procExecuted := False;
expectedValue := 50;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State.Signal,
function: Integer
begin
procExecuted := True;
Result := expectedValue;
end
);
Assert.IsNotNull(funcLazy, 'TMycFuncLazy<Integer> instance should not be nil');
Assert.IsTrue(funcLazy.GetChanged.IsSet, 'Changed.IsSet should be true before the first Pop by design');
value := 0;
result := funcLazy.Pop(value);
Assert.IsTrue(result, 'The first Pop should return true by design, indicating evaluation');
Assert.IsTrue(procExecuted, 'FProc should have been executed on the first Pop');
Assert.AreEqual(expectedValue, value, 'Value should be the result from FProc after the first Pop');
Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed.IsSet should be false after the first Pop, as Pop resets it');
end;
// == Tests for TMycFuncLazy<T> - Behavior After Initial Pop ==
procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_GetChangedIsFalse;
var
funcLazy: TLazy<Integer>.ILazy;
sourceDirty: TFlag.IFlag;
initialProcValue: Integer;
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
initialProcValue := 33;
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State.Signal, function: Integer begin Result := initialProcValue; end);
Helper_ConsumeInitialPop(funcLazy, initialProcValue);
Assert.IsFalse(funcLazy.GetChanged.IsSet, 'After initial Pop and no source change, GetChanged.IsSet should be false');
end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterFirstPop_IfNoSourceChange_PopReturnsFalse_ValueUndefined;
var
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
sourceDirty: TFlag.IFlag;
initialProcValue: Integer;
procExecutedCount: Integer;
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
initialProcValue := 44;
procExecutedCount := 0;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State.Signal,
function: Integer
begin
Inc(procExecutedCount);
Result := initialProcValue;
end
);
Helper_ConsumeInitialPop(funcLazy, initialProcValue);
Assert.AreEqual(1, procExecutedCount, 'Proc should have executed once for initial pop');
result := funcLazy.Pop(value);
Assert.IsFalse(result, 'Second Pop (after initial, no source change) should return false');
Assert.AreEqual(1, procExecutedCount, 'Proc should not have executed again');
end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_GetChanged_IsSet;
var
funcLazy: TLazy<Integer>.ILazy;
changedState: TState.IState;
sourceDirty: TFlag.IFlag;
initialProcValue: Integer;
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
initialProcValue := 20;
funcLazy := TMycFuncLazy<Integer>.Create(sourceDirty.State.Signal, function: Integer begin Result := initialProcValue; end);
Helper_ConsumeInitialPop(funcLazy, initialProcValue);
sourceDirty.Notify;
changedState := funcLazy.GetChanged;
Assert.IsTrue(changedState.IsSet, 'After source change (post-initial pop), funcLazy.GetChanged.IsSet should be true');
end;
procedure TTestMycCoreLazy.TestFuncLazy_AfterSourceChange_Pop_ReturnsTrueAndProcResult_ResetsChanged;
var
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
sourceDirty: TFlag.IFlag;
procCallCount: Integer;
currentExpectedValue: Integer;
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
procCallCount := 0;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State.Signal,
function: Integer
begin
Inc(procCallCount);
if procCallCount = 1 then
Result := 30
else
Result := 300 + procCallCount;
end
);
currentExpectedValue := 30;
Helper_ConsumeInitialPop(funcLazy, currentExpectedValue);
Assert.AreEqual(1, procCallCount, 'Proc executed for initial Pop');
sourceDirty.Notify;
Assert.IsTrue(funcLazy.GetChanged.IsSet, 'Changed state should be true after source notification');
value := 0;
result := funcLazy.Pop(value);
currentExpectedValue := 300 + 2;
Assert.IsTrue(result, 'Pop should return true after source changed');
Assert.AreEqual(2, procCallCount, 'Proc should have been executed again');
Assert.AreEqual(currentExpectedValue, value, 'Value should be the new result of FProc');
Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after Pop');
end;
procedure TTestMycCoreLazy.TestFuncLazy_Pop_Twice_SourceUnchanged_SecondPopReturnsFalse_ValueUndefined;
var
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
resultPop1, resultPop2: Boolean;
sourceDirty: TFlag.IFlag;
procCallCount: Integer;
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
procCallCount := 0;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State.Signal,
function: Integer
begin
Inc(procCallCount);
Result := 40;
end
);
resultPop1 := funcLazy.Pop(value);
Assert.IsTrue(resultPop1, 'First Pop should return true by design');
Assert.AreEqual(40, value, 'Value from first Pop');
Assert.AreEqual(1, procCallCount, 'Proc called for first Pop');
Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after first Pop');
resultPop2 := funcLazy.Pop(value);
Assert.IsFalse(resultPop2, 'Second Pop should return false as state was reset and not changed again by source');
Assert.AreEqual(1, procCallCount, 'Proc should not be called for second Pop if no source change');
end;
// TestFuncLazy_SourceChanges_Pop_SourceChangesAgain_PopAgain has been RENAMED and RESTRUCTURED
// to TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy
procedure TTestMycCoreLazy.TestFuncLazy_SourceInteraction_NotifyOnSetSourceDoesNotRetriggerLazy;
var
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
sourceDirty: TFlag.IFlag;
procCallCount: Integer;
initialValue, firstSourceChangeValue: Integer;
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset; // sourceDirty.IsSet is FALSE
procCallCount := 0;
initialValue := 51;
firstSourceChangeValue := 52;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State.Signal,
function: Integer
begin
Inc(procCallCount);
if procCallCount = 1 then
Result := initialValue // For initial pop
else if procCallCount = 2 then
Result := firstSourceChangeValue // For pop after first effective source change
else
Result := 999; // Should not be reached in this specific test logic
end
);
// 1. Initial Pop (consumes "by design" changed state)
Assert.IsTrue(funcLazy.GetChanged.IsSet, 'Changed state should be true before first Pop (by design)');
result := funcLazy.Pop(value); // procCallCount becomes 1
Assert.IsTrue(result, 'First Pop should return true');
Assert.AreEqual(initialValue, value, 'Value from first Pop');
Assert.AreEqual(1, procCallCount, 'Proc called for initial Pop');
Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after first Pop');
// 2. First effective source change (sourceDirty: false -> true)
sourceDirty.Notify; // sourceDirty.IsSet becomes TRUE, notifies funcLazy.FChanged, funcLazy.GetChanged.IsSet becomes TRUE
Assert.IsTrue(sourceDirty.State.IsSet, 'sourceDirty should be set after first Notify');
Assert.IsTrue(funcLazy.GetChanged.IsSet, 'Changed state should be true after first effective source notification');
result := funcLazy.Pop(value); // procCallCount becomes 2
Assert.IsTrue(result, 'Pop after first effective source change should return true');
Assert.AreEqual(firstSourceChangeValue, value, 'Value from Pop after first effective source change');
Assert.AreEqual(2, procCallCount, 'Proc called again');
Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after second Pop');
// 3. Notify sourceDirty again (sourceDirty is already TRUE)
Assert.IsTrue(sourceDirty.State.IsSet, 'sourceDirty is still set before second Notify attempt');
sourceDirty.Notify; // Since sourceDirty is already set, this does NOT notify funcLazy.FChanged.
// funcLazy.GetChanged.IsSet remains FALSE.
Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should REMAIN false after Notify on an already-set source');
// 4. Attempt to Pop again
result := funcLazy.Pop(value); // procCallCount should remain 2
Assert.IsFalse(result, 'Pop after Notify on an already-set source should return false');
Assert.AreEqual(2, procCallCount, 'Proc should NOT have been called again');
// Value of 'value' is undefined here and not checked.
end;
procedure TTestMycCoreLazy.TestFuncLazy_Destroy_UnsubscribesFromSource;
var
funcLazyObj: TMycFuncLazy<Integer>;
sourceDirty: TFlag.IFlag;
tempVal: Integer;
begin
sourceDirty := TFlag.CreateFlag;
sourceDirty.Reset;
funcLazyObj := TMycFuncLazy<Integer>.Create(sourceDirty.State.Signal, function: Integer begin Result := 1; end);
Assert.IsTrue(funcLazyObj.Pop(tempVal), 'Initial Pop should succeed');
funcLazyObj.Destroy;
Assert.WillNotRaise(
procedure begin sourceDirty.Notify; end,
nil,
'Destroy test assumes TMycSubscription.Unsubscribe works. Verified by no crash on source notify post-destroy.'
);
sourceDirty := nil;
end;
procedure TTestMycCoreLazy.TestFuncLazy_SourceIsInitiallySet_PopBehavesCorrectly;
var
funcLazy: TLazy<Integer>.ILazy;
value: Integer;
result: Boolean;
sourceDirty: TFlag.IFlag;
expectedValue: Integer;
procExecuted: Boolean;
begin
sourceDirty := TFlag.CreateFlag(true);
Assert.IsTrue(sourceDirty.State.IsSet, 'SourceDirty should be initially set for this test scenario');
expectedValue := 70;
procExecuted := False;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State.Signal,
function: Integer
begin
procExecuted := True;
Result := expectedValue;
end
);
Assert.IsNotNull(funcLazy, 'TMycFuncLazy<Integer> instance should not be nil');
Assert.IsTrue(funcLazy.GetChanged.IsSet, 'funcLazy.GetChanged.IsSet should be true after creation (by design)');
value := 0;
result := funcLazy.Pop(value);
Assert.IsTrue(result, 'First Pop should return true');
Assert.IsTrue(procExecuted, 'FProc should have been executed on first Pop');
Assert.AreEqual(expectedValue, value, 'Value should be the result of FProc');
Assert.IsFalse(funcLazy.GetChanged.IsSet, 'Changed state should be false after the first Pop');
end;
initialization
TDUnitX.RegisterTestFixture(TTestMycCoreLazy);
end.
-453
View File
@@ -1,453 +0,0 @@
unit Myc.Test.Lazy;
interface
uses
System.SysUtils,
DUnitX.TestFramework,
Myc.Signals, // For IMycState, TState, IMycDirty
Myc.Lazy; // The unit under test
type
[TestFixture]
TTestMyLazy = class(TObject)
private
FChangingSignal: TFlag.IFlag; // Used as the 'Changing' state for functional lazy objects
// Helper to consume the initial pop, which is always expected to succeed
// for a TLazy wrapping a functional lazy object due to "Changed.IsSet initially true" design.
procedure ConsumeInitialPop(var ALazyRec: TLazy<Integer>; ExpectedInitialValue: Integer; const MsgPrefix: string);
public
[Setup]
procedure Setup;
[TearDown]
procedure TearDown;
// Tests for TLazy<T>.Create(nil) - Null Object Pattern
[Test]
procedure TestCreateWithNil_Changed_IsAlwaysTrue;
[Test]
procedure TestCreateWithNil_Pop_ReturnsTrueAndDefaultInteger;
[Test]
procedure TestCreateWithNil_Pop_ReturnsTrueAndDefaultString;
// Tests for TLazy<T>.Construct (creates a functional lazy object)
[Test]
procedure TestConstruct_InitialChanged_IsAlwaysTrue;
[Test]
procedure TestConstruct_FirstPop_SucceedsAndResetsChanged;
[Test]
procedure TestConstruct_StateInteraction_SignalTriggersChanged;
[Test]
procedure TestConstruct_StateInteraction_PopResetsChangedAfterSignal;
[Test]
procedure TestConstruct_StateInteraction_NotifyOnAlreadySetSource_DoesNotRetrigger;
[Test]
procedure TestConstruct_Destruction_UnsubscribesAndNoCrashOnSourceNotify;
// Tests for TLazy<T>.Create with a pre-existing (non-nil) IMycLazy
[Test]
procedure TestCreateWithExistingLazy_DelegatesChangedCorrectly;
[Test]
procedure TestCreateWithExistingLazy_DelegatesPopCorrectly;
// Tests for TLazy<T> implicit operators
[Test]
procedure TestImplicitOperator_FromInterfaceToRecord;
[Test]
procedure TestImplicitOperator_FromRecordToInterface;
// Tests for TLazy<T>.Pop specific behaviors (Res undefined)
[Test]
procedure TestPop_AfterInitialAndNoSignal_ReturnsFalseAndResUndefined;
end;
implementation
// No direct uses of Myc.Core.* units here
{ TTestMyLazy }
procedure TTestMyLazy.Setup;
begin
// Create a common signal source for tests that need it.
// TState.CreateDirty is from Myc.Signals.pas (interface part)
// Its implementation might rely on Myc.Core.Signals, but that's an indirect usage.
FChangingSignal := TFlag.CreateFlag;
FChangingSignal.Reset; // Start with a clean (not set) signal for predictable test starts
end;
procedure TTestMyLazy.TearDown;
begin
FChangingSignal := nil; // Release the common signal source
end;
procedure TTestMyLazy.ConsumeInitialPop(var ALazyRec: TLazy<Integer>; ExpectedInitialValue: Integer; const MsgPrefix: string);
var
val: Integer;
popResult: Boolean;
begin
Assert.IsTrue(ALazyRec.Changed.IsSet, MsgPrefix + ': Changed.IsSet should be true before initial Pop');
popResult := ALazyRec.Pop(val);
Assert.IsTrue(popResult, MsgPrefix + ': Initial Pop should return true');
Assert.AreEqual(ExpectedInitialValue, val, MsgPrefix + ': Value from initial Pop mismatch');
Assert.IsFalse(ALazyRec.Changed.IsSet, MsgPrefix + ': Changed.IsSet should be false after initial Pop');
end;
// == Tests for TLazy<T>.Create(nil) - Null Object Pattern ==
procedure TTestMyLazy.TestCreateWithNil_Changed_IsAlwaysTrue;
var
lazyRec: TLazy<Integer>;
begin
lazyRec := TLazy<Integer>.Create(nil); // This uses the internal FNull (TMycNullLazy)
Assert.IsTrue(lazyRec.Changed.IsSet, 'For TLazy created with nil, Changed.IsSet should be true (TMycNullLazy behavior)');
// Second check to ensure it's consistently true
Assert.IsTrue(lazyRec.Changed.IsSet, 'For TLazy created with nil, Changed.IsSet should remain true');
end;
procedure TTestMyLazy.TestCreateWithNil_Pop_ReturnsTrueAndDefaultInteger;
var
lazyRec: TLazy<Integer>;
val: Integer;
popResult: Boolean;
begin
lazyRec := TLazy<Integer>.Create(nil);
val := 12345; // Pre-assign to check if Pop overwrites it with Default
popResult := lazyRec.Pop(val);
Assert.IsTrue(popResult, 'Pop on TLazy created with nil should return true');
Assert.AreEqual(Default(Integer), val, 'Pop on TLazy created with nil should set Res to Default(Integer)');
end;
procedure TTestMyLazy.TestCreateWithNil_Pop_ReturnsTrueAndDefaultString;
var
lazyRec: TLazy<string>;
val: string;
popResult: Boolean;
begin
lazyRec := TLazy<string>.Create(nil);
val := 'test'; // Pre-assign
popResult := lazyRec.Pop(val);
Assert.IsTrue(popResult, 'Pop on TLazy created with nil (string) should return true');
Assert.AreEqual(Default(string), val, 'Pop on TLazy created with nil (string) should set Res to Default(string)');
end;
// == Tests for TLazy<T>.Construct static method ==
procedure TTestMyLazy.TestConstruct_InitialChanged_IsAlwaysTrue;
var
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
procExecuted: Boolean;
begin
procExecuted := False;
// FChangingSignal is reset in Setup
lazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State.Signal,
function: Integer
begin
procExecuted := True;
Result := 10;
end
);
Assert.IsNotNull(lazyIntf, 'TLazy.Construct should return a valid interface');
lazyRec := lazyIntf; // Implicit conversion
Assert.IsTrue(lazyRec.Changed.IsSet, 'Constructed lazy object: Initial Changed.IsSet should be true by design');
Assert.IsFalse(procExecuted, 'Proc should not have been executed by Construct or by checking Changed state');
end;
procedure TTestMyLazy.TestConstruct_FirstPop_SucceedsAndResetsChanged;
var
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
procExecuted: Boolean;
expectedValue: Integer;
begin
procExecuted := False;
expectedValue := 20;
lazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State.Signal,
function: Integer
begin
procExecuted := True;
Result := expectedValue;
end
);
lazyRec := lazyIntf;
ConsumeInitialPop(lazyRec, expectedValue, 'TestConstruct_FirstPop');
Assert.IsTrue(procExecuted, 'Proc should have been executed by the initial Pop');
end;
procedure TTestMyLazy.TestConstruct_StateInteraction_SignalTriggersChanged;
var
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
expectedValue: Integer;
begin
expectedValue := 30;
lazyIntf := TLazy<Integer>.Construct(FChangingSignal.State.Signal, function: Integer begin Result := expectedValue; end);
lazyRec := lazyIntf;
ConsumeInitialPop(lazyRec, expectedValue, 'TestConstruct_StateInteraction_SignalTriggersChanged (Initial)');
Assert.IsFalse(lazyRec.Changed.IsSet, 'After initial Pop, Changed.IsSet should be false');
FChangingSignal.Notify; // Trigger the source signal
Assert.IsTrue(lazyRec.Changed.IsSet, 'After source signal Notify, Changed.IsSet should become true');
end;
procedure TTestMyLazy.TestConstruct_StateInteraction_PopResetsChangedAfterSignal;
var
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
val: Integer;
popResult: Boolean;
procCallCount: Integer;
begin
procCallCount := 0;
lazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State.Signal,
function: Integer
begin
Inc(procCallCount);
Result := 100 + procCallCount; // Value changes per call
end
);
lazyRec := lazyIntf;
ConsumeInitialPop(lazyRec, 101, 'TestConstruct_StateInteraction_PopResetsChangedAfterSignal (Initial)'); // procCallCount = 1
FChangingSignal.Notify;
Assert.IsTrue(lazyRec.Changed.IsSet, 'Changed.IsSet should be true after signal');
popResult := lazyRec.Pop(val); // procCallCount = 2
Assert.IsTrue(popResult, 'Pop after signal should return true');
Assert.AreEqual(102, val, 'Value from Pop after signal mismatch');
Assert.AreEqual(2, procCallCount, 'Proc call count after second pop mismatch');
Assert.IsFalse(lazyRec.Changed.IsSet, 'Changed.IsSet should be false after Pop following signal');
end;
procedure TTestMyLazy.TestConstruct_StateInteraction_NotifyOnAlreadySetSource_DoesNotRetrigger;
var
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
val: Integer;
popResult: Boolean;
procCallCount: Integer;
begin
procCallCount := 0;
lazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State.Signal,
function: Integer
begin
Inc(procCallCount);
Result := 200 + procCallCount;
end
);
lazyRec := lazyIntf;
// 1. Initial Pop
ConsumeInitialPop(lazyRec, 201, 'TestConstruct_NotifyOnAlreadySetSource (Initial)'); // procCallCount = 1
Assert.IsFalse(FChangingSignal.State.IsSet, 'Source signal FChangingSignal should still be false (was reset in Setup)');
// 2. Trigger source, make it set, Pop
FChangingSignal.Notify; // FChangingSignal.IsSet becomes TRUE
Assert.IsTrue(lazyRec.Changed.IsSet, 'Lazy state should be true after FChangingSignal.Notify');
popResult := lazyRec.Pop(val); // procCallCount = 2
Assert.IsTrue(popResult);
Assert.AreEqual(202, val);
Assert.IsFalse(lazyRec.Changed.IsSet, 'Lazy state should be false after second Pop');
// 3. Notify FChangingSignal again. It's already set.
// This should NOT re-notify subscribers (like the lazy object's internal trigger)
// because TMycDirty only notifies on a false -> true transition.
Assert.IsTrue(FChangingSignal.State.IsSet, 'FChangingSignal should still be true before redundant Notify');
FChangingSignal.Notify;
Assert.IsFalse(lazyRec.Changed.IsSet, 'Lazy state should REMAIN false after Notify on an already-set source');
// 4. Attempt to Pop again
popResult := lazyRec.Pop(val); // procCallCount should remain 2
Assert.IsFalse(popResult, 'Pop after Notify on an already-set source should return false');
Assert.AreEqual(2, procCallCount, 'Proc should not have been called for this Pop');
end;
procedure TTestMyLazy.TestConstruct_Destruction_UnsubscribesAndNoCrashOnSourceNotify;
var
lazyIntf: TLazy<Integer>.ILazy;
localChangingSignal: TFlag.IFlag; // Use a local signal for this test to control its lifetime
begin
localChangingSignal := TFlag.CreateFlag;
localChangingSignal.Reset;
lazyIntf := TLazy<Integer>.Construct(localChangingSignal.State.Signal, function: Integer begin Result := 1; end);
Assert.IsNotNull(lazyIntf, 'Constructed lazy interface should not be nil');
// Simulate usage and release of the lazy object
var lazyRec: TLazy<Integer> := lazyIntf; // Wrap for initial pop
ConsumeInitialPop(lazyRec, 1, 'TestConstruct_Destruction (Initial)');
lazyIntf := nil; // Release the ILazy interface. ARC should destroy the TMycFuncLazy object.
// This should trigger its destructor, which should unsubscribe from localChangingSignal.
Assert.WillNotRaise(
procedure
begin
localChangingSignal.Notify; // Notify the source AFTER the lazy object is supposed to be gone.
end,
nil, // Default: any exception is a failure
'Notifying source after lazy object is freed should not crash, indicating unsubscription.'
);
localChangingSignal := nil; // Clean up the local signal itself.
end;
// == Tests for TLazy<T>.Create with a pre-existing (non-nil) ILazy ==
procedure TTestMyLazy.TestCreateWithExistingLazy_DelegatesChangedCorrectly;
var
originalLazyIntf: TLazy<Integer>.ILazy;
wrappedLazyRec: TLazy<Integer>;
expectedValue: Integer;
begin
expectedValue := 60;
originalLazyIntf := TLazy<Integer>.Construct(FChangingSignal.State.Signal, function: Integer begin Result := expectedValue; end);
// originalLazyIntf.Changed.IsSet is true by design
wrappedLazyRec := TLazy<Integer>.Create(originalLazyIntf);
Assert.IsTrue(wrappedLazyRec.Changed.IsSet, 'Wrapped lazy: Changed.IsSet should reflect original (initially true)');
ConsumeInitialPop(wrappedLazyRec, expectedValue, 'TestCreateWithExistingLazy_DelegatesChangedCorrectly (Initial)');
// Now wrappedLazyRec.Changed.IsSet is false, and so should originalLazyIntf.Changed.IsSet
Assert.IsFalse(originalLazyIntf.Changed.IsSet, 'Original lazy: Changed.IsSet should also be false after wrapped Pop');
FChangingSignal.Notify;
Assert.IsTrue(wrappedLazyRec.Changed.IsSet, 'Wrapped lazy: Changed.IsSet should be true after source signal');
Assert.IsTrue(originalLazyIntf.Changed.IsSet, 'Original lazy: Changed.IsSet should also be true after source signal');
end;
procedure TTestMyLazy.TestCreateWithExistingLazy_DelegatesPopCorrectly;
var
originalLazyIntf: TLazy<Integer>.ILazy;
wrappedLazyRec: TLazy<Integer>;
val: Integer;
popResult: Boolean;
procCallCount: Integer;
begin
procCallCount := 0;
originalLazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State.Signal,
function: Integer
begin
Inc(procCallCount);
Result := 70 + procCallCount;
end
);
wrappedLazyRec := TLazy<Integer>.Create(originalLazyIntf);
// First Pop via wrapper (initial pop)
ConsumeInitialPop(wrappedLazyRec, 71, 'TestCreateWithExistingLazy_DelegatesPopCorrectly (Initial)'); // procCallCount = 1
Assert.AreEqual(1, procCallCount, 'Proc call count after wrapped initial Pop');
// Second Pop via wrapper (no source change yet)
popResult := wrappedLazyRec.Pop(val);
Assert.IsFalse(popResult, 'Second Pop via wrapper (no source change) should return false');
Assert.AreEqual(1, procCallCount, 'Proc call count should not change');
// Trigger source, Pop via wrapper
FChangingSignal.Notify;
Assert.IsTrue(wrappedLazyRec.Changed.IsSet, 'Wrapped lazy: Changed.IsSet true after source signal');
popResult := wrappedLazyRec.Pop(val); // procCallCount = 2
Assert.IsTrue(popResult, 'Pop via wrapper after source signal should return true');
Assert.AreEqual(72, val, 'Value from Pop via wrapper after signal');
Assert.AreEqual(2, procCallCount, 'Proc call count after signal and Pop');
Assert.IsFalse(wrappedLazyRec.Changed.IsSet, 'Wrapped lazy: Changed.IsSet false after Pop');
end;
// == Tests for TLazy<T> implicit operators ==
procedure TTestMyLazy.TestImplicitOperator_FromInterfaceToRecord;
var
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
expectedValue: Integer;
begin
expectedValue := 80;
lazyIntf := TLazy<Integer>.Construct(FChangingSignal.State.Signal, function: Integer begin Result := expectedValue; end);
Assert.IsNotNull(lazyIntf, 'Interface should be assigned');
lazyRec := lazyIntf; // Implicit conversion: ILazy<T> to TLazy<T>
// Verify by using the record
Assert.IsTrue(lazyRec.Changed.IsSet, 'Record (from intf): Initial Changed.IsSet should be true');
ConsumeInitialPop(lazyRec, expectedValue, 'TestImplicitOperator_FromInterfaceToRecord');
end;
procedure TTestMyLazy.TestImplicitOperator_FromRecordToInterface;
var
lazyIntfFromConstruct: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
lazyIntfFromRecord: TLazy<Integer>.ILazy;
val: Integer;
expectedValue: Integer;
begin
expectedValue := 90;
lazyIntfFromConstruct := TLazy<Integer>.Construct(FChangingSignal.State.Signal, function: Integer begin Result := expectedValue; end);
lazyRec.Create(lazyIntfFromConstruct); // Explicitly create record
lazyIntfFromRecord := lazyRec; // Implicit conversion: TLazy<T> to ILazy<T>
// Verify by using the converted interface
Assert.AreSame(lazyIntfFromConstruct, lazyIntfFromRecord, 'Converted interface should be the same as the original wrapped one');
Assert.IsTrue(lazyIntfFromRecord.Changed.IsSet, 'Interface (from rec): Initial Changed.IsSet should be true');
var popResult := lazyIntfFromRecord.Pop(val); // This also tests if the interface is functional
Assert.IsTrue(popResult);
Assert.AreEqual(expectedValue, val);
Assert.IsFalse(lazyIntfFromRecord.Changed.IsSet);
end;
// == Tests for TLazy<T>.Pop specific behaviors (Res undefined) ==
procedure TTestMyLazy.TestPop_AfterInitialAndNoSignal_ReturnsFalseAndResUndefined;
var
lazyIntf: TLazy<Integer>.ILazy;
lazyRec: TLazy<Integer>;
val: Integer; // Value will not be checked as Pop returns false
popResult: Boolean;
procExecuted: Boolean;
begin
procExecuted := False;
lazyIntf :=
TLazy<Integer>.Construct(
FChangingSignal.State.Signal,
function: Integer
begin
procExecuted := True;
Result := 100;
end
);
lazyRec := lazyIntf;
ConsumeInitialPop(lazyRec, 100, 'TestPop_AfterInitialAndNoSignal (Initial)');
Assert.IsTrue(procExecuted, 'Proc should have run for initial pop');
procExecuted := False; // Reset for next check
// FChangingSignal has not been notified again
Assert.IsFalse(lazyRec.Changed.IsSet, 'Changed.IsSet must be false before this Pop attempt');
popResult := lazyRec.Pop(val);
Assert.IsFalse(popResult, 'Pop when Changed.IsSet is false should return false');
Assert.IsFalse(procExecuted, 'Proc should NOT have run as Pop returned false');
// Do NOT check 'val' as its content is undefined when Pop returns false.
end;
initialization
TDUnitX.RegisterTestFixture(TTestMyLazy);
end.
+1
View File
@@ -6,6 +6,7 @@ uses
System.SysUtils,
System.Classes,
DUnitX.TestFramework,
Myc.Trade.Types,
Myc.Trade.DataPoint;
type
+2 -1
View File
@@ -7,8 +7,9 @@ uses
System.Generics.Collections,
DUnitX.TestFramework,
Myc.Signals,
Myc.Lazy,
Myc.Mutable,
Myc.Futures,
Myc.Trade.Types,
Myc.Trade.DataPoint,
Myc.Trade.DataStream;
+1 -1
View File
@@ -4,7 +4,7 @@ interface
uses
Myc.Signals,
Myc.Lazy,
Myc.Mutable,
Myc.Trade.DataPoint,
Myc.Trade.DataStream;
+1 -1
View File
@@ -25,7 +25,7 @@ uses
System.IOUtils,
Myc.Futures,
Myc.Signals,
Myc.Lazy,
Myc.Mutable,
Myc.Trade.DataPoint,
Myc.Core.FileCache;