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
+2 -1
View File
@@ -30,7 +30,8 @@ uses
Myc.Trade.DataStream in '..\Src\Myc.Trade.DataStream.pas',
Myc.Mutable in '..\Src\Myc.Mutable.pas',
Test.Core.Mutable in 'Test.Core.Mutable.pas',
TestDataRecord in 'TestDataRecord.pas';
TestDataRecord in 'TestDataRecord.pas',
TestDataRecord.RawAccess in 'TestDataRecord.RawAccess.pas';
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
{$IFNDEF TESTINSIGHT}
+1
View File
@@ -131,6 +131,7 @@ $(PreBuildEvent)]]></PreBuildEvent>
<DCCReference Include="..\Src\Myc.Mutable.pas"/>
<DCCReference Include="Test.Core.Mutable.pas"/>
<DCCReference Include="TestDataRecord.pas"/>
<DCCReference Include="TestDataRecord.RawAccess.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
+140
View File
@@ -0,0 +1,140 @@
unit TestDataRecord.RawAccess;
interface
uses
DUnitX.TestFramework,
Myc.DataRecord;
type
[TestFixture]
TTestDataRecordRawAccess = class
public
[Test]
procedure TestRawAccess_Integer;
[Test]
procedure TestRawAccess_String;
[Test]
procedure TestRawAccess_Interface;
end;
implementation
uses
System.Classes;
type
// A simple interface for testing managed type behavior.
IMyTestInterface = interface
function GetValue: integer;
end;
// Implementation of the test interface.
TMyTestInterfaceImpl = class(TInterfacedObject, IMyTestInterface)
private
FValue: Integer;
public
constructor Create(const AValue: Integer);
function GetValue: integer;
end;
constructor TMyTestInterfaceImpl.Create(const AValue: Integer);
begin
inherited Create;
FValue := AValue;
end;
function TMyTestInterfaceImpl.GetValue: integer;
begin
Result := FValue;
end;
procedure TTestDataRecordRawAccess.TestRawAccess_Integer;
var
rec: TDataRecord;
field: TDataRecord.IField;
readValue, newValue: Integer;
begin
// Arrange: Create a record with an integer field.
var builder := TDataRecord.CreateBuilder;
builder.AddField<Integer>('IntField', 123);
rec := builder.CreateRec;
field := rec.GetField('IntField');
Assert.IsNotNull(field, 'Field should exist');
// Act & Assert (GetRaw): Read the initial value via raw pointer.
readValue := 0;
field.GetRaw(@readValue);
Assert.AreEqual(123, readValue, 'GetRaw should retrieve the correct integer value');
// Act (SetRaw): Set a new value via raw pointer.
newValue := 456;
field.SetRaw(@newValue);
// Assert: Verify the new value using the typed accessor.
var typedField := rec.GetField<Integer>('IntField');
Assert.AreEqual(456, typedField.Value, 'SetRaw should update the integer value correctly');
end;
procedure TTestDataRecordRawAccess.TestRawAccess_Interface;
var
rec: TDataRecord;
field: TDataRecord.IField;
initialObj, readObj, newObj: IMyTestInterface;
begin
// Arrange: Create a record with an interface field.
initialObj := TMyTestInterfaceImpl.Create(10);
var builder := TDataRecord.CreateBuilder;
builder.AddField<IMyTestInterface>('IField', initialObj);
rec := builder.CreateRec;
field := rec.GetField('IField');
Assert.IsNotNull(field, 'Field should exist');
// Act & Assert (GetRaw): Read the initial interface via raw pointer.
readObj := nil;
field.GetRaw(@readObj);
Assert.IsNotNull(readObj, 'GetRaw should retrieve a non-nil interface');
Assert.AreSame(initialObj, readObj, 'GetRaw should retrieve the same interface instance');
Assert.AreEqual(10, readObj.GetValue, 'Value of retrieved interface should be correct');
// Act (SetRaw): Set a new interface via raw pointer.
newObj := TMyTestInterfaceImpl.Create(20);
field.SetRaw(@newObj);
// Assert: Verify the new interface is set correctly.
var typedField := rec.GetField<IMyTestInterface>('IField');
Assert.AreSame(newObj, typedField.Value, 'SetRaw should update to the new interface instance');
Assert.AreEqual(20, typedField.Value.GetValue, 'Value of new interface should be correct');
end;
procedure TTestDataRecordRawAccess.TestRawAccess_String;
var
rec: TDataRecord;
field: TDataRecord.IField;
readString, newString: string;
begin
// Arrange: Create a record with a string field.
var builder := TDataRecord.CreateBuilder;
builder.AddField<string>('StrField', 'Hello');
rec := builder.CreateRec;
field := rec.GetField('StrField');
Assert.IsNotNull(field, 'Field should exist');
// Act & Assert (GetRaw): Read the initial string via raw pointer.
readString := '';
field.GetRaw(@readString);
Assert.AreEqual('Hello', readString, 'GetRaw should retrieve the correct string value');
// Act (SetRaw): Set a new string via raw pointer.
newString := 'World';
field.SetRaw(@newString);
// Assert: Verify the new string using the typed accessor.
var typedField := rec.GetField<string>('StrField');
Assert.AreEqual('World', typedField.Value, 'SetRaw should update the string value correctly');
end;
initialization
TDUnitX.RegisterTestFixture(TTestDataRecordRawAccess);
end.
+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 }