DataRecord

This commit is contained in:
Michael Schimmel
2025-07-20 20:21:55 +02:00
parent 6010f61953
commit dd50049b06
8 changed files with 363 additions and 60 deletions
+108 -33
View File
@@ -101,10 +101,19 @@ type
procedure Test_tkVariant_Succeeds;
[Test]
procedure TestFinalizeWithValue_True_Succeeds;
procedure Test_Assign_CreatesIndependentCopy;
[Test]
procedure TestFinalizeWithValue_False_Leaks;
procedure Test_Reassign_ReleasesOldData;
// New tests for RTTI-based creation and assignment
[Test]
[IgnoreMemoryLeaks]
procedure Test_CreateFrom_AssignTo_Succeeds;
[Test]
[IgnoreMemoryLeaks]
procedure Test_Assign_FromCreateFrom_IsIndependentCopy;
end;
implementation
@@ -329,47 +338,113 @@ begin
Assert.AreEqual(testVar, field.Value, 'Variant value should match');
end;
procedure TDataRecordTests.TestFinalizeWithValue_True_Succeeds;
procedure TDataRecordTests.Test_Assign_CreatesIndependentCopy;
var
Buffer: TBytes;
P: Pointer;
s: string;
rec1, rec2: TDataRecord;
field1, field2: TDataRecord.IField<string>;
begin
// 1. Create a managed string and place it in a raw buffer
s := 'This is a test string that should be finalized.';
SetLength(Buffer, SizeOf(string));
P := @Buffer[0];
PString(P)^ := s;
s := ''; // Clear the original variable, the buffer is now the owner
// Create original record
rec1 := CreateRecord<string>('Value', 'Original');
// 2. Try to finalize the string in the buffer using TValue
var Val: TValue;
TValue.MakeWithoutCopy(P, TypeInfo(string), Val, true);
// Assign to a new record, invoking operator Assign. This covers 'CreateFromRec'.
rec2 := rec1;
// If 'true' works as expected (takes ownership and finalizes), this test will pass without leaks.
Assert.IsTrue(true);
// Verify the copy
Assert.IsTrue(Length(rec2.Layout) = 1, 'Copied record should have one field');
Assert.AreEqual('Value', rec2.Layout[0].Name, 'Field name should be copied');
field2 := rec2.GetField<string>('Value');
Assert.IsNotNull(field2, 'Field should exist in copied record');
Assert.AreEqual('Original', field2.Value, 'Field value should be copied');
// Modify the copy and check for independence (deep copy)
field2.Value := 'Modified';
field1 := rec1.GetField<string>('Value');
Assert.AreEqual('Original', field1.Value, 'Original record should not be modified');
Assert.AreEqual('Modified', field2.Value, 'Copied record should reflect modification');
end;
procedure TDataRecordTests.TestFinalizeWithValue_False_Leaks;
procedure TDataRecordTests.Test_Reassign_ReleasesOldData;
var
Buffer: TBytes;
P: Pointer;
s: string;
rec1, rec2: TDataRecord;
field: TDataRecord.IField<string>;
begin
// 1. Create a managed string and place it in a raw buffer
s := 'This string will be leaked.';
SetLength(Buffer, SizeOf(string));
P := @Buffer[0];
PString(P)^ := s;
s := ''; // Clear the original variable, the buffer is now the owner
// Create two independent records with managed string fields.
// DUnitX will report a leak if the memory for rec1 is not freed upon reassignment.
rec1 := CreateRecord<string>('OldValue', 'This should be released');
rec2 := CreateRecord<string>('NewValue', 'This should remain');
// 2. Try to "finalize" the string using 'false'
var Val: TValue;
TValue.MakeWithoutCopy(P, TypeInfo(string), Val, false);
// Re-assign rec1. This should Finalize the old rec1 and Assign the new data.
rec1 := rec2;
// If 'false' does NOT take ownership, this test will report a memory leak.
// Log.Info('This test is expected to report a memory leak.');
Assert.IsTrue(true);
// Verify the state of the reassigned rec1
Assert.AreEqual(-1, rec1.IndexOf('OldValue'), 'Old field should not exist anymore');
Assert.AreNotEqual(-1, rec1.IndexOf('NewValue'), 'New field should exist');
field := rec1.GetField<string>('NewValue');
Assert.AreEqual('This should remain', field.Value, 'Value should be from the new record');
// Also check that rec2 is unaffected by the assignment
field := rec2.GetField<string>('NewValue');
Assert.AreEqual('This should remain', field.Value, 'Source record for assignment should be unchanged');
end;
procedure TDataRecordTests.Test_CreateFrom_AssignTo_Succeeds;
var
srcRec, dstRec: TTestRecord;
dataRec: TDataRecord;
fieldI: TDataRecord.IField<Integer>;
fieldS: TDataRecord.IField<string>;
begin
// Setup a standard record with data
srcRec.I := 123;
srcRec.S := 'Hello from RTTI';
// Create a TDataRecord from the standard record via RTTI
dataRec := TDataRecord.CreateFrom<TTestRecord>(srcRec);
// Verify that the data was correctly transferred into the TDataRecord
Assert.IsTrue(Length(dataRec.Layout) = 2, 'Layout should have 2 fields');
fieldI := dataRec.GetField<Integer>('I');
Assert.IsNotNull(fieldI, 'Integer field should exist');
Assert.AreEqual(srcRec.I, fieldI.Value, 'Integer value should match');
fieldS := dataRec.GetField<string>('S');
Assert.IsNotNull(fieldS, 'String field should exist');
Assert.AreEqual(srcRec.S, fieldS.Value, 'String value should match');
// Assign the data back from the TDataRecord to another standard record
dataRec.AssignTo<TTestRecord>(dstRec);
// Verify that the data was correctly restored
Assert.AreEqual(srcRec.I, dstRec.I, 'Assigned back record integer should match');
Assert.AreEqual(srcRec.S, dstRec.S, 'Assigned back record string should match');
end;
procedure TDataRecordTests.Test_Assign_FromCreateFrom_IsIndependentCopy;
var
srcRec: TTestRecord;
rec1, rec2: TDataRecord;
field1, field2: TDataRecord.IField<string>;
begin
// Setup a record and create a TDataRecord from it
srcRec.I := 456;
srcRec.S := 'Original RTTI value';
rec1 := TDataRecord.CreateFrom<TTestRecord>(srcRec);
// Assign the TDataRecord to another, invoking operator Assign
rec2 := rec1;
// Modify the string field in the copy
field2 := rec2.GetField<string>('S');
Assert.IsNotNull(field2, 'Field must exist in copy');
field2.Value := 'Modified in copy';
// Verify that the original TDataRecord is unchanged
field1 := rec1.GetField<string>('S');
Assert.IsNotNull(field1, 'Field must exist in original');
Assert.AreEqual('Original RTTI value', field1.Value, 'Original record should not be modified');
Assert.AreEqual('Modified in copy', field2.Value, 'Copied record should reflect modification');
end;
{ TTestClass }