unit TestSignals_Latch; interface uses DUnitX.TestFramework, Myc.Signals, // Unit under test, using TMycLatch static members System.SysUtils, System.StrUtils; type // Helper class to mock a subscriber. TMockSubscriber = class(TInterfacedObject, IMycSubscriber) public NotifyCount: Integer; NotifyShouldReturn: Boolean; WasCalled: Boolean; constructor Create(ShouldReturn: Boolean = True); function Notify: Boolean; // IMycSubscriber 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', '0,True')] // TMycLatch.CreateLatch(0) will return TMycLatch.Null [TestCase('InitialNegative', '-1,True')] // TMycLatch.CreateLatch(-1) will return TMycLatch.Null [TestCase('InitialLargePositive', '5,False')] procedure TestCreate_InitialState(InitialCount: Integer; ExpectedIsSet: Boolean); [TestCase('DecrementPositiveToZero', '1,False,True')] [TestCase('DecrementPositiveToPositive', '2,True,False')] [TestCase('DecrementZeroToNegative', '0,False,True')] [TestCase('DecrementNegativeToNegative', '-1,False,True')] 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_AfterLatchSet; [Test] procedure TestSubscribe_ToAlreadySetLatch_NotifiesImmediately; // --- Category 3: Chaining and Cascading --- [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; [Test] procedure TestUnsubscribe_SubscriberNotNotified; // --- Category 5: Signal Loss / Counter Integrity --- [Test] procedure TestMultipleTriggersNoSubscriber_StateCorrectForLaterSubscriber; end; implementation { TMockSubscriber } constructor TMockSubscriber.Create(ShouldReturn: Boolean = True); begin inherited Create; NotifyCount := 0; NotifyShouldReturn := ShouldReturn; WasCalled := False; end; function TMockSubscriber.Notify: Boolean; begin Inc(NotifyCount); WasCalled := True; Result := NotifyShouldReturn; 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; procedure TTestMycLatch.TestCreate_InitialState(InitialCount: Integer; ExpectedIsSet: Boolean); var latch: IMycLatch; begin latch := TMycLatch.CreateLatch(InitialCount); // Changed to use TMycLatch.CreateLatch Assert.AreEqual(ExpectedIsSet, latch.State.IsSet, 'Latch initial IsSet state mismatch.'); end; procedure TTestMycLatch.TestNotify_ReturnValueAndState_AfterOneNotify(InitialCount: Integer; ExpectedReturn: Boolean; ExpectedIsSet: Boolean); var latch: IMycLatch; returnedValue: Boolean; begin latch := TMycLatch.CreateLatch(InitialCount); // Changed to use TMycLatch.CreateLatch returnedValue := latch.Notify; Assert.AreEqual(ExpectedReturn, returnedValue, 'Unexpected return value from Latch.Notify call.'); Assert.AreEqual(ExpectedIsSet, latch.State.IsSet, 'Unexpected IsSet state after Latch.Notify call.'); end; procedure TTestMycLatch.TestNotify_DecrementsCounter_StateChanges; var latch: IMycLatch; begin latch := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch Assert.IsFalse(latch.State.IsSet, 'Latch should initially not be set.'); latch.Notify; Assert.IsTrue(latch.State.IsSet, 'Latch should be set after Notify.'); end; procedure TTestMycLatch.TestNotify_MultipleNotifies_CounterBecomesNegativeAndStaysSet; var latch: IMycLatch; begin latch := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch latch.Notify; Assert.IsTrue(latch.State.IsSet, 'Latch should be set after first Notify.'); latch.Notify; Assert.IsTrue(latch.State.IsSet, 'Latch should remain set after second Notify.'); end; procedure TTestMycLatch.TestSubscriber_Single_IsNotifiedWhenLatchSet; var latch: IMycLatch; mockSub: TMockSubscriber; subscription: TMycSubscription; begin latch := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch mockSub := TMockSubscriber.Create; subscription := latch.State.Subscribe(mockSub); 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; 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: IMycLatch; mockSub1, mockSub2, mockSub3: TMockSubscriber; sub1, sub2, sub3: TMycSubscription; begin latch := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch mockSub1 := TMockSubscriber.Create; mockSub2 := TMockSubscriber.Create; mockSub3 := TMockSubscriber.Create; sub1 := latch.State.Subscribe(mockSub1); sub2 := latch.State.Subscribe(mockSub2); sub3 := latch.State.Subscribe(mockSub3); latch.Notify; 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: IMycLatch; mockSub: TMockSubscriber; subscription: TMycSubscription; begin latch := TMycLatch.CreateLatch(2); // Changed to use TMycLatch.CreateLatch mockSub := TMockSubscriber.Create; subscription := latch.State.Subscribe(mockSub); Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not be notified upon subscription if latch is not set.'); latch.Notify; Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should still not be notified as latch is not yet set.'); end; procedure TTestMycLatch.TestSubscriber_NotifiedOnlyOnce_AfterLatchSet; var latch: IMycLatch; mockSub: TMockSubscriber; subscription: TMycSubscription; begin latch := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch mockSub := TMockSubscriber.Create; subscription := latch.State.Subscribe(mockSub); latch.Notify; Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub notify count should be 1 after latch set.'); latch.Notify; 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: IMycLatch; mockSub1, mockSub2: TMockSubscriber; subscription1, subscription2: TMycSubscription; begin latch := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch mockSub1 := TMockSubscriber.Create; subscription1 := latch.State.Subscribe(mockSub1); latch.Notify; Assert.IsTrue(latch.State.IsSet, 'Latch should be set after initial trigger.'); Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 should have been notified once.'); mockSub2 := TMockSubscriber.Create; subscription2 := latch.State.Subscribe(mockSub2); Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 should be notified immediately upon subscribing to an already set latch.'); latch.Notify; Assert.AreEqual(1, mockSub1.NotifyCount, 'MockSub1 notify count should remain 1.'); Assert.AreEqual(1, mockSub2.NotifyCount, 'MockSub2 notify count should remain 1 after re-triggering.'); end; procedure TTestMycLatch.TestChaining_A_Notifies_B; var latchA, latchB: IMycLatch; mockSubForB: TMockSubscriber; subHandle_B_listens_A, subHandle_Mock_listens_B: TMycSubscription; begin latchA := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch latchB := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch mockSubForB := TMockSubscriber.Create; subHandle_Mock_listens_B := latchB.State.Subscribe(mockSubForB); subHandle_B_listens_A := latchA.State.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; 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: IMycLatch; mockSubForC: TMockSubscriber; sub_Mock_C, sub_C_B, sub_B_A: TMycSubscription; begin latchA := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch latchB := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch latchC := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch mockSubForC := TMockSubscriber.Create; sub_Mock_C := latchC.State.Subscribe(mockSubForC); sub_C_B := latchB.State.Subscribe(latchC); sub_B_A := latchA.State.Subscribe(latchB); latchA.Notify; 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: IMycLatch; mockSubForB: TMockSubscriber; sub_Mock_B, sub_B_A: TMycSubscription; begin latchA := TMycLatch.CreateLatch(2); // Changed to use TMycLatch.CreateLatch latchB := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch mockSubForB := TMockSubscriber.Create; sub_Mock_B := latchB.State.Subscribe(mockSubForB); sub_B_A := latchA.State.Subscribe(latchB); latchA.Notify; 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; Assert.IsTrue(latchA.State.IsSet, 'LatchA should be set after second notify.'); 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; procedure TTestMycLatch.TestInitiallySetLatch_NotifiesNewSubscriberImmediately; var latch: IMycLatch; mockSub: TMockSubscriber; subscription: TMycSubscription; begin latch := TMycLatch.CreateLatch(0); // Changed to use TMycLatch.CreateLatch. Will return TMycLatch.Null. mockSub := TMockSubscriber.Create; subscription := latch.State.Subscribe(mockSub); Assert.IsTrue(latch.State.IsSet, 'Latch IsSet property mismatch after creation with count 0.'); Assert.AreEqual(1, mockSub.NotifyCount, 'MockSub should be notified immediately upon subscribing to an already set latch.'); end; procedure TTestMycLatch.TestUnsubscribe_SubscriberNotNotified; var latch: IMycLatch; mockSub: TMockSubscriber; subscription: TMycSubscription; begin latch := TMycLatch.CreateLatch(1); // Changed to use TMycLatch.CreateLatch mockSub := TMockSubscriber.Create; subscription := latch.State.Subscribe(mockSub); subscription := Default(TMycSubscription); // Simulates leaving scope / destruction, calls Finalize for unsubscription. latch.Notify; Assert.AreEqual(0, mockSub.NotifyCount, 'Unsubscribed MockSub should not be notified.'); end; procedure TTestMycLatch.TestMultipleTriggersNoSubscriber_StateCorrectForLaterSubscriber; var latch: IMycLatch; mockSub: TMockSubscriber; subscription: TMycSubscription; begin latch := TMycLatch.CreateLatch(3); // Changed to use TMycLatch.CreateLatch latch.Notify; latch.Notify; Assert.IsFalse(latch.State.IsSet, 'Latch should not be set yet after partial notifies.'); mockSub := TMockSubscriber.Create; subscription := latch.State.Subscribe(mockSub); Assert.AreEqual(0, mockSub.NotifyCount, 'MockSub should not be notified on subscribe if latch not yet set.'); latch.Notify; 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; initialization TDUnitX.RegisterTestFixture(TTestMycLatch); end.