unit TestStack; interface uses DUnitX.TestFramework, System.SysUtils; // For GUIDToString if used in messages, and other utils type // Helper record type for testing generic capabilities (as before) TTestRecord = record ID: Integer; Name: string; Value: Double; end; // --- Interface and implementing class for interface tests --- ITestInterface = interface ['{E5A8A1C9-8A8B-45A3-99E1-3A5A0F8D7C6B}'] // Example GUID - Generate a new one for real projects function GetValue: Integer; procedure SetValue(AValue: Integer); property Value: Integer read GetValue write SetValue; end; TTestImplementingObject = class(TInterfacedObject, ITestInterface) private FValue: Integer; public constructor Create(AValue: Integer); function GetValue: Integer; procedure SetValue(AValue: Integer); end; [TestFixture] TMycTestStackTests = class(TObject) public [Setup] procedure Setup; [TearDown] procedure TearDown; // --- Tests for TMycAtomicStack --- [Test] procedure TestInteger_PopOnEmpty_ReturnsDefault; [Test] procedure TestInteger_TryPopOnEmpty_ReturnsFalseAndDefault; [Test] procedure TestInteger_PushOne_PopOne_CorrectValue; [Test] procedure TestInteger_PushThree_PopThree_LIFO_Order; [Test] procedure TestInteger_TryPopWithItems_ReturnsTrueAndCorrectValue; [Test] procedure TestInteger_PushPopMixed_MaintainsIntegrity; [Test] procedure TestInteger_StressTest_ManyItems; // --- Tests for TMycAtomicStack --- [Test] procedure TestString_PopOnEmpty_ReturnsDefault; [Test] procedure TestString_TryPopOnEmpty_ReturnsFalseAndDefault; [Test] procedure TestString_PushOne_PopOne_CorrectValue; [Test] procedure TestString_PushThree_PopThree_LIFO_Order; [Test] procedure TestString_HandleEmptyAndNilStrings; // --- Tests for TMycAtomicStack --- [Test] procedure TestRecord_PopOnEmpty_ReturnsDefault; [Test] procedure TestRecord_TryPopOnEmpty_ReturnsFalseAndDefault; [Test] procedure TestRecord_PushOne_PopOne_CorrectValue; [Test] procedure TestRecord_PushThree_PopThree_LIFO_Order; // --- Tests for TMycAtomicStack --- [Test] procedure TestInterface_PopOnEmpty_ReturnsNil; [Test] procedure TestInterface_TryPopOnEmpty_ReturnsFalseAndNil; [Test] procedure TestInterface_PushOne_PopOne_CorrectObjectAndValue; [Test] procedure TestInterface_PushThree_PopThree_LIFO_OrderAndValues; [Test] procedure TestInterface_PushNilInterface_PopNil; [Test] procedure TestInterface_ReferenceCountingImplicitCheck; end; implementation uses Myc.Core.Atomic; // The unit containing TMycAtomicStack // --- Implementation for TTestImplementingObject --- constructor TTestImplementingObject.Create(AValue: Integer); begin inherited Create; FValue := AValue; end; function TTestImplementingObject.GetValue: Integer; begin Result := FValue; end; procedure TTestImplementingObject.SetValue(AValue: Integer); begin FValue := AValue; end; // --- TestFixture Method Implementations --- procedure TMycTestStackTests.Setup; begin // No specific setup needed per test if stack is local variable in methods. end; procedure TMycTestStackTests.TearDown; begin // No specific teardown needed per test. end; // --- Helper Methods for TTestRecord (as before) --- function AreRecordsEqual(const Rec1, Rec2: TTestRecord): Boolean; begin Result := (Rec1.ID = Rec2.ID) and (Rec1.Name = Rec2.Name) and (Rec1.Value = Rec2.Value); end; function TestRecordToString(const Rec: TTestRecord): string; begin Result := Format('ID: %d, Name: "%s", Value: %f', [Rec.ID, Rec.Name, Rec.Value]); end; // --- Tests for TMycAtomicStack (implementations as before, omitted for brevity) --- [Test] procedure TMycTestStackTests.TestInteger_PopOnEmpty_ReturnsDefault; var stack: TMycAtomicStack; value: Integer; begin value := stack.Pop; Assert.AreEqual(Default(Integer), value, 'Pop on empty integer stack should return Default(Integer).'); end; [Test] procedure TMycTestStackTests.TestInteger_TryPopOnEmpty_ReturnsFalseAndDefault; var stack: TMycAtomicStack; value: Integer; success: Boolean; begin value := 123; success := stack.TryPop(value); Assert.IsFalse(success, 'TryPop on empty integer stack should return False.'); Assert.AreEqual(Default(Integer), value, 'Item from TryPop on empty integer stack should be Default(Integer).'); end; [Test] procedure TMycTestStackTests.TestInteger_PushOne_PopOne_CorrectValue; var stack: TMycAtomicStack; pushedValue: Integer; poppedValue: Integer; success: Boolean; begin pushedValue := 100; stack.Push(pushedValue); poppedValue := stack.Pop; Assert.AreEqual(pushedValue, poppedValue, 'Popped value does not match pushed value.'); success := stack.TryPop(poppedValue); Assert.IsFalse(success, 'Stack should be empty after popping the only item.'); end; [Test] procedure TMycTestStackTests.TestInteger_PushThree_PopThree_LIFO_Order; var stack: TMycAtomicStack; val1, val2, val3: Integer; begin val1 := 1; val2 := 2; val3 := 3; stack.Push(val1); stack.Push(val2); stack.Push(val3); Assert.AreEqual(val3, stack.Pop, '1st Pop: Expected val3 (LIFO).'); Assert.AreEqual(val2, stack.Pop, '2nd Pop: Expected val2 (LIFO).'); Assert.AreEqual(val1, stack.Pop, '3rd Pop: Expected val1 (LIFO).'); Assert.IsFalse(stack.TryPop(val1), 'Stack should be empty after popping all items.'); end; [Test] procedure TMycTestStackTests.TestInteger_TryPopWithItems_ReturnsTrueAndCorrectValue; var stack: TMycAtomicStack; pushedValue: Integer; poppedValue: Integer; success: Boolean; begin pushedValue := 77; stack.Push(pushedValue); success := stack.TryPop(poppedValue); Assert.IsTrue(success, 'TryPop should return True when stack is not empty.'); Assert.AreEqual(pushedValue, poppedValue, 'TryPop: Popped value does not match pushed value.'); success := stack.TryPop(poppedValue); Assert.IsFalse(success, 'Stack should be empty after TryPop on the only item.'); end; [Test] procedure TMycTestStackTests.TestInteger_PushPopMixed_MaintainsIntegrity; var stack: TMycAtomicStack; value: Integer; begin stack.Push(10); stack.Push(20); Assert.AreEqual(20, stack.Pop, 'Pop 20'); stack.Push(30); Assert.AreEqual(30, stack.Pop, 'Pop 30'); stack.Push(40); Assert.AreEqual(40, stack.Pop, 'Pop 40'); Assert.AreEqual(10, stack.Pop, 'Pop 10'); Assert.IsFalse(stack.TryPop(value), 'Stack should be empty after all operations.'); end; [Test] procedure TMycTestStackTests.TestInteger_StressTest_ManyItems; var stack: TMycAtomicStack; i: Integer; count: Integer; begin count := 10000; for i := 1 to count do begin stack.Push(i); end; for i := count downto 1 do begin Assert.AreEqual(i, stack.Pop, Format('Stress test: Popped value mismatch for item %d.', [i])); end; Assert.IsFalse(stack.TryPop(i), 'Stack should be empty after stress test.'); end; // --- Tests for TMycAtomicStack (implementations as before, omitted for brevity) --- [Test] procedure TMycTestStackTests.TestString_PopOnEmpty_ReturnsDefault; var stack: TMycAtomicStack; value: string; begin value := stack.Pop; Assert.AreEqual(Default(string), value, 'Pop on empty string stack should return Default(string) (nil).'); end; [Test] procedure TMycTestStackTests.TestString_TryPopOnEmpty_ReturnsFalseAndDefault; var stack: TMycAtomicStack; value: string; success: Boolean; begin value := 'not nil'; success := stack.TryPop(value); Assert.IsFalse(success, 'TryPop on empty string stack should return False.'); Assert.AreEqual(Default(string), value, 'Item from TryPop on empty string stack should be Default(string) (nil).'); end; [Test] procedure TMycTestStackTests.TestString_PushOne_PopOne_CorrectValue; var stack: TMycAtomicStack; pushedValue: string; poppedValue: string; begin pushedValue := 'Hello Delphi'; stack.Push(pushedValue); poppedValue := stack.Pop; Assert.AreEqual(pushedValue, poppedValue, 'String Pop: Popped value does not match pushed value.'); Assert.IsFalse(stack.TryPop(poppedValue), 'Stack should be empty.'); end; [Test] procedure TMycTestStackTests.TestString_PushThree_PopThree_LIFO_Order; var stack: TMycAtomicStack; s1, s2, s3: string; begin s1 := 'first'; s2 := 'second'; s3 := 'third'; stack.Push(s1); stack.Push(s2); stack.Push(s3); Assert.AreEqual(s3, stack.Pop, 'String LIFO: Expected s3.'); Assert.AreEqual(s2, stack.Pop, 'String LIFO: Expected s2.'); Assert.AreEqual(s1, stack.Pop, 'String LIFO: Expected s1.'); Assert.IsFalse(stack.TryPop(s1), 'Stack should be empty.'); end; [Test] procedure TMycTestStackTests.TestString_HandleEmptyAndNilStrings; var stack: TMycAtomicStack; poppedValue: string; begin stack.Push(''); stack.Push(Default(string)); stack.Push('actual string'); Assert.AreEqual('actual string', stack.Pop, 'Pop "actual string"'); Assert.AreEqual(Default(string), stack.Pop, 'Pop nil string'); Assert.AreEqual('', stack.Pop, 'Pop empty string'); Assert.IsFalse(stack.TryPop(poppedValue), 'Stack should be empty.'); end; // --- Tests for TMycAtomicStack (implementations as before, omitted for brevity) --- [Test] procedure TMycTestStackTests.TestRecord_PopOnEmpty_ReturnsDefault; var stack: TMycAtomicStack; value: TTestRecord; begin value := stack.Pop; Assert.IsTrue(AreRecordsEqual(Default(TTestRecord), value), Format('Pop on empty record stack should return Default(TTestRecord). Got %s', [TestRecordToString(value)])); end; [Test] procedure TMycTestStackTests.TestRecord_TryPopOnEmpty_ReturnsFalseAndDefault; var stack: TMycAtomicStack; value: TTestRecord; success: Boolean; begin value.ID := -1; success := stack.TryPop(value); Assert.IsFalse(success, 'TryPop on empty record stack should return False.'); Assert.IsTrue(AreRecordsEqual(Default(TTestRecord), value), Format('Item from TryPop on empty record stack should be Default(TTestRecord). Got %s', [TestRecordToString(value)])); end; [Test] procedure TMycTestStackTests.TestRecord_PushOne_PopOne_CorrectValue; var stack: TMycAtomicStack; pushedValue: TTestRecord; poppedValue: TTestRecord; begin pushedValue.ID := 1; pushedValue.Name := 'Test Record 1'; pushedValue.Value := 3.14; stack.Push(pushedValue); poppedValue := stack.Pop; Assert.IsTrue(AreRecordsEqual(pushedValue, poppedValue), Format('Record Pop: Popped value (%s) does not match pushed value (%s).', [TestRecordToString(poppedValue), TestRecordToString(pushedValue)])); Assert.IsFalse(stack.TryPop(poppedValue), 'Stack should be empty.'); end; [Test] procedure TMycTestStackTests.TestRecord_PushThree_PopThree_LIFO_Order; var stack: TMycAtomicStack; r1, r2, r3: TTestRecord; popped: TTestRecord; begin r1.ID := 1; r1.Name := 'R1'; r1.Value := 1.0; r2.ID := 2; r2.Name := 'R2'; r2.Value := 2.0; r3.ID := 3; r3.Name := 'R3'; r3.Value := 3.0; stack.Push(r1); stack.Push(r2); stack.Push(r3); popped := stack.Pop; Assert.IsTrue(AreRecordsEqual(r3, popped), Format('Record LIFO: Expected r3, got %s', [TestRecordToString(popped)])); popped := stack.Pop; Assert.IsTrue(AreRecordsEqual(r2, popped), Format('Record LIFO: Expected r2, got %s', [TestRecordToString(popped)])); popped := stack.Pop; Assert.IsTrue(AreRecordsEqual(r1, popped), Format('Record LIFO: Expected r1, got %s', [TestRecordToString(popped)])); Assert.IsFalse(stack.TryPop(popped), 'Stack should be empty.'); end; // --- Tests for TMycAtomicStack --- [Test] procedure TMycTestStackTests.TestInterface_PopOnEmpty_ReturnsNil; var stack: TMycAtomicStack; value: ITestInterface; begin value := stack.Pop; Assert.IsNull(value, 'Pop on empty interface stack should return nil.'); end; [Test] procedure TMycTestStackTests.TestInterface_TryPopOnEmpty_ReturnsFalseAndNil; var stack: TMycAtomicStack; value: ITestInterface; success: Boolean; begin value := TTestImplementingObject.Create(-1); // Assign a non-nil to check if TryPop nils it success := stack.TryPop(value); Assert.IsFalse(success, 'TryPop on empty interface stack should return False.'); Assert.IsNull(value, 'Item from TryPop on empty interface stack should be nil.'); end; [Test] procedure TMycTestStackTests.TestInterface_PushOne_PopOne_CorrectObjectAndValue; var stack: TMycAtomicStack; pushedIntf: ITestInterface; poppedIntf: ITestInterface; originalObject: TTestImplementingObject; begin originalObject := TTestImplementingObject.Create(123); pushedIntf := originalObject; // Interface variable now holds the object stack.Push(pushedIntf); poppedIntf := stack.Pop; Assert.IsNotNull(poppedIntf, 'Popped interface should not be nil.'); Assert.AreSame(originalObject, poppedIntf as TObject, 'Popped interface should point to the same object instance.'); if Assigned(poppedIntf) then begin Assert.AreEqual(123, poppedIntf.Value, 'Popped interface has incorrect Value.'); end; Assert.IsFalse(stack.TryPop(poppedIntf), 'Stack should be empty after popping the only item.'); end; [Test] procedure TMycTestStackTests.TestInterface_PushThree_PopThree_LIFO_OrderAndValues; var stack: TMycAtomicStack; i1, i2, i3: ITestInterface; o1, o2, o3: TTestImplementingObject; popped: ITestInterface; begin o1 := TTestImplementingObject.Create(10); i1 := o1; o2 := TTestImplementingObject.Create(20); i2 := o2; o3 := TTestImplementingObject.Create(30); i3 := o3; stack.Push(i1); stack.Push(i2); stack.Push(i3); popped := stack.Pop; Assert.IsNotNull(popped, '1st popped interface should not be nil.'); Assert.AreSame(o3, popped as TObject, '1st pop should be o3.'); if Assigned(popped) then Assert.AreEqual(30, popped.Value); popped := stack.Pop; Assert.IsNotNull(popped, '2nd popped interface should not be nil.'); Assert.AreSame(o2, popped as TObject, '2nd pop should be o2.'); if Assigned(popped) then Assert.AreEqual(20, popped.Value); popped := stack.Pop; Assert.IsNotNull(popped, '3rd popped interface should not be nil.'); Assert.AreSame(o1, popped as TObject, '3rd pop should be o1.'); if Assigned(popped) then Assert.AreEqual(10, popped.Value); Assert.IsNull(stack.Pop, 'Stack should be empty and Pop return nil.'); end; [Test] procedure TMycTestStackTests.TestInterface_PushNilInterface_PopNil; var stack: TMycAtomicStack; nilIntf: ITestInterface; // This is already nil by default nonNilIntf: ITestInterface; begin nilIntf := nil; // Explicitly nil nonNilIntf := TTestImplementingObject.Create(999); stack.Push(nonNilIntf); stack.Push(nilIntf); // Push a nil interface stack.Push(TTestImplementingObject.Create(777)); Assert.AreEqual(777, stack.Pop.Value, 'Pop 777.'); Assert.IsNull(stack.Pop, 'Pop nil interface.'); // Pop the nil interface Assert.AreEqual(999, stack.Pop.Value, 'Pop 999.'); Assert.IsNull(stack.Pop, 'Stack should be empty and Pop return nil.'); end; [Test] procedure TMycTestStackTests.TestInterface_ReferenceCountingImplicitCheck; var stack: TMycAtomicStack; intf1: ITestInterface; objRawPtr: TObject; // To observe, not for direct management initialRefCount, afterPushRefCount, afterPopRefCount, afterClearRefCount: Integer; begin // This test is more conceptual as direct ref count assertion is tricky // and an implementation detail. Correct working of other tests implies // ref counting is likely correct due to ARC. // We rely on ARC and TInterfacedObject. intf1 := TTestImplementingObject.Create(505); objRawPtr := intf1 as TObject; initialRefCount := TInterfacedObject(objRawPtr).RefCount; // Should be 1 stack.Push(intf1); afterPushRefCount := TInterfacedObject(objRawPtr).RefCount; // Should be 2 (intf1 + stack's copy) Assert.AreEqual(initialRefCount + 1, afterPushRefCount, 'Ref count should increment after push.'); intf1 := nil; // Release local variable's reference afterClearRefCount := TInterfacedObject(objRawPtr).RefCount; // Should be 1 (only stack's copy) Assert.AreEqual(initialRefCount, afterClearRefCount, 'Ref count should decrement after local var nilled.'); intf1 := stack.Pop; // Retrieve from stack afterPopRefCount := TInterfacedObject(objRawPtr).RefCount; // Should be 1 (only intf1's copy, stack's copy released) Assert.AreEqual(initialRefCount, afterPopRefCount, 'Ref count should be back to initial after pop and stack release.'); // When intf1 goes out of scope, object should be freed. // The stack itself is a managed record and its Finalize will clear any remaining // interface references, decrementing their ref counts. Assert.IsFalse(stack.TryPop(intf1), 'Stack should be empty.'); // Note: Direct RefCount checking can be fragile and version-dependent. // The main check is that objects are released and no AVs occur. // The above Asserts on RefCount are illustrative. end; initialization TDUnitX.RegisterTestFixture(TMycTestStackTests); end.