renamed some units, added TMycAtomicStack

This commit is contained in:
Michael Schimmel
2025-05-24 16:02:00 +02:00
parent 08a110b02f
commit 46f7b594d9
8 changed files with 653 additions and 298 deletions
-108
View File
@@ -1,108 +0,0 @@
unit Myc.Atomic;
interface
uses
Myc.Heap;
type
IAtomicStack<T> = interface
function Pop: T;
function TryPop( out Item: T ): Boolean;
procedure Push( const Data: T );
end;
TMycAtomicStack<T> = class( TInterfacedObject, IAtomicStack<T> )
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<T>.Create;
begin
inherited Create;
FSList := TSList.Create;
end;
destructor TMycAtomicStack<T>.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<T>.Alloc: PItem;
begin
Result := AllocMem( sizeof( TItem ) );
end;
function TMycAtomicStack<T>.Pop: T;
begin
if not TryPop( Result ) then
Result := Default ( T );
end;
function TMycAtomicStack<T>.PopPtr: PItem;
begin
Result := PItem( FSList.Pop );
end;
procedure TMycAtomicStack<T>.Push(const Data: T);
var
Item: PItem;
begin
Item := Alloc;
Item.Data := Data;
PushPtr( Item );
end;
procedure TMycAtomicStack<T>.PushPtr(Item: PItem);
begin
FSList.Push( PSListEntry(Item) );
end;
function TMycAtomicStack<T>.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.
+250
View File
@@ -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<T> = 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<T>);
// Finalizes the atomic stack, freeing all items and the SList.
class operator Finalize(var Dest: TMycAtomicStack<T>);
// 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<T> }
class operator TMycAtomicStack<T>.Initialize(out Dest: TMycAtomicStack<T>);
begin
FSList := TSList.Create;
end;
class operator TMycAtomicStack<T>.Finalize(var Dest: TMycAtomicStack<T>);
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<T>.Alloc: PItem;
begin
GetMemAligned(Pointer(Result), SizeOf(TItem));
FillChar(Result^, SizeOf(TItem), 0);
Result.Data := Default(T);
end;
function TMycAtomicStack<T>.Pop: T;
begin
if not TryPop(Result) then
begin
Result := Default(T);
end;
end;
function TMycAtomicStack<T>.PopPtr: PItem;
begin
Result := PItem(FSList.Pop);
end;
procedure TMycAtomicStack<T>.Push(const Data: T);
var
item: PItem;
begin
item := Alloc;
item.Data := Data;
PushPtr(item);
end;
procedure TMycAtomicStack<T>.PushPtr(Item: PItem);
begin
FSList.Push(PSListEntry(Item));
end;
function TMycAtomicStack<T>.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.
-144
View File
@@ -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.
+2 -1
View File
@@ -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}
+2 -1
View File
@@ -106,7 +106,8 @@
<MainSource>MainSource</MainSource>
</DelphiCompile>
<DCCReference Include="TestSignals.pas"/>
<DCCReference Include="TestHeap.pas"/>
<DCCReference Include="TestSList.pas"/>
<DCCReference Include="TestStack.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
+30 -42
View File
@@ -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.
+1 -2
View File
@@ -3,8 +3,7 @@ unit TestSignals;
interface
uses
DUnitX.TestFramework,
Myc.Atomic;
DUnitX.TestFramework;
type
[TestFixture]
+368
View File
@@ -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<Integer> ---
[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<string> ---
[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<TTestRecord> ---
[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<Integer> ---
[Test]
procedure TMycTestStackTests.TestInteger_PopOnEmpty_ReturnsDefault;
var
stack: TMycAtomicStack<Integer>;
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<Integer>;
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<Integer>;
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<Integer>;
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<Integer>;
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<Integer>;
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<Integer>;
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<string> ---
[Test]
procedure TMycTestStackTests.TestString_PopOnEmpty_ReturnsDefault;
var
stack: TMycAtomicStack<string>;
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<string>;
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<string>;
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<string>;
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<string>;
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<TTestRecord> ---
[Test]
procedure TMycTestStackTests.TestRecord_PopOnEmpty_ReturnsDefault;
var
stack: TMycAtomicStack<TTestRecord>;
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<TTestRecord>;
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<TTestRecord>;
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<TTestRecord>;
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.