DataRecord
This commit is contained in:
+2
-1
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user