711 lines
31 KiB
ObjectPascal
711 lines
31 KiB
ObjectPascal
unit TestDataTypes;
|
|
|
|
interface
|
|
|
|
uses
|
|
DUnitX.TestFramework;
|
|
|
|
type
|
|
[TestFixture]
|
|
[IgnoreMemoryLeaks]
|
|
TMyTestObject = class
|
|
public
|
|
[Test]
|
|
procedure TestRecords;
|
|
[Test]
|
|
procedure TestRecords_Generic;
|
|
[Test]
|
|
procedure TestTuples;
|
|
[Test]
|
|
procedure TestTuples_Generic;
|
|
[Test]
|
|
procedure TestArrays;
|
|
[Test]
|
|
procedure TestArrays_OptimizedAndGeneric;
|
|
[Test]
|
|
procedure TestTexts;
|
|
[Test]
|
|
procedure TestTexts_OptimizedEmpty;
|
|
[Test]
|
|
procedure TestFloats_OptimizedAndGeneric;
|
|
[Test]
|
|
procedure TestTimestamps;
|
|
[Test]
|
|
procedure TestEnums;
|
|
[Test]
|
|
procedure TestAsString;
|
|
[Test]
|
|
procedure TestAsTValue;
|
|
[Test]
|
|
procedure TestMethods;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.SysUtils,
|
|
System.Math,
|
|
System.Rtti,
|
|
System.TypInfo,
|
|
Myc.Data.Types;
|
|
|
|
procedure TMyTestObject.TestRecords;
|
|
var
|
|
intType: TDataType.TOrdinal;
|
|
floatType: TDataType.TFloat;
|
|
personType1, personType2, otherType: TDataType.TRecord;
|
|
personValue: IDataRecordValue;
|
|
idValue: IDataOrdinalValue;
|
|
floatValue: IDataFloatValue;
|
|
begin
|
|
// This test covers the optimized implementation for 2 fields.
|
|
// --- 1. Setup: Define base types and a record structure ---
|
|
intType := TDataType.Ordinal;
|
|
floatType := TDataType.Float;
|
|
|
|
// --- 2. Test Type Creation and Caching ---
|
|
personType1 := TDataType.RecordOf([TRecordField.Create('ID', intType), TRecordField.Create('Value', floatType)]);
|
|
|
|
// Assertions for the created type
|
|
Assert.IsNotNull(IDataRecordType(personType1), 'RecordType should be created');
|
|
Assert.AreEqual(2, personType1.FieldCount, 'FieldCount should be 2');
|
|
Assert.AreEqual('ID', personType1.Fields[0].Name, 'First field name should be ID');
|
|
Assert.AreSame(IDataType(intType), personType1.Fields[0].DataType, 'First field type should be Integer');
|
|
Assert.AreEqual('Value', personType1.Fields[1].Name, 'Second field name should be Value');
|
|
Assert.AreSame(IDataType(floatType), personType1.Fields[1].DataType, 'Second field type should be Float');
|
|
Assert.AreEqual(0, personType1.IndexOf('ID'), 'IndexOf ID should be 0');
|
|
Assert.AreEqual(1, personType1.IndexOf('Value'), 'IndexOf Value should be 1');
|
|
Assert.AreEqual('Record<ID: Integer, Value: Float>', TDataType(personType1).Name, 'Type name should match expected format');
|
|
|
|
// Test if the same definition returns the same cached instance
|
|
personType2 := TDataType.RecordOf([TRecordField.Create('ID', intType), TRecordField.Create('Value', floatType)]);
|
|
Assert.AreSame(IDataRecordType(personType1), IDataRecordType(personType2), 'Types should be cached and return the same instance');
|
|
|
|
// Test if a different definition returns a new instance
|
|
otherType := TDataType.RecordOf([TRecordField.Create('ID', intType), TRecordField.Create('Data', floatType)]);
|
|
Assert.AreNotSame(IDataRecordType(personType1), IDataRecordType(otherType), 'Different definitions should result in different types');
|
|
|
|
// --- 3. Test Value Creation and Access ---
|
|
personValue := personType1.CreateValue([TDataType.FromOrdinal(123), TDataType.FromFloat(45.67)]);
|
|
|
|
Assert.IsNotNull(personValue, 'RecordValue should be created');
|
|
Assert.AreSame(IDataRecordType(personType1), personValue.DataType, 'Value should have the correct data type');
|
|
|
|
// Access by index
|
|
idValue := TDataType.TValue(personValue.Items[0]).AsOrdinal;
|
|
Assert.AreEqual(Int64(123), idValue.Value, 'Value at index 0 is incorrect');
|
|
floatValue := TDataType.TValue(personValue.Items[1]).AsFloat;
|
|
Assert.AreEqual(45.67, floatValue.Value, 'Value at index 1 is incorrect');
|
|
|
|
// Access by name
|
|
floatValue := TDataType.TValue(personValue.Items[personType1.IndexOf('Value')]).AsFloat;
|
|
Assert.AreEqual(45.67, floatValue.Value, 'Value accessed by name is incorrect');
|
|
|
|
// --- 4. Test Validation and Error Handling ---
|
|
// Test for duplicate field names during type creation
|
|
Assert.WillRaise(
|
|
procedure begin TDataType.RecordOf([TRecordField.Create('ID', intType), TRecordField.Create('ID', floatType)]); end,
|
|
EArgumentException,
|
|
'Duplicate field names should raise an exception'
|
|
);
|
|
|
|
// Test for wrong number of items during value creation
|
|
Assert.WillRaise(
|
|
procedure begin personType1.CreateValue([TDataType.FromOrdinal(99)]); end,
|
|
EArgumentException,
|
|
'Wrong number of items should raise exception'
|
|
);
|
|
|
|
// Test for wrong item type during value creation
|
|
Assert.WillRaise(
|
|
procedure
|
|
begin
|
|
// Passing Float instead of Integer for the first item
|
|
personType1.CreateValue([TDataType.FromFloat(1.0), TDataType.FromFloat(2.0)]);
|
|
end,
|
|
EArgumentException,
|
|
'Wrong item type should raise exception'
|
|
);
|
|
end;
|
|
|
|
procedure TMyTestObject.TestRecords_Generic;
|
|
var
|
|
dataType: TDataType.TRecord;
|
|
dataValue: IDataRecordValue;
|
|
begin
|
|
// Test the generic implementation for records with > 2 fields.
|
|
dataType :=
|
|
TDataType.RecordOf(
|
|
[
|
|
TRecordField.Create('A', TDataType.Ordinal),
|
|
TRecordField.Create('B', TDataType.Text),
|
|
TRecordField.Create('C', TDataType.Float)
|
|
]
|
|
);
|
|
dataValue := dataType.CreateValue([TDataType.FromOrdinal(1), TDataType.FromText('two'), TDataType.FromFloat(3.0)]);
|
|
|
|
Assert.IsNotNull(dataValue, 'Generic record value should be created');
|
|
Assert.AreEqual(3, dataType.FieldCount, 'Generic record should have 3 fields');
|
|
Assert.AreEqual(Int64(1), TDataType.TValue(dataValue.Items[0]).AsOrdinal.Value, 'Field A is incorrect');
|
|
Assert.AreEqual('two', TDataType.TValue(dataValue.Items[1]).AsText.Value, 'Field B is incorrect');
|
|
Assert.AreEqual(3.0, TDataType.TValue(dataValue.Items[2]).AsFloat.Value, 'Field C is incorrect');
|
|
Assert.AreEqual('<A: 1, B: two, C: 3>', dataValue.AsString, 'Generic record AsString is incorrect');
|
|
end;
|
|
|
|
procedure TMyTestObject.TestTuples;
|
|
var
|
|
intValue: IDataOrdinalValue;
|
|
floatValue: IDataFloatValue;
|
|
tuple1, tuple2, tuple3: IDataTupleValue;
|
|
type1, type2, type3: IDataType; // Keep as interface to test AreSame on the raw interface pointer
|
|
begin
|
|
// This test covers optimized implementations for 1 and 2 elements.
|
|
// --- 1. Setup: Create some values ---
|
|
intValue := TDataType.FromOrdinal(123);
|
|
floatValue := TDataType.FromFloat(45.67);
|
|
|
|
// --- 2. Test Value Creation and basic properties ---
|
|
tuple1 := TDataType.FromTuple([intValue, floatValue]);
|
|
|
|
Assert.IsNotNull(tuple1, 'Tuple value should be created');
|
|
Assert.AreEqual(2, tuple1.ItemCount, 'ItemCount should be on the value');
|
|
Assert.AreSame(intValue, tuple1.Items[0], 'Item at index 0 is incorrect');
|
|
Assert.AreSame(floatValue, tuple1.Items[1], 'Item at index 1 is incorrect');
|
|
|
|
// --- 3. Test Singleton Type Behavior ---
|
|
// Create more tuples with different structures
|
|
tuple2 := TDataType.FromTuple([TDataType.FromOrdinal(99), TDataType.FromFloat(1.1)]);
|
|
tuple3 := TDataType.FromTuple([intValue]);
|
|
|
|
// Access the DataType via the underlying interface
|
|
type1 := tuple1.DataType;
|
|
type2 := tuple2.DataType;
|
|
type3 := tuple3.DataType;
|
|
|
|
Assert.IsNotNull(type1, 'DataType interface should be accessible');
|
|
Assert.AreEqual('Tuple', type1.Name, 'The type name for all tuples should be Tuple');
|
|
|
|
// The core test: All tuples, regardless of their content, must share the exact same singleton type object.
|
|
Assert.AreSame(type1, type2, 'All tuple types should be the same singleton instance');
|
|
Assert.AreSame(type1, type3, 'All tuple types should be the same singleton instance');
|
|
end;
|
|
|
|
procedure TMyTestObject.TestTuples_Generic;
|
|
var
|
|
tupleValue: IDataTupleValue;
|
|
begin
|
|
// Test the generic implementation for tuples with > 5 elements.
|
|
tupleValue :=
|
|
TDataType.FromTuple(
|
|
[
|
|
TDataType.FromOrdinal(1),
|
|
TDataType.FromOrdinal(2),
|
|
TDataType.FromOrdinal(3),
|
|
TDataType.FromOrdinal(4),
|
|
TDataType.FromOrdinal(5),
|
|
TDataType.FromOrdinal(6)
|
|
]
|
|
);
|
|
|
|
Assert.IsNotNull(tupleValue, 'Generic tuple value should be created');
|
|
Assert.AreEqual(6, tupleValue.ItemCount, 'Generic tuple should have 6 items');
|
|
Assert.AreEqual(Int64(6), TDataType.TValue(tupleValue.Items[5]).AsOrdinal.Value, 'Last item is incorrect');
|
|
Assert.AreEqual('(1, 2, 3, 4, 5, 6)', tupleValue.AsString, 'Generic tuple AsString is incorrect');
|
|
end;
|
|
|
|
procedure TMyTestObject.TestArrays;
|
|
var
|
|
intType: TDataType.TOrdinal;
|
|
floatType: TDataType.TFloat;
|
|
intArrayType1, intArrayType2: TDataType.TArray;
|
|
floatArrayType: TDataType.TArray;
|
|
arrayValue: IDataArrayValue;
|
|
v1, v2: IDataOrdinalValue;
|
|
begin
|
|
// --- 1. Setup ---
|
|
intType := TDataType.Ordinal;
|
|
floatType := TDataType.Float;
|
|
|
|
// --- 2. Test Type Creation and Caching ---
|
|
intArrayType1 := TDataType.ArrayOf(intType);
|
|
|
|
Assert.IsNotNull(IDataArrayType(intArrayType1), 'ArrayType should be created');
|
|
Assert.AreSame(IDataType(intType), intArrayType1.ElementType, 'ElementType should be Integer');
|
|
Assert.AreEqual('Array<Integer>', TDataType(intArrayType1).Name, 'Type name should be Array<Integer>');
|
|
|
|
// Test caching
|
|
intArrayType2 := TDataType.ArrayOf(intType);
|
|
Assert.AreSame(IDataArrayType(intArrayType1), IDataArrayType(intArrayType2), 'Array types should be cached');
|
|
|
|
// Test uniqueness
|
|
floatArrayType := TDataType.ArrayOf(floatType);
|
|
Assert.AreNotSame(
|
|
IDataArrayType(intArrayType1),
|
|
IDataArrayType(floatArrayType),
|
|
'Different element types should result in different array types'
|
|
);
|
|
Assert.AreEqual('Array<Float>', TDataType(floatArrayType).Name, 'Type name should be Array<Float>');
|
|
|
|
// --- 3. Test Value Creation and Access ---
|
|
v1 := TDataType.FromOrdinal(10);
|
|
v2 := TDataType.FromOrdinal(20);
|
|
arrayValue := intArrayType1.CreateValue([v1, v2]);
|
|
|
|
Assert.IsNotNull(arrayValue, 'ArrayValue should be created');
|
|
Assert.AreEqual(2, arrayValue.ElementCount, 'ElementCount should be 2');
|
|
|
|
// Access items and check values
|
|
Assert.AreEqual(Int64(10), TDataType.TValue(arrayValue.Items[0]).AsOrdinal.Value, 'Item at index 0 is incorrect');
|
|
Assert.AreEqual(Int64(20), TDataType.TValue(arrayValue.Items[1]).AsOrdinal.Value, 'Item at index 1 is incorrect');
|
|
|
|
// --- 4. Test Validation and Error Handling ---
|
|
// Test creating an array with a nil element type
|
|
Assert.WillRaise(procedure begin TDataType.ArrayOf(nil); end, EArgumentException, 'Nil element type should raise an exception');
|
|
|
|
// Test creating a value with an incorrect element type (homogeneity check)
|
|
Assert.WillRaise(
|
|
procedure
|
|
begin
|
|
// Try to add a float value to an Array<Integer>
|
|
intArrayType1.CreateValue([v1, TDataType.FromFloat(3.14)]);
|
|
end,
|
|
EArgumentException,
|
|
'Wrong element type should raise an exception'
|
|
);
|
|
end;
|
|
|
|
procedure TMyTestObject.TestArrays_OptimizedAndGeneric;
|
|
var
|
|
arrayType: TDataType.TArray;
|
|
empty1, empty2, single, generic: IDataArrayValue;
|
|
begin
|
|
arrayType := TDataType.ArrayOf(TDataType.Ordinal);
|
|
|
|
// Test optimized path for 0 elements (cached singleton per type)
|
|
empty1 := arrayType.CreateValue([]);
|
|
empty2 := arrayType.CreateValue([]);
|
|
Assert.IsNotNull(empty1, 'Empty array value should be created');
|
|
Assert.AreSame(empty1, empty2, 'Empty array value should be cached and reused');
|
|
Assert.AreEqual(0, empty1.ElementCount, 'Empty array should have 0 elements');
|
|
Assert.AreEqual('[]', empty1.AsString, 'Empty array AsString is incorrect');
|
|
|
|
// Test optimized path for 1 element
|
|
single := arrayType.CreateValue([TDataType.FromOrdinal(42)]);
|
|
Assert.IsNotNull(single, 'Single element array should be created');
|
|
Assert.AreEqual(1, single.ElementCount, 'Single element array should have 1 element');
|
|
Assert.AreEqual(Int64(42), TDataType.TValue(single.Items[0]).AsOrdinal.Value, 'Single element value is incorrect');
|
|
Assert.AreEqual('[42]', single.AsString, 'Single element array AsString is incorrect');
|
|
|
|
// Test generic path for > 1 elements
|
|
generic := arrayType.CreateValue([TDataType.FromOrdinal(1), TDataType.FromOrdinal(2), TDataType.FromOrdinal(3)]);
|
|
Assert.IsNotNull(generic, 'Generic array should be created');
|
|
Assert.AreEqual(3, generic.ElementCount, 'Generic array should have 3 elements');
|
|
Assert.AreEqual('[1, 2, 3]', generic.AsString, 'Generic array AsString is incorrect');
|
|
end;
|
|
|
|
procedure TMyTestObject.TestTexts;
|
|
var
|
|
textType: TDataType.TText;
|
|
textValue1, textValue2: IDataTextValue;
|
|
dataValue: IDataValue;
|
|
begin
|
|
// --- 1. Test Type Creation ---
|
|
textType := TDataType.Text;
|
|
|
|
Assert.IsNotNull(IDataTextType(textType), 'TextType should be created');
|
|
Assert.AreEqual('Text', TDataType(textType).Name, 'Type name should be Text');
|
|
Assert.AreEqual(dkText, TDataType(textType).Kind, 'Type kind should be dkText');
|
|
|
|
// --- 2. Test Value Creation and Access ---
|
|
textValue1 := TDataType.FromText('Hello World');
|
|
|
|
Assert.IsNotNull(textValue1, 'TextValue should be created');
|
|
Assert.AreSame(IDataType(textType), textValue1.DataType, 'Value should have the correct data type');
|
|
Assert.AreEqual('Hello World', textValue1.Value, 'Value should be ''Hello World''');
|
|
|
|
// Test another text value
|
|
textValue2 := TDataType.FromText('Delphi');
|
|
Assert.AreEqual('Delphi', textValue2.Value, 'Value should be ''Delphi''');
|
|
|
|
// --- 3. Test AsText casting ---
|
|
dataValue := TDataType.FromText('Test Text');
|
|
Assert.AreEqual('Test Text', TDataType.TValue(dataValue).AsText.Value, 'AsText should return correct value');
|
|
|
|
// Test invalid cast
|
|
dataValue := TDataType.FromOrdinal(123);
|
|
Assert.WillRaise(
|
|
procedure begin TDataType.TValue(dataValue).AsText; end,
|
|
EInvalidCast,
|
|
'Casting non-text to AsText should raise EInvalidCast'
|
|
);
|
|
end;
|
|
|
|
procedure TMyTestObject.TestTexts_OptimizedEmpty;
|
|
var
|
|
empty1, empty2: IDataTextValue;
|
|
begin
|
|
// Test the singleton implementation for the empty string.
|
|
empty1 := TDataType.FromText('');
|
|
empty2 := TDataType.FromText('');
|
|
|
|
Assert.IsNotNull(empty1, 'Empty text value should be created');
|
|
Assert.AreSame(empty1, empty2, 'Empty text value should be a singleton');
|
|
Assert.AreEqual('', empty1.Value, 'Value of empty text should be empty');
|
|
Assert.AreEqual('', empty1.AsString, 'AsString of empty text should be empty');
|
|
end;
|
|
|
|
procedure TMyTestObject.TestFloats_OptimizedAndGeneric;
|
|
var
|
|
zero1, zero2, nan1, nan2, generic: IDataFloatValue;
|
|
begin
|
|
// Test singleton for 0.0
|
|
zero1 := TDataType.FromFloat(0.0);
|
|
zero2 := TDataType.FromFloat(0.0);
|
|
Assert.IsNotNull(zero1, 'Zero float should be created');
|
|
Assert.AreSame(zero1, zero2, 'Zero float should be a singleton');
|
|
Assert.AreEqual(0.0, zero1.Value, 'Value of zero float is incorrect');
|
|
|
|
// Test singleton for NaN
|
|
nan1 := TDataType.FromFloat(NaN);
|
|
nan2 := TDataType.FromFloat(NaN);
|
|
Assert.IsNotNull(nan1, 'NaN float should be created');
|
|
Assert.AreSame(nan1, nan2, 'NaN float should be a singleton');
|
|
Assert.IsTrue(IsNaN(nan1.Value), 'Value of NaN float should be NaN');
|
|
Assert.AreEqual('NaN', nan1.AsString, 'NaN AsString is incorrect');
|
|
|
|
// Test generic path
|
|
generic := TDataType.FromFloat(123.45);
|
|
Assert.IsNotNull(generic, 'Generic float should be created');
|
|
Assert.AreNotSame(zero1, generic, 'Generic float should not be the zero singleton');
|
|
Assert.AreNotSame(nan1, generic, 'Generic float should not be the NaN singleton');
|
|
Assert.AreEqual(123.45, generic.Value, 'Value of generic float is incorrect');
|
|
end;
|
|
|
|
procedure TMyTestObject.TestTimestamps;
|
|
var
|
|
tsType: TDataType.TTimestamp;
|
|
tsValue1, tsValue2: IDataTimestampValue;
|
|
dataValue: IDataValue;
|
|
now: TDateTime;
|
|
begin
|
|
// --- 1. Test Type Creation ---
|
|
tsType := TDataType.Timestamp;
|
|
|
|
Assert.IsNotNull(IDataTimestampType(tsType), 'TimestampType should be created');
|
|
Assert.AreEqual('Timestamp', TDataType(tsType).Name, 'Type name should be Timestamp');
|
|
Assert.AreEqual(dkTimestamp, TDataType(tsType).Kind, 'Type kind should be dkTimestamp');
|
|
|
|
// --- 2. Test Value Creation and Access ---
|
|
now := System.Sysutils.Now;
|
|
tsValue1 := TDataType.FromTimestamp(now);
|
|
|
|
Assert.IsNotNull(tsValue1, 'TimestampValue should be created');
|
|
Assert.AreSame(IDataType(tsType), tsValue1.DataType, 'Value should have the correct data type');
|
|
Assert.AreEqual(now, tsValue1.Value, 'Value should be the same');
|
|
|
|
// Test another text value
|
|
tsValue2 := TDataType.FromTimestamp(Date);
|
|
Assert.AreEqual(Date, tsValue2.Value, 'Value should be the same');
|
|
|
|
// --- 3. Test AsTimestamp casting ---
|
|
dataValue := TDataType.FromTimestamp(now);
|
|
Assert.AreEqual(now, TDataType.TValue(dataValue).AsTimestamp.Value, 'AsTimestamp should return correct value');
|
|
|
|
// Test invalid cast
|
|
dataValue := TDataType.FromOrdinal(123);
|
|
Assert.WillRaise(
|
|
procedure begin TDataType.TValue(dataValue).AsTimestamp; end,
|
|
EInvalidCast,
|
|
'Casting non-timestamp to AsTimestamp should raise EInvalidCast'
|
|
);
|
|
end;
|
|
|
|
procedure TMyTestObject.TestEnums;
|
|
var
|
|
colorType: TDataType.TEnum;
|
|
red, green, blue: IDataEnumValue;
|
|
begin
|
|
// --- 1. Test Type Creation ---
|
|
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
|
|
|
|
Assert.IsNotNull(IDataEnumType(colorType), 'EnumType should be created');
|
|
Assert.AreEqual('Color', TDataType(colorType).Name, 'Name should be Color');
|
|
Assert.AreEqual(dkEnum, TDataType(colorType).Kind, 'Kind should be dkEnum');
|
|
Assert.AreEqual(3, colorType.IdentifierCount, 'IdentifierCount should be 3');
|
|
Assert.AreEqual('Red', colorType.Identifiers[0], 'Identifier at index 0 should be Red');
|
|
Assert.AreEqual('Green', colorType.Identifiers[1], 'Identifier at index 1 should be Green');
|
|
Assert.AreEqual('Blue', colorType.Identifiers[2], 'Identifier at index 2 should be Blue');
|
|
Assert.AreEqual(1, colorType.IndexOf('Green'), 'IndexOf Green should be 1');
|
|
Assert.AreEqual(-1, colorType.IndexOf('Yellow'), 'IndexOf Yellow should be -1');
|
|
|
|
// --- 2. Test Value Creation and Access ---
|
|
red := colorType.CreateValue(0);
|
|
green := colorType.CreateValue('Green');
|
|
blue := colorType.CreateValue(2);
|
|
|
|
Assert.IsNotNull(red, 'EnumValue from index should be created');
|
|
Assert.AreSame(IDataType(colorType), red.DataType, 'Red should have the correct data type');
|
|
Assert.AreEqual(0, red.Value, 'Value of Red should be 0');
|
|
Assert.AreEqual('Red', red.AsString, 'AsText of Red should be Red');
|
|
|
|
Assert.IsNotNull(green, 'EnumValue from identifier should be created');
|
|
Assert.AreSame(IDataType(colorType), green.DataType, 'Green should have the correct data type');
|
|
Assert.AreEqual(1, green.Value, 'Value of Green should be 1');
|
|
Assert.AreEqual('Green', green.AsString, 'AsText of Green should be Green');
|
|
|
|
Assert.AreEqual(2, blue.Value, 'Value of Blue should be 2');
|
|
|
|
// --- 3. Test Validation and Error Handling ---
|
|
// Test creating a type with duplicate identifiers
|
|
Assert.WillRaise(
|
|
procedure begin TDataType.EnumOf('Fails', ['A', 'B', 'A']); end,
|
|
EArgumentException,
|
|
'Duplicate identifiers should raise an exception'
|
|
);
|
|
|
|
// Test creating a value with an invalid index
|
|
Assert.WillRaise(procedure begin colorType.CreateValue(3); end, EArgumentException, 'Invalid index should raise an exception');
|
|
Assert.WillRaise(procedure begin colorType.CreateValue(-1); end, EArgumentException, 'Negative index should raise an exception');
|
|
|
|
// Test creating a value with an unknown identifier
|
|
Assert.WillRaise(
|
|
procedure begin colorType.CreateValue('Yellow'); end,
|
|
EArgumentException,
|
|
'Unknown identifier should raise an exception'
|
|
);
|
|
end;
|
|
|
|
procedure TMyTestObject.TestAsString;
|
|
var
|
|
ordinalValue: IDataOrdinalValue;
|
|
floatValue: IDataFloatValue;
|
|
textValue: IDataTextValue;
|
|
tsValue: IDataTimestampValue;
|
|
enumValue: IDataEnumValue;
|
|
arrayValue: IDataArrayValue;
|
|
recordValue: IDataRecordValue;
|
|
tupleValue: IDataTupleValue;
|
|
colorType: TDataType.TEnum;
|
|
personType: TDataType.TRecord;
|
|
intArrayType: TDataType.TArray;
|
|
now: TDateTime;
|
|
begin
|
|
// Ordinal
|
|
ordinalValue := TDataType.FromOrdinal(123);
|
|
Assert.AreEqual('123', ordinalValue.AsString, 'Ordinal AsString incorrect');
|
|
|
|
// Float
|
|
floatValue := TDataType.FromFloat(45.67);
|
|
Assert.AreEqual(FloatToStr(45.67), floatValue.AsString, 'Float AsString incorrect');
|
|
|
|
// Text
|
|
textValue := TDataType.FromText('Hello');
|
|
Assert.AreEqual('Hello', textValue.AsString, 'Text AsString incorrect');
|
|
|
|
// Timestamp
|
|
now := System.Sysutils.Now;
|
|
tsValue := TDataType.FromTimestamp(now);
|
|
Assert.AreEqual(DateTimeToStr(now), tsValue.AsString, 'Timestamp AsString incorrect');
|
|
|
|
// Enum
|
|
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
|
|
enumValue := colorType.CreateValue('Green');
|
|
Assert.AreEqual('Green', enumValue.AsString, 'Enum AsString incorrect');
|
|
|
|
// Array
|
|
intArrayType := TDataType.ArrayOf(TDataType.Ordinal);
|
|
arrayValue := intArrayType.CreateValue([TDataType.FromOrdinal(1), TDataType.FromOrdinal(2)]);
|
|
Assert.AreEqual('[1, 2]', arrayValue.AsString, 'Array AsString incorrect');
|
|
|
|
// Record
|
|
personType := TDataType.RecordOf([TRecordField.Create('ID', TDataType.Ordinal), TRecordField.Create('Name', TDataType.Text)]);
|
|
recordValue := personType.CreateValue([TDataType.FromOrdinal(1), TDataType.FromText('Bob')]);
|
|
Assert.AreEqual('<ID: 1, Name: Bob>', recordValue.AsString, 'Record AsString incorrect');
|
|
|
|
// Tuple
|
|
tupleValue := TDataType.FromTuple([TDataType.FromOrdinal(10), TDataType.FromText('Tuple')]);
|
|
Assert.AreEqual('(10, Tuple)', tupleValue.AsString, 'Tuple AsString incorrect');
|
|
end;
|
|
|
|
procedure TMyTestObject.TestAsTValue;
|
|
var
|
|
dataValue: IDataValue; // Keep as generic interface to test AsTValue
|
|
tv, element: TValue;
|
|
now: TDateTime;
|
|
colorType: TDataType.TEnum;
|
|
intArrayType: TDataType.TArray;
|
|
personType: TDataType.TRecord;
|
|
begin
|
|
// --- Ordinal ---
|
|
dataValue := TDataType.FromOrdinal(123);
|
|
tv := dataValue.AsTValue;
|
|
Assert.IsFalse(tv.IsEmpty, 'Ordinal TValue should not be empty');
|
|
Assert.AreEqual(tkInt64, tv.Kind, 'Ordinal TValue kind should be tkInt64');
|
|
Assert.AreEqual(Int64(123), tv.AsInt64, 'Ordinal TValue content is incorrect');
|
|
|
|
// --- Float ---
|
|
dataValue := TDataType.FromFloat(45.67);
|
|
tv := dataValue.AsTValue;
|
|
Assert.IsFalse(tv.IsEmpty, 'Float TValue should not be empty');
|
|
Assert.AreEqual(tkFloat, tv.Kind, 'Float TValue kind should be tkFloat');
|
|
Assert.AreEqual(45.67, tv.AsExtended, 'Float TValue content is incorrect');
|
|
|
|
// --- Text ---
|
|
dataValue := TDataType.FromText('Hello');
|
|
tv := dataValue.AsTValue;
|
|
Assert.IsFalse(tv.IsEmpty, 'Text TValue should not be empty');
|
|
Assert.AreEqual(tkUString, tv.Kind, 'Text TValue kind should be tkUString');
|
|
Assert.AreEqual('Hello', tv.AsString, 'Text TValue content is incorrect');
|
|
|
|
// --- Timestamp ---
|
|
now := System.SysUtils.Now;
|
|
dataValue := TDataType.FromTimestamp(now);
|
|
tv := dataValue.AsTValue;
|
|
Assert.IsFalse(tv.IsEmpty, 'Timestamp TValue should not be empty');
|
|
Assert.AreEqual(tkFloat, tv.Kind, 'Timestamp TValue kind is incorrect');
|
|
Assert.AreEqual(now, tv.AsType<TDateTime>, 'Timestamp TValue content is incorrect');
|
|
|
|
// --- Enum ---
|
|
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
|
|
dataValue := colorType.CreateValue('Green');
|
|
tv := dataValue.AsTValue;
|
|
Assert.IsFalse(tv.IsEmpty, 'Enum TValue should not be empty');
|
|
Assert.AreEqual(tkInteger, tv.Kind, 'Enum TValue kind should be tkInteger');
|
|
Assert.AreEqual(1, tv.AsInteger, 'Enum TValue content is incorrect');
|
|
|
|
// --- Array ---
|
|
intArrayType := TDataType.ArrayOf(TDataType.Ordinal);
|
|
dataValue := intArrayType.CreateValue([TDataType.FromOrdinal(10), TDataType.FromOrdinal(20)]);
|
|
tv := dataValue.AsTValue;
|
|
Assert.IsFalse(tv.IsEmpty, 'Array TValue should not be empty');
|
|
Assert.IsTrue(tv.IsArray, 'Array TValue should be an array');
|
|
Assert.AreEqual(tkDynArray, tv.Kind, 'Array TValue kind should be tkDynArray');
|
|
Assert.AreEqual(NativeInt(2), tv.GetArrayLength, 'Array TValue length is incorrect');
|
|
|
|
// Correctly unwrap the nested TValue before asserting kind and value
|
|
element := tv.GetArrayElement(0).AsType<TValue>;
|
|
Assert.AreEqual(tkInt64, element.Kind, 'Unwrapped array element 0 kind is incorrect');
|
|
Assert.AreEqual(Int64(10), element.AsInt64, 'Array TValue element 0 is incorrect');
|
|
|
|
element := tv.GetArrayElement(1).AsType<TValue>;
|
|
Assert.AreEqual(Int64(20), element.AsInt64, 'Array TValue element 1 is incorrect');
|
|
|
|
// Check empty array
|
|
dataValue := intArrayType.CreateValue([]);
|
|
tv := dataValue.AsTValue;
|
|
Assert.IsTrue(tv.IsArray, 'Empty Array TValue should be an array');
|
|
Assert.AreEqual(NativeInt(0), tv.GetArrayLength, 'Empty Array TValue length is incorrect');
|
|
|
|
// --- Record ---
|
|
personType := TDataType.RecordOf([TRecordField.Create('ID', TDataType.Ordinal), TRecordField.Create('Name', TDataType.Text)]);
|
|
dataValue := personType.CreateValue([TDataType.FromOrdinal(1), TDataType.FromText('Bob')]);
|
|
tv := dataValue.AsTValue;
|
|
Assert.IsFalse(tv.IsEmpty, 'Record TValue should not be empty');
|
|
Assert.IsTrue(tv.IsArray, 'Record TValue should be represented as an array');
|
|
Assert.AreEqual(tkDynArray, tv.Kind, 'Record TValue kind should be tkDynArray');
|
|
Assert.AreEqual(NativeInt(2), tv.GetArrayLength, 'Record TValue length is incorrect');
|
|
Assert.AreEqual(Int64(1), tv.GetArrayElement(0).AsType<TValue>.AsInt64, 'Record TValue element 0 is incorrect');
|
|
Assert.AreEqual('Bob', tv.GetArrayElement(1).AsType<TValue>.AsString, 'Record TValue element 1 is incorrect');
|
|
|
|
// --- Tuple ---
|
|
dataValue := TDataType.FromTuple([TDataType.FromOrdinal(99), TDataType.FromText('Red')]);
|
|
tv := dataValue.AsTValue;
|
|
Assert.IsFalse(tv.IsEmpty, 'Tuple TValue should not be empty');
|
|
Assert.IsTrue(tv.IsArray, 'Tuple TValue should be represented as an array');
|
|
Assert.AreEqual(tkDynArray, tv.Kind, 'Tuple TValue kind should be tkDynArray');
|
|
Assert.AreEqual(NativeInt(2), tv.GetArrayLength, 'Tuple TValue length is incorrect');
|
|
Assert.AreEqual(Int64(99), tv.GetArrayElement(0).AsType<TValue>.AsInt64, 'Tuple TValue element 0 is incorrect');
|
|
Assert.AreEqual('Red', tv.GetArrayElement(1).AsType<TValue>.AsString, 'Tuple TValue element 1 is incorrect');
|
|
end;
|
|
|
|
procedure TMyTestObject.TestMethods;
|
|
var
|
|
ordinalType: TDataType.TOrdinal;
|
|
textType: TDataType.TText;
|
|
methodType1, methodType2, otherMethodType: TDataType.TMethod;
|
|
myFunc: TDataType.TMethod.TProc;
|
|
methodValue: IDataMethodValue;
|
|
inputValue: IDataOrdinalValue;
|
|
resultValue: IDataValue;
|
|
tv: TValue;
|
|
begin
|
|
// --- 1. Setup: Define base types ---
|
|
ordinalType := TDataType.Ordinal;
|
|
textType := TDataType.Text;
|
|
|
|
// --- 2. Test Type Creation and Caching ---
|
|
methodType1 := TDataType.MethodOf(ordinalType, textType);
|
|
|
|
// Assertions for the created type
|
|
Assert.IsNotNull(IDataMethodType(methodType1), 'MethodType should be created');
|
|
Assert.AreEqual(dkMethod, TDataType(methodType1).Kind, 'Kind should be dkMethod');
|
|
Assert.AreSame(IDataType(ordinalType), methodType1.ArgType, 'ArgType should be Ordinal');
|
|
Assert.AreSame(IDataType(textType), methodType1.ResultType, 'ResultType should be Text');
|
|
Assert.AreEqual('Method<Integer -> Text>', TDataType(methodType1).Name, 'Type name should match expected format');
|
|
|
|
// Test if the same definition returns the same cached instance
|
|
methodType2 := TDataType.MethodOf(ordinalType, textType);
|
|
Assert
|
|
.AreSame(IDataMethodType(methodType1), IDataMethodType(methodType2), 'Method types should be cached and return the same instance');
|
|
|
|
// Test if a different definition returns a new instance
|
|
otherMethodType := TDataType.MethodOf(textType, ordinalType);
|
|
Assert.AreNotSame(
|
|
IDataMethodType(methodType1),
|
|
IDataMethodType(otherMethodType),
|
|
'Different signatures should result in different types'
|
|
);
|
|
Assert.AreEqual('Method<Text -> Integer>', TDataType(otherMethodType).Name, 'Name of other method type is incorrect');
|
|
|
|
// --- 3. Test Value Creation ---
|
|
// Define a function that matches the signature Ordinal -> Text
|
|
myFunc :=
|
|
function(const AValue: TDataType.TValue): TDataType.TValue
|
|
var
|
|
ordinalValue: IDataOrdinalValue;
|
|
val: Int64;
|
|
begin
|
|
ordinalValue := AValue.AsOrdinal;
|
|
val := ordinalValue.Value;
|
|
Result := TDataType.FromText(val.ToString);
|
|
end;
|
|
|
|
methodValue := TDataType.FromMethod(methodType1, myFunc);
|
|
Assert.IsNotNull(methodValue, 'MethodValue should be created');
|
|
Assert.AreSame(IDataType(methodType1), methodValue.DataType, 'Value should have the correct data type');
|
|
|
|
// --- 4. Test Execution (Simulated) ---
|
|
inputValue := TDataType.FromOrdinal(42);
|
|
// Execute the function retrieved from the value
|
|
resultValue := methodValue.Value(inputValue);
|
|
|
|
Assert.IsNotNull(resultValue, 'Execution result should not be nil');
|
|
Assert.AreSame(IDataType(textType), resultValue.DataType, 'Result value should have Text type');
|
|
Assert.AreEqual('42', TDataType.TValue(resultValue).AsText.Value, 'Result value content is incorrect');
|
|
|
|
// --- 5. Test AsString and AsTValue ---
|
|
Assert.AreEqual('<METHOD>', methodValue.AsString, 'Method AsString is incorrect');
|
|
|
|
tv := methodValue.AsTValue;
|
|
Assert.IsFalse(tv.IsEmpty, 'Method TValue should not be empty');
|
|
Assert.AreEqual(tkInterface, tv.Kind, 'TValue kind for a TMethodProc should be tkInterface, as it is a reference-to-function');
|
|
Assert.IsTrue(TypeInfo(TDataMethodProc) = tv.TypeInfo, 'TValue should hold TMethodProc type info');
|
|
|
|
// --- 6. Test Validation and Error Handling ---
|
|
// Test creating a value with a nil type
|
|
Assert.WillRaise(
|
|
procedure begin TDataType.FromMethod(nil, myFunc); end,
|
|
EArgumentException,
|
|
'FromMethod with nil type should raise an exception'
|
|
);
|
|
|
|
// Test creating a value with a nil function
|
|
Assert.WillRaise(
|
|
procedure begin TDataType.FromMethod(methodType1, nil); end,
|
|
EArgumentException,
|
|
'FromMethod with nil function should raise an exception'
|
|
);
|
|
end;
|
|
|
|
end.
|