Files
UnrealEngineUWP/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BPCompilerTests.cpp
Mike Beach 134cb04d27 Copying //UE4/Dev-Blueprints to Dev-Main (//UE4/Dev-Main) @ 2781164
#lockdown Nick.Penwarden

==========================
MAJOR FEATURES + CHANGES
==========================

Change 2716841 on 2015/10/05 by Mike.Beach

	(WIP) Cleaning up how we setup script assets for replacement on cook (aligning more with the Blueprint conversion tool).

	#codereview Maciej.Mroz

Change 2719089 on 2015/10/07 by Maciej.Mroz

	ToValidCPPIdentifierChars handles propertly '?' char.

	#codereview Dan.Oconnor

Change 2719361 on 2015/10/07 by Maciej.Mroz

	Generated native code for AnimBPGC - some preliminary changes.

	Refactor: UAnimBlueprintGeneratedClass is not accessed directly in runtime. It is accessed via UAnimClassInterface interface.
	Properties USkeletalMeshComponent::AnimBlueprintGeneratedClass and UInterpTrackFloatAnimBPParam::AnimBlueprintClass were changed into "TSubclassOf<UAnimInstance> AnimClass"

	The UDynamicClass also can deliver the IAnimClassInterface interface. See UAnimClassData, IAnimClassInterface::GetFromClass and UDynamicClass::AnimClassImplementation.

	#codereview Lina.Halper, Thomas.Sarkanen

Change 2719383 on 2015/10/07 by Maciej.Mroz

	Debug-only code removed

Change 2720528 on 2015/10/07 by Dan.Oconnor

	Fix for determinsitc cooking of async tasks and load asset nodes
	#codereview Mike.Beach, Maciej.Mroz

Change 2721273 on 2015/10/08 by Maciej.Mroz

	Blueprint Compiler Cpp Backend
	- Anim Blueprints can be converted
	- Various fixes/improvements

Change 2721310 on 2015/10/08 by Maciej.Mroz

	refactor (cl#2719361) - no "auto" keyword

Change 2721727 on 2015/10/08 by Mike.Beach

	(WIP) Setup the cook commandlet so it handles converted assets, replacing them with generated classes.

	    - Refactored the conversion manifest (using a map over an array)
	    - Centralized destination paths into a helper struct (for the manifest)
	    - Generating an Editor module that automatically hooks into the cook process when enabled
	    - Loading and applying native replacments for the cook

Change 2723276 on 2015/10/09 by Michael.Schoell

	Blueprints duplicated for PIE will no longer register as dependencies to other Blueprint.

	#jira UE-16695 - Editor freezes then crashes while attempting to save during PIE
	#jira UE-21614 - [CrashReport] Crash while saving during PIE - FKismetEditorUtilities::CompileBlueprint() kismet2.cpp:736

Change 2724345 on 2015/10/11 by Ben.Cosh

	Blueprint profiler at first pass, this includes the ability to instrument specific blueprints with realtime editor stat display.
	#UEBP-21 - Profiling data capture and storage
	#UEBP-13 - Performance capture landing page
	#Branch UE4
	#Proj BlueprintProfiler, BlueprintGraph, EditorStyle, Kismet, UnrealEd, CoreUObject, Engine

Change 2724613 on 2015/10/12 by Ben.Cosh

	Incremental update for blueprint profiler to fix the way some of the reported stats cascade through events and branches and additionally some missed bits of code are refactored/removed.
	#Branch UE4
	#Proj BlueprintProfiler

	#info Whilst looking into this I spotted the reason why the stats seem so erratic, There appears to be an issue with FText's use of EXPERIMENTAL_TEXT_FAST_DECIMAL_FORMAT which I have reported, but ideally disable this locally until a fix is integrated.

Change 2724723 on 2015/10/12 by Maciej.Mroz

	Constructor of a dynamic class creates CDO.

	#codereview Robert.Manuszewski

Change 2725108 on 2015/10/12 by Mike.Beach

	[UE-21891] Minor fix to the array shuffle() function; now processes the last entry like all the others.

Change 2726358 on 2015/10/13 by Maciej.Mroz

	UDataTable is properly saved even if its RowStruct is null.

	https://udn.unrealengine.com/questions/264064/crash-using-hotreload-in-custom-datatable-cdo-clas.html

Change 2727395 on 2015/10/13 by Mike.Beach

	(WIP) Second pass on the Blueprint conversion pipeline; setting it up for more optimal (speedier) performance.
	    * Using stubs for replacements (rather than loading dynamic replacement).
	    * Giving the cook commandlet more control (so a conversion could be ran directly).
	    * Now logging replacements by old object path (to account for UPackage replacement queries).
	    * Fix for [UE-21947], unshelved from CL 2724944 (by Maciej.Mroz).

	#codereview Maciej.Mroz

Change 2727484 on 2015/10/13 by Mike.Beach

	[UE-22008] Fixing up comment/tooltip typo for UActorComponent::bAutoActivate.

Change 2727527 on 2015/10/13 by Mike.Beach

	Downgrading an inactionable EdGraph warning, while adding more info so we could possibly determine what's happening.

Change 2727702 on 2015/10/13 by Dan.Oconnor

	Fix for crash in UDelegateProperty::GetCPPType when called on a function with no OwnerClass (events)

Change 2727968 on 2015/10/14 by Maciej.Mroz

	Since ConstructorHelpers::FClassFinder is usually static, the loaded class should be in root set, to prevent the pointer stored in  ConstructorHelpers::FClassFinder from being obsolete.
	FindOrLoadClass behaves now like FindOrLoadObject.

	#codereview Robert.Manuszewski, Nick.Whiting

Change 2728139 on 2015/10/14 by Phillip.Kavan
2015-11-25 18:47:20 -05:00

455 lines
13 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "BlueprintCompilerCppBackendModulePrivatePCH.h"
class FArchiveSkipTransientObjectCRC32 : public FArchiveObjectCrc32
{
// Begin FArchive Interface
virtual bool ShouldSkipProperty(const UProperty* InProperty) const override
{
return FArchiveObjectCrc32::ShouldSkipProperty(InProperty)
|| InProperty->HasAllPropertyFlags(CPF_Transient)
|| InProperty->HasAllPropertyFlags(CPF_EditorOnly)
|| InProperty->GetName() == TEXT("BlueprintCreatedComponents")
|| InProperty->GetName() == TEXT("CreationMethod")
|| InProperty->GetName() == TEXT("InstanceComponents")
|| InProperty->GetName() == TEXT("bNetAddressable")
|| InProperty->GetName() == TEXT("OwnedComponents");
}
// End FArchive Interface
};
void ClearNativeRecursive(UObject* OnObject)
{
OnObject->ClearInternalFlags(EInternalObjectFlags::Native);
TArray<UObject*> Children;
GetObjectsWithOuter(OnObject, Children, false);
for (auto Entry : Children)
{
ClearNativeRecursive(Entry);
}
}
// We mess with the rootset flag instead of using FGCObject because it's an error for any test data to remain in the rootset after the test runs
struct FOwnedObjectsHelper
{
~FOwnedObjectsHelper()
{
for (auto Entry : OwnedObjects)
{
Entry->RemoveFromRoot();
Entry->ClearFlags(RF_Standalone);
if (Entry->IsNative())
{
ClearNativeRecursive(Entry);
}
// Actors need to be explicitly destroyed, probably just to remove them from their owning
// level:
if (AActor* AsActor = Cast<AActor>(Entry))
{
AsActor->Destroy();
}
}
CollectGarbage(RF_NoFlags);
}
void Push(UObject* Obj)
{
Obj->AddToRoot();
OwnedObjects.Push(Obj);
}
private:
TArray<UObject*> OwnedObjects;
};
// Helper functions introduced to load classes (generated or native:
static UClass* GetGeneratedClass(const TCHAR* TestFolder, const TCHAR* ClassName, FAutomationTestBase* Context, FOwnedObjectsHelper &OwnedObjects)
{
FString FullName = FString::Printf(TEXT("/Engine/NotForLicensees/Automation/CompilerTests/%s/%s.%s"), TestFolder, ClassName, ClassName);
UBlueprint* Blueprint = LoadObject<UBlueprint>(NULL, *FullName);
if (!Blueprint)
{
Context->AddWarning(FString::Printf(TEXT("Missing blueprint for test: '%s'"), *FullName));
return nullptr;
}
TArray<UObject*> Objects;
GetObjectsWithOuter(Blueprint->GetOuter(), Objects, false);
for (auto Entry : Objects)
{
OwnedObjects.Push(Entry);
}
CollectGarbage(RF_NoFlags);
return Blueprint->GeneratedClass;
}
static UClass* GetNativeClass(const TCHAR* TestFolder, const TCHAR* ClassName, FAutomationTestBase* Context, FOwnedObjectsHelper &OwnedObjects)
{
FString FullName = FString::Printf(TEXT("/Game/Blueprints/CompilerTests/%s/%s"), TestFolder, ClassName);
UPackage* NativePackage = CreatePackage(NULL, *FullName);
check(NativePackage);
CollectGarbage(RF_NoFlags);
const FString FStringFullPathName = FString::Printf(TEXT("%s.%s_C"), *FullName, ClassName);
UClass* Ret = (UClass*)ConstructDynamicType(*FStringFullPathName); //FindObjectFast<UClass>(NativePackage, *(FString(ClassName) + TEXT("_C")));
if (!Ret)
{
Context->AddWarning(FString::Printf(TEXT("Missing native type for test: '%s'"), ClassName));
return nullptr;
}
TArray<UObject*> Objects;
GetObjectsWithOuter(NativePackage, Objects, false);
for (auto Entry : Objects)
{
OwnedObjects.Push(Entry);
}
CollectGarbage(RF_NoFlags);
return Ret;
}
typedef UClass* (*ClassAccessor)(const TCHAR*, const TCHAR*, FAutomationTestBase*, FOwnedObjectsHelper&);
typedef uint32(*TestImpl)(ClassAccessor F, FAutomationTestBase* Context);
// This pattern is repeated in each test, so I'm just writing a helper function rather than copy/pasting it:
static bool RunTestHelper(TestImpl T, FAutomationTestBase* Context)
{
uint32 ResultsGenerated = T(&GetGeneratedClass, Context);
uint32 ResultsNative = T(&GetNativeClass, Context);
if (ResultsGenerated == 0)
{
Context->AddError(TEXT("Test failed to run!"));
}
else if (ResultsGenerated != ResultsNative)
{
Context->AddError(TEXT("Native differs from generated!"));
}
return true;
}
// Helper functions introduced to avoid crashing if classes are missing:
static UObject* NewTestObject(UClass* Class, FOwnedObjectsHelper &OwnedObjects)
{
if (Class)
{
UObject* Result = NewObject<UObject>(GetTransientPackage(), Class, NAME_None);
if (Result)
{
OwnedObjects.Push(Result);
}
return Result;
}
return nullptr;
}
static UObject* NewTestActor(UClass* ActorClass, FOwnedObjectsHelper &OwnedObjects)
{
if (ActorClass)
{
AActor* Actor = GWorld->SpawnActor(ActorClass);
#if WITH_EDITORONLY_DATA
if (Actor)
{
OwnedObjects.Push(Actor);
Actor->GetRootComponent()->bVisualizeComponent = true;
}
#endif
return Actor;
}
return nullptr;
}
static void Call(UObject* Target, const TCHAR* FunctionName, void* Args = nullptr)
{
if (!Target)
{
return;
}
UFunction* Fn = Target->FindFunction(FunctionName);
if (Fn)
{
Target->ProcessEvent(Fn, Args);
}
}
// Remove EAutomationTestFlags::Disabled to enable these tests, note that these will need to be moved into the ClientContext because we can only test cooked content:
static const uint32 CompilerTestFlags = EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter;
// Tests:
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerArrayTest, "Project.Blueprints.NativeBackend.ArrayTest", CompilerTestFlags)
bool FBPCompilerArrayTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
TArray<FString> Input;
Input.Push(TEXT("addedString"));
UObject* TestInstance = NewTestObject(F(TEXT("Array"), TEXT("BP_Array_Basic"), Context, OwnedObjects), OwnedObjects);
if (!TestInstance)
{
return 0u;
}
Call(TestInstance, TEXT("RunArrayTest"), &Input);
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(TestInstance);
};
return RunTestHelper(TestBody, this);
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerCDOTest, "Project.Blueprints.NativeBackend.CDOTest", CompilerTestFlags)
bool FBPCompilerCDOTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
UObject* TestInstance = NewTestObject(F(TEXT("CDO"), TEXT("BP_CDO_Basic"), Context, OwnedObjects), OwnedObjects);
if (!TestInstance)
{
return 0u;
}
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(TestInstance->GetClass()->ClassDefaultObject);
};
return RunTestHelper(TestBody, this);
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerCommunicationTest, "Project.Blueprints.NativeBackend.CommunicationTest", CompilerTestFlags)
bool FBPCompilerCommunicationTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
UObject* A = NewTestObject(F(TEXT("Communication"), TEXT("BP_Comm_Test_A"), Context, OwnedObjects), OwnedObjects);
UObject* B = NewTestObject(F(TEXT("Communication"), TEXT("BP_Comm_Test_B"), Context, OwnedObjects), OwnedObjects);
if (!A || !B)
{
return 0u;
}
Call(A, TEXT("Flop"));
Call(B, TEXT("Flip"));
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(B, Results.Crc32(A));
};
return RunTestHelper(TestBody, this);
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerConstructionScriptTest, "Project.Blueprints.NativeBackend.ConstructionScriptTest", CompilerTestFlags)
bool FBPCompilerConstructionScriptTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
UObject* TestInstance = NewTestActor(F(TEXT("ConstructionScript"), TEXT("BP_ConstructionScript_Test"), Context, OwnedObjects), OwnedObjects);
if (!TestInstance)
{
return 0u;
}
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(TestInstance);
};
return RunTestHelper(TestBody, this);
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerControlFlowTest, "Project.Blueprints.NativeBackend.ControlFlow", CompilerTestFlags)
bool FBPCompilerControlFlowTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
UObject* TestInstance = NewTestObject(F(TEXT("ControlFlow"), TEXT("BP_ControlFlow_Basic"), Context, OwnedObjects), OwnedObjects);
if (!TestInstance)
{
return 0u;
}
Call(TestInstance, TEXT("RunControlFlowTest"));
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(TestInstance);
};
return RunTestHelper(TestBody, this);
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerEnumTest, "Project.Blueprints.NativeBackend.EnumTest", CompilerTestFlags)
bool FBPCompilerEnumTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
UObject* TestInstance = NewTestObject(F(TEXT("Enum"), TEXT("BP_Enum_Reader_Writer"), Context, OwnedObjects), OwnedObjects);
if (!TestInstance)
{
return 0u;
}
Call(TestInstance, TEXT("UpdateEnum"));
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(TestInstance);
};
return RunTestHelper(TestBody, this);
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerEventTest, "Project.Blueprints.NativeBackend.Event", CompilerTestFlags)
bool FBPCompilerEventTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
UObject* TestInstance = NewTestObject(F(TEXT("Event"), TEXT("BP_Event_Basic"), Context, OwnedObjects), OwnedObjects);
if (!TestInstance)
{
return 0u;
}
Call(TestInstance, TEXT("BeginEventChain"));
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(TestInstance);
};
return RunTestHelper(TestBody, this);
}
struct FInheritenceTestParams
{
bool bFlag;
TArray<FString> Strings;
TArray<int32> Result;
};
inline bool operator==(const FInheritenceTestParams& LHS, const FInheritenceTestParams& RHS)
{
return
LHS.bFlag == RHS.bFlag &&
LHS.Strings == RHS.Strings &&
LHS.Result == RHS.Result;
}
inline bool operator!=(const FInheritenceTestParams& LHS, const FInheritenceTestParams& RHS)
{
return !(LHS == RHS);
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerInheritenceTest, "Project.Blueprints.NativeBackend.Inheritence", CompilerTestFlags)
bool FBPCompilerInheritenceTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
UObject* TestInstance = NewTestObject(F(TEXT("Inheritence"), TEXT("BP_Child_Basic"), Context, OwnedObjects), OwnedObjects);
if (!TestInstance)
{
return 0u;
}
FInheritenceTestParams Params;
Params.bFlag = true;
Call(TestInstance, TEXT("VirtualFunction"), &Params);
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(TestInstance);
};
return RunTestHelper(TestBody, this);
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerStructureTest, "Project.Blueprints.NativeBackend.Structure", CompilerTestFlags)
bool FBPCompilerStructureTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
UObject* TestInstance = NewTestObject(F(TEXT("Structure"), TEXT("BP_Structure_Driver"), Context, OwnedObjects), OwnedObjects);
if (!TestInstance)
{
return 0u;
}
Call(TestInstance, TEXT("RunStructTest"));
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(TestInstance);
};
return RunTestHelper(TestBody, this);
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerNodeTest, "Project.Blueprints.NativeBackend.Node", CompilerTestFlags)
bool FBPCompilerNodeTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
UObject* TestInstance = NewTestObject(F(TEXT("Node"), TEXT("BP_Node_Basic"), Context, OwnedObjects), OwnedObjects);
if (!TestInstance)
{
return 0u;
}
Call(TestInstance, TEXT("RunNodes"));
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(TestInstance);
};
return RunTestHelper(TestBody, this);
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBPCompilerLatentTest, "Project.Blueprints.NativeBackend.Latent", CompilerTestFlags)
bool FBPCompilerLatentTest::RunTest(const FString& Parameters)
{
auto TestBody = [](ClassAccessor F, FAutomationTestBase* Context)
{
FOwnedObjectsHelper OwnedObjects;
TGuardValue<bool> AutoRestore(GAllowActorScriptExecutionInEditor, true);
UObject* TestInstance = NewTestActor(F(TEXT("Node"), TEXT("BP_Latent_Basic"), Context, OwnedObjects), OwnedObjects);
if (!TestInstance)
{
return 0u;
}
Call(TestInstance, TEXT("RunDelayTest"));
Call(TestInstance, TEXT("RunDownloadTest"));
FArchiveSkipTransientObjectCRC32 Results;
return Results.Crc32(TestInstance);
};
return RunTestHelper(TestBody, this);
}