From 46f7b594d98aa35faf36ed2258781ed1609fad37 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Sat, 24 May 2025 16:02:00 +0200 Subject: [PATCH] renamed some units, added TMycAtomicStack --- Src/Myc.Atomic.pas | 108 -------- Src/Myc.Core.Atomic.pas | 250 ++++++++++++++++++ Src/Myc.Heap.pas | 144 ----------- Test/MycTests.dpr | 3 +- Test/MycTests.dproj | 3 +- Test/{TestHeap.pas => TestSList.pas} | 72 +++--- Test/TestSignals.pas | 3 +- Test/TestStack.pas | 368 +++++++++++++++++++++++++++ 8 files changed, 653 insertions(+), 298 deletions(-) delete mode 100644 Src/Myc.Atomic.pas create mode 100644 Src/Myc.Core.Atomic.pas delete mode 100644 Src/Myc.Heap.pas rename Test/{TestHeap.pas => TestSList.pas} (85%) create mode 100644 Test/TestStack.pas diff --git a/Src/Myc.Atomic.pas b/Src/Myc.Atomic.pas deleted file mode 100644 index 26a6d6a..0000000 --- a/Src/Myc.Atomic.pas +++ /dev/null @@ -1,108 +0,0 @@ -unit Myc.Atomic; - -interface - -uses - Myc.Heap; - -type - IAtomicStack = interface - function Pop: T; - function TryPop( out Item: T ): Boolean; - procedure Push( const Data: T ); - end; - - TMycAtomicStack = class( TInterfacedObject, IAtomicStack ) - private - type - PItem = ^TItem; - TItem = packed record - Next: Pointer; - Data: T; - end; - - var - FSList: PSList; - - public - constructor Create; - destructor Destroy; override; - function Pop: T; inline; - function TryPop(out Item: T): Boolean; inline; - procedure Push( const Data: T ); inline; - function Alloc: PItem; inline; - procedure PushPtr(Item: PItem); inline; - function PopPtr: PItem; - end; - -implementation - -uses Winapi.Windows; - -constructor TMycAtomicStack.Create; -begin - inherited Create; - FSList := TSList.Create; -end; - -destructor TMycAtomicStack.Destroy; -var - Item: PItem; -begin - Item := PopPtr; - while Item <> nil do - begin - Item.Data := Default ( T ); - FreeMem( Item ); - Item := PopPtr; - end; - - FSList.Free; - inherited; -end; - -function TMycAtomicStack.Alloc: PItem; -begin - Result := AllocMem( sizeof( TItem ) ); -end; - -function TMycAtomicStack.Pop: T; -begin - if not TryPop( Result ) then - Result := Default ( T ); -end; - -function TMycAtomicStack.PopPtr: PItem; -begin - Result := PItem( FSList.Pop ); -end; - -procedure TMycAtomicStack.Push(const Data: T); -var - Item: PItem; -begin - Item := Alloc; - Item.Data := Data; - PushPtr( Item ); -end; - -procedure TMycAtomicStack.PushPtr(Item: PItem); -begin - FSList.Push( PSListEntry(Item) ); -end; - -function TMycAtomicStack.TryPop(out Item: T): Boolean; -var - P: PItem; -begin - P := PopPtr; - Result := P <> nil; - if Result then - begin - Item := P.Data; - P.Data := Default ( T ); - FreeMem( P ); - end; -end; - -end. diff --git a/Src/Myc.Core.Atomic.pas b/Src/Myc.Core.Atomic.pas new file mode 100644 index 0000000..d2f8255 --- /dev/null +++ b/Src/Myc.Core.Atomic.pas @@ -0,0 +1,250 @@ +unit Myc.Core.Atomic; + +interface + +{$align on} +uses + {$ifndef NO_FASTMM} + FastMM5, + {$endif} + Winapi.Windows; + +type + TSListEntry = Winapi.Windows.TSListEntry; + PSListEntry = Winapi.Windows.PSListEntry; + + PSList = ^TSList; + TSList = packed record + private + FList: TSListHeader; + public + // Creates and initializes an aligned SList header. + class function Create: PSList; static; + // Initializes the SList header for use. + procedure Init; + // Frees the SList header. + procedure Free; + // Atomically pushes an entry onto the SList. + function Push(ListEntry: PSListEntry): PSListEntry; + // Atomically pops an entry from the SList. + function Pop: PSListEntry; + // Returns the current number of entries in the SList. + function QueryDepth: Integer; + end; + + TMycAtomicStack = record + private type + PItem = ^TItem; + TItem = packed record + Next: TSListEntry; + Data: T; + end; + + var + FSList: PSList; + + public + // Initializes the atomic stack, creating the underlying SList. + class operator Initialize(out Dest: TMycAtomicStack); + // Finalizes the atomic stack, freeing all items and the SList. + class operator Finalize(var Dest: TMycAtomicStack); + + // Pops an item from the stack; returns Default(T) if empty. + function Pop: T; inline; + // Tries to pop an item from the stack; returns true if successful. + function TryPop(out Item: T): Boolean; inline; + // Pushes an item onto the stack. + procedure Push(const Data: T); inline; + + // Allocates an aligned memory block for a stack item. + function Alloc: PItem; inline; + // Pushes a pre-allocated item pointer onto the SList. + procedure PushPtr(Item: PItem); inline; + // Pops a raw item pointer from the SList. + function PopPtr: PItem; inline; + end; + +// Allocates memory with a specified alignment. +procedure GetMemAligned(var P; Size: NativeUInt); +// Frees memory allocated by GetMemAligned. +procedure FreeMemAligned(var P); + +implementation + +const + AlignmentBoundary = 16; + AlignmentMask = AlignmentBoundary - 1; + +{$ifdef NO_FASTMM} + +const + MetaDataSize = SizeOf(Pointer); + +procedure GetMemAligned(var P; Size: NativeUInt); +var + rawAllocatedPtr: Pointer; + alignedUserPtrVal: NativeUInt; + rawAllocatedPtrVal: NativeUInt; + totalSizeToAllocate: NativeUInt; +begin + totalSizeToAllocate := Size + MetaDataSize + AlignmentMask; + System.GetMem(rawAllocatedPtr, totalSizeToAllocate); + if rawAllocatedPtr = nil then + begin + Pointer(P) := nil; + exit; + end; + rawAllocatedPtrVal := NativeUInt(rawAllocatedPtr); + alignedUserPtrVal := (rawAllocatedPtrVal + MetaDataSize + AlignmentMask) and not AlignmentMask; + Pointer(P) := Pointer(alignedUserPtrVal); + PPointer(alignedUserPtrVal - MetaDataSize)^ := rawAllocatedPtr; + Assert((NativeUInt(P) and AlignmentMask) = 0, 'GetMemAligned did not return an aligned pointer.'); +end; + +procedure FreeMemAligned(var P); +var + alignedUserPtrVal: NativeUInt; + rawAllocatedPtr: Pointer; + ptrToRawAllocatedPtr: PPointer; +begin + if Pointer(P) = nil then + exit; + alignedUserPtrVal := NativeUInt(P); + ptrToRawAllocatedPtr := PPointer(alignedUserPtrVal - MetaDataSize); + rawAllocatedPtr := ptrToRawAllocatedPtr^; + System.FreeMem(rawAllocatedPtr); + Pointer(P) := nil; +end; + +{$else} + +procedure GetMemAligned(var P; Size: NativeUInt); inline; +begin + FastMM_EnterMinimumAddressAlignment(maa16Bytes); + try + GetMem(Pointer(P), Size); + finally + FastMM_ExitMinimumAddressAlignment(maa16Bytes); + end; +end; + +procedure FreeMemAligned(var P); inline; +begin + FreeMem(Pointer(P)); + Pointer(P) := nil; +end; + +{$endif} + +{ TSList } + +class function TSList.Create: PSList; +begin + GetMemAligned(Result, SizeOf(TSList)); + Result.Init; +end; + +procedure TSList.Free; +var + tempSelf: Pointer; +begin + tempSelf := Pointer(@Self); + FreeMemAligned(tempSelf); +end; + +procedure TSList.Init; +begin + Assert((NativeUInt(@FList) and AlignmentMask) = 0, 'TSList.FList member not aligned'); + InitializeSListHead(@FList); +end; + +function TSList.Pop: PSListEntry; +begin + Result := InterlockedPopEntrySList(@FList); +end; + +function TSList.Push(ListEntry: PSListEntry): PSListEntry; +begin + Assert((NativeUInt(ListEntry) and AlignmentMask) = 0, 'ListEntry to be pushed is not aligned'); + Result := InterlockedPushEntrySList(@FList, ListEntry); +end; + +function TSList.QueryDepth: Integer; +begin + Result := QueryDepthSList(@FList); +end; + +{ TMycAtomicStack } + +class operator TMycAtomicStack.Initialize(out Dest: TMycAtomicStack); +begin + FSList := TSList.Create; +end; + +class operator TMycAtomicStack.Finalize(var Dest: TMycAtomicStack); +var + item: PItem; +begin + item := PopPtr; + while item <> nil do + begin + item.Data := Default(T); + FreeMemAligned(item); + item := PopPtr; + end; + FSList.Free; +end; + +function TMycAtomicStack.Alloc: PItem; +begin + GetMemAligned(Pointer(Result), SizeOf(TItem)); + FillChar(Result^, SizeOf(TItem), 0); + Result.Data := Default(T); +end; + +function TMycAtomicStack.Pop: T; +begin + if not TryPop(Result) then + begin + Result := Default(T); + end; +end; + +function TMycAtomicStack.PopPtr: PItem; +begin + Result := PItem(FSList.Pop); +end; + +procedure TMycAtomicStack.Push(const Data: T); +var + item: PItem; +begin + item := Alloc; + item.Data := Data; + PushPtr(item); +end; + +procedure TMycAtomicStack.PushPtr(Item: PItem); +begin + FSList.Push(PSListEntry(Item)); +end; + +function TMycAtomicStack.TryPop(out Item: T): Boolean; +var + currentPItem: PItem; +begin + currentPItem := PopPtr; + Result := currentPItem <> nil; + if Result then + begin + Item := currentPItem.Data; + currentPItem.Data := Default(T); + FreeMemAligned(currentPItem); + end + else + begin + Item := Default(T); + end; +end; + +end. diff --git a/Src/Myc.Heap.pas b/Src/Myc.Heap.pas deleted file mode 100644 index 9d2e547..0000000 --- a/Src/Myc.Heap.pas +++ /dev/null @@ -1,144 +0,0 @@ -unit Myc.Heap; - -interface - -{$align on} -uses - {$ifndef NO_FASTMM} - FastMM5, - {$endif} - Winapi.Windows; - -type - TSListEntry = Winapi.Windows.TSListEntry; - PSListEntry = Winapi.Windows.PSListEntry; - - PSList = ^TSList; - TSList = packed record - private - FList: TSListHeader; - - public - class function Create: PSList; static; - procedure Init; - procedure Free; - function Push( ListEntry: PSListEntry ): PSListEntry; - function Pop: PSListEntry; - function QueryDepth: Integer; - end; - -procedure GetMemAligned( var P; Size: NativeUInt ); -procedure FreeMemAligned( var P ); - -procedure InitializeSListHead( ListHead: PSListHeader ); stdcall; external kernel32; -function InterlockedPushEntrySList( ListHead: PSListHeader; ListEntry: PSListEntry ): PSListEntry; stdcall; external kernel32; -function InterlockedPopEntrySList( ListHead: PSListHeader ): PSListEntry; stdcall; external kernel32; -function QueryDepthSList( ListHead: PSListHeader ): Word; stdcall; external kernel32; - -implementation - -const - // Defines the alignment boundary (16 bytes for SList and common SIMD operations) - AlignmentBoundary = 16; - // Mask to achieve alignment, (AlignmentBoundary - 1) - AlignmentMask = AlignmentBoundary - 1; - -{$ifdef NO_FASTMM} - -const - MetaDataSize = SizeOf(Pointer); - -procedure GetMemAligned(var P; Size: NativeUInt); -var - rawAllocatedPtr: Pointer; - alignedUserPtrVal: NativeUInt; - rawAllocatedPtrVal: NativeUInt; - totalSizeToAllocate: NativeUInt; -begin - totalSizeToAllocate := Size + MetaDataSize + AlignmentMask; - System.GetMem(rawAllocatedPtr, totalSizeToAllocate); - if rawAllocatedPtr = nil then - begin - Pointer(P) := nil; - Exit; - end; - rawAllocatedPtrVal := NativeUInt(rawAllocatedPtr); - alignedUserPtrVal := (rawAllocatedPtrVal + MetaDataSize + AlignmentMask) and not AlignmentMask; - Pointer(P) := Pointer(alignedUserPtrVal); - PPointer(alignedUserPtrVal - MetaDataSize)^ := rawAllocatedPtr; - Assert((NativeUInt(P) and AlignmentMask) = 0, 'GetMemAligned did not return an aligned pointer.'); -end; - -procedure FreeMemAligned( var P ); -var - alignedUserPtrVal: NativeUInt; - rawAllocatedPtr: Pointer; - ptrToRawAllocatedPtr: PPointer; -begin - if Pointer(P) = nil then - Exit; - alignedUserPtrVal := NativeUInt(P); - ptrToRawAllocatedPtr := PPointer(alignedUserPtrVal - MetaDataSize); - rawAllocatedPtr := ptrToRawAllocatedPtr^; - System.FreeMem(rawAllocatedPtr); - Pointer(P) := nil; -end; - -{$else} - -procedure GetMemAligned(var P; Size: NativeUInt); inline; -begin - FastMM_EnterMinimumAddressAlignment(maa16Bytes); - try - GetMem( Pointer(P), Size ); - finally - FastMM_ExitMinimumAddressAlignment(maa16Bytes) - end; -end; - -procedure FreeMemAligned( var P ); inline; -begin - FreeMem( Pointer(P) ); -end; - -{$endif} - -{ TSList } - -class function TSList.Create: PSList; -begin - GetMemAligned( Result, sizeof( TSList ) ); - Result.Init; -end; - -procedure TSList.Free; -var - P: Pointer; -begin - P := @Self; - FreeMemAligned( P ); -end; - -procedure TSList.Init; -begin - Assert( NativeUInt( @FList ) and AlignmentMask = 0, 'ListHeader not aligned' ); - InitializeSListHead( @FList ); -end; - -function TSList.Pop: PSListEntry; -begin - Result := InterlockedPopEntrySList( @FList ); -end; - -function TSList.Push( ListEntry: PSListEntry ): PSListEntry; -begin - Assert( NativeUInt( ListEntry ) and AlignmentMask = 0, 'ListEntry not aligned' ); - Result := InterlockedPushEntrySList( @FList, ListEntry ); -end; - -function TSList.QueryDepth: Integer; -begin - Result := QueryDepthSList( @FList ); -end; - -end. diff --git a/Test/MycTests.dpr b/Test/MycTests.dpr index 9fc7fdd..cfb6fae 100644 --- a/Test/MycTests.dpr +++ b/Test/MycTests.dpr @@ -16,7 +16,8 @@ uses {$ENDIF } DUnitX.TestFramework, TestSignals in 'TestSignals.pas', - TestHeap in 'TestHeap.pas'; + TestSList in 'TestSList.pas', + TestStack in 'TestStack.pas'; { keep comment here to protect the following conditional from being removed by the IDE when adding a unit } {$IFNDEF TESTINSIGHT} diff --git a/Test/MycTests.dproj b/Test/MycTests.dproj index 2bb4434..84da01a 100644 --- a/Test/MycTests.dproj +++ b/Test/MycTests.dproj @@ -106,7 +106,8 @@ MainSource - + + Base diff --git a/Test/TestHeap.pas b/Test/TestSList.pas similarity index 85% rename from Test/TestHeap.pas rename to Test/TestSList.pas index 81654b7..6e3669e 100644 --- a/Test/TestHeap.pas +++ b/Test/TestSList.pas @@ -1,10 +1,10 @@ -unit TestHeap; +unit TestSList; interface uses DUnitX.TestFramework, - Myc.Heap, // The unit to be tested. TSListEntry and PSListEntry are expected from here. + Myc.Core.Atomic, // The unit to be tested. TSListEntry and PSListEntry are expected from here. System.SysUtils, // For NativeUInt, FillChar, Format static class, etc. System.SyncObjs, // TInterlocked Winapi.Windows, // For general Windows API types if needed by Myc.Heap for TSListHeader. @@ -19,7 +19,7 @@ type end; [TestFixture] - TMycTestHeap = class + TMycTestSList = class private const TestAlignmentMask = 15; // Corresponds to AlignmentBoundary = 16 in Myc.Heap.pas @@ -53,10 +53,6 @@ type [Test] [Timeout(30000)] // Timeout in milliseconds procedure TestTSList_ParallelPushPop; - - // Sample method (can be removed if not needed) - [Test] - procedure Test1; end; implementation @@ -68,40 +64,40 @@ uses // --- Helper Implementations (static) --- -class function TMycTestHeap.CreateTestListItem(ID: Integer): PTestListItem; +class function TMycTestSList.CreateTestListItem(ID: Integer): PTestListItem; begin - Myc.Heap.GetMemAligned(Result, SizeOf(TTestListItem)); + GetMemAligned(Result, SizeOf(TTestListItem)); Assert.IsNotNull(Result, Format('Failed to allocate memory for TestListItem ID %d', [ID])); FillChar(Result^, SizeOf(TTestListItem), 0); Result.ID := ID; end; -class procedure TMycTestHeap.FreeTestListItem(var Item: PTestListItem); +class procedure TMycTestSList.FreeTestListItem(var Item: PTestListItem); var tempPtr: Pointer; begin if Item <> nil then begin tempPtr := Item; - Myc.Heap.FreeMemAligned(tempPtr); + FreeMemAligned(tempPtr); Item := nil; end; end; // --- Fixture Setup/TearDown --- -procedure TMycTestHeap.Setup; +procedure TMycTestSList.Setup; begin Randomize; end; -procedure TMycTestHeap.TearDown; +procedure TMycTestSList.TearDown; begin end; // --- Alignment Stress Test Implementation --- -procedure TMycTestHeap.TestStressAlignmentAndDataIntegrity; +procedure TMycTestSList.TestStressAlignmentAndDataIntegrity; const NumIterations = 300000; MaxBlockSize = 1024 * 4; @@ -112,8 +108,6 @@ var bytePtr: System.PByte; // Variable renamed from pByte j: System.NativeUInt; fillValue: Byte; - isNilAfterFreeExpected: Boolean; - originalPtrBeforeFree: Pointer; begin for i := 1 to NumIterations do begin @@ -131,7 +125,7 @@ begin ptr := nil; try - Myc.Heap.GetMemAligned(ptr, sizeToAllocate); + GetMemAligned(ptr, sizeToAllocate); if sizeToAllocate = 0 then begin Assert.IsTrue(True, Format('Zero-size allocation processed (Iteration %d). Pointer: %p', [i, ptr])); @@ -162,14 +156,14 @@ begin end; end; finally - Myc.Heap.FreeMemAligned(ptr); + FreeMemAligned(ptr); end; end; end; // --- TSList Single-Threaded Test Implementations --- -procedure TMycTestHeap.TestTSList_CreateAndFree; +procedure TMycTestSList.TestTSList_CreateAndFree; var sListPtr: PSList; begin @@ -179,7 +173,7 @@ begin sListPtr.Free; end; -procedure TMycTestHeap.TestTSList_PushPopSingleEntry; +procedure TMycTestSList.TestTSList_PushPopSingleEntry; var sListPtr: PSList; item1: PTestListItem; @@ -188,7 +182,7 @@ var begin sListPtr := TSList.Create; Assert.IsNotNull(sListPtr); - item1 := TMycTestHeap.CreateTestListItem(101); + item1 := TMycTestSList.CreateTestListItem(101); Assert.IsNotNull(item1); sListPtr.Push(@item1.ListEntry); Assert.AreEqual(1, sListPtr.QueryDepth, 'Depth should be 1 after one push.'); @@ -198,11 +192,11 @@ begin Assert.AreEqual(NativeUInt(item1), NativeUInt(poppedItem), 'Popped item should be the same as pushed item.'); Assert.AreEqual(item1.ID, poppedItem.ID, 'Popped item ID mismatch.'); Assert.AreEqual(0, sListPtr.QueryDepth, 'Depth should be 0 after popping the item.'); - TMycTestHeap.FreeTestListItem(item1); + TMycTestSList.FreeTestListItem(item1); sListPtr.Free; end; -procedure TMycTestHeap.TestTSList_PushPopMultipleEntries_LIFO; +procedure TMycTestSList.TestTSList_PushPopMultipleEntries_LIFO; const NumItems = 5; var @@ -216,7 +210,7 @@ begin Assert.IsNotNull(sListPtr); for i := 1 to NumItems do begin - items[i] := TMycTestHeap.CreateTestListItem(200 + i); + items[i] := TMycTestSList.CreateTestListItem(200 + i); sListPtr.Push(@items[i].ListEntry); Assert.AreEqual(i, sListPtr.QueryDepth, Format('Depth should be %d after %d pushes.', [i,i])); end; @@ -234,12 +228,12 @@ begin Assert.AreEqual(0, sListPtr.QueryDepth, 'Depth should be 0 after all items are popped.'); for i := 1 to NumItems do begin - TMycTestHeap.FreeTestListItem(items[i]); + TMycTestSList.FreeTestListItem(items[i]); end; sListPtr.Free; end; -procedure TMycTestHeap.TestTSList_QueryDepth; +procedure TMycTestSList.TestTSList_QueryDepth; var sListPtr: PSList; item1, item2: PTestListItem; @@ -247,22 +241,22 @@ begin sListPtr := TSList.Create; Assert.IsNotNull(sListPtr); Assert.AreEqual(0, sListPtr.QueryDepth, 'Initial depth should be 0.'); - item1 := TMycTestHeap.CreateTestListItem(301); + item1 := TMycTestSList.CreateTestListItem(301); sListPtr.Push(@item1.ListEntry); Assert.AreEqual(1, sListPtr.QueryDepth, 'Depth should be 1 after one push.'); - item2 := TMycTestHeap.CreateTestListItem(302); + item2 := TMycTestSList.CreateTestListItem(302); sListPtr.Push(@item2.ListEntry); Assert.AreEqual(2, sListPtr.QueryDepth, 'Depth should be 2 after two pushes.'); sListPtr.Pop; Assert.AreEqual(1, sListPtr.QueryDepth, 'Depth should be 1 after one pop.'); sListPtr.Pop; Assert.AreEqual(0, sListPtr.QueryDepth, 'Depth should be 0 after two pops.'); - TMycTestHeap.FreeTestListItem(item1); - TMycTestHeap.FreeTestListItem(item2); + TMycTestSList.FreeTestListItem(item1); + TMycTestSList.FreeTestListItem(item2); sListPtr.Free; end; -procedure TMycTestHeap.TestTSList_PopFromEmptyList; +procedure TMycTestSList.TestTSList_PopFromEmptyList; var sListPtr: PSList; poppedInternalListEntry: PSListEntry; @@ -277,7 +271,7 @@ begin end; // --- TSList Parallel Access Test Implementation --- -procedure TMycTestHeap.TestTSList_ParallelPushPop; +procedure TMycTestSList.TestTSList_ParallelPushPop; const NumThreads = 12; OperationsPerThread = 50000; @@ -325,7 +319,7 @@ begin if actionRand < 0.5 then // Attempt to Push begin itemID := TInterlocked.Increment(globalItemID); // TInterlocked from System.SysUtils or System - item := TMycTestHeap.CreateTestListItem(itemID); + item := TMycTestSList.CreateTestListItem(itemID); if item <> nil then begin sharedSList.Push(@item.ListEntry); @@ -343,7 +337,7 @@ begin pushLogResult := poppedIDs.PushItem(item.ID); // Use PushItem for TThreadedQueue Assert.AreEqual(TWaitResult.wrSignaled, pushLogResult, Format('Failed to push item ID %d to poppedIDs logging queue.', [item.ID])); - TMycTestHeap.FreeTestListItem(item); + TMycTestSList.FreeTestListItem(item); end; end; if j mod (OperationsPerThread div 10) = 0 then // Occasional sleep @@ -373,7 +367,7 @@ begin waitResult := poppedIDs.PushItem(remainingItem.ID); // Use PushItem Assert.AreEqual(TWaitResult.wrSignaled, waitResult, Format('Failed to push remaining item ID %d to poppedIDs logging queue during drain.', [remainingItem.ID])); - TMycTestHeap.FreeTestListItem(remainingItem); + TMycTestSList.FreeTestListItem(remainingItem); end; Assert.AreEqual(0, sharedSList.QueryDepth, 'TSList should be empty after all operations and draining.'); @@ -425,12 +419,6 @@ begin poppedIDs.Free; end; -// --- Existing Sample Test --- -procedure TMycTestHeap.Test1; -begin - // Example: Assert.IsTrue(True, 'Test1 passed'); -end; - initialization - TDUnitX.RegisterTestFixture(TMycTestHeap); + TDUnitX.RegisterTestFixture(TMycTestSList); end. diff --git a/Test/TestSignals.pas b/Test/TestSignals.pas index 312bc9f..046a9ce 100644 --- a/Test/TestSignals.pas +++ b/Test/TestSignals.pas @@ -3,8 +3,7 @@ unit TestSignals; interface uses - DUnitX.TestFramework, - Myc.Atomic; + DUnitX.TestFramework; type [TestFixture] diff --git a/Test/TestStack.pas b/Test/TestStack.pas new file mode 100644 index 0000000..b618a35 --- /dev/null +++ b/Test/TestStack.pas @@ -0,0 +1,368 @@ +unit TestStack; + +interface + +uses + DUnitX.TestFramework; + +type + // Helper record type for testing generic capabilities + TTestRecord = record + ID: Integer; + Name: string; + Value: Double; + end; + + [TestFixture] + TMycTestStackTests = class(TObject) // Changed class name for clarity + public + // Setup and TearDown are often not strictly necessary for tests + // involving managed records as local variables, as their + // Initialize/Finalize are handled automatically. + [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; + + end; + +implementation + +uses + System.SysUtils, // For Default() if not implicitly available via DUnitX/Generics + Myc.Core.Atomic; // The unit containing TMycAtomicStack + +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 --- + +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 --- + +[Test] +procedure TMycTestStackTests.TestInteger_PopOnEmpty_ReturnsDefault; +var + stack: TMycAtomicStack; + value: Integer; +begin + // Initialize is called automatically for managed record 'stack' + value := stack.Pop; + Assert.AreEqual(Default(Integer), value, 'Pop on empty integer stack should return Default(Integer).'); + // Finalize is called automatically when 'stack' goes out of scope +end; + +[Test] +procedure TMycTestStackTests.TestInteger_TryPopOnEmpty_ReturnsFalseAndDefault; +var + stack: TMycAtomicStack; + value: Integer; + success: Boolean; +begin + value := 123; // Non-default initial value to check if TryPop modifies it + 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.'); + + // Stack should be empty now + 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: 10 + stack.Push(30); // Stack: 10, 30 + Assert.AreEqual(30, stack.Pop, 'Pop 30'); // Stack: 10 + stack.Push(40); // Stack: 10, 40 + Assert.AreEqual(40, stack.Pop, 'Pop 40'); // Stack: 10 + Assert.AreEqual(10, stack.Pop, 'Pop 10'); // Stack: empty + + 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 --- + +[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(''); // Empty string + stack.Push(Default(string)); // Nil 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 --- + +[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; // Non-default + 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; + +initialization + TDUnitX.RegisterTestFixture(TMycTestStackTests); +end.