renamed some units, added TMycAtomicStack
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
@@ -3,8 +3,7 @@ unit TestSignals;
|
||||
interface
|
||||
|
||||
uses
|
||||
DUnitX.TestFramework,
|
||||
Myc.Atomic;
|
||||
DUnitX.TestFramework;
|
||||
|
||||
type
|
||||
[TestFixture]
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user