Files
UnrealEngine/Engine/Source/Runtime/Serialization/Private/Tests/StructSerializerTest.cpp
2025-05-18 13:04:45 +08:00

1027 lines
57 KiB
C++

// 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<typename KeyType, typename ValueType>
void CopyKeys(TMap<KeyType, ValueType>& OutMap, const TMap<KeyType, ValueType>& SourceMap)
{
OutMap.Empty(SourceMap.Num());
Algo::ForEach(SourceMap, [&OutMap](const TPair<KeyType, ValueType>& Other)
{
OutMap.Add(Other.Key);
});
}
template<>
void CopyKeys(TMap<FString, FVector>& OutMap, const TMap<FString, FVector>& SourceMap)
{
OutMap.Empty(SourceMap.Num());
Algo::ForEach(SourceMap, [&OutMap](const TPair<FString, FVector>& Other)
{
OutMap.Add(Other.Key, FVector(76.7f));
});
}
template<>
void CopyKeys(TMap<FString, FStructSerializerBuiltinTestStruct>& OutMap, const TMap<FString, FStructSerializerBuiltinTestStruct>& SourceMap)
{
OutMap.Empty(SourceMap.Num());
Algo::ForEach(SourceMap, [&OutMap](const TPair<FString, FStructSerializerBuiltinTestStruct>& 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<FString, FVector>& 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<FString, FVector3f>& 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<typename TSerializerBackend, typename TDeserializerBackend>
void TestElementSerialization()
{
// serialization
FStructSerializerTestStruct OriginalStruct;
UClass* ObjectTestClass = UObjectTest::StaticClass();
UObjectTest* ObjectTestObject = NewObject<UObjectTest>();
// 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<uint8> 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<FProperty>(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<uint8> 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<FProperty>(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<uint8> 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<FProperty>(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<uint8> 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<FProperty>(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<uint8> 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<FProperty>(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<uint8> 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<FProperty>(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<uint8> 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<FProperty>(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<uint8> 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<FProperty>(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<uint8> element
{
TArray<uint8> 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<FProperty>(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<Struct> element
{
TArray<uint8> 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<FProperty>(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<uint8> 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<FProperty>(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<uint8> 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<FProperty>(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<int32, FString> element
{
TArray<uint8> 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<FProperty>(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<int32> 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<FString, FVector> element
{
TArray<uint8> 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<FProperty>(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<FString> 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<FName> element
{
TArray<uint8> 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<FProperty>(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<FName> SetArray1 = TestStruct.NameSet.Array();
TArray<FName> 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<UObjectTest>();
// 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<uint8> 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<uint8> 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<uint8> 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<uint8> 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<uint8> 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<FJsonStructSerializerBackend, FJsonStructDeserializerBackend>();
StructSerializerTest::TestElementSerialization<FCborStructSerializerBackend, FCborStructDeserializerBackend>();
}
}
TEST_CASE_NAMED(FStructSerializerCborByteArrayTest, "System::Core::Serialization::StructSerializerCborByteArray", "[ApplicationContextMask][EngineFilter][StructSerializer]")
{
// Ensure TArray<uint8>/TArray<int8> 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<uint8> 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<uint8> must be the same before and after de-/serialization."), ReadStruct.Dummy1 == 1);
CHECK_MESSAGE(TEXT("Value after TArray<uint8> must be the same before and after de-/serialization."), ReadStruct.Dummy2 == 2);
CHECK_MESSAGE(TEXT("Value after TArray<int8> 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<uint8>/TArray<int8> encoded in CBOR byte string are skipped on deserialization if required by the policy.
{
// Serialization
TArray<uint8> 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<uint8> must be the same before and after de-/serialization."), ReadStruct.Dummy1 == 1);
CHECK_MESSAGE(TEXT("Per deserializer policy, value after TArray<uint8> must be the same before and after de-/serialization."), ReadStruct.Dummy2 == 2);
CHECK_MESSAGE(TEXT("Per deserializer policy, value after TArray<int8> must be the same before and after de-/serialization."), ReadStruct.Dummy3 == 3);
CHECK_MESSAGE(TEXT("Per deserializer policy, TArray<uint8> must be skipped on deserialization"), ReadStruct.ByteArray.Num() == 0);
CHECK_MESSAGE(TEXT("Per deserializer policy, TArray<int8> must be skipped on deserialization"), ReadStruct.Int8Array.Num() == 0);
}
// Ensure empty TArray<uint8>/TArray<int8> are written as zero-length CBOR byte string.
{
// Serialization
TArray<uint8> 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<uint8> must be the same before and after de-/serialization."), ReadStruct.Dummy1 == 1);
CHECK_MESSAGE(TEXT("Value after TArray<uint8> must be the same before and after de-/serialization."), ReadStruct.Dummy2 == 2);
CHECK_MESSAGE(TEXT("Value after TArray<int8> 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<uint8>/TArray<int8> 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<uint8>/TArray<int8> it they were prior 4.25. (CBOR array rather than CBOR byte string)
TArray<uint8> 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<uint8>/TArray<int8> 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<uint8> must be readable as CBOR array of number."), WrittenStruct.ByteArray == ReadStruct.ByteArray);
CHECK_MESSAGE(TEXT("Backward compatibility: TArray<int8> must be readable as CBOR array of number."), WrittenStruct.Int8Array == ReadStruct.Int8Array);
}
}
#endif // WITH_TESTS