unit TestDataTypes; interface uses DUnitX.TestFramework; type [TestFixture] TMyTestObject = class public [Test] procedure TestCreateRecords; [Test] procedure TestTuples; [Test] procedure TestArrays; [Test] procedure TestTexts; [Test] procedure TestTimestamps; end; implementation uses System.SysUtils, Myc.Data.Types; { TMyTestObject } procedure TMyTestObject.TestCreateRecords; var intType: IDataType; floatType: IDataType; personType1, personType2, otherType: IDataRecordType; personValue: IDataValue; idValue: IDataOrdinalValue; floatValue: IDataFloatValue; begin // --- 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(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(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(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', 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(personType1, 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(personType1, otherType, 'Different definitions should result in different types'); // --- 3. Test Value Creation and Access --- personValue := personType1.CreateValue([TDataValue.FromOrdinal(123), TDataValue.FromFloat(45.67)]); Assert.IsNotNull(TDataValue(personValue).AsRecord, 'RecordValue should be created'); Assert.AreSame(personType1, TDataValue(personValue).DataType, 'Value should have the correct data type'); // Access by index idValue := TDataValue(TDataValue(personValue).AsRecord.Items[0]).AsOrdinal; Assert.AreEqual(Int64(123), idValue.Value, 'Value at index 0 is incorrect'); floatValue := TDataValue(TDataValue(personValue).AsRecord.Items[1]).AsFloat; Assert.AreEqual(45.67, floatValue.Value, 'Value at index 1 is incorrect'); // Access by name floatValue := TDataValue(TDataValue(personValue).AsRecord.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([TDataValue.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([TDataValue.FromFloat(1.0), TDataValue.FromFloat(2.0)]); end, EArgumentException, 'Wrong item type should raise exception' ); end; procedure TMyTestObject.TestTuples; var intValue: IDataValue; floatValue: IDataValue; tuple1, tuple2, tuple3: IDataTupleValue; type1, type2, type3: IDataType; begin // --- 1. Setup: Create some values --- intValue := TDataValue.FromOrdinal(123); floatValue := TDataValue.FromFloat(45.67); // --- 2. Test Value Creation and basic properties --- tuple1 := TDataValue.FromTuple([intValue, floatValue]); Assert.IsNotNull(tuple1, 'Tuple value should be created'); Assert.AreEqual(2, TDataValue(tuple1).AsTuple.ItemCount, 'ItemCount should be on the value'); Assert.AreSame(intValue, TDataValue(tuple1).AsTuple.Items[0], 'Item at index 0 is incorrect'); Assert.AreSame(floatValue, TDataValue(tuple1).AsTuple.Items[1], 'Item at index 1 is incorrect'); // --- 3. Test Singleton Type Behavior --- // Create more tuples with different structures tuple2 := TDataValue.FromTuple([TDataValue.FromOrdinal(99), TDataValue.FromFloat(1.1)]); tuple3 := TDataValue.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.TestArrays; var intType: IDataType; floatType: IDataType; intArrayType1, intArrayType2, floatArrayType: IDataArrayType; arrayValue: IDataArrayValue; v1, v2: IDataValue; begin // --- 1. Setup --- intType := TDataType.Ordinal; floatType := TDataType.Float; // --- 2. Test Type Creation and Caching --- intArrayType1 := TDataType.ArrayOfType(intType); Assert.IsNotNull(intArrayType1, 'ArrayType should be created'); Assert.AreSame(intType, intArrayType1.ElementType, 'ElementType should be Integer'); Assert.AreEqual('Array', intArrayType1.Name, 'Type name should be Array'); // Test caching intArrayType2 := TDataType.ArrayOfType(intType); Assert.AreSame(intArrayType1, intArrayType2, 'Array types should be cached'); // Test uniqueness floatArrayType := TDataType.ArrayOfType(floatType); Assert.AreNotSame(intArrayType1, floatArrayType, 'Different element types should result in different array types'); Assert.AreEqual('Array', floatArrayType.Name, 'Type name should be Array'); // --- 3. Test Value Creation and Access --- v1 := TDataValue.FromOrdinal(10); v2 := TDataValue.FromOrdinal(20); arrayValue := intArrayType1.CreateValue([v1, v2]); Assert.IsNotNull(arrayValue, 'ArrayValue should be created'); Assert.AreEqual(2, TDataValue(arrayValue).AsArray.ElementCount, 'ElementCount should be 2'); // Access items and check values Assert.AreEqual(Int64(10), TDataValue(TDataValue(arrayValue).AsArray.Items[0]).AsOrdinal.Value, 'Item at index 0 is incorrect'); Assert.AreEqual(Int64(20), TDataValue(TDataValue(arrayValue).AsArray.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.ArrayOfType(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 intArrayType1.CreateValue([v1, TDataValue.FromFloat(3.14)]); end, EArgumentException, 'Wrong element type should raise an exception' ); end; procedure TMyTestObject.TestTexts; var textType: IDataTextType; textValue1, textValue2: IDataTextValue; dataValue: IDataValue; begin // --- 1. Test Type Creation --- textType := TDataType.Text; Assert.IsNotNull(textType, 'TextType should be created'); Assert.AreEqual('Text', textType.Name, 'Type name should be Text'); Assert.AreEqual(dkText, textType.Kind, 'Type kind should be dkText'); // --- 2. Test Value Creation and Access --- textValue1 := TDataValue.FromText('Hello World'); Assert.IsNotNull(textValue1, 'TextValue should be created'); Assert.AreSame(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 := TDataValue.FromText('Delphi'); Assert.AreEqual('Delphi', textValue2.Value, 'Value should be ''Delphi'''); // --- 3. Test AsText casting --- dataValue := TDataValue.FromText('Test Text'); Assert.AreEqual('Test Text', TDataValue(dataValue).AsText.Value, 'AsText should return correct value'); // Test invalid cast dataValue := TDataValue.FromOrdinal(123); Assert .WillRaise(procedure begin TDataValue(dataValue).AsText; end, EInvalidCast, 'Casting non-text to AsText should raise EInvalidCast'); end; procedure TMyTestObject.TestTimestamps; var tsType: IDataTimestampType; tsValue1, tsValue2: IDataTimestampValue; dataValue: IDataValue; now: TDateTime; begin // --- 1. Test Type Creation --- tsType := TDataType.Timestamp; Assert.IsNotNull(tsType, 'TimestampType should be created'); Assert.AreEqual('Timestamp', tsType.Name, 'Type name should be Timestamp'); Assert.AreEqual(dkTimestamp, tsType.Kind, 'Type kind should be dkTimestamp'); // --- 2. Test Value Creation and Access --- now := Now; tsValue1 := TDataValue.FromTimestamp(now); Assert.IsNotNull(tsValue1, 'TimestampValue should be created'); Assert.AreSame(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 := TDataValue.FromTimestamp(Date); Assert.AreEqual(Date, tsValue2.Value, 'Value should be the same'); // --- 3. Test AsTimestamp casting --- dataValue := TDataValue.FromTimestamp(now); Assert.AreEqual(now, TDataValue(dataValue).AsTimestamp.Value, 'AsTimestamp should return correct value'); // Test invalid cast dataValue := TDataValue.FromOrdinal(123); Assert.WillRaise( procedure begin TDataValue(dataValue).AsTimestamp; end, EInvalidCast, 'Casting non-timestamp to AsTimestamp should raise EInvalidCast' ); end; initialization TDUnitX.RegisterTestFixture(TMyTestObject); end.