// Copyright (c) Samuel Kahn (samuel@kahncode.com). All Rights Reserved. #include "BlobSerializationTests.h" #include "Blob.h" #include "BlobSerializationArchive.h" #include "BlobSerializationEditorPCH.h" #include "Misc/AutomationTest.h" #include "Net/UnrealNetwork.h" #include "NetSerialization.h" #include "Serialization.h" #include "Serialization/StructuredArchive.h" #include "TextSerialization.h" #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) void UBlobSerializationTestClass::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(UBlobSerializationTestClass, IntVariable); DOREPLIFETIME(UBlobSerializationTestClass, FloatVariable); } namespace BlobSerialization { struct NotSerializable { }; struct TriviallySerializable { TriviallySerializable() : IntVariable(987654) , FloatVariable(123.456) , StringVariable("Hello World") { } int IntVariable; float FloatVariable; FString StringVariable; bool operator==(const TriviallySerializable& Other) const { return IntVariable == Other.IntVariable && FloatVariable == Other.FloatVariable && StringVariable == Other.StringVariable; } }; inline FArchive& operator<<(FArchive& Ar, TriviallySerializable& Value) { Ar << Value.IntVariable; Ar << Value.FloatVariable; Ar << Value.StringVariable; return Ar; } struct CustomSerializable { CustomSerializable() : IntVariable(987654) , FloatVariable(123.456) , StringVariable("Hello World") { } int IntVariable; float FloatVariable; FString StringVariable; void Serialize(FArchive& Ar) { Ar << IntVariable; Ar << FloatVariable; Ar << StringVariable; } void Serialize(FStructuredArchive::FRecord Record) { Record << SA_VALUE(TEXT("IntVariable"), IntVariable); Record << SA_VALUE(TEXT("FloatVariable"), FloatVariable); Record << SA_VALUE(TEXT("StringVariable"), StringVariable); } bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) { Ar << IntVariable; Ar << FloatVariable; Ar << StringVariable; bOutSuccess = true; return true; } bool operator==(const CustomSerializable& Other) const { return IntVariable == Other.IntVariable && FloatVariable == Other.FloatVariable && StringVariable == Other.StringVariable; } }; ////////////////////////////////////////////////////////////////////////// // Test Type-Traits static_assert(IsTriviallySerializable::Value, "TriviallySerializable is trivially serializable"); static_assert(IsTriviallySerializable::Value, "FBlob is trivially serializable"); static_assert(!IsTriviallySerializable::Value, "FBlobSerializationTestStruct is not trivially serializable"); static_assert(!IsTriviallySerializable::Value, "NotSerializable is not trivially serializable"); static_assert(!IsTriviallySerializable::Value, "UObject* is not trivially serializable"); static_assert(!IsTriviallySerializable::Value, "UBlobSerializationTestClass* is not trivially serializable"); static_assert(!IsTriviallySerializable::Value, "FBlobSerializationTestStruct* is not trivially serializable"); static_assert(!IsUObject::value, "TriviallySerializable is not UObject"); static_assert(!IsUObject::value, "FBlob is not UObject"); static_assert(!IsUObject::value, "FBlobSerializationTestStruct is not UObject"); static_assert(IsUObject::value, "UObject is UObject"); static_assert(!IsUObject::value, "CustomSerializable is UObject"); static_assert(!IsUStruct::Value, "TriviallySerializable is not USTRUCT"); static_assert(IsUStruct::Value, "FBlob is USTRUCT"); static_assert(IsUStruct::Value, "FBlobSerializationTestStruct is USTRUCT"); static_assert(!IsUStruct::Value, "UObject is not USTRUCT"); static_assert(!IsUStruct::Value, "CustomSerializable is not USTRUCT"); static_assert(!HasSerializeMethod::Value, "TriviallySerializable doesn't have Serialize()"); static_assert(!HasSerializeMethod::Value, "FBlob doesn't have Serialize()"); static_assert(HasSerializeMethod::Value, "UObject has Serialize()"); static_assert(HasSerializeMethod::Value, "CustomSerializable has Serialize()"); static_assert(!HasNetSerializeMethod::Value, "TriviallySerializable doesn't have NetSerialize()"); static_assert(!HasNetSerializeMethod::Value, "FBlob doesn't have NetSerialize()"); static_assert(!HasNetSerializeMethod::Value, "UObject has NetSerialize()"); static_assert(HasNetSerializeMethod::Value, "CustomSerializable doesn't have NetSerialize()"); static_assert(HasNetSerializeMethod::Value, "CustomSerializable doesn't have NetSerialize()"); ////////////////////////////////////////////////////////////////////////// IMPLEMENT_SIMPLE_AUTOMATION_TEST(SerializationTestFArchive, "BlobSerialization.SerializationTest.FArchive", EAutomationTestFlags::EditorContext | EAutomationTestFlags::CommandletContext | EAutomationTestFlags::EngineFilter) bool SerializationTestFArchive::RunTest(const FString& Parameters) { // TriviallySerializable { TriviallySerializable Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; TriviallySerializable Output; TArray Buffer; FBlobWriter Writer(Buffer); FBlobReader Reader(Buffer); Serialize(Input, Writer); Serialize(Output, Reader); UE_LOG(LogBlobSerialization, Log, TEXT("TriviallySerializable/Serialize BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("TriviallySerializable/Serialize BlobReader/BlobWriter test", Input == Output); } // TriviallySerializable / bin { TriviallySerializable Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; TriviallySerializable Output; TArray Buffer; FBlobWriter Writer(Buffer); FBlobReader Reader(Buffer); SerializeBin(Input, Writer); SerializeBin(Output, Reader); UE_LOG(LogBlobSerialization, Log, TEXT("TriviallySerializable/SerializeBin BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("TriviallySerializable/SerializeBin BlobReader/BlobWriter test", Input == Output); } // TriviallySerializable / bin { TriviallySerializable Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; TriviallySerializable Output; TArray Buffer; FBlobWriter Writer(Buffer); Writer.ArIsNetArchive = true; FBlobReader Reader(Buffer); Reader.ArIsNetArchive = true; NetSerialize(Input, Writer, nullptr); NetSerialize(Output, Reader, nullptr); UE_LOG(LogBlobSerialization, Log, TEXT("TriviallySerializable/NetSerialize BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("TriviallySerializable/NetSerialize BlobReader/BlobWriter test", Input == Output); } // CustomSerializable { CustomSerializable Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; CustomSerializable Output; TArray Buffer; FBlobWriter Writer(Buffer); FBlobReader Reader(Buffer); Serialize(Input, Writer); Serialize(Output, Reader); UE_LOG(LogBlobSerialization, Log, TEXT("CustomSerializable/Serialize BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("CustomSerializable/Serialize BlobReader/BlobWriter test", Input == Output); } // CustomSerializable / bin { CustomSerializable Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; CustomSerializable Output; TArray Buffer; FBlobWriter Writer(Buffer); FBlobReader Reader(Buffer); SerializeBin(Input, Writer); SerializeBin(Output, Reader); UE_LOG(LogBlobSerialization, Log, TEXT("CustomSerializable/SerializeBin BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("CustomSerializable/SerializeBin BlobReader/BlobWriter test", Input == Output); } // CustomSerializable / net { CustomSerializable Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; CustomSerializable Output; TArray Buffer; FBlobWriter Writer(Buffer); Writer.ArIsNetArchive = true; FBlobReader Reader(Buffer); Reader.ArIsNetArchive = true; NetSerialize(Input, Writer, nullptr); NetSerialize(Output, Reader, nullptr); UE_LOG(LogBlobSerialization, Log, TEXT("CustomSerializable/NetSerialize BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("CustomSerializable/NetSerialize BlobReader/BlobWriter test", Input == Output); } // UStruct { FBlobSerializationTestStruct Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; FBlobSerializationTestStruct Output; TArray Buffer; FBlobWriter Writer(Buffer); FBlobReader Reader(Buffer); Serialize(Input, Writer); Serialize(Output, Reader); UE_LOG(LogBlobSerialization, Log, TEXT("UStruct/Serialize BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("UStruct/Serialize BlobReader/BlobWriter test", Input == Output); } // UStruct / bin { FBlobSerializationTestStruct Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; FBlobSerializationTestStruct Output; TArray Buffer; FBlobWriter Writer(Buffer); FBlobReader Reader(Buffer); SerializeBin(Input, Writer); SerializeBin(Output, Reader); UE_LOG(LogBlobSerialization, Log, TEXT("UStruct/SerializeBin BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("UStruct/SerializeBin BlobReader/BlobWriter test", Input == Output); } // UStruct / net { FBlobSerializationTestStruct Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; FBlobSerializationTestStruct Output; TArray Buffer; FBlobWriter Writer(Buffer); Writer.ArIsNetArchive = true; FBlobReader Reader(Buffer); Reader.ArIsNetArchive = true; NetSerialize(Input, Writer, nullptr); NetSerialize(Output, Reader, nullptr); UE_LOG(LogBlobSerialization, Log, TEXT("UStruct/NetSerialize BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("UStruct/NetSerialize BlobReader/BlobWriter test", Input.IntVariable == Output.IntVariable && Input.FloatVariable == Output.FloatVariable && Input.StringVariable != Output.StringVariable); } // UStruct with custom NetSerialize { FBlobSerializationTestStruct2 Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.BoolVariable = true; FBlobSerializationTestStruct2 Output; TArray Buffer; FBlobWriter Writer(Buffer); Writer.ArIsNetArchive = true; FBlobReader Reader(Buffer); Reader.ArIsNetArchive = true; NetSerialize(Input, Writer, nullptr); NetSerialize(Output, Reader, nullptr); UE_LOG(LogBlobSerialization, Log, TEXT("UStruct/CustomNetSerialize BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("UStruct/CustomNetSerialize BlobReader/BlobWriter test", Input.IntVariable == Output.IntVariable && Input.FloatVariable == Output.FloatVariable && Input.BoolVariable != Output.BoolVariable); } // UObject { UBlobSerializationTestClass* Input = NewObject(); Input->IntVariable = -333; Input->FloatVariable = 33.33; Input->StringVariable = "Hello Underworld"; UBlobSerializationTestClass* Output = NewObject(); TArray Buffer; FBlobWriter Writer(Buffer); FBlobReader Reader(Buffer); Serialize(Input, Writer); Serialize(Output, Reader); UE_LOG(LogBlobSerialization, Log, TEXT("UObject/Serialize BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("UObject/Serialize BlobReader/BlobWriter test", *Input == *Output); } // UObject / bin { UBlobSerializationTestClass* Input = NewObject(); Input->IntVariable = -333; Input->FloatVariable = 33.33; Input->StringVariable = "Hello Underworld"; UBlobSerializationTestClass* Output = NewObject(); TArray Buffer; FBlobWriter Writer(Buffer); FBlobReader Reader(Buffer); SerializeBin(Input, Writer); SerializeBin(Output, Reader); UE_LOG(LogBlobSerialization, Log, TEXT("UObject/SerializeBin BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("UObject/SerializeBin BlobReader/BlobWriter test", *Input == *Output); } // UObject / net { UBlobSerializationTestClass* Input = NewObject(); Input->IntVariable = -333; Input->FloatVariable = 33.33; Input->StringVariable = "Hello Underworld"; UBlobSerializationTestClass* Output = NewObject(); TArray Buffer; FBlobWriter Writer(Buffer); Writer.ArIsNetArchive = true; FBlobReader Reader(Buffer); Reader.ArIsNetArchive = true; NetSerialize(Input, Writer, nullptr); NetSerialize(Output, Reader, nullptr); UE_LOG(LogBlobSerialization, Log, TEXT("UObject/NetSerialize BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("UObject/NetSerialize BlobReader/BlobWriter test", Input->IntVariable == Output->IntVariable && Input->FloatVariable == Output->FloatVariable && Input->StringVariable != Output->StringVariable); } // UObject / poly { UBlobSerializationTestClass2* Input = NewObject(); Input->IntVariable = -333; Input->FloatVariable = 33.33; Input->StringVariable = "Hello Underworld"; Input->BoolVariable = true; TArray Buffer; FBlobWriter Writer(Buffer); SerializePoly(Input, Writer); { UBlobSerializationTestClass2* Output = NewObject(); FBlobReader Reader(Buffer); SerializePoly(Output, Reader); UE_LOG(LogBlobSerialization, Log, TEXT("UObject/Serialize BlobReader/BlobWriter buffer length: %d"), Buffer.Num()); TestTrue("UObject/SerializePoly BlobReader/BlobWriter test", *Input == *Output); } { UBlobSerializationTestClass* OutputParentClass = NewObject(); UBlobSerializationTestClass* OutputParentClassCache = OutputParentClass; FBlobReader Reader(Buffer); SerializePoly(OutputParentClass, Reader); TestTrue("UObject/SerializePoly parent class test", OutputParentClassCache != OutputParentClass); TestTrue("UObject/Serialize parent class test", *Input == *Cast(OutputParentClass)); } { UBlobSerializationTestClass2* Output = nullptr; FBlobReader Reader(Buffer); SerializePoly(Output, Reader); TestTrue("UObject/Serialize null input test", *Input == *Output); } // Next test will error AddExpectedError(TEXT("SerializePoly"), EAutomationExpectedErrorFlags::Contains, 1); { UClass* OutputWrongClass = NewObject(); FBlobReader Reader(Buffer); SerializePoly(OutputWrongClass, Reader); TestTrue("UObject/Serialize wrong class test", !OutputWrongClass); } { TArray InputArray = {Input, Input, Input}; TArray OutputArray; TArray Buffer2; FBlobWriter Writer2(Buffer2); FBlobReader Reader2(Buffer2); SerializePoly(InputArray, Writer2); SerializePoly(OutputArray, Reader2); TestTrue("UObject/Serialize object array", OutputArray.Num() == 3 && !OutputArray.Contains(nullptr)); } } return true; } ////////////////////////////////////////////////////////////////////////// IMPLEMENT_SIMPLE_AUTOMATION_TEST(SerializationTestFStructuredArchive, "BlobSerialization.SerializationTest.FStructuredArchive", EAutomationTestFlags::EditorContext | EAutomationTestFlags::CommandletContext | EAutomationTestFlags::EngineFilter) bool SerializationTestFStructuredArchive::RunTest(const FString& Parameters) { // TriviallySerializable { TriviallySerializable Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; TriviallySerializable Output; TArray Buffer; FBlobWriter Writer(Buffer); FBinaryArchiveFormatter OutputFormatter(Writer); FStructuredArchive StructuredWriter(OutputFormatter); FBlobReader Reader(Buffer); FBinaryArchiveFormatter InputFormatter(Reader); FStructuredArchive StructuredReader(InputFormatter); Serialize(Input, StructuredWriter); Serialize(Output, StructuredReader); UE_LOG(LogBlobSerialization, Log, TEXT("TriviallySerializable/Serialize Structured buffer length: %d"), Buffer.Num()); TestTrue("TriviallySerializable/Serialize Structured test", Input == Output); } // CustomSerializable { CustomSerializable Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; CustomSerializable Output; TArray Buffer; FBlobWriter Writer(Buffer); FBinaryArchiveFormatter OutputFormatter(Writer); FStructuredArchive StructuredWriter(OutputFormatter); FBlobReader Reader(Buffer); FBinaryArchiveFormatter InputFormatter(Reader); FStructuredArchive StructuredReader(InputFormatter); Serialize(Input, StructuredWriter); Serialize(Output, StructuredReader); UE_LOG(LogBlobSerialization, Log, TEXT("CustomSerializable/Serialize Structured buffer length: %d"), Buffer.Num()); TestTrue("CustomSerializable/Serialize Structured test", Input == Output); } // UStruct { FBlobSerializationTestStruct Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; FBlobSerializationTestStruct Output; TArray Buffer; FBlobWriter Writer(Buffer); FBinaryArchiveFormatter OutputFormatter(Writer); FStructuredArchive StructuredWriter(OutputFormatter); FBlobReader Reader(Buffer); FBinaryArchiveFormatter InputFormatter(Reader); FStructuredArchive StructuredReader(InputFormatter); Serialize(Input, StructuredWriter); Serialize(Output, StructuredReader); UE_LOG(LogBlobSerialization, Log, TEXT("UStruct/Serialize Structured buffer length: %d"), Buffer.Num()); TestTrue("UStruct/Serialize Structured test", Input == Output); } // UObject { UBlobSerializationTestClass* Input = NewObject(); Input->IntVariable = -333; Input->FloatVariable = 33.33; Input->StringVariable = "Hello Underworld"; UBlobSerializationTestClass* Output = NewObject(); TArray Buffer; FBlobWriter Writer(Buffer); FBinaryArchiveFormatter OutputFormatter(Writer); FStructuredArchive StructuredWriter(OutputFormatter); FBlobReader Reader(Buffer); FBinaryArchiveFormatter InputFormatter(Reader); FStructuredArchive StructuredReader(InputFormatter); Serialize(Input, StructuredWriter); Serialize(Output, StructuredReader); UE_LOG(LogBlobSerialization, Log, TEXT("UObject/Serialize Structured buffer length: %d"), Buffer.Num()); TestTrue("UObject/Serialize Structured test", *Input == *Output); } // UObject / poly { UBlobSerializationTestClass* Input = NewObject(); Input->IntVariable = -333; Input->FloatVariable = 33.33; Input->StringVariable = "Hello Underworld"; UBlobSerializationTestClass* Output = NewObject(); { TArray Buffer; FBlobWriter Writer(Buffer); FBinaryArchiveFormatter OutputFormatter(Writer); FStructuredArchive StructuredWriter(OutputFormatter); FBlobReader Reader(Buffer); FBinaryArchiveFormatter InputFormatter(Reader); FStructuredArchive StructuredReader(InputFormatter); SerializePoly(Input, StructuredWriter); SerializePoly(Output, StructuredReader); UE_LOG(LogBlobSerialization, Log, TEXT("UObject/Serialize Structured buffer length: %d"), Buffer.Num()); TestTrue("UObject/Serialize Structured test", *Input == *Output); } { TArray InputArray = {Input, Input, Input}; TArray OutputArray; TArray Buffer; FBlobWriter Writer(Buffer); FBinaryArchiveFormatter OutputFormatter(Writer); FStructuredArchive StructuredWriter(OutputFormatter); FBlobReader Reader(Buffer); FBinaryArchiveFormatter InputFormatter(Reader); FStructuredArchive StructuredReader(InputFormatter); SerializePoly(InputArray, StructuredWriter); SerializePoly(OutputArray, StructuredReader); TestTrue("UObject/Serialize object array", OutputArray.Num() == 3 && !OutputArray.Contains(nullptr)); } } return true; } ////////////////////////////////////////////////////////////////////////// IMPLEMENT_SIMPLE_AUTOMATION_TEST(BlobTest, "BlobSerialization.BlobTest", EAutomationTestFlags::EditorContext | EAutomationTestFlags::CommandletContext | EAutomationTestFlags::EngineFilter) bool RunBlobTest(BlobTest& InTest, EBlobArchiveType ArchiveType) { // UStruct { FBlobSerializationTestStruct Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; FBlobSerializationTestStruct Output; FBlob Blob; Blob.SerializeValue(Input, ArchiveType); Blob.DeserializeValue(Output, ArchiveType); if (ArchiveType == EBlobArchiveType::SaveGame) { InTest.TestTrue("UStruct/FBlob test SaveGame", Input.IntVariable == Output.IntVariable); InTest.TestFalse("UStruct/FBlob test SaveGame", Input == Output); } else InTest.TestTrue("UStruct/FBlob test", Input == Output); } // UObject { UBlobSerializationTestClass* Input = NewObject(); Input->IntVariable = -333; Input->FloatVariable = 33.33; Input->StringVariable = "Hello Underworld"; UBlobSerializationTestClass* Output = NewObject(); FBlob Blob; Blob.SerializeValue(Input, ArchiveType); Blob.DeserializeValue(Output, ArchiveType); if (ArchiveType == EBlobArchiveType::SaveGame) { InTest.TestTrue("UObject/FBlob test SaveGame", Input->IntVariable == Output->IntVariable); InTest.TestFalse("UObject/FBlob test SaveGame", *Input == *Output); } else InTest.TestTrue("UObject/FBlob test", *Input == *Output); } return true; } bool BlobTest::RunTest(const FString& Parameters) { bool Success = true; Success &= RunBlobTest(*this, EBlobArchiveType::Default); Success &= RunBlobTest(*this, EBlobArchiveType::Persistent); Success &= RunBlobTest(*this, EBlobArchiveType::SaveGame); Success &= RunBlobTest(*this, EBlobArchiveType::Binary); // Net unit tests will assert without a valid NetContext object, we will test them in blueprint // Success &= RunBlobTest(*this, EBlobArchiveType::Net); return Success; } ////////////////////////////////////////////////////////////////////////// IMPLEMENT_SIMPLE_AUTOMATION_TEST(JsonTest, "BlobSerialization.JsonTest", EAutomationTestFlags::EditorContext | EAutomationTestFlags::CommandletContext | EAutomationTestFlags::EngineFilter) bool JsonTest::RunTest(const FString& Parameters) { # if WITH_TEXT_ARCHIVE_SUPPORT // UStruct { FBlobSerializationTestStruct Input; Input.IntVariable = -333; Input.FloatVariable = 33.33; Input.StringVariable = "Hello Underworld"; FBlobSerializationTestStruct Output; FString JsonString; SerializeToJsonString(Input, JsonString); SerializeFromJsonString(JsonString, Output); TestTrue("UStruct/FBlob test", Input == Output); } // UObject { UBlobSerializationTestClass* Input = NewObject(); Input->IntVariable = -333; Input->FloatVariable = 33.33; Input->StringVariable = "Hello Underworld"; UBlobSerializationTestClass* Output = NewObject(); FString JsonString; SerializeToJsonString(Input, JsonString); SerializeFromJsonString(JsonString, Output); TestTrue("UObject/FBlob test", *Input == *Output); } # endif // WITH_TEXT_ARCHIVE_SUPPORT return true; } } // namespace BlobSerialization #endif