514 lines
21 KiB
ObjectPascal
514 lines
21 KiB
ObjectPascal
unit Myc.Test.Signals.Latch;
|
|
|
|
interface
|
|
|
|
uses
|
|
DUnitX.TestFramework,
|
|
System.SysUtils,
|
|
Myc.Signals; // Unit under test, using TMycLatch static members
|
|
|
|
type
|
|
// Helper class to mock a subscriber.
|
|
TMockSubscriber = class(TInterfacedObject, TSignal.ISubscriber)
|
|
public
|
|
NotifyCount: Integer;
|
|
NotifyShouldReturn: Boolean; // Determines what this mock's Notify method returns
|
|
WasCalled: Boolean;
|
|
|
|
constructor Create(AShouldReturn: Boolean = True); // Parameter name AShouldReturn for clarity
|
|
function Notify: Boolean; // TSignal.ISubscriber implementation.
|
|
end;
|
|
|
|
[TestFixture]
|
|
TTestMycLatch = class(TObject)
|
|
public
|
|
[Setup]
|
|
procedure Setup;
|
|
[TearDown]
|
|
procedure TearDown;
|
|
|
|
// --- Category 1: Basic Counter and State Functionality (Parameterized) ---
|
|
[TestCase('InitialPositive', '1,False')]
|
|
[TestCase('InitialZero_ReturnsNullLatch', '0,True')]
|
|
[TestCase('InitialNegative_ReturnsNullLatch', '-1,True')]
|
|
[TestCase('InitialLargePositive', '5,False')]
|
|
procedure TestCreate_InitialState(InitialCount: Integer; ExpectedIsSet: Boolean);
|
|
|
|
[TestCase('DecrementPositiveToZero', '1,False,True')] // InitialCount, ExpectedReturnFromLatchNotify, ExpectedIsSetAfterNotify
|
|
[TestCase('DecrementPositiveToPositive', '2,True,False')]
|
|
[TestCase('DecrementZeroToNegative', '0,False,True')] // Latch.Notify on count 0 returns false (0 > 0 is false)
|
|
[TestCase('DecrementNegativeToNegative', '-1,False,True')] // Latch.Notify on count -1 returns false (-1 > 0 is false)
|
|
procedure TestNotify_ReturnValueAndState_AfterOneNotify(InitialCount: Integer; ExpectedReturn: Boolean; ExpectedIsSet: Boolean);
|
|
|
|
[Test]
|
|
procedure TestNotify_DecrementsCounter_StateChanges;
|
|
[Test]
|
|
procedure TestNotify_MultipleNotifies_CounterBecomesNegativeAndStaysSet;
|
|
|
|
// --- Category 2: Notification to Subscribers ---
|
|
[Test]
|
|
procedure TestSubscriber_Single_IsNotifiedWhenLatchSet;
|
|
[Test]
|
|
procedure TestSubscriber_Multiple_AreNotifiedWhenLatchSet;
|
|
[Test]
|
|
procedure TestSubscriber_NotNotified_BeforeLatchSet;
|
|
[Test]
|
|
procedure TestSubscriber_NotifiedOnlyOnce_AfterLatchSetAndUnadvised; // Clarified name
|
|
[Test]
|
|
procedure TestSubscribe_ToAlreadySetLatch_NotifiesImmediately;
|
|
|
|
// --- Category 3: Chaining and Cascading Latches ---
|
|
[Test]
|
|
procedure TestChaining_A_Notifies_B;
|
|
[Test]
|
|
procedure TestCascade_A_Notifies_B_Notifies_C;
|
|
[Test]
|
|
procedure TestChaining_B_NeedsMultipleNotifiesFromA_WithSubscriberOnB;
|
|
|
|
// --- Category 4: Special Cases ---
|
|
[Test]
|
|
procedure TestInitiallySetLatch_NotifiesNewSubscriberImmediately; // Using CreateLatch(0)
|
|
|
|
[Test]
|
|
procedure TestUnsubscribe_SubscriberNotNotified;
|
|
|
|
// --- Category 5: Counter Integrity / State for Later Subscribers ---
|
|
[Test]
|
|
procedure TestMultipleTriggersNoSubscriber_StateCorrectForLaterSubscriber;
|
|
|
|
// --- Category 6: UnadviseAll Behavior on Latch Set ---
|
|
[Test]
|
|
procedure TestLatchSet_ClearsSubscribers_NoFurtherNotification;
|
|
[Test]
|
|
procedure TestLatchSet_SubscriptionFinalize_IsSafePostUnadviseAll;
|
|
[Test]
|
|
procedure TestLatchSet_NewSubscriberToSetLatch_NotifiedImmediatelyButNotPersistedForLatchNotify;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TMockSubscriber }
|
|
|
|
constructor TMockSubscriber.Create(AShouldReturn: Boolean = True);
|
|
begin
|
|
inherited Create;
|
|
NotifyCount := 0;
|
|
NotifyShouldReturn := AShouldReturn;
|
|
WasCalled := False;
|
|
end;
|
|
|
|
function TMockSubscriber.Notify: Boolean;
|
|
begin
|
|
Inc(NotifyCount);
|
|
WasCalled := True;
|
|
Result := NotifyShouldReturn; // This mock subscriber returns what it's configured to.
|
|
end;
|
|
|
|
{ TTestMycLatch }
|
|
|
|
procedure TTestMycLatch.Setup;
|
|
begin
|
|
// Per-test setup code can go here (if any).
|
|
end;
|
|
|
|
procedure TTestMycLatch.TearDown;
|
|
begin
|
|
// Per-test teardown code can go here (if any).
|
|
end;
|
|
|
|
// --- Category 1: Basic Counter and State Functionality (Parameterized) ---
|
|
|
|
procedure TTestMycLatch.TestCreate_InitialState(InitialCount: Integer; ExpectedIsSet: Boolean);
|
|
var
|
|
latch: TLatch.ILatch;
|
|
begin
|
|
// TMycLatch.CreateLatch returns TMycLatch.Null if InitialCount <= 0
|
|
latch := TLatch.CreateLatch(InitialCount);
|
|
Assert.AreEqual(ExpectedIsSet, latch.State.IsSet, 'Latch initial IsSet state mismatch for count ' + IntToStr(InitialCount) + '.');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestNotify_ReturnValueAndState_AfterOneNotify(
|
|
InitialCount: Integer;
|
|
ExpectedReturn: Boolean;
|
|
ExpectedIsSet: Boolean
|
|
);
|
|
var
|
|
latch: TLatch.ILatch;
|
|
returnedValueFromNotify: Boolean;
|
|
begin
|
|
latch := TLatch.CreateLatch(InitialCount);
|
|
returnedValueFromNotify := latch.Notify; // This is TLatch.ILatch (as TSignal.ISubscriber).Notify
|
|
|
|
Assert.AreEqual(
|
|
ExpectedReturn,
|
|
returnedValueFromNotify,
|
|
'Unexpected return value from Latch.Notify call for initial count ' + IntToStr(InitialCount) + '.'
|
|
);
|
|
Assert.AreEqual(
|
|
ExpectedIsSet,
|
|
latch.State.IsSet,
|
|
'Unexpected IsSet state after Latch.Notify call for initial count ' + IntToStr(InitialCount) + '.'
|
|
);
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestNotify_DecrementsCounter_StateChanges;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
begin
|
|
latch := TLatch.CreateLatch(1);
|
|
Assert.IsFalse(latch.State.IsSet, 'Latch should initially not be set (Count=1).');
|
|
latch.Notify; // Count becomes 0
|
|
Assert.IsTrue(latch.State.IsSet, 'Latch should be set after Notify (Count became 0).');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestNotify_MultipleNotifies_CounterBecomesNegativeAndStaysSet;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
begin
|
|
latch := TLatch.CreateLatch(1);
|
|
latch.Notify; // Count becomes 0, IsSet = True
|
|
Assert.IsTrue(latch.State.IsSet, 'Latch should be set after first Notify.');
|
|
latch.Notify; // Count becomes -1, IsSet should remain True
|
|
Assert.IsTrue(latch.State.IsSet, 'Latch should remain set after second Notify.');
|
|
end;
|
|
|
|
// --- Category 2: Notification to Subscribers ---
|
|
|
|
procedure TTestMycLatch.TestSubscriber_Single_IsNotifiedWhenLatchSet;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
mockSub: TMockSubscriber;
|
|
subscription: TSignal.TSubscription;
|
|
begin
|
|
latch := TLatch.CreateLatch(1);
|
|
mockSub := TMockSubscriber.Create;
|
|
subscription := latch.State.Signal.Subscribe(mockSub); // Subscribing to the Latch's state
|
|
|
|
Assert.IsFalse(latch.State.IsSet, 'Latch should initially not be set.');
|
|
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not have been notified yet (before latch set).');
|
|
|
|
latch.Notify; // Latch becomes set, notifies subscribers
|
|
|
|
Assert.IsTrue(latch.State.IsSet, 'Latch should be set after its Notify is called.');
|
|
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should have been notified once when latch becomes set.');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestSubscriber_Multiple_AreNotifiedWhenLatchSet;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
mockSub1, mockSub2, mockSub3: TMockSubscriber;
|
|
sub1, sub2, sub3: TSignal.TSubscription; // Keep subscriptions in scope
|
|
begin
|
|
latch := TLatch.CreateLatch(1);
|
|
mockSub1 := TMockSubscriber.Create;
|
|
mockSub2 := TMockSubscriber.Create;
|
|
mockSub3 := TMockSubscriber.Create;
|
|
|
|
sub1 := latch.State.Signal.Subscribe(mockSub1);
|
|
sub2 := latch.State.Signal.Subscribe(mockSub2);
|
|
sub3 := latch.State.Signal.Subscribe(mockSub3);
|
|
|
|
latch.Notify; // Latch becomes set
|
|
|
|
Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 notification count mismatch.');
|
|
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 notification count mismatch.');
|
|
Assert.AreEqual(1, mockSub3.NotifyCount, 'MockSub3 notification count mismatch.');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestSubscriber_NotNotified_BeforeLatchSet;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
mockSub: TMockSubscriber;
|
|
subscription: TSignal.TSubscription;
|
|
begin
|
|
latch := TLatch.CreateLatch(2); // Count = 2
|
|
mockSub := TMockSubscriber.Create;
|
|
subscription := latch.State.Signal.Subscribe(mockSub);
|
|
|
|
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not be notified upon subscription if latch is not set.');
|
|
latch.Notify; // Count becomes 1, still not set
|
|
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should still not be notified as latch is not yet set (Count=1).');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestSubscriber_NotifiedOnlyOnce_AfterLatchSetAndUnadvised;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
mockSub: TMockSubscriber;
|
|
subscription: TSignal.TSubscription;
|
|
begin
|
|
latch := TLatch.CreateLatch(1);
|
|
mockSub := TMockSubscriber.Create;
|
|
subscription := latch.State.Signal.Subscribe(mockSub);
|
|
|
|
latch.Notify; // Sets latch, notifies MockSub. Subscribers are then unadvised by UnadviseAll.
|
|
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub notify count should be 1 after latch set.');
|
|
|
|
latch.Notify; // Call Notify on latch again (FCount becomes more negative).
|
|
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub notify count should remain 1 (not notified again as it was unadvised).');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestSubscribe_ToAlreadySetLatch_NotifiesImmediately;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
mockSub1, mockSub2: TMockSubscriber;
|
|
subscription1, subscription2: TSignal.TSubscription;
|
|
begin
|
|
latch := TLatch.CreateLatch(1); // Create with count 1
|
|
mockSub1 := TMockSubscriber.Create;
|
|
subscription1 := latch.State.Signal.Subscribe(mockSub1); // Subscribe before set
|
|
|
|
latch.Notify; // Latch becomes set. mockSub1 is notified.
|
|
|
|
Assert.IsTrue(latch.State.IsSet, 'Latch should be set after initial trigger.');
|
|
Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 should have been notified once.');
|
|
|
|
// Attempt to subscribe mockSub2 after the latch is already set.
|
|
mockSub2 := TMockSubscriber.Create;
|
|
subscription2 :=
|
|
latch.State.Signal.Subscribe(mockSub2); // TMycLatch.Subscribe (via TMycAbstractFlag) notifies immediately if already set.
|
|
|
|
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 should be notified immediately upon subscribing to an already set latch.');
|
|
|
|
latch.Notify; // Call Notify on latch again (FCount becomes more negative).
|
|
Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 notify count should remain 1 (was unadvised).');
|
|
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 notify count should remain 1 (was only notified on subscribe, not added to list).');
|
|
end;
|
|
|
|
// --- Category 3: Chaining and Cascading Latches ---
|
|
|
|
procedure TTestMycLatch.TestChaining_A_Notifies_B;
|
|
var
|
|
latchA, latchB: TLatch.ILatch;
|
|
mockSubForB: TMockSubscriber;
|
|
subHandle_B_listens_A, subHandle_Mock_listens_B: TSignal.TSubscription;
|
|
begin
|
|
latchA := TLatch.CreateLatch(1);
|
|
latchB := TLatch.CreateLatch(1); // latchB is an TSignal.ISubscriber
|
|
mockSubForB := TMockSubscriber.Create;
|
|
|
|
subHandle_Mock_listens_B := latchB.State.Signal.Subscribe(mockSubForB);
|
|
// LatchA's state is an IMycSignal. LatchB (as TSignal.ISubscriber) subscribes to it.
|
|
subHandle_B_listens_A := latchA.State.Signal.Subscribe(latchB);
|
|
|
|
Assert.IsFalse(latchA.State.IsSet, 'LatchA initially not set.');
|
|
Assert.IsFalse(latchB.State.IsSet, 'LatchB initially not set.');
|
|
Assert.AreEqual(0, mockSubForB.NotifyCount, 'MockSubForB initially not notified.');
|
|
|
|
latchA.Notify; // Call Notify on LatchA (as TSignal.ISubscriber)
|
|
|
|
Assert.IsTrue(latchA.State.IsSet, 'LatchA should be set after notify.');
|
|
Assert.IsTrue(latchB.State.IsSet, 'LatchB should be set after being notified by LatchA.');
|
|
Assert.AreEqual(1, mockSubForB.NotifyCount, 'MockSubForB should have been notified by LatchB.');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestCascade_A_Notifies_B_Notifies_C;
|
|
var
|
|
latchA, latchB, latchC: TLatch.ILatch;
|
|
mockSubForC: TMockSubscriber;
|
|
sub_Mock_C, sub_C_B, sub_B_A: TSignal.TSubscription;
|
|
begin
|
|
latchA := TLatch.CreateLatch(1);
|
|
latchB := TLatch.CreateLatch(1);
|
|
latchC := TLatch.CreateLatch(1);
|
|
mockSubForC := TMockSubscriber.Create;
|
|
|
|
sub_Mock_C := latchC.State.Signal.Subscribe(mockSubForC);
|
|
sub_C_B := latchB.State.Signal.Subscribe(latchC); // LatchC subscribes to LatchB's state
|
|
sub_B_A := latchA.State.Signal.Subscribe(latchB); // LatchB subscribes to LatchA's state
|
|
|
|
latchA.Notify; // Call Notify on LatchA
|
|
|
|
Assert.IsTrue(latchA.State.IsSet, 'LatchA state mismatch in cascade.');
|
|
Assert.IsTrue(latchB.State.IsSet, 'LatchB state mismatch in cascade.');
|
|
Assert.IsTrue(latchC.State.IsSet, 'LatchC state mismatch in cascade.');
|
|
Assert.AreEqual(1, mockSubForC.NotifyCount, 'MockSubForC notification count mismatch in cascade.');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestChaining_B_NeedsMultipleNotifiesFromA_WithSubscriberOnB;
|
|
var
|
|
latchA, latchB: TLatch.ILatch;
|
|
mockSubForB: TMockSubscriber;
|
|
sub_Mock_B, sub_B_A: TSignal.TSubscription;
|
|
begin
|
|
// LatchA needs 2 notifies to become set. LatchB needs 1 notify to become set.
|
|
// LatchB subscribes to LatchA. So LatchB will be notified once LatchA becomes set.
|
|
latchA := TLatch.CreateLatch(2);
|
|
latchB := TLatch.CreateLatch(1);
|
|
mockSubForB := TMockSubscriber.Create;
|
|
|
|
sub_Mock_B := latchB.State.Signal.Subscribe(mockSubForB);
|
|
sub_B_A := latchA.State.Signal.Subscribe(latchB);
|
|
|
|
latchA.Notify; // First notify for LatchA (count becomes 1)
|
|
Assert.IsFalse(latchA.State.IsSet, 'LatchA should not be set after first notify of two.');
|
|
Assert.IsFalse(latchB.State.IsSet, 'LatchB should not have been triggered yet by LatchA.');
|
|
Assert.AreEqual(0, mockSubForB.NotifyCount, 'MockSubForB should not have been notified yet.');
|
|
|
|
latchA.Notify; // Second notify for LatchA (count becomes 0, LatchA becomes set)
|
|
Assert.IsTrue(latchA.State.IsSet, 'LatchA should be set after second notify.');
|
|
// When LatchA becomes set, it notifies its subscribers (LatchB). LatchB.Notify is called.
|
|
Assert.IsTrue(latchB.State.IsSet, 'LatchB should now be set by LatchA.');
|
|
Assert.AreEqual(1, mockSubForB.NotifyCount, 'MockSubForB should have been notified by LatchB.');
|
|
end;
|
|
|
|
// --- Category 4: Special Cases ---
|
|
|
|
procedure TTestMycLatch.TestInitiallySetLatch_NotifiesNewSubscriberImmediately;
|
|
var
|
|
latch: TLatch.ILatch; // Will be TMycLatch.Null
|
|
mockSub: TMockSubscriber;
|
|
subscription: TSignal.TSubscription;
|
|
begin
|
|
latch := TLatch.CreateLatch(0); // Returns TMycLatch.Null which is set.
|
|
mockSub := TMockSubscriber.Create;
|
|
|
|
subscription := latch.State.Signal.Subscribe(mockSub); // TMycLatch.Subscribe notifies immediately if already set.
|
|
|
|
Assert.IsTrue(latch.State.IsSet, 'Latch IsSet property mismatch (should be true for Null latch).');
|
|
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should be notified immediately upon subscribing to an initially set latch.');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestUnsubscribe_SubscriberNotNotified;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
mockSub: TMockSubscriber;
|
|
subscription: TSignal.TSubscription;
|
|
begin
|
|
latch := TLatch.CreateLatch(1);
|
|
mockSub := TMockSubscriber.Create;
|
|
|
|
subscription := latch.State.Signal.Subscribe(mockSub);
|
|
subscription.Unsubscribe; // Explicitly call Unsubscribe on the record
|
|
|
|
latch.Notify; // Latch becomes set
|
|
|
|
Assert.AreEqual(0, mockSub.NotifyCount, 'Unsubscribed MockSub should not be notified.');
|
|
end;
|
|
|
|
// --- Category 5: Counter Integrity / State for Later Subscribers ---
|
|
|
|
procedure TTestMycLatch.TestMultipleTriggersNoSubscriber_StateCorrectForLaterSubscriber;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
mockSub: TMockSubscriber;
|
|
subscription: TSignal.TSubscription;
|
|
begin
|
|
latch := TLatch.CreateLatch(3);
|
|
|
|
latch.Notify; // Count -> 2
|
|
latch.Notify; // Count -> 1
|
|
Assert.IsFalse(latch.State.IsSet, 'Latch should not be set yet after partial notifies.');
|
|
|
|
mockSub := TMockSubscriber.Create;
|
|
subscription := latch.State.Signal.Subscribe(mockSub);
|
|
Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not be notified on subscribe if latch not yet set.');
|
|
|
|
latch.Notify; // Count -> 0, Latch becomes set and notifies mockSub
|
|
Assert.IsTrue(latch.State.IsSet, 'Latch should now be set.');
|
|
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should have been notified when latch becomes set.');
|
|
end;
|
|
|
|
// --- Category 6: UnadviseAll Behavior on Latch Set ---
|
|
|
|
procedure TTestMycLatch.TestLatchSet_ClearsSubscribers_NoFurtherNotification;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
mockSub1, mockSub2: TMockSubscriber;
|
|
subscription1, subscription2: TSignal.TSubscription;
|
|
begin
|
|
latch := TLatch.CreateLatch(1);
|
|
mockSub1 := TMockSubscriber.Create;
|
|
mockSub2 := TMockSubscriber.Create;
|
|
|
|
subscription1 := latch.State.Signal.Subscribe(mockSub1);
|
|
subscription2 := latch.State.Signal.Subscribe(mockSub2);
|
|
|
|
Assert.AreEqual(0, mockSub1.NotifyCount, 'MockSub1 initial NotifyCount should be 0.');
|
|
Assert.AreEqual(0, mockSub2.NotifyCount, 'MockSub2 initial NotifyCount should be 0.');
|
|
|
|
latch.Notify; // FCount becomes 0. Subscribers notified, then UnadviseAll called.
|
|
|
|
Assert.IsTrue(latch.State.IsSet, 'Latch should be set.');
|
|
Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 should have been notified once when latch became set.');
|
|
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 should have been notified once when latch became set.');
|
|
|
|
latch.Notify; // Call Notify on the latch again (FCount becomes -1)
|
|
|
|
Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 should NOT be notified again after UnadviseAll.');
|
|
Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 should NOT be notified again after UnadviseAll.');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestLatchSet_SubscriptionFinalize_IsSafePostUnadviseAll;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
mockSub: TMockSubscriber;
|
|
// 'subscription' will be declared in an inner scope to control its finalization
|
|
begin
|
|
latch := TLatch.CreateLatch(1);
|
|
mockSub := TMockSubscriber.Create;
|
|
|
|
var noException: Boolean := True;
|
|
try
|
|
begin // Inner block to control lifetime of 'subscription'
|
|
var subscription: TSignal.TSubscription; // Local to this block
|
|
subscription := latch.State.Signal.Subscribe(mockSub);
|
|
|
|
latch.Notify; // Sets the latch, mockSub is notified, UnadviseAll is called internally.
|
|
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub was notified when latch set.');
|
|
|
|
// When 'subscription' goes out of scope at the end of this 'begin..end' block,
|
|
// its Finalize operator will be called, which in turn calls subscription.Unsubscribe.
|
|
// This happens *after* UnadviseAll was triggered by latch.Notify.
|
|
end; // <-- subscription.Finalize (and thus Unsubscribe) is called here.
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
noException := False;
|
|
// Optional: Log.Error('Exception in TestLatchSet_SubscriptionFinalize_IsSafePostUnadviseAll: ' + E.Message);
|
|
end;
|
|
end;
|
|
Assert.IsTrue(noException, 'Finalizing subscription after Latch set (and UnadviseAll) should not raise an exception.');
|
|
|
|
// Further check: calling Notify on latch again should not affect the original mockSub
|
|
latch.Notify;
|
|
Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should not be notified after its subscription was finalized post UnadviseAll.');
|
|
end;
|
|
|
|
procedure TTestMycLatch.TestLatchSet_NewSubscriberToSetLatch_NotifiedImmediatelyButNotPersistedForLatchNotify;
|
|
var
|
|
latch: TLatch.ILatch;
|
|
mockSubInitial, mockSubNew: TMockSubscriber;
|
|
subscriptionInitial, subscriptionNew: TSignal.TSubscription;
|
|
begin
|
|
latch := TLatch.CreateLatch(1);
|
|
mockSubInitial := TMockSubscriber.Create;
|
|
subscriptionInitial := latch.State.Signal.Subscribe(mockSubInitial);
|
|
|
|
// Set the latch; mockSubInitial is notified, UnadviseAll is called.
|
|
latch.Notify;
|
|
Assert.AreEqual(1, mockSubInitial.NotifyCount, 'Initial subscriber notified.');
|
|
|
|
// Subscribe a new subscriber to the already set latch
|
|
mockSubNew := TMockSubscriber.Create;
|
|
subscriptionNew := latch.State.Signal.Subscribe(mockSubNew);
|
|
|
|
// New subscriber should be notified immediately upon subscription to an already set latch
|
|
Assert.AreEqual(1, mockSubNew.NotifyCount, 'New subscriber should be notified immediately upon subscription.');
|
|
// The direct check of subscriptionNew.FTag was removed as FTag is private.
|
|
// The correct behavior (no persistent subscription) is verified by the next step.
|
|
|
|
// Call Notify on the latch again.
|
|
latch.Notify;
|
|
|
|
// mockSubInitial should not be notified again (was unadvised).
|
|
Assert.AreEqual(1, mockSubInitial.NotifyCount, 'Initial subscriber (unadvised) not notified again.');
|
|
// mockSubNew should also not be notified again, as it was only notified immediately
|
|
// and not persistently added to FSubscribers for subsequent Latch.Notify calls.
|
|
Assert
|
|
.AreEqual(1, mockSubNew.NotifyCount, 'New subscriber (notified once on subscribe) not notified by subsequent Latch.Notify calls.');
|
|
end;
|
|
|
|
initialization
|
|
TDUnitX.RegisterTestFixture(TTestMycLatch);
|
|
end.
|