Files
UnrealEngineUWP/Engine/Source/Runtime/CoreUObject/Tests/ObjectPropertyTest.cpp
joe pribele 4fe33224d4 fix for failing tests on console
#preflight 640a442270639dfc946b595c

[CL 24581784 by joe pribele in ue5-main branch]
2023-03-09 15:57:35 -05:00

372 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#if WITH_LOW_LEVEL_TESTS
#include "TestHarness.h"
#include "TestMacros/Assertions.h"
#include "ObjectPtrTestClass.h"
#include "UObject/Package.h"
#include "Misc/ScopeExit.h"
#include "UObject/UnrealType.h"
#include "UObject/LinkerPlaceholderExportObject.h"
#include "UObject/LinkerPlaceholderClass.h"
#include "UObject/ObjectHandleTracking.h"
#include "LowLevelTestsRunner/WarnFilterScope.h"
#include "Misc/AssetRegistryInterface.h"
#include "AssetRegistry/AssetData.h"
TEST_CASE("UE::CoreUObject::FObjectProperty::CheckValidAddress")
{
bool bAllowRead = false;
#if UE_WITH_OBJECT_HANDLE_TRACKING
auto CallbackHandle = UE::CoreUObject::AddObjectHandleReadCallback([&bAllowRead](TArrayView<const UObject* const> Objects)
{
if (!bAllowRead)
FAIL("Unexpected read during CheckValidObject");
});
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReadCallback(CallbackHandle);
};
#endif
UClass* Class = UObjectPtrTestClassWithRef::StaticClass();
FObjectProperty* Property = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtr")));
REQUIRE(Property != nullptr);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("TestPackageName"), RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
};
UObjectPtrTestClassWithRef* Obj = NewObject<UObjectPtrTestClassWithRef>(TestPackage, TEXT("Object"));
UObjectPtrTestClass* Other = NewObject<UObjectPtrTestClass>(Obj, TEXT("Other"));
//verify nothing happens by default
CHECK(!Obj->ObjectPtr);
Property->CheckValidObject(&Obj->ObjectPtr, nullptr);
CHECK(!Obj->ObjectPtr);
//valid assignment
Obj->ObjectPtr = Other;
Property->CheckValidObject(&Obj->ObjectPtr, nullptr);
CHECK(Obj->ObjectPtr == Other);
bAllowRead = true; //has to read the fullpath from the object to make the error message
//assign a bad value to the pointer
Obj->ObjectPtr = reinterpret_cast<UObjectPtrTestClass*>(Obj);
CHECK(Obj->ObjectPtr != nullptr);
UE::Testing::FWarnFilterScope _([](const TCHAR* Message, ELogVerbosity::Type Verbosity, const FName& Category)
{
if (Category == TEXT("LogProperty") && FCString::Strstr(Message, TEXT("Reference will be nullptred")) && Verbosity == ELogVerbosity::Type::Warning)
{
return true;
}
return false;
});
Property->CheckValidObject(&Obj->ObjectPtr, Other);
CHECK(!Obj->ObjectPtr); //value should be nulled since the type was not compatible
}
TEST_CASE("UE::CoreUObject::FObjectProperty::CheckValidAddressNonNullable")
{
bool bAllowRead = false;
#if UE_WITH_OBJECT_HANDLE_TRACKING
auto CallbackHandle = UE::CoreUObject::AddObjectHandleReadCallback([&bAllowRead](TArrayView<const UObject* const> Objects)
{
if (!bAllowRead)
{
FAIL("Unexpected read during CheckValidObject");
}
});
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReadCallback(CallbackHandle);
};
#endif
UClass* Class = UObjectPtrTestClassWithRef::StaticClass();
FObjectProperty* Property = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtrNonNullable")));
REQUIRE(Property != nullptr);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("TestPackageName"), RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
};
UObjectPtrTestClassWithRef* Obj = NewObject<UObjectPtrTestClassWithRef>(TestPackage, TEXT("Object"));
UObjectPtrTestClass* Other = NewObject<UObjectPtrTestClass>(Obj, TEXT("Other"));
//property is already null should stay null
CHECK(!Obj->ObjectPtrNonNullable);
Property->CheckValidObject(&Obj->ObjectPtrNonNullable, Other);
CHECK(!Obj->ObjectPtrNonNullable);
//valid assignment
Obj->ObjectPtrNonNullable = Other;
Property->CheckValidObject(&Obj->ObjectPtrNonNullable, nullptr);
CHECK(Obj->ObjectPtrNonNullable == Other);
bAllowRead = true; //has to read the fullpath from the object to make the error message
//assign a bad value to the pointer
Obj->ObjectPtrNonNullable = reinterpret_cast<UObjectPtrTestClass*>(Obj);
CHECK(Obj->ObjectPtrNonNullable != nullptr);
UE::Testing::FWarnFilterScope _([](const TCHAR* Message, ELogVerbosity::Type Verbosity, const FName& Category)
{
if (Category == TEXT("LogProperty") && FCString::Strstr(Message, TEXT("Reference will be reverted back to")) && Verbosity == ELogVerbosity::Type::Warning)
{
return true;
}
return false;
});
Property->CheckValidObject(&Obj->ObjectPtrNonNullable, Other);
CHECK(Obj->ObjectPtrNonNullable == Other); //non nullable properties should be assigned the old value
//assign a bad value to the pointer
Obj->ObjectPtrNonNullable = reinterpret_cast<UObjectPtrTestClass*>(Obj);
CHECK(Obj->ObjectPtrNonNullable != nullptr);
//old value is required for non nullable properties
REQUIRE_CHECK(Property->CheckValidObject(&Obj->ObjectPtrNonNullable, nullptr));
}
class FMockArchive : public FArchive
{
public:
TFunction<void(FObjectPtr& Value)> OnFObjectPtr;
virtual FArchive& operator<<(FObjectPtr& Value)
{
if (OnFObjectPtr)
OnFObjectPtr(Value);
return *this;
}
};
template<typename T>
static void TestSerializeItem(FName ObjectName)
{
UClass* Class = T::StaticClass();
FObjectProperty* Property = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtr")));
REQUIRE(Property != nullptr);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("TestPackageName"), RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
};
T* Obj = NewObject<T>(TestPackage, ObjectName);
UObjectPtrTestClass* Other = NewObject<UObjectPtrTestClass>(Obj, TEXT("Other"));
ULinkerPlaceholderExportObject* PlaceHolderExport = NewObject<ULinkerPlaceholderExportObject>(TestPackage, TEXT("PlaceHolderExport"));
ULinkerPlaceholderClass* PlaceHolderClass = NewObject<ULinkerPlaceholderClass>(TestPackage, TEXT("PlaceHolderClass"));
PlaceHolderClass->Bind(); //must call bind or crashes on shutdown
int ReadCount = 0;
#if UE_WITH_OBJECT_HANDLE_TRACKING
auto CallbackId = UE::CoreUObject::AddObjectHandleReadCallback([&ReadCount](TArrayView<const UObject* const> Objects)
{
++ReadCount;
});
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReadCallback(CallbackId);
};
#endif
{
//verify that if the property is null no reads are triggered
FMockArchive MockArchive;
MockArchive.OnFObjectPtr = [&](FObjectPtr& Value)
{
Value = PlaceHolderExport;
};
FBinaryArchiveFormatter Formatter(MockArchive);
FStructuredArchive Ar(Formatter);
FStructuredArchiveSlot Slot = Ar.Open();
FObjectPtrProperty::StaticSerializeItem(Property, Slot, &Obj->ObjectPtr, nullptr);
CHECK(ReadCount == 0);
CHECK(*reinterpret_cast<ULinkerPlaceholderExportObject**>(&Obj->ObjectPtr) == PlaceHolderExport);
}
{
//verify that if the property is not null no reads are triggered
FMockArchive MockArchive;
MockArchive.OnFObjectPtr = [&](FObjectPtr& Value)
{
Value = PlaceHolderClass;
};
FBinaryArchiveFormatter Formatter(MockArchive);
FStructuredArchive Ar(Formatter);
FStructuredArchiveSlot Slot = Ar.Open();
Obj->ObjectPtr = Other;
FObjectPtrProperty::StaticSerializeItem(Property, Slot, &Obj->ObjectPtr, nullptr);
CHECK(ReadCount == 0);
CHECK(*reinterpret_cast<ULinkerPlaceholderClass**>(&Obj->ObjectPtr) == PlaceHolderClass);
}
}
TEST_CASE("UE::CoreUObject::FObjectPtrProperty::StaticSerializeItem")
{
TestSerializeItem<UObjectPtrTestClassWithRef>(TEXT("Object1"));
}
TEST_CASE("UE::CoreUObject::FObjectProperty::StaticSerializeItem")
{
TestSerializeItem<UObjectWithRawProperty>(TEXT("Object2"));
}
class MockAssetRegistryInterface : public IAssetRegistryInterface
{
public:
MockAssetRegistryInterface()
: Old(IAssetRegistryInterface::Default)
{
IAssetRegistryInterface::Default = this;
}
virtual ~MockAssetRegistryInterface()
{
IAssetRegistryInterface::Default = Old;
}
virtual void GetDependencies(FName InPackageName, TArray<FName>& OutDependencies, UE::AssetRegistry::EDependencyCategory Category = UE::AssetRegistry::EDependencyCategory::Package, const UE::AssetRegistry::FDependencyQuery& Flags = UE::AssetRegistry::FDependencyQuery()) override
{
}
virtual UE::AssetRegistry::EExists TryGetAssetByObjectPath(const FSoftObjectPath& ObjectPath, FAssetData& OutAssetData) const override
{
const FAssetData* Found = AssetData.Find(ObjectPath);
if (Found)
{
OutAssetData = *Found;
return UE::AssetRegistry::EExists::Exists;
}
return UE::AssetRegistry::EExists::DoesNotExist;
}
virtual UE::AssetRegistry::EExists TryGetAssetPackageData(FName PackageName, FAssetPackageData& OutPackageData) const override
{
return UE::AssetRegistry::EExists::Exists;
}
IAssetRegistryInterface* Old;
TMap<FSoftObjectPath, FAssetData> AssetData;
};
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE("UE::CoreUObject::FObjectProperty::ParseObjectPropertyValue")
{
UClass* Class = UObjectPtrTestClassWithRef::StaticClass();
FObjectProperty* Property = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtr")));
//make a fake entry for the asset registry
//this allows lazy load to return an unresolved handle
MockAssetRegistryInterface MockAssetRegistry;
const TCHAR* Text = TEXT("/TestPackageName.Other");
FSoftObjectPath Path(Text);
FAssetData AssetData;
AssetData.PackageName = "/TestPackageName";
AssetData.PackagePath = "/";
AssetData.AssetName = "Other";
AssetData.AssetClassPath.TrySetPath(UObjectPtrTestClass::StaticClass());
MockAssetRegistry.AssetData.Add(Path, AssetData);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("TestPackageName"), RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
};
UObjectPtrTestClassWithRef* Obj = NewObject<UObjectPtrTestClassWithRef>(TestPackage, "ObjectName");
TObjectPtr<UObject> Result;
FObjectPropertyBase::ParseObjectPropertyValue(Property, Obj, UObjectPtrTestClass::StaticClass(), 0, Text, Result);
CHECK(Result != nullptr);
CHECK(!Result.IsResolved());
{
const TCHAR * Buffer = TEXT("ObjectPtr=/TestPackageName.Other");
TArray<FDefinedProperty> DefinedProperties;
Buffer = FProperty::ImportSingleProperty(Buffer, Obj, Class, Obj, PPF_Delimited, nullptr, DefinedProperties);
CHECK(Obj->ObjectPtr != nullptr);
CHECK(!Obj->ObjectPtr.IsResolved());
}
{
const TCHAR* Buffer = TEXT("ObjectPtr=/TestPackageName.Other");
FProperty* ObjProperty = Class->FindPropertyByName(TEXT("ObjectPtr"));
ObjProperty->ImportText_InContainer(Buffer, Obj, Obj, 0);
CHECK(Obj->ObjectPtr == nullptr); //TODO this should not resolve and not be null
CHECK(Obj->ObjectPtr.IsResolved());
}
{
const TCHAR * Buffer = TEXT("ArrayObjPtr=(/TestPackageName.Other)");
TArray<FDefinedProperty> DefinedProperties;
Buffer = FProperty::ImportSingleProperty(Buffer, Obj, Class, Obj, PPF_Delimited, nullptr, DefinedProperties);
CHECK(Obj->ArrayObjPtr.Num() == 1);
CHECK(!Obj->ArrayObjPtr[0].IsResolved());
}
{
const TCHAR* Buffer = TEXT("ArrayObjPtr=(/TestPackageName.Other)");
FProperty* ArrayProperty = Class->FindPropertyByName(TEXT("ArrayObjPtr"));
ArrayProperty->ImportText_InContainer(Buffer, Obj, Obj, 0);
CHECK(Obj->ArrayObjPtr.Num() == 1);
CHECK(!Obj->ArrayObjPtr[0].IsResolved());
}
{
const TCHAR * Buffer = TEXT("ObjectPtr=/TestPackageName.Other");
TArray<FDefinedProperty> DefinedProperties;
Buffer = FProperty::ImportSingleProperty(Buffer, Obj, Class, Obj, PPF_Delimited, nullptr, DefinedProperties);
CHECK(Obj->ObjectPtr != nullptr);
CHECK(!Obj->ObjectPtr.IsResolved());
}
{
const TCHAR* Buffer = TEXT("ObjectPtr=/TestPackageName.Other");
FProperty* ObjProperty = Class->FindPropertyByName(TEXT("ObjectPtr"));
ObjProperty->ImportText_InContainer(Buffer, Obj, Obj, 0);
CHECK(Obj->ObjectPtr == nullptr); //TODO this should not resolve and not be null
CHECK(Obj->ObjectPtr.IsResolved());
}
{
const TCHAR * Buffer = TEXT("ArrayObjPtr=(/TestPackageName.Other)");
TArray<FDefinedProperty> DefinedProperties;
Buffer = FProperty::ImportSingleProperty(Buffer, Obj, Class, Obj, PPF_Delimited, nullptr, DefinedProperties);
CHECK(Obj->ArrayObjPtr.Num() == 1);
CHECK(!Obj->ArrayObjPtr[0].IsResolved());
}
{
const TCHAR* Buffer = TEXT("ArrayObjPtr=(/TestPackageName.Other)");
FProperty* ArrayProperty = Class->FindPropertyByName(TEXT("ArrayObjPtr"));
ArrayProperty->ImportText_InContainer(Buffer, Obj, Obj, 0);
CHECK(Obj->ArrayObjPtr.Num() == 1);
CHECK(!Obj->ArrayObjPtr[0].IsResolved());
}
}
#endif
#endif