Files
MycLib/Src/Myc.Test.Core.Lazy.pas
T
Michael Schimmel c4d8607d17 TMutable
2025-06-06 01:22:25 +02:00

453 lines
18 KiB
ObjectPascal

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;
procedure Helper_ConsumeInitialPop(const ALazy: TLazy<String>.ILazy; const InitialExpectedValue: string); 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;
procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: TLazy<String>.ILazy; const InitialExpectedValue: string);
var
tempValue: string;
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, 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,
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, 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,
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, 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,
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,
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,
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, 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;
Assert.IsTrue(sourceDirty.State.IsSet, 'SourceDirty should be initially set for this test scenario');
expectedValue := 70;
procExecuted := False;
funcLazy :=
TMycFuncLazy<Integer>.Create(
sourceDirty.State,
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.