453 lines
18 KiB
ObjectPascal
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.
|