// Copyright Epic Games, Inc. All Rights Reserved. #if WITH_TESTS #include "Algo/ForEach.h" #include "CoreMinimal.h" #include "Misc/Guid.h" #include "Serialization/MemoryWriter.h" #include "Serialization/MemoryReader.h" #include "Misc/AutomationTest.h" #include "Templates/SubclassOf.h" #include "Backends/JsonStructDeserializerBackend.h" #include "Backends/JsonStructSerializerBackend.h" #include "Backends/CborStructDeserializerBackend.h" #include "Backends/CborStructSerializerBackend.h" #include "StructDeserializer.h" #include "StructSerializer.h" #include "Tests/StructSerializerTestTypes.h" #include "Tests/TestHarnessAdapter.h" #include "UObject/CoreNet.h" #if WITH_LOW_LEVEL_TESTS #include "TestCommon/Expectations.h" #endif /* Internal helpers *****************************************************************************/ namespace StructSerializerTest { template void CopyKeys(TMap& OutMap, const TMap& SourceMap) { OutMap.Empty(SourceMap.Num()); Algo::ForEach(SourceMap, [&OutMap](const TPair& Other) { OutMap.Add(Other.Key); }); } template<> void CopyKeys(TMap& OutMap, const TMap& SourceMap) { OutMap.Empty(SourceMap.Num()); Algo::ForEach(SourceMap, [&OutMap](const TPair& Other) { OutMap.Add(Other.Key, FVector(76.7f)); }); } template<> void CopyKeys(TMap& OutMap, const TMap& SourceMap) { OutMap.Empty(SourceMap.Num()); Algo::ForEach(SourceMap, [&OutMap](const TPair& Other) { OutMap.Add(Other.Key, { NoInit }); }); } void ValidateNumerics(const FStructSerializerNumericTestStruct& Struct1, const FStructSerializerNumericTestStruct& Struct2) { CHECK_EQUALS(TEXT("Numerics.Int8 value must be the same before and after de-/serialization"), Struct1.Int8, Struct2.Int8); CHECK_EQUALS(TEXT("Numerics.Int16 value must be the same before and after de-/serialization"), Struct1.Int16, Struct2.Int16); CHECK_EQUALS(TEXT("Numerics.Int32 value must be the same before and after de-/serialization"), Struct1.Int32, Struct2.Int32); CHECK_EQUALS(TEXT("Numerics.Int64 value must be the same before and after de-/serialization"), Struct1.Int64, Struct2.Int64); CHECK_EQUALS(TEXT("Numerics.UInt8 value must be the same before and after de-/serialization"), Struct1.UInt8, Struct2.UInt8); CHECK_EQUALS(TEXT("Numerics.UInt16 value must be the same before and after de-/serialization"), Struct1.UInt16, Struct2.UInt16); CHECK_EQUALS(TEXT("Numerics.UInt32 value must be the same before and after de-/serialization"), Struct1.UInt32, Struct2.UInt32); CHECK_EQUALS(TEXT("Numerics.UInt64 value must be the same before and after de-/serialization"), Struct1.UInt64, Struct2.UInt64); CHECK_EQUALS(TEXT("Numerics.Float value must be the same before and after de-/serialization"), Struct1.Float, Struct2.Float); CHECK_EQUALS(TEXT("Numerics.Double value must be the same before and after de-/serialization"), Struct1.Double, Struct2.Double); } void ValidateBooleans(const FStructSerializerBooleanTestStruct& Struct1, const FStructSerializerBooleanTestStruct& Struct2) { CHECK_EQUALS(TEXT("Booleans.BoolFalse must be the same before and after de-/serialization"), Struct1.BoolFalse, Struct2.BoolFalse); CHECK_EQUALS(TEXT("Booleans.BoolTrue must be the same before and after de-/serialization"), Struct1.BoolTrue, Struct2.BoolTrue); CHECK_EQUALS(TEXT("Booleans.Bitfield0 must be the same before and after de-/serialization"), Struct1.Bitfield0, Struct2.Bitfield0); CHECK_EQUALS(TEXT("Booleans.Bitfield1 must be the same before and after de-/serialization"), Struct1.Bitfield1, Struct2.Bitfield1); CHECK_EQUALS(TEXT("Booleans.Bitfield2Set must be the same before and after de-/serialization"), Struct1.Bitfield2Set, Struct2.Bitfield2Set); CHECK_EQUALS(TEXT("Booleans.Bitfield3 must be the same before and after de-/serialization"), Struct1.Bitfield3, Struct2.Bitfield3); CHECK_EQUALS(TEXT("Booleans.Bitfield4Set must be the same before and after de-/serialization"), Struct1.Bitfield4Set, Struct2.Bitfield4Set); CHECK_EQUALS(TEXT("Booleans.Bitfield5Set must be the same before and after de-/serialization"), Struct1.Bitfield5Set, Struct2.Bitfield5Set); CHECK_EQUALS(TEXT("Booleans.Bitfield6 must be the same before and after de-/serialization"), Struct1.Bitfield6, Struct2.Bitfield6); CHECK_EQUALS(TEXT("Booleans.Bitfield7 must be the same before and after de-/serialization"), Struct1.Bitfield7Set, Struct2.Bitfield7Set); } void ValidateObjects(const FStructSerializerObjectTestStruct& Struct1, const FStructSerializerObjectTestStruct& Struct2) { CHECK_EQUALS(TEXT("Objects.RawClass must be the same before and after de-/serialization"), Struct1.RawClass, Struct2.RawClass); CHECK_EQUALS(TEXT("Objects.WrappedClass must be the same before and after de-/serialization"), Struct1.WrappedClass, Struct2.WrappedClass); CHECK_EQUALS(TEXT("Objects.SubClass must be the same before and after de-/serialization"), Struct1.SubClass, Struct2.SubClass); CHECK_EQUALS(TEXT("Objects.SoftClass must be the same before and after de-/serialization"), Struct1.SoftClass, Struct2.SoftClass); CHECK_EQUALS(TEXT("Objects.RawObject must be the same before and after de-/serialization"), Struct1.RawObject, Struct2.RawObject); CHECK_EQUALS(TEXT("Objects.WrappedObject must be the same before and after de-/serialization"), Struct1.WrappedObject, Struct2.WrappedObject); CHECK_EQUALS(TEXT("Objects.WeakObject must be the same before and after de-/serialization"), Struct1.WeakObject, Struct2.WeakObject); CHECK_EQUALS(TEXT("Objects.SoftObject must be the same before and after de-/serialization"), Struct1.SoftObject, Struct2.SoftObject); CHECK_EQUALS(TEXT("Objects.ClassPath must be the same before and after de-/serialization"), Struct1.ClassPath, Struct2.ClassPath); CHECK_EQUALS(TEXT("Objects.ObjectPath must be the same before and after de-/serialization"), Struct1.ObjectPath, Struct2.ObjectPath); } void ValidateBuiltIns(const FStructSerializerBuiltinTestStruct& Struct1, const FStructSerializerBuiltinTestStruct& Struct2) { CHECK_EQUALS(TEXT("Builtins.Guid must be the same before and after de-/serialization"), Struct1.Guid, Struct2.Guid); CHECK_EQUALS(TEXT("Builtins.Name must be the same before and after de-/serialization"), Struct1.Name, Struct2.Name); CHECK_EQUALS(TEXT("Builtins.String must be the same before and after de-/serialization"), Struct1.String, Struct2.String); CHECK_EQUALS(TEXT("Builtins.Text must be the same before and after de-/serialization"), Struct1.Text.ToString(), Struct2.Text.ToString()); CHECK_EQUALS(TEXT("Builtins.Datetime must be the same before and after de-/serialization"), Struct1.Datetime, Struct2.Datetime); CHECK_EQUALS(TEXT("Builtins.Timespan must be the same before and after de-/serialization"), Struct1.Timespan, Struct2.Timespan); CHECK_EQUALS(TEXT("Builtins.Vector must be the same before and after de-/serialization"), Struct1.Vector, Struct2.Vector); CHECK_EQUALS(TEXT("Builtins.Vector4 must be the same before and after de-/serialization"), Struct1.Vector4, Struct2.Vector4); CHECK_EQUALS(TEXT("Builtins.Rotator must be the same before and after de-/serialization"), Struct1.Rotator, Struct2.Rotator); CHECK_EQUALS(TEXT("Builtins.Quat must be the same before and after de-/serialization"), Struct1.Quat, Struct2.Quat); CHECK_EQUALS(TEXT("Builtins.Color must be the same before and after de-/serialization"), Struct1.Color, Struct2.Color); } void ValidateArrays(const FStructSerializerArrayTestStruct& Struct1, const FStructSerializerArrayTestStruct& Struct2) { CHECK_EQUALS(TEXT("Arrays.Int32Array must be the same before and after de-/serialization"), Struct1.Int32Array, Struct2.Int32Array); CHECK_EQUALS(TEXT("Arrays.ByteArray must be the same before and after de-/serialization"), Struct1.ByteArray, Struct2.ByteArray); CHECK_EQUALS(TEXT("Arrays.StaticSingleElement[0] must be the same before and after de-/serialization"), Struct1.StaticSingleElement[0], Struct2.StaticSingleElement[0]); CHECK_EQUALS(TEXT("Arrays.StaticInt32Array[0] must be the same before and after de-/serialization"), Struct1.StaticInt32Array[0], Struct2.StaticInt32Array[0]); CHECK_EQUALS(TEXT("Arrays.StaticInt32Array[1] must be the same before and after de-/serialization"), Struct1.StaticInt32Array[1], Struct2.StaticInt32Array[1]); CHECK_EQUALS(TEXT("Arrays.StaticInt32Array[2] must be the same before and after de-/serialization"), Struct1.StaticInt32Array[2], Struct2.StaticInt32Array[2]); CHECK_EQUALS(TEXT("Arrays.StaticFloatArray[0] must be the same before and after de-/serialization"), Struct1.StaticFloatArray[0], Struct2.StaticFloatArray[0]); CHECK_EQUALS(TEXT("Arrays.StaticFloatArray[1] must be the same before and after de-/serialization"), Struct1.StaticFloatArray[1], Struct2.StaticFloatArray[1]); CHECK_EQUALS(TEXT("Arrays.StaticFloatArray[2] must be the same before and after de-/serialization"), Struct1.StaticFloatArray[2], Struct2.StaticFloatArray[2]); CHECK_EQUALS(TEXT("Arrays.VectorArray must be the same before and after de-/serialization"), Struct1.VectorArray, Struct2.VectorArray); } void ValidateMaps(const FStructSerializerMapTestStruct& Struct1, const FStructSerializerMapTestStruct& Struct2) { CHECK_MESSAGE(TEXT("Maps.IntToStr must be the same before and after de-/serialization"), Struct1.IntToStr.OrderIndependentCompareEqual(Struct2.IntToStr)); CHECK_MESSAGE(TEXT("Maps.StrToStr must be the same before and after de-/serialization"), Struct1.StrToStr.OrderIndependentCompareEqual(Struct2.StrToStr)); CHECK_MESSAGE(TEXT("Maps.StrToVec must be the same before and after de-/serialization"), Struct1.StrToVec.OrderIndependentCompareEqual(Struct2.StrToVec)); } void ValidateSets(const FStructSerializerSetTestStruct& Struct1, const FStructSerializerSetTestStruct& Struct2) { CHECK_MESSAGE(TEXT("Sets.IntSet must be the same before and after de-/serialization"), Struct1.IntSet.Num() == Struct2.IntSet.Num() && Struct1.IntSet.Difference(Struct2.IntSet).Num() == 0); CHECK_MESSAGE(TEXT("Sets.StrSet must be the same before and after de-/serialization"), Struct1.StrSet.Num() == Struct2.StrSet.Num() && Struct1.StrSet.Difference(Struct2.StrSet).Num() == 0); CHECK_MESSAGE(TEXT("Sets.NameSet must be the same before and after de-/serialization"), Struct1.NameSet.Num() == Struct2.NameSet.Num() && Struct1.NameSet.Difference(Struct2.NameSet).Num() == 0); CHECK_MESSAGE(TEXT("Sets.StructSet must be the same before and after de-/serialization"), Struct1.StructSet.Num() == Struct2.StructSet.Num() && Struct1.StructSet.Difference(Struct2.StructSet).Num() == 0); } void ValidateOptionals(const FStructSerializerOptionalTestStruct& Struct1, const FStructSerializerOptionalTestStruct& Struct2) { CHECK_EQUALS(TEXT("Optionals.StrOptional must be the same before and after de-/serialization"), Struct1.StrOptional, Struct2.StrOptional); CHECK_EQUALS(TEXT("Optionals.StrOptionalUnset must be the same before and after de-/serialization"), Struct1.StrOptionalUnset, Struct2.StrOptionalUnset); CHECK_EQUALS(TEXT("Optionals.IntOptional must be the same before and after de-/serialization"), Struct1.IntOptional, Struct2.IntOptional); CHECK_EQUALS(TEXT("Optionals.IntOptionalUnset must be the same before and after de-/serialization"), Struct1.IntOptionalUnset, Struct2.IntOptionalUnset); CHECK_EQUALS(TEXT("Optionals.NameOptional must be the same before and after de-/serialization"), Struct1.NameOptional, Struct2.NameOptional); CHECK_EQUALS(TEXT("Optionals.NameOptionalUnset must be the same before and after de-/serialization"), Struct1.NameOptionalUnset, Struct2.NameOptionalUnset); CHECK_EQUALS(TEXT("Optionals.StructOptional must be the same before and after de-/serialization"), Struct1.StructOptional, Struct2.StructOptional); CHECK_EQUALS(TEXT("Optionals.StructOptionalUnset must be the same before and after de-/serialization"), Struct1.StructOptionalUnset, Struct2.StructOptionalUnset); } void ValidateLWCSerializationBackwardCompatibility(const FStructSerializerLWCTypesTest& Struct1, const FStructSerializerNonLWCTypesTest& Struct2) { //Make comparison by casting the double (lwc) version down to float (non-lwc) since this is what will happen during serialization CHECK_EQUALS(TEXT("LWC Vector must be deserialized to a Non-LWC Vector"), (FVector3f)Struct1.Vector, Struct2.Vector); CHECK_EQUALS(TEXT("LWC Vector2D must be deserialized to a Non-LWC Vector2D"), (FVector2f)Struct1.Vector2D, Struct2.Vector2D); CHECK_EQUALS(TEXT("LWC Vector4 must be deserialized to a Non-LWC Vector4"), (FVector4f)Struct1.Vector4, Struct2.Vector4); CHECK_EQUALS(TEXT("LWC Matrix must be deserialized to a Non-LWC Matrix"), (FMatrix44f)Struct1.Matrix, Struct2.Matrix); CHECK_EQUALS(TEXT("LWC Plane must be deserialized to a Non-LWC Plane"), (FPlane4f)Struct1.Plane, Struct2.Plane); CHECK_EQUALS(TEXT("LWC Quat must be deserialized to a Non-LWC Quat"), (FQuat4f)Struct1.Quat, Struct2.Quat); CHECK_EQUALS(TEXT("LWC Rotator must be deserialized to a Non-LWC Rotator"), (FRotator3f)Struct1.Rotator, Struct2.Rotator); CHECK_MESSAGE(TEXT("LWC Transform must be deserialized to a Non-LWC Transform"), Struct2.Transform.Equals((FTransform3f)Struct1.Transform)); CHECK_EQUALS(TEXT("LWC Box must be deserialized to a Non-LWC Box"), (FBox3f)Struct1.Box, Struct2.Box); CHECK_EQUALS(TEXT("LWC Box2D must be deserialized to a Non-LWC Box2D"), (FBox2f)Struct1.Box2D, Struct2.Box2D); CHECK_EQUALS(TEXT("LWC BoxSphereBounds must be deserialized to a Non-LWC BoxSphereBounds"), (FBoxSphereBounds3f)Struct1.BoxSphereBounds, Struct2.BoxSphereBounds); CHECK_EQUALS(TEXT("LWC struct float must be the same when deserialized to a non-LWC struct float"), Struct1.Float, Struct2.Float); CHECK_EQUALS(TEXT("LWC struct double must be the same when deserialized to a non-LWC struct double"), Struct1.Double, Struct2.Double); const bool bIsOrientedBoxEqual = Struct1.OrientedBox.AxisX == FVector(Struct2.OrientedBox.AxisX) && Struct1.OrientedBox.AxisY == FVector(Struct2.OrientedBox.AxisY) && Struct1.OrientedBox.AxisZ == FVector(Struct2.OrientedBox.AxisZ) && Struct1.OrientedBox.Center == FVector(Struct2.OrientedBox.Center) && Struct1.OrientedBox.ExtentX == Struct2.OrientedBox.ExtentX && Struct1.OrientedBox.ExtentY == Struct2.OrientedBox.ExtentY && Struct1.OrientedBox.ExtentZ == Struct2.OrientedBox.ExtentZ; CHECK_MESSAGE(TEXT("LWC OrientedBox must be deserialized to a Non-LWC OrientedBox"), bIsOrientedBoxEqual); //Container testing bool bAreArrayEqual = Struct1.VectorArray.Num() == Struct2.VectorArray.Num(); for (int32 Index = 0; Index < Struct1.VectorArray.Num() && bAreArrayEqual; ++Index) { if ((FVector3f)Struct1.VectorArray[Index] != Struct2.VectorArray[Index]) { bAreArrayEqual = false; } } CHECK_MESSAGE(TEXT("Array of LWC Vectors must be deserialized to an Array of Non-LWC Vectors"), bAreArrayEqual); bool bAreMapsEqual = Struct1.StrToVec.Num() == Struct2.StrToVec.Num(); for (const TPair& Struct1Pair : Struct1.StrToVec) { const FVector3f* Struct2Value = Struct2.StrToVec.Find(Struct1Pair.Key); if (Struct2Value == nullptr) { bAreMapsEqual = false; break; } const FVector3f Value = *Struct2Value; if (Value != (FVector3f)Struct1Pair.Value) { bAreMapsEqual = false; break; } } CHECK_MESSAGE(TEXT("Map of LWC Vectors must be deserialized to a Map of Non-LWC Vectors"), bAreMapsEqual); bool bAreSetsEqual = Struct1.VectorSet.Num() == Struct2.VectorSet.Num(); for (const FVector& Vector : Struct1.VectorSet) { //Cast down like serialization has done const FVector3f FloatVector = (FVector3f)Vector; if (Struct2.VectorSet.Contains(FloatVector) == false) { bAreSetsEqual = false; break; } } CHECK_MESSAGE(TEXT("Set of LWC Vectors must be deserialized to a Set of Non-LWC Vectors"), bAreSetsEqual); } void ValidateLWCDeserializationBackwardCompatibility(const FStructSerializerNonLWCTypesTest& Struct1, const FStructSerializerLWCTypesTest& Struct2) { //Make comparison by casting the float (non-lwc) version up to double (lwc) since this is what will happen during deserialization CHECK_EQUALS(TEXT("Non-LWC Vector must be deserialized to a LWC Vector"), (FVector)Struct1.Vector, Struct2.Vector); CHECK_EQUALS(TEXT("Non-LWC Vector2D must be deserialized to a LWC Vector2D"), (FVector2D)Struct1.Vector2D, Struct2.Vector2D); CHECK_EQUALS(TEXT("Non-LWC Vector4 must be deserialized toa LWC Vector4"), (FVector4)Struct1.Vector4, Struct2.Vector4); CHECK_EQUALS(TEXT("Non-LWC Matrix must be deserialized to a LWC Matrix"), (FMatrix)Struct1.Matrix, Struct2.Matrix); CHECK_EQUALS(TEXT("Non-LWC Plane must be deserialized to a LWC Plane"), (FPlane)Struct1.Plane, Struct2.Plane); CHECK_EQUALS(TEXT("Non-LWC Quat must be deserialized to a LWC Quat"), (FQuat)Struct1.Quat, Struct2.Quat); CHECK_EQUALS(TEXT("Non-LWC Rotator must be deserialized to a LWC Rotator"), (FRotator)Struct1.Rotator, Struct2.Rotator); CHECK_MESSAGE(TEXT("Non-LWC Transform must be deserialized to a LWC Transform"), Struct2.Transform.Equals((FTransform)Struct1.Transform)); CHECK_EQUALS(TEXT("Non-LWC Box must be deserialized to a LWC Box"), (FBox)Struct1.Box, Struct2.Box); CHECK_EQUALS(TEXT("Non-LWC Box2D must be deserialized to a LWC Box2D"), (FBox2D)Struct1.Box2D, Struct2.Box2D); CHECK_EQUALS(TEXT("Non-LWC BoxSphereBounds must be deserialized to a LWC BoxSphereBounds"), (FBoxSphereBounds)Struct1.BoxSphereBounds, Struct2.BoxSphereBounds); CHECK_EQUALS(TEXT("Non-LWC struct float must be the same when deserialized to a LWC struct float"), Struct1.Float, Struct2.Float); CHECK_EQUALS(TEXT("Non-LWC struct double must be the same when deserialized to a LWC struct double"), Struct1.Double, Struct2.Double); const bool bIsOrientedBoxEqual = FVector(Struct1.OrientedBox.AxisX) == Struct2.OrientedBox.AxisX && FVector(Struct1.OrientedBox.AxisY) == Struct2.OrientedBox.AxisY && FVector(Struct1.OrientedBox.AxisZ) == Struct2.OrientedBox.AxisZ && FVector(Struct1.OrientedBox.Center) == Struct2.OrientedBox.Center && Struct1.OrientedBox.ExtentX == Struct2.OrientedBox.ExtentX && Struct1.OrientedBox.ExtentY == Struct2.OrientedBox.ExtentY && Struct1.OrientedBox.ExtentZ == Struct2.OrientedBox.ExtentZ; CHECK_MESSAGE(TEXT("Non-LWC OrientedBox must be deserialized to a LWC OrientedBox"), bIsOrientedBoxEqual); //Container testing bool bAreArrayEqual = Struct1.VectorArray.Num() == Struct2.VectorArray.Num(); for (int32 Index = 0; Index < Struct1.VectorArray.Num() && bAreArrayEqual; ++Index) { if ((FVector)Struct1.VectorArray[Index] != Struct2.VectorArray[Index]) { bAreArrayEqual = false; } } CHECK_MESSAGE(TEXT("Array of Non-LWC Vectors must be deserialized to an Array of LWC Vectors"), bAreArrayEqual); bool bAreMapsEqual = Struct1.StrToVec.Num() == Struct2.StrToVec.Num(); for (const TPair& Struct1Pair : Struct1.StrToVec) { const FVector* Struct2Value = Struct2.StrToVec.Find(Struct1Pair.Key); if (Struct2Value == nullptr) { bAreMapsEqual = false; break; } const FVector Value = *Struct2Value; if (Value != (FVector)Struct1Pair.Value) { bAreMapsEqual = false; break; } } CHECK_MESSAGE(TEXT("Map of Non-LWC Vectors must be deserialized to a Map of LWC Vectors"), bAreMapsEqual); bool bAreSetsEqual = Struct1.VectorSet.Num() == Struct2.VectorSet.Num(); for (const FVector3f& Vector : Struct1.VectorSet) { //Cast down like serialization has done const FVector FloatVector = (FVector)Vector; if (Struct2.VectorSet.Contains(FloatVector) == false) { bAreSetsEqual = false; break; } } CHECK_MESSAGE(TEXT("Set of Non-LWC Vectors must be deserialized to a Set of LWC Vectors"), bAreSetsEqual); } void ValidateLWCTypes(const FStructSerializerLWCTypesTest& Struct1, const FStructSerializerLWCTypesTest& Struct2) { CHECK_EQUALS(TEXT("LWC Vector must be the same before and after de-serialization"), Struct1.Vector, Struct2.Vector); CHECK_EQUALS(TEXT("LWC Vector2D must be the same before and after de-serialization"), Struct1.Vector2D, Struct2.Vector2D); CHECK_EQUALS(TEXT("LWC Vector4 must be the same before and after de-serialization"), Struct1.Vector4, Struct2.Vector4); CHECK_EQUALS(TEXT("LWC Matrix must be the same before and after de-serialization"), Struct1.Matrix, Struct2.Matrix); CHECK_EQUALS(TEXT("LWC Plane must be the same before and after de-serialization"), Struct1.Plane, Struct2.Plane); CHECK_EQUALS(TEXT("LWC Quat must be the same before and after de-serialization"), Struct1.Quat, Struct2.Quat); CHECK_EQUALS(TEXT("LWC Rotator must be the same before and after de-serialization"), Struct1.Rotator, Struct2.Rotator); CHECK_MESSAGE(TEXT("LWC Transform must be the same before and after de-serialization"), Struct2.Transform.Equals(Struct1.Transform)); CHECK_EQUALS(TEXT("LWC Box must be the same before and after de-serialization"), Struct1.Box, Struct2.Box); CHECK_EQUALS(TEXT("LWC Box2D must be the same before and after de-serialization"), Struct1.Box2D, Struct2.Box2D); CHECK_EQUALS(TEXT("LWC BoxSphereBounds must be the same before and after de-serialization"), Struct1.BoxSphereBounds, Struct2.BoxSphereBounds); CHECK_EQUALS(TEXT("LWC struct float must be the same before and after de-serialization"), Struct1.Float, Struct2.Float); CHECK_EQUALS(TEXT("LWC struct double must be the same before and after de-serialization"), Struct1.Double, Struct2.Double); const bool bIsOrientedBoxEqual = Struct1.OrientedBox.AxisX == Struct2.OrientedBox.AxisX && Struct1.OrientedBox.AxisY == Struct2.OrientedBox.AxisY && Struct1.OrientedBox.AxisZ == Struct2.OrientedBox.AxisZ && Struct1.OrientedBox.Center == Struct2.OrientedBox.Center && Struct1.OrientedBox.ExtentX == Struct2.OrientedBox.ExtentX && Struct1.OrientedBox.ExtentY == Struct2.OrientedBox.ExtentY && Struct1.OrientedBox.ExtentZ == Struct2.OrientedBox.ExtentZ; CHECK_MESSAGE(TEXT("LWC OrientedBox must be the same before and after de-serialization"), bIsOrientedBoxEqual); CHECK_EQUALS(TEXT("LWC test - Arrays.VectorArray must be the same before and after de-/serialization"), Struct1.VectorArray, Struct2.VectorArray); CHECK_MESSAGE(TEXT("LWC test - Maps.StrToVec must be the same before and after de-/serialization"), Struct1.StrToVec.OrderIndependentCompareEqual(Struct2.StrToVec)); CHECK_MESSAGE(TEXT("LWC test - Sets.VectorSet must be the same before and after de-/serialization"), Struct1.VectorSet.Num() == Struct2.VectorSet.Num() && Struct1.VectorSet.Difference(Struct2.VectorSet).Num() == 0); } template void TestElementSerialization() { // serialization FStructSerializerTestStruct OriginalStruct; UClass* ObjectTestClass = UObjectTest::StaticClass(); UObjectTest* ObjectTestObject = NewObject(); // setup object tests OriginalStruct.Objects.RawClass = ObjectTestClass; OriginalStruct.Objects.WrappedClass = ObjectTestClass; OriginalStruct.Objects.SubClass = ObjectTestClass; OriginalStruct.Objects.SoftClass = ObjectTestClass; OriginalStruct.Objects.RawObject = ObjectTestObject; OriginalStruct.Objects.WrappedObject = ObjectTestObject; OriginalStruct.Objects.WeakObject = ObjectTestObject; OriginalStruct.Objects.SoftObject = ObjectTestObject; OriginalStruct.Objects.ClassPath = ObjectTestClass; OriginalStruct.Objects.ObjectPath = ObjectTestObject; { FStructSerializerPolicies Policies; Policies.MapSerialization = EStructSerializerMapPolicies::Array; FStructDeserializerPolicies DeserializerPolicies; DeserializerPolicies.MissingFields = EStructDeserializerErrorPolicies::Warning; DeserializerPolicies.MapPolicies = EStructDeserializerMapPolicies::Array; //Numerics { FStructSerializerTestStruct TestStruct = OriginalStruct; TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerTestStruct, Numerics); FProperty* Property = FindFProperty(FStructSerializerTestStruct::StaticStruct(), Member); FStructSerializer::SerializeElement(&TestStruct, Property, INDEX_NONE, SerializerBackend, Policies); FStructSerializerTestStruct TestStruct2(NoInit); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerTestStruct::StaticStruct(), INDEX_NONE, DeserializerBackend, DeserializerPolicies)); ValidateNumerics(TestStruct.Numerics, TestStruct2.Numerics); } //Booleans { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerTestStruct TestStruct = OriginalStruct; const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerTestStruct, Booleans); FProperty* Property = FindFProperty(FStructSerializerTestStruct::StaticStruct(), Member); FStructSerializer::SerializeElement(&TestStruct, Property, INDEX_NONE, SerializerBackend, Policies); FStructSerializerTestStruct TestStruct2(NoInit); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerTestStruct::StaticStruct(), INDEX_NONE, DeserializerBackend, DeserializerPolicies)); ValidateBooleans(TestStruct.Booleans, TestStruct2.Booleans); } //Objects { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerTestStruct TestStruct = OriginalStruct; const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerTestStruct, Objects); FProperty* Property = FindFProperty(FStructSerializerTestStruct::StaticStruct(), Member); FStructSerializer::SerializeElement(&TestStruct, Property, INDEX_NONE, SerializerBackend, Policies); FStructSerializerTestStruct TestStruct2(NoInit); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerTestStruct::StaticStruct(), INDEX_NONE, DeserializerBackend, DeserializerPolicies)); ValidateObjects(TestStruct.Objects, TestStruct2.Objects); } //Built ins { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerTestStruct TestStruct = OriginalStruct; const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerTestStruct, Builtins); FProperty* Property = FindFProperty(FStructSerializerTestStruct::StaticStruct(), Member); FStructSerializer::SerializeElement(&TestStruct, Property, INDEX_NONE, SerializerBackend, Policies); FStructSerializerTestStruct TestStruct2(NoInit); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerTestStruct::StaticStruct(), INDEX_NONE, DeserializerBackend, DeserializerPolicies)); ValidateBuiltIns(TestStruct.Builtins, TestStruct2.Builtins); } //Arrays { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerTestStruct TestStruct = OriginalStruct; const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerTestStruct, Arrays); FProperty* Property = FindFProperty(FStructSerializerTestStruct::StaticStruct(), Member); FStructSerializer::SerializeElement(&TestStruct, Property, INDEX_NONE, SerializerBackend, Policies); FStructSerializerTestStruct TestStruct2(NoInit); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerTestStruct::StaticStruct(), INDEX_NONE, DeserializerBackend, DeserializerPolicies)); ValidateArrays(TestStruct.Arrays, TestStruct2.Arrays); } //Maps { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerTestStruct TestStruct = OriginalStruct; const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerTestStruct, Maps); FProperty* Property = FindFProperty(FStructSerializerTestStruct::StaticStruct(), Member); FStructSerializer::SerializeElement(&TestStruct, Property, INDEX_NONE, SerializerBackend, Policies); FStructSerializerTestStruct TestStruct2(NoInit); // Target Maps start off with keys copied as the map serialization policy is array and only deals with values. Leaves keys empty. CopyKeys(TestStruct2.Maps.IntToStr, TestStruct.Maps.IntToStr); CopyKeys(TestStruct2.Maps.StrToStr, TestStruct.Maps.StrToStr); CopyKeys(TestStruct2.Maps.StrToStruct, TestStruct.Maps.StrToStruct); CopyKeys(TestStruct2.Maps.StrToVec, TestStruct.Maps.StrToVec); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerTestStruct::StaticStruct(), INDEX_NONE, DeserializerBackend, DeserializerPolicies)); ValidateMaps(TestStruct.Maps, TestStruct2.Maps); } //Sets { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerTestStruct TestStruct = OriginalStruct; const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerTestStruct, Sets); FProperty* Property = FindFProperty(FStructSerializerTestStruct::StaticStruct(), Member); FStructSerializer::SerializeElement(&TestStruct, Property, INDEX_NONE, SerializerBackend, Policies); FStructSerializerTestStruct TestStruct2(NoInit); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerTestStruct::StaticStruct(), INDEX_NONE, DeserializerBackend, DeserializerPolicies)); ValidateSets(TestStruct.Sets, TestStruct2.Sets); } //Optionals { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerTestStruct TestStruct = OriginalStruct; const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerTestStruct, Optionals); FProperty* Property = FindFProperty(FStructSerializerTestStruct::StaticStruct(), Member); FStructSerializer::SerializeElement(&TestStruct, Property, INDEX_NONE, SerializerBackend, Policies); // Inputs: // - TestStruct: all the properties will be defaulted - "Unset" properties unset, and the rest set. // - TestStruct2: all the "Unset" properties will be set, and the rest will be unset. // Expected Output: // - TestStruct2: all the "Unset" properties will be unset, and the rest will be set. // Goal: // - Validate that unset properties are populated when data is available for deserialization. // - Validate that set properties are cleared when data isn't available for deserialization. FStructSerializerTestStruct TestStruct2(NoInit); TestStruct2.Optionals.StrOptionalUnset = TestStruct.Optionals.StrOptional; TestStruct2.Optionals.IntOptionalUnset = TestStruct.Optionals.IntOptional; TestStruct2.Optionals.NameOptionalUnset = TestStruct.Optionals.NameOptional; TestStruct2.Optionals.StructOptionalUnset = TestStruct.Optionals.StructOptional; CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerTestStruct::StaticStruct(), INDEX_NONE, DeserializerBackend, DeserializerPolicies)); ValidateOptionals(TestStruct.Optionals, TestStruct2.Optionals); } //TArray element { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerArrayTestStruct TestStruct = OriginalStruct.Arrays; FStructSerializerArrayTestStruct TestStruct2(NoInit); TestStruct2.ByteArray.SetNumUninitialized(TestStruct.ByteArray.Num()); TestStruct2.ByteArray[0] = 89; TestStruct2.ByteArray[1] = 91; TestStruct2.ByteArray[2] = 93; const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerArrayTestStruct, ByteArray); FProperty* Property = FindFProperty(FStructSerializerArrayTestStruct::StaticStruct(), Member); constexpr int32 TestIndex = 1; FStructSerializer::SerializeElement(&TestStruct, Property, TestIndex, SerializerBackend, Policies); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerArrayTestStruct::StaticStruct(), TestIndex, DeserializerBackend, DeserializerPolicies)); CHECK_NOT_EQUALS(TEXT("Arrays.ByteArray[0] must not be the same before and after de-/serialization of element 1"), TestStruct.ByteArray[0], TestStruct2.ByteArray[0]); CHECK_EQUALS(TEXT("Arrays.ByteArray[1] must be the same before and after de-/serialization of element 1"), TestStruct.ByteArray[TestIndex], TestStruct2.ByteArray[TestIndex]); CHECK_NOT_EQUALS(TEXT("Arrays.ByteArray[2] must not be the same before and after de-/serialization of element 1"), TestStruct.ByteArray[2], TestStruct2.ByteArray[2]); } //TArray element { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerArrayTestStruct TestStruct = OriginalStruct.Arrays; FStructSerializerArrayTestStruct TestStruct2(NoInit); TestStruct2.StructArray.SetNumZeroed(TestStruct.StructArray.Num()); const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerArrayTestStruct, StructArray); FProperty* Property = FindFProperty(FStructSerializerArrayTestStruct::StaticStruct(), Member); constexpr int32 TestIndex = 1; FStructSerializer::SerializeElement(&TestStruct, Property, TestIndex, SerializerBackend, Policies); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerArrayTestStruct::StaticStruct(), TestIndex, DeserializerBackend, DeserializerPolicies)); CHECK_FALSE_MESSAGE(TEXT("Arrays.StructArray[0] must not be the same before and after de-/serialization of element 1"), TestStruct.StructArray[0] == TestStruct2.StructArray[0]); ValidateBuiltIns(TestStruct.StructArray[TestIndex], TestStruct2.StructArray[TestIndex]); } //static single element { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerArrayTestStruct TestStruct = OriginalStruct.Arrays; FStructSerializerArrayTestStruct TestStruct2(NoInit); TestStruct2.StaticSingleElement[0] = 998; const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerArrayTestStruct, StaticSingleElement); FProperty* Property = FindFProperty(FStructSerializerArrayTestStruct::StaticStruct(), Member); constexpr int32 TestIndex = 0; FStructSerializer::SerializeElement(&TestStruct, Property, TestIndex, SerializerBackend, Policies); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerArrayTestStruct::StaticStruct(), TestIndex, DeserializerBackend, DeserializerPolicies)); CHECK_EQUALS(TEXT("Arrays.StaticSingleElement[0] must be the same before and after de-/serialization"), TestStruct.StaticSingleElement[TestIndex], TestStruct2.StaticSingleElement[TestIndex]); } //static float array element { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerArrayTestStruct TestStruct = OriginalStruct.Arrays; FStructSerializerArrayTestStruct TestStruct2(NoInit); FMemory::Memset(&TestStruct2.StaticFloatArray, 99, sizeof(TestStruct2.StaticFloatArray)); const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerArrayTestStruct, StaticFloatArray); FProperty* Property = FindFProperty(FStructSerializerArrayTestStruct::StaticStruct(), Member); constexpr int32 TestIndex = 1; FStructSerializer::SerializeElement(&TestStruct, Property, TestIndex, SerializerBackend, Policies); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerArrayTestStruct::StaticStruct(), TestIndex, DeserializerBackend, DeserializerPolicies)); CHECK_NOT_EQUALS(TEXT("Arrays.StaticFloatArray[0] must not be the same before and after de-/serialization of element 1"), TestStruct.StaticFloatArray[0], TestStruct2.StaticFloatArray[0]); CHECK_EQUALS(TEXT("Arrays.StaticFloatArray[1] must be the same before and after de-/serialization"), TestStruct.StaticFloatArray[TestIndex], TestStruct2.StaticFloatArray[TestIndex]); CHECK_NOT_EQUALS(TEXT("Arrays.StaticFloatArray[2] must not be the same before and after de-/serialization of element 1"), TestStruct.StaticFloatArray[2], TestStruct2.StaticFloatArray[2]); } //TMap element { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerMapTestStruct TestStruct = OriginalStruct.Maps; FStructSerializerMapTestStruct TestStruct2(NoInit); CopyKeys(TestStruct2.IntToStr, TestStruct.IntToStr); const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerMapTestStruct, IntToStr); FProperty* Property = FindFProperty(FStructSerializerMapTestStruct::StaticStruct(), Member); constexpr int32 TestIndex = 1; FStructSerializer::SerializeElement(&TestStruct, Property, TestIndex, SerializerBackend, Policies); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerMapTestStruct::StaticStruct(), TestIndex, DeserializerBackend, DeserializerPolicies)); TArray Keys; Keys.Reserve(TestStruct.IntToStr.Num()); TestStruct.IntToStr.GenerateKeyArray(Keys); CHECK_NOT_EQUALS(TEXT("Maps.IntToStr[0] must not be the same before and after de-/serialization of element 1"), TestStruct.IntToStr[Keys[0]], TestStruct2.IntToStr[Keys[0]]); CHECK_EQUALS(TEXT("Maps.IntToStr[1] must be the same before and after de-/serialization of element 1"), TestStruct.IntToStr[Keys[TestIndex]], TestStruct2.IntToStr[Keys[TestIndex]]); CHECK_NOT_EQUALS(TEXT("Maps.IntToStr[2] must not be the same before and after de-/serialization of element 1"), TestStruct.IntToStr[Keys[2]], TestStruct2.IntToStr[Keys[2]]); } //TMap element { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerMapTestStruct TestStruct = OriginalStruct.Maps; FStructSerializerMapTestStruct TestStruct2(NoInit); CopyKeys(TestStruct2.StrToVec, TestStruct.StrToVec); const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerMapTestStruct, StrToVec); FProperty* Property = FindFProperty(FStructSerializerMapTestStruct::StaticStruct(), Member); constexpr int32 TestIndex = 1; FStructSerializer::SerializeElement(&TestStruct, Property, TestIndex, SerializerBackend, Policies); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerMapTestStruct::StaticStruct(), TestIndex, DeserializerBackend, DeserializerPolicies)); TArray Keys; Keys.Reserve(TestStruct.IntToStr.Num()); TestStruct.StrToVec.GenerateKeyArray(Keys); CHECK_NOT_EQUALS(TEXT("Maps.StrToVec[0] must not be the same before and after de-/serialization of element 1"), TestStruct.StrToVec[Keys[0]], TestStruct2.StrToVec[Keys[0]]); CHECK_EQUALS(TEXT("Maps.StrToVec[1] must be the same before and after de-/serialization of element 1"), TestStruct.StrToVec[Keys[TestIndex]], TestStruct2.StrToVec[Keys[TestIndex]]); CHECK_NOT_EQUALS(TEXT("Maps.StrToVec[2] must not be the same before and after de-/serialization of element 1"), TestStruct.StrToVec[Keys[2]], TestStruct2.StrToVec[Keys[2]]); } //TSet element { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); TSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); TDeserializerBackend DeserializerBackend(Reader); FStructSerializerSetTestStruct TestStruct = OriginalStruct.Sets; FStructSerializerSetTestStruct TestStruct2(NoInit); TestStruct2.NameSet = { TEXT("Pre1"), TEXT("Pre2") , TEXT("Pre3") }; const FName Member = GET_MEMBER_NAME_CHECKED(FStructSerializerSetTestStruct, NameSet); FProperty* Property = FindFProperty(FStructSerializerSetTestStruct::StaticStruct(), Member); constexpr int32 TestIndex = 1; FStructSerializer::SerializeElement(&TestStruct, Property, TestIndex, SerializerBackend, Policies); CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::DeserializeElement(&TestStruct2, *FStructSerializerSetTestStruct::StaticStruct(), TestIndex, DeserializerBackend, DeserializerPolicies)); TArray SetArray1 = TestStruct.NameSet.Array(); TArray SetArray2 = TestStruct2.NameSet.Array(); CHECK_NOT_EQUALS(TEXT("Sets.NameSet[0] must not be the same before and after de-/serialization of element 1"), SetArray1[0], SetArray2[0]); CHECK_EQUALS(TEXT("Sets.NameSet[1] must be the same before and after de-/serialization of element 1"), SetArray1[TestIndex], SetArray2[TestIndex]); CHECK_NOT_EQUALS(TEXT("Sets.NameSet[2] must not be the same before and after de-/serialization of element 1"), SetArray1[2], SetArray2[2]); } } } void TestSerialization(IStructSerializerBackend& SerializerBackend, IStructDeserializerBackend& DeserializerBackend ) { // serialization FStructSerializerTestStruct TestStruct; UClass* ObjectTestClass = UObjectTest::StaticClass(); UObjectTest* ObjectTestObject = NewObject(); // setup object tests TestStruct.Objects.RawClass = ObjectTestClass; TestStruct.Objects.WrappedClass = ObjectTestClass; TestStruct.Objects.SubClass = ObjectTestClass; TestStruct.Objects.SoftClass = ObjectTestClass; TestStruct.Objects.RawObject = ObjectTestObject; TestStruct.Objects.WrappedObject = ObjectTestObject; TestStruct.Objects.WeakObject = ObjectTestObject; TestStruct.Objects.SoftObject = ObjectTestObject; TestStruct.Objects.ClassPath = ObjectTestClass; TestStruct.Objects.ObjectPath = ObjectTestObject; { FStructSerializer::Serialize(TestStruct, SerializerBackend); } // deserialization FStructSerializerTestStruct TestStruct2(NoInit); { FStructDeserializerPolicies Policies; Policies.MissingFields = EStructDeserializerErrorPolicies::Warning; CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::Deserialize(TestStruct2, DeserializerBackend, Policies)); } // test numerics ValidateNumerics(TestStruct.Numerics, TestStruct2.Numerics); // test booleans ValidateBooleans(TestStruct.Booleans, TestStruct2.Booleans); // test objects ValidateObjects(TestStruct.Objects, TestStruct2.Objects); // test built-ins ValidateBuiltIns(TestStruct.Builtins, TestStruct2.Builtins); // test arrays ValidateArrays(TestStruct.Arrays, TestStruct2.Arrays); // test maps ValidateMaps(TestStruct.Maps, TestStruct2.Maps); // test sets ValidateSets(TestStruct.Sets, TestStruct2.Sets); // test optionals ValidateOptionals(TestStruct.Optionals, TestStruct2.Optionals); //Test LWC types with standard de-serialization ValidateLWCTypes(TestStruct.LWCTypes, TestStruct2.LWCTypes); } void TestLWCSerialization(IStructSerializerBackend& SerializerBackend, IStructDeserializerBackend& DeserializerBackend) { // Serialization of LWC struct into non-LWC mode to mimick sending to older UE FStructSerializerLWCTypesTest TestLWCStruct; { FStructSerializer::Serialize(TestLWCStruct, SerializerBackend); } // Deserialization into non-LWC to mimick reception in an older UE FStructSerializerNonLWCTypesTest TestNonLWCStruct(NoInit); { FStructDeserializerPolicies Policies; Policies.MissingFields = EStructDeserializerErrorPolicies::Warning; CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::Deserialize(TestNonLWCStruct, DeserializerBackend, Policies)); } ValidateLWCSerializationBackwardCompatibility(TestLWCStruct, TestNonLWCStruct); } void TestLWCDeserialization(IStructSerializerBackend& SerializerBackend, IStructDeserializerBackend& DeserializerBackend) { // Serialization of non lwc struct to mimick a struct coming from older UE FStructSerializerNonLWCTypesTest TestNonLWCStruct; { FStructSerializer::Serialize(TestNonLWCStruct, SerializerBackend); } // Deserialization into LWC type to mimick reception into a newer UE FStructSerializerLWCTypesTest TestLWCStruct(NoInit); { FStructDeserializerPolicies Policies; Policies.MissingFields = EStructDeserializerErrorPolicies::Warning; CHECK_MESSAGE(TEXT("Deserialization must succeed"), FStructDeserializer::Deserialize(TestLWCStruct, DeserializerBackend, Policies)); } ValidateLWCDeserializationBackwardCompatibility(TestNonLWCStruct, TestLWCStruct); } } /* Tests *****************************************************************************/ TEST_CASE_NAMED(FStructSerializerTest, "System::Core::Serialization::StructSerializer", "[ApplicationContextMask][EngineFilter][StructSerializer]") { const EStructSerializerBackendFlags BackendFlags = EStructSerializerBackendFlags::Default; // json { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); FJsonStructSerializerBackend SerializerBackend(Writer, BackendFlags); FJsonStructDeserializerBackend DeserializerBackend(Reader); StructSerializerTest::TestSerialization(SerializerBackend, DeserializerBackend); // uncomment this to look at the serialized data //GLog->Logf(TEXT("%s"), (TCHAR*)Buffer.GetData()); } // cbor { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); FCborStructSerializerBackend SerializerBackend(Writer, BackendFlags); FCborStructDeserializerBackend DeserializerBackend(Reader); StructSerializerTest::TestSerialization(SerializerBackend, DeserializerBackend); } // cbor standard compliant endianness (big endian) { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); FCborStructSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default | EStructSerializerBackendFlags::WriteCborStandardEndianness); FCborStructDeserializerBackend DeserializerBackend(Reader, ECborEndianness::StandardCompliant); StructSerializerTest::TestSerialization(SerializerBackend, DeserializerBackend); } // cbor LWC (UE5) to NonLWC (UE4) { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); FCborStructSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::LegacyUE4 | EStructSerializerBackendFlags::WriteCborStandardEndianness); constexpr bool bIsLWCCompatibilityMode = false; FCborStructDeserializerBackend DeserializerBackend(Reader, ECborEndianness::StandardCompliant, bIsLWCCompatibilityMode); StructSerializerTest::TestLWCSerialization(SerializerBackend, DeserializerBackend); } // cbor Non LWC (UE4) to LWC (UE5) { TArray Buffer; FMemoryReader Reader(Buffer); FMemoryWriter Writer(Buffer); FCborStructSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::LegacyUE4 | EStructSerializerBackendFlags::WriteCborStandardEndianness); constexpr bool bIsLWCCompatibilityMode = true; FCborStructDeserializerBackend DeserializerBackend(Reader, ECborEndianness::StandardCompliant, bIsLWCCompatibilityMode); StructSerializerTest::TestLWCDeserialization(SerializerBackend, DeserializerBackend); } } TEST_CASE_NAMED(FStructElementSerializerTest, "System::Core::Serialization::StructElementSerializer", "[ApplicationContextMask][EngineFilter][StructSerializer]") { //Element de/serialization for both types of backend { StructSerializerTest::TestElementSerialization(); StructSerializerTest::TestElementSerialization(); } } TEST_CASE_NAMED(FStructSerializerCborByteArrayTest, "System::Core::Serialization::StructSerializerCborByteArray", "[ApplicationContextMask][EngineFilter][StructSerializer]") { // Ensure TArray/TArray are written as CBOR byte string (~2x more compact) by default rather than a CBOR array. { static_assert((EStructSerializerBackendFlags::Default & EStructSerializerBackendFlags::WriteByteArrayAsByteStream) == EStructSerializerBackendFlags::WriteByteArrayAsByteStream, "Test below expects 'EStructSerializerBackendFlags::Default' to contain 'EStructSerializerBackendFlags::WriteByteArrayAsByteStream'"); // Serialization TArray Buffer; FMemoryWriter Writer(Buffer); FCborStructSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); FStructSerializerByteArray WrittenStruct; FStructSerializer::Serialize(WrittenStruct, SerializerBackend); CHECK_MESSAGE(TEXT("Arrays of int8/uint8 must be encoded in byte string (compact)"), Buffer.Num() == 54); // Copy the 54 bytes from VC++ Memory viewer to CBOR playground http://cbor.me/ to validate the count/content. // Deserialization FMemoryReader Reader(Buffer); FCborStructDeserializerBackend DeserializerBackend(Reader); FStructDeserializerPolicies Policies; Policies.MissingFields = EStructDeserializerErrorPolicies::Warning; FStructSerializerByteArray ReadStruct(NoInit); FStructDeserializer::Deserialize(ReadStruct, DeserializerBackend, Policies); CHECK_MESSAGE(TEXT("Value before TArray must be the same before and after de-/serialization."), ReadStruct.Dummy1 == 1); CHECK_MESSAGE(TEXT("Value after TArray must be the same before and after de-/serialization."), ReadStruct.Dummy2 == 2); CHECK_MESSAGE(TEXT("Value after TArray must be the same before and after de-/serialization."), ReadStruct.Dummy3 == 3); CHECK_MESSAGE(TEXT("Array uint8 must be the same before and after de-/serialization"), WrittenStruct.ByteArray == ReadStruct.ByteArray); CHECK_MESSAGE(TEXT("Array int8 must be the same before and after de-/serialization"), WrittenStruct.Int8Array == ReadStruct.Int8Array); } // Ensure TArray/TArray encoded in CBOR byte string are skipped on deserialization if required by the policy. { // Serialization TArray Buffer; FMemoryWriter Writer(Buffer); FCborStructSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); FStructSerializerByteArray WrittenStruct; FStructSerializer::Serialize(WrittenStruct, SerializerBackend); // Deserialization FMemoryReader Reader(Buffer); FCborStructDeserializerBackend DeserializerBackend(Reader); // Skip the array properties named "ByteArray" and "Int8Array". FStructDeserializerPolicies Policies; Policies.PropertyFilter = [](const FProperty* CurrentProp, const FProperty* ParentProp) { const bool bFilteredOut = (CurrentProp->GetFName() == FName(TEXT("ByteArray")) || CurrentProp->GetFName() == FName(TEXT("Int8Array"))); return !bFilteredOut; }; Policies.MissingFields = EStructDeserializerErrorPolicies::Warning; FStructSerializerByteArray ReadStruct(NoInit); FStructDeserializer::Deserialize(ReadStruct, DeserializerBackend, Policies); CHECK_MESSAGE(TEXT("Per deserializer policy, value before TArray must be the same before and after de-/serialization."), ReadStruct.Dummy1 == 1); CHECK_MESSAGE(TEXT("Per deserializer policy, value after TArray must be the same before and after de-/serialization."), ReadStruct.Dummy2 == 2); CHECK_MESSAGE(TEXT("Per deserializer policy, value after TArray must be the same before and after de-/serialization."), ReadStruct.Dummy3 == 3); CHECK_MESSAGE(TEXT("Per deserializer policy, TArray must be skipped on deserialization"), ReadStruct.ByteArray.Num() == 0); CHECK_MESSAGE(TEXT("Per deserializer policy, TArray must be skipped on deserialization"), ReadStruct.Int8Array.Num() == 0); } // Ensure empty TArray/TArray are written as zero-length CBOR byte string. { // Serialization TArray Buffer; FMemoryWriter Writer(Buffer); FCborStructSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Default); FStructSerializerByteArray WrittenStruct(NoInit); // Keep the TArray<> empty. WrittenStruct.Dummy1 = 1; WrittenStruct.Dummy2 = 2; WrittenStruct.Dummy3 = 3; FStructSerializer::Serialize(WrittenStruct, SerializerBackend); CHECK_MESSAGE(TEXT("Arrays of int8/uint8 must be encoded in byte string (compact)"), Buffer.Num() == 48); // Copy the 48 bytes from VC++ Memory viewer to CBOR playground http://cbor.me/ to validate the count/content. // Deserialization FMemoryReader Reader(Buffer); FCborStructDeserializerBackend DeserializerBackend(Reader); FStructDeserializerPolicies Policies; Policies.MissingFields = EStructDeserializerErrorPolicies::Warning; FStructSerializerByteArray ReadStruct(NoInit); FStructDeserializer::Deserialize(ReadStruct, DeserializerBackend, Policies); CHECK_MESSAGE(TEXT("Value before TArray must be the same before and after de-/serialization."), ReadStruct.Dummy1 == 1); CHECK_MESSAGE(TEXT("Value after TArray must be the same before and after de-/serialization."), ReadStruct.Dummy2 == 2); CHECK_MESSAGE(TEXT("Value after TArray must be the same before and after de-/serialization."), ReadStruct.Dummy3 == 3); CHECK_MESSAGE(TEXT("Array uint8 must be the same before and after de-/serialization"), WrittenStruct.ByteArray == ReadStruct.ByteArray); CHECK_MESSAGE(TEXT("Array int8 must be the same before and after de-/serialization"), WrittenStruct.Int8Array == ReadStruct.Int8Array); } // Ensure TArray/TArray CBOR serialization is backward compatible. (Serializer can write the old format and deserializer can read it) { static_assert((EStructSerializerBackendFlags::Legacy & EStructSerializerBackendFlags::WriteByteArrayAsByteStream) == EStructSerializerBackendFlags::None, "Test below expects 'EStructSerializerBackendFlags::Legacy' to not have 'EStructSerializerBackendFlags::WriteByteArrayAsByteStream'"); // Serialize TArray/TArray it they were prior 4.25. (CBOR array rather than CBOR byte string) TArray Buffer; FMemoryWriter Writer(Buffer); FCborStructSerializerBackend SerializerBackend(Writer, EStructSerializerBackendFlags::Legacy); // Legacy mode doesn't enable EStructSerializerBackendFlags::WriteByteArrayAsByteStream. FStructSerializerByteArray WrittenStruct; FStructSerializer::Serialize(WrittenStruct, SerializerBackend); CHECK_MESSAGE(TEXT("Backward compatibility: Serialized size check"), Buffer.Num() == 60); // Copy the 60 bytes from VC++ Memory viewer to CBOR playground http://cbor.me/ to validate the count/content. // Deserialize TArray/TArray as they were prior 4.25. FMemoryReader Reader(Buffer); FCborStructDeserializerBackend DeserializerBackend(Reader); FStructDeserializerPolicies Policies; Policies.MissingFields = EStructDeserializerErrorPolicies::Warning; FStructSerializerByteArray ReadStruct(NoInit); FStructDeserializer::Deserialize(ReadStruct, DeserializerBackend, Policies); CHECK_MESSAGE(TEXT("Backward compatibility: Integer must be the same before and after de-/serialization."), ReadStruct.Dummy1 == 1); CHECK_MESSAGE(TEXT("Backward compatibility: Integer must be the same before and after de-/serialization."), ReadStruct.Dummy2 == 2); CHECK_MESSAGE(TEXT("Backward compatibility: Integer must be the same before and after de-/serialization."), ReadStruct.Dummy3 == 3); CHECK_MESSAGE(TEXT("Backward compatibility: TArray must be readable as CBOR array of number."), WrittenStruct.ByteArray == ReadStruct.ByteArray); CHECK_MESSAGE(TEXT("Backward compatibility: TArray must be readable as CBOR array of number."), WrittenStruct.Int8Array == ReadStruct.Int8Array); } } #endif // WITH_TESTS