270 lines
11 KiB
ObjectPascal
270 lines
11 KiB
ObjectPascal
unit TestDataRecord;
|
|
|
|
interface
|
|
|
|
uses
|
|
DunitX.TestFramework,
|
|
System.SysUtils,
|
|
System.DateUtils,
|
|
System.Rtti,
|
|
Myc.Data.Records;
|
|
|
|
type
|
|
// A record that contains all supported data types for testing
|
|
TMyTestRecord = record
|
|
MyInteger: Integer;
|
|
MyInt64: Int64;
|
|
MySingle: Single;
|
|
MyDouble: Double;
|
|
MyString: string;
|
|
MyTimestamp: TDateTime;
|
|
MyRecord: TDataRecord;
|
|
end;
|
|
|
|
// A simple record to be nested inside another
|
|
TNestedTestRecord = record
|
|
NestedValue: Integer;
|
|
NestedString: string;
|
|
end;
|
|
|
|
// A record that contains another record field of type TDataRecord
|
|
TOuterTestRecord = record
|
|
OuterValue: Integer;
|
|
MyNestedRecord: TDataRecord;
|
|
end;
|
|
|
|
[TestFixture]
|
|
TTestDataRecord = class(TObject)
|
|
public
|
|
[Test]
|
|
[IgnoreMemoryLeaks]
|
|
procedure TestLayoutCreation;
|
|
|
|
[Test]
|
|
[IgnoreMemoryLeaks]
|
|
procedure TestCreateFromInstanceAndGetValue;
|
|
|
|
[Test]
|
|
[IgnoreMemoryLeaks]
|
|
procedure TestSetValueAndGetValue;
|
|
|
|
[Test]
|
|
// [IgnoreMemoryLeaks]
|
|
procedure TestNestedDataRecord;
|
|
|
|
[Test]
|
|
// [IgnoreMemoryLeaks]
|
|
procedure TestInternalRecordAssignment;
|
|
|
|
[Test]
|
|
// [IgnoreMemoryLeaks]
|
|
procedure TestNestedSetValueAndGetValue;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TTestDataRecord }
|
|
|
|
procedure TTestDataRecord.TestLayoutCreation;
|
|
var
|
|
layout: TDataRecord.TLayout;
|
|
idx: Integer;
|
|
begin
|
|
// Test: Create layout from a record type
|
|
layout := TDataRecord.TLayout.FromRecord<TMyTestRecord>;
|
|
|
|
// Assert: Check field count
|
|
Assert.IsTrue(Length(layout.Fields) = 7, 'Layout should have 7 fields');
|
|
|
|
// Assert: Check specific fields for correct type mapping
|
|
idx := layout.IndexOf('MyTimestamp');
|
|
Assert.IsTrue(idx > -1, 'Field MyTimestamp not found');
|
|
Assert.AreEqual(TDataRecord.TFieldType.dfTimestamp, layout.Fields[idx].FieldType, 'MyTimestamp should be mapped to dfTimestamp');
|
|
|
|
idx := layout.IndexOf('MyInteger');
|
|
Assert.IsTrue(idx > -1, 'Field MyInteger not found');
|
|
Assert.AreEqual(TDataRecord.TFieldType.dfInteger, layout.Fields[idx].FieldType, 'MyInteger should be mapped to dfInteger');
|
|
|
|
idx := layout.IndexOf('MyString');
|
|
Assert.IsTrue(idx > -1, 'Field MyString not found');
|
|
Assert.AreEqual(TDataRecord.TFieldType.dfString, layout.Fields[idx].FieldType, 'MyString should be mapped to dfString');
|
|
|
|
idx := layout.IndexOf('MySingle');
|
|
Assert.IsTrue(idx > -1, 'Field MySingle not found');
|
|
Assert.AreEqual(TDataRecord.TFieldType.dfFloat, layout.Fields[idx].FieldType, 'MySingle should be mapped to dfFloat');
|
|
|
|
idx := layout.IndexOf('MyRecord');
|
|
Assert.IsTrue(idx > -1, 'Field MyRecord not found');
|
|
Assert.AreEqual(TDataRecord.TFieldType.dfRecord, layout.Fields[idx].FieldType, 'MyRecord should be mapped to dfRecord');
|
|
end;
|
|
|
|
procedure TTestDataRecord.TestCreateFromInstanceAndGetValue;
|
|
var
|
|
srcRecord: TMyTestRecord;
|
|
dataRecord: TDataRecord;
|
|
begin
|
|
// Setup: Create and populate a source record
|
|
srcRecord.MyInteger := 12345;
|
|
srcRecord.MyInt64 := 9876543210;
|
|
srcRecord.MySingle := 123.456;
|
|
srcRecord.MyDouble := 789.012345;
|
|
srcRecord.MyString := 'Hello Delphi!';
|
|
srcRecord.MyTimestamp := EncodeDateTime(2024, 7, 19, 10, 30, 0, 0);
|
|
srcRecord.MyRecord := default(TDataRecord); // Not testing nested here
|
|
|
|
// Test: Create a TDataRecord from the source record instance
|
|
dataRecord := TDataRecord.FromRecord<TMyTestRecord>(srcRecord);
|
|
|
|
// Assert: Retrieve each value and check for correctness
|
|
Assert.AreEqual(srcRecord.MyInt64, dataRecord.GetValue<Int64>('MyInt64'), 'Int64 value mismatch');
|
|
Assert.AreEqual(srcRecord.MyInteger, dataRecord.GetValue<Integer>('MyInteger'), 'Integer value mismatch');
|
|
Assert.AreEqual(srcRecord.MyString, dataRecord.GetValue<string>('MyString'), 'String value mismatch');
|
|
Assert.AreEqual(srcRecord.MyTimestamp, dataRecord.GetValue<TDateTime>('MyTimestamp'), 'TDateTime value mismatch');
|
|
|
|
// Assert: Check float values with a tolerance for precision differences
|
|
Assert.AreEqual(srcRecord.MySingle, dataRecord.GetValue<Single>('MySingle'), 1e-6, 'Single value mismatch');
|
|
Assert.AreEqual(srcRecord.MyDouble, dataRecord.GetValue<Double>('MyDouble'), 1e-9, 'Double value mismatch');
|
|
|
|
// Assert: Test type promotion (reading an Integer as Int64)
|
|
Assert.IsTrue(srcRecord.MyInteger = dataRecord.GetValue<Int64>('MyInteger'), 'Integer to Int64 promotion failed');
|
|
end;
|
|
|
|
procedure TTestDataRecord.TestSetValueAndGetValue;
|
|
var
|
|
dataRecord: TDataRecord;
|
|
testInt: Integer;
|
|
testInt64: Int64;
|
|
testSingle: Single;
|
|
testDouble: Double;
|
|
testString: string;
|
|
testTimestamp: TDateTime;
|
|
begin
|
|
// Setup: Create an empty TDataRecord with the layout of TMyTestRecord
|
|
dataRecord := TDataRecord.FromRecord<TMyTestRecord>;
|
|
|
|
// Setup: Define test values
|
|
testInt := 54321;
|
|
testInt64 := 12345678901;
|
|
testSingle := 987.654;
|
|
testDouble := 543.210987;
|
|
testString := 'Testing SetValue';
|
|
testTimestamp := Now;
|
|
|
|
// Test: Set values for each field by name
|
|
dataRecord.SetValue<Integer>('MyInteger', testInt);
|
|
dataRecord.SetValue<Int64>('MyInt64', testInt64);
|
|
dataRecord.SetValue<Single>('MySingle', testSingle);
|
|
dataRecord.SetValue<Double>('MyDouble', testDouble);
|
|
dataRecord.SetValue<string>('MyString', testString);
|
|
dataRecord.SetValue<TDateTime>('MyTimestamp', testTimestamp);
|
|
|
|
// Assert: Retrieve the values and check if they match what was set
|
|
Assert.AreEqual(testInt, dataRecord.GetValue<Integer>('MyInteger'), 'SetValue failed for Integer');
|
|
Assert.AreEqual(testInt64, dataRecord.GetValue<Int64>('MyInt64'), 'SetValue failed for Int64');
|
|
Assert.AreEqual(testString, dataRecord.GetValue<string>('MyString'), 'SetValue failed for String');
|
|
Assert.AreEqual(testTimestamp, dataRecord.GetValue<TDateTime>('MyTimestamp'), 'SetValue failed for TDateTime');
|
|
|
|
Assert.AreEqual(testSingle, dataRecord.GetValue<Single>('MySingle'), 1e-6, 'SetValue failed for Single');
|
|
Assert.AreEqual(testDouble, dataRecord.GetValue<Double>('MyDouble'), 1e-9, 'SetValue failed for Double');
|
|
end;
|
|
|
|
procedure TTestDataRecord.TestNestedDataRecord;
|
|
var
|
|
nestedSrc: TNestedTestRecord;
|
|
nestedDataRecord: TDataRecord;
|
|
outerSrc: TOuterTestRecord;
|
|
mainDataRecord: TDataRecord;
|
|
retrievedNestedRecord: TDataRecord;
|
|
begin
|
|
// Setup: Create the nested record and convert it to a TDataRecord
|
|
nestedSrc.NestedValue := 999;
|
|
const str = 'Nested Record Test';
|
|
nestedSrc.NestedString := str;
|
|
nestedDataRecord := TDataRecord.FromRecord<TNestedTestRecord>(nestedSrc);
|
|
|
|
// Setup: Create the outer record containing the nested TDataRecord
|
|
outerSrc.OuterValue := 111;
|
|
outerSrc.MyNestedRecord := nestedDataRecord;
|
|
|
|
// Test: Create the main TDataRecord from the outer record instance
|
|
mainDataRecord := TDataRecord.FromRecord<TOuterTestRecord>(outerSrc);
|
|
|
|
// Assert: Retrieve the nested record and check its contents
|
|
retrievedNestedRecord := mainDataRecord.GetValue<TDataRecord>('MyNestedRecord');
|
|
|
|
Assert.IsNotNull(retrievedNestedRecord.Layout.Fields, 'Nested record layout should not be nil');
|
|
Assert.IsTrue(Length(retrievedNestedRecord.Layout.Fields) = 2, 'Nested record should have 2 fields');
|
|
|
|
// Assert values within the nested record
|
|
Assert.AreEqual(999, retrievedNestedRecord.GetValue<Integer>('NestedValue'), 'Nested integer value mismatch');
|
|
Assert.AreEqual(str, retrievedNestedRecord.GetValue<string>('NestedString'), 'Nested string value mismatch');
|
|
|
|
// Assert value in the outer record
|
|
Assert.AreEqual(111, mainDataRecord.GetValue<Integer>('OuterValue'), 'Outer integer value mismatch');
|
|
end;
|
|
|
|
procedure TTestDataRecord.TestInternalRecordAssignment;
|
|
var
|
|
nestedSrc: TNestedTestRecord;
|
|
nestedDataRecord: TDataRecord;
|
|
outerSrc: TOuterTestRecord;
|
|
mainDataRecord: TDataRecord;
|
|
retrievedNestedRecord: TDataRecord;
|
|
begin
|
|
// Setup: Create the nested record and convert it to a TDataRecord
|
|
const str = 'Nested Record Test';
|
|
nestedSrc.NestedString := str;
|
|
nestedDataRecord := TDataRecord.FromRecord<TNestedTestRecord>(nestedSrc);
|
|
|
|
// Setup: Create the outer record containing the nested TDataRecord
|
|
outerSrc.MyNestedRecord := nestedDataRecord;
|
|
|
|
// Place a record on the heap. FAILS: This erases the nested record data!! -- Fixed 25.07.2025!
|
|
TDataRecord.FromRecord<TOuterTestRecord>(outerSrc);
|
|
|
|
// Test: Create the main TDataRecord from the outer record instance
|
|
mainDataRecord := TDataRecord.FromRecord<TOuterTestRecord>(outerSrc);
|
|
|
|
// Assert: Retrieve the nested record and check its contents
|
|
retrievedNestedRecord := mainDataRecord.GetValue<TDataRecord>('MyNestedRecord');
|
|
|
|
// Assert values within the nested record
|
|
Assert.AreEqual(str, retrievedNestedRecord.GetValue<string>('NestedString'), 'Nested string value mismatch');
|
|
end;
|
|
|
|
procedure TTestDataRecord.TestNestedSetValueAndGetValue;
|
|
var
|
|
outerRecord: TDataRecord;
|
|
nestedRecord: TDataRecord;
|
|
retrievedNestedRecord: TDataRecord;
|
|
begin
|
|
// Setup: Create an empty TDataRecord for the outer structure
|
|
outerRecord := TDataRecord.FromRecord<TOuterTestRecord>;
|
|
|
|
// Setup: Create a TDataRecord for the nested structure and populate it
|
|
nestedRecord := TDataRecord.FromRecord<TNestedTestRecord>;
|
|
nestedRecord.SetValue<Integer>('NestedValue', 888);
|
|
nestedRecord.SetValue<string>('NestedString', 'Manual Nested Set');
|
|
|
|
// Test: Set the nested record and another value in the outer record
|
|
outerRecord.SetValue<Integer>('OuterValue', 222);
|
|
outerRecord.SetValue<TDataRecord>('MyNestedRecord', nestedRecord);
|
|
|
|
// Assert: Retrieve the nested record and check its contents
|
|
retrievedNestedRecord := outerRecord.GetValue<TDataRecord>('MyNestedRecord');
|
|
Assert.IsNotNull(retrievedNestedRecord.Layout.Fields, 'Retrieved nested record layout should not be nil');
|
|
Assert.IsTrue(Length(retrievedNestedRecord.Layout.Fields) = 2, 'Retrieved nested record should have 2 fields');
|
|
|
|
Assert.AreEqual(888, retrievedNestedRecord.GetValue<Integer>('NestedValue'), 'Nested integer check failed after SetValue');
|
|
Assert
|
|
.AreEqual('Manual Nested Set', retrievedNestedRecord.GetValue<string>('NestedString'), 'Nested string check failed after SetValue');
|
|
|
|
// Assert: Check the outer value as well
|
|
Assert.AreEqual(222, outerRecord.GetValue<Integer>('OuterValue'), 'Outer value check failed after SetValue');
|
|
end;
|
|
|
|
initialization
|
|
TDUnitX.RegisterTestFixture(TTestDataRecord);
|
|
|
|
end.
|