Files
UnrealEngine/Engine/Source/Editor/Kismet/Private/Tests/BlueprintEditorTests.cpp
2025-05-18 13:04:45 +08:00

1408 lines
52 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
#include "Misc/AutomationTest.h"
#include "Modules/ModuleManager.h"
#include "UObject/Class.h"
#include "UObject/Package.h"
#include "Misc/PackageName.h"
#include "Framework/Commands/InputChord.h"
#include "Templates/SubclassOf.h"
#include "EdGraph/EdGraphPin.h"
#include "Components/ActorComponent.h"
#include "GameFramework/Actor.h"
#include "Engine/Blueprint.h"
#include "Components/StaticMeshComponent.h"
#include "EdGraph/EdGraph.h"
#include "Factories/BlueprintFactory.h"
#include "Particles/ParticleSystem.h"
#include "Engine/StaticMesh.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "AssetRegistry/AssetData.h"
#include "Editor.h"
#include "UObject/SavePackage.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "EdGraphSchema_K2.h"
#include "EdGraphSchema_K2_Actions.h"
#include "K2Node_Event.h"
#include "K2Node_CallFunction.h"
#include "K2Node_AddComponent.h"
#include "K2Node_CustomEvent.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_VariableGet.h"
#include "K2Node_VariableSet.h"
#include "Engine/SCS_Node.h"
#include "BlueprintEditorModes.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "ComponentAssetBroker.h"
#include "AssetRegistry/ARFilter.h"
#include "ScopedTransaction.h"
#include "ObjectTools.h"
// Automation
#include "AssetRegistry/AssetRegistryModule.h"
#include "Tests/AutomationTestSettings.h"
#include "Tests/AutomationEditorCommon.h"
#include "Tests/AutomationEditorPromotionCommon.h"
#include "Subsystems/AssetEditorSubsystem.h"
#if WITH_DEV_AUTOMATION_TESTS
#define LOCTEXT_NAMESPACE "BlueprintEditorPromotionTests"
DEFINE_LOG_CATEGORY_STATIC(LogBlueprintEditorPromotionTests, Log, All);
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBlueprintEditorPromotionTest, "System.Promotion.Editor.Blueprint Editor", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter);
/**
* Helper functions used by the blueprint editor promotion automation test
*/
namespace BlueprintEditorPromotionUtils
{
/**
* Constants
*/
static const FString BlueprintNameString = TEXT("BlueprintEditorPromotionBlueprint");
static const FName BlueprintStringVariableName(TEXT("MyStringVariable"));
/**
* Gets the full path to the folder on disk
*/
static FString GetFullPath()
{
return FPackageName::FilenameToLongPackageName(FPaths::ProjectContentDir() + TEXT("BlueprintEditorPromotionTest"));
}
/**
* Helper class to track once a certain time has passed
*/
class FDelayHelper
{
public:
/**
* Constructor
*/
FDelayHelper() :
bIsRunning(false),
StartTime(0.f),
Duration(0.f)
{
}
/**
* Returns if the delay is still running
*/
bool IsRunning()
{
return bIsRunning;
}
/**
* Sets the helper state to not running
*/
void Reset()
{
bIsRunning = false;
}
/**
* Starts the delay timer
*
* @param InDuration - How long to delay for in seconds
*/
void Start(double InDuration)
{
bIsRunning = true;
StartTime = FPlatformTime::Seconds();
Duration = InDuration;
}
/**
* Returns true if the desired amount of time has passed
*/
bool IsComplete()
{
if (IsRunning())
{
const double CurrentTime = FPlatformTime::Seconds();
return CurrentTime - StartTime >= Duration;
}
else
{
return false;
}
}
private:
/** If true, this delay timer is active */
bool bIsRunning;
/** The time the delay started */
double StartTime;
/** How long the timer is for */
double Duration;
};
/**
* Sends the AssetEditor->SaveAsset UI command
*/
static void SendBlueprintResetViewCommand()
{
const FString Context = TEXT("BlueprintEditor");
const FString Command = TEXT("ResetCamera");
FInputChord CurrentSaveChord = FEditorPromotionTestUtilities::GetOrSetUICommand(Context, Command);
const FName FocusWidgetType(TEXT("SSCSEditorViewport"));
FEditorPromotionTestUtilities::SendCommandToCurrentEditor(CurrentSaveChord, FocusWidgetType);
}
/**
* Compiles the blueprint
*
* @param InBlueprint - The blueprint to compile
*/
static void CompileBlueprint(UBlueprint* InBlueprint)
{
FBlueprintEditorUtils::RefreshAllNodes(InBlueprint);
FKismetEditorUtilities::CompileBlueprint(InBlueprint, EBlueprintCompileOptions::SkipGarbageCollection);
if (InBlueprint->Status == EBlueprintStatus::BS_UpToDate)
{
UE_LOG(LogBlueprintEditorPromotionTests, Display, TEXT("Blueprint compiled successfully (%s)"), *InBlueprint->GetName());
}
else if (InBlueprint->Status == EBlueprintStatus::BS_UpToDateWithWarnings)
{
UE_LOG(LogBlueprintEditorPromotionTests, Display, TEXT("Blueprint compiled successfully with warnings(%s)"), *InBlueprint->GetName());
}
else if (InBlueprint->Status == EBlueprintStatus::BS_Error)
{
UE_LOG(LogBlueprintEditorPromotionTests, Display, TEXT("Blueprint failed to compile (%s)"), *InBlueprint->GetName());
}
else
{
UE_LOG(LogBlueprintEditorPromotionTests, Error, TEXT("Blueprint is in an unexpected state after compiling (%s)"), *InBlueprint->GetName());
}
}
/**
* Creates a blueprint component based off the supplied asset
*
* @param InBlueprint - The blueprint to modify
* @param InAsset - The asset to use for the component
*/
static USCS_Node* CreateBlueprintComponent(UBlueprint* InBlueprint, UObject* InAsset)
{
IAssetEditorInstance* OpenEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(InBlueprint, true);
FBlueprintEditor* CurrentBlueprintEditor = (FBlueprintEditor*)OpenEditor;
TSubclassOf<UActorComponent> ComponentClass = FComponentAssetBrokerage::GetPrimaryComponentForAsset(InAsset->GetClass());
USCS_Node* NewNode = InBlueprint->SimpleConstructionScript->CreateNode(ComponentClass);
// Assign the asset to the template
FComponentAssetBrokerage::AssignAssetToComponent(NewNode->ComponentTemplate, InAsset);
// Add node to the SCS
TArray<USCS_Node*> AllNodes = InBlueprint->SimpleConstructionScript->GetAllNodes();
USCS_Node* RootNode = AllNodes.Num() > 0 ? AllNodes[0] : NULL;
if (!RootNode || (RootNode == InBlueprint->SimpleConstructionScript->GetDefaultSceneRootNode()))
{
//New Root
InBlueprint->SimpleConstructionScript->AddNode(NewNode);
}
else
{
//Add as a child
RootNode->AddChildNode(NewNode);
}
// Recompile skeleton because of the new component we added
FKismetEditorUtilities::GenerateBlueprintSkeleton(InBlueprint, true);
CurrentBlueprintEditor->UpdateSubobjectPreview(true);
return NewNode;
}
/**
* Sets a new component as the root
*
* @param InBlueprint - The blueprint to modify
* @param NewRoot - The new root
*/
static void SetComponentAsRoot(UBlueprint* InBlueprint, USCS_Node* NewRoot)
{
// @FIXME: Current usages doesn't guarantee NewRoot is valid!!! Check first!
//Get all the construction script nodes
TArray<USCS_Node*> AllNodes = InBlueprint->SimpleConstructionScript->GetAllNodes();
USCS_Node* OldRootNode = AllNodes[0];
USCS_Node* OldParent = NULL;
//Find old parent
for (int32 NodeIndex = 0; NodeIndex < AllNodes.Num(); ++NodeIndex)
{
if (AllNodes[NodeIndex]->ChildNodes.Contains(NewRoot))
{
OldParent = AllNodes[NodeIndex];
break;
}
}
check(OldParent);
//Remove the new root from its old parent and
OldParent->RemoveChildNode(NewRoot);
NewRoot->Modify();
NewRoot->AttachToName = NAME_None;
//Remove the old root, add the new root, and attach the old root as a child
InBlueprint->SimpleConstructionScript->RemoveNode(OldRootNode);
InBlueprint->SimpleConstructionScript->AddNode(NewRoot);
NewRoot->AddChildNode(OldRootNode);
}
/**
* Removes a blueprint component from the simple construction script
*
* @param InBlueprint - The blueprint to modify
* @param InNode - The node of the component to remove
*/
static void RemoveBlueprintComponent(UBlueprint* InBlueprint, USCS_Node* InNode)
{
if (InNode != NULL)
{
// Remove node from SCS tree
InNode->GetSCS()->RemoveNodeAndPromoteChildren(InNode);
// Clear the delegate
InNode->SetOnNameChanged(FSCSNodeNameChanged());
}
}
/**
* Creates a new graph node from a given template
*
* @param NodeTemplate - The template to use for the node
* @param InGraph - The graph to create the new node in
* @param GraphLocation - The location to place the node
* @param ConnectPin - The pin to connect the node to
*/
static UEdGraphNode* CreateNewGraphNodeFromTemplate(UK2Node* NodeTemplate, UEdGraph* InGraph, const FVector2f& GraphLocation, UEdGraphPin* ConnectPin = NULL)
{
TSharedPtr<FEdGraphSchemaAction_K2NewNode> Action = TSharedPtr<FEdGraphSchemaAction_K2NewNode>(new FEdGraphSchemaAction_K2NewNode(FText::GetEmpty(), FText::GetEmpty(), FText::GetEmpty(), 0));
Action->NodeTemplate = NodeTemplate;
return Action->PerformAction(InGraph, ConnectPin, GraphLocation, false);
}
/**
* Creates an AddComponent action to the blueprint graph
*
* @param InBlueprint - The blueprint to modify
* @param InGraph - The blueprint graph to use
* @param InAsset - The asset to use
*/
static UEdGraphNode* CreateAddComponentActionNode(UBlueprint* InBlueprint, UEdGraph* InGraph, UObject* InAsset)
{
UEdGraph* TempOuter = NewObject<UEdGraph>((UObject*)InBlueprint);
TempOuter->SetFlags(RF_Transient);
const FScopedTransaction PropertyChanged(LOCTEXT("AddedGraphNode", "Added a graph node"));
InGraph->Modify();
// Make an add component node
UK2Node_CallFunction* CallFuncNode = NewObject<UK2Node_AddComponent>(TempOuter);
UFunction* AddComponentFn = FindFieldChecked<UFunction>(AActor::StaticClass(), UK2Node_AddComponent::GetAddComponentFunctionName());
CallFuncNode->FunctionReference.SetFromField<UFunction>(AddComponentFn, FBlueprintEditorUtils::IsActorBased(InBlueprint));
UEdGraphNode* NewNode = CreateNewGraphNodeFromTemplate(CallFuncNode, InGraph, FVector2f(200.0f, 0.0f));
TSubclassOf<UActorComponent> ComponentClass = InAsset ? FComponentAssetBrokerage::GetPrimaryComponentForAsset(InAsset->GetClass()) : NULL;
if ((NewNode != NULL) && (InBlueprint != NULL))
{
UK2Node_AddComponent* AddCompNode = CastChecked<UK2Node_AddComponent>(NewNode);
ensure(NULL != Cast<UBlueprintGeneratedClass>(InBlueprint->GeneratedClass));
// Then create a new template object, and add to array in
UActorComponent* NewTemplate = NewObject<UActorComponent>(InBlueprint->GeneratedClass, ComponentClass, NAME_None, RF_ArchetypeObject | RF_Public);
InBlueprint->ComponentTemplates.Add(NewTemplate);
// Set the name of the template as the default for the TemplateName param
UEdGraphPin* TemplateNamePin = AddCompNode->GetTemplateNamePinChecked();
if (TemplateNamePin)
{
TemplateNamePin->DefaultValue = NewTemplate->GetName();
}
// Set the return type to be the type of the template
UEdGraphPin* ReturnPin = AddCompNode->GetReturnValuePin();
if (ReturnPin)
{
ReturnPin->PinType.PinSubCategoryObject = *ComponentClass;
}
// Set the asset
if (InAsset != NULL)
{
FComponentAssetBrokerage::AssignAssetToComponent(NewTemplate, InAsset);
}
AddCompNode->ReconstructNode();
}
FBlueprintEditorUtils::MarkBlueprintAsModified(InBlueprint);
return NewNode;
}
/**
* Creates a SetStaticMesh action in the blueprint graph
*
* @param InBlueprint - The blueprint to modify
* @param InGraph - The blueprint graph to use
*/
static UEdGraphNode* AddSetStaticMeshNode(UBlueprint* InBlueprint, UEdGraph* InGraph)
{
UEdGraph* TempOuter = NewObject<UEdGraph>((UObject*)InBlueprint);
TempOuter->SetFlags(RF_Transient);
// Make a call function template
UK2Node_CallFunction* CallFuncNode = NewObject<UK2Node_CallFunction>(TempOuter);
static FName PrintStringFunctionName(TEXT("SetStaticMesh"));
UFunction* DelayFn = FindFieldChecked<UFunction>(UStaticMeshComponent::StaticClass(), PrintStringFunctionName);
CallFuncNode->FunctionReference.SetFromField<UFunction>(DelayFn, false);
return CreateNewGraphNodeFromTemplate(CallFuncNode, InGraph, FVector2f(850.0f, 0.0f));
}
/**
* Connects two nodes using the supplied pin names
*
* @param NodeA - The first node to connect
* @param PinAName - The name of the pin on the first node
* @param NodeB - The second node to connect
* @param PinBName - The name of the pin on the second node
*/
static void ConnectGraphNodes(UEdGraphNode* NodeA, const FName& PinAName, UEdGraphNode* NodeB, const FName& PinBName)
{
const FScopedTransaction PropertyChanged(LOCTEXT("ConnectedNode", "Connected graph nodes"));
NodeA->GetGraph()->Modify();
UEdGraphPin* PinA = NodeA->FindPin(PinAName);
UEdGraphPin* PinB = NodeB->FindPin(PinBName);
if (PinA && PinB)
{
PinA->MakeLinkTo(PinB);
}
else
{
UE_LOG(LogBlueprintEditorPromotionTests, Error, TEXT("Could not connect pins %s and %s "), *PinAName.ToString(), *PinBName.ToString());
}
}
/**
* Promotes a pin to a variable
*
* @param InBlueprint - The blueprint to modify
* @param Node - The node that owns the pin
* @param PinName - The name of the pin to promote
*/
static void PromotePinToVariable(UBlueprint* InBlueprint, UEdGraphNode* Node, const FName& PinName)
{
IAssetEditorInstance* OpenEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(InBlueprint, true);
FBlueprintEditor* CurrentBlueprintEditor = (FBlueprintEditor*)OpenEditor;
UEdGraphPin* PinToPromote = Node->FindPin(PinName);
CurrentBlueprintEditor->DoPromoteToVariable2f(InBlueprint, PinToPromote, true);
}
/**
* Creates a ReceiveBeginPlay event node
*
* @param InBlueprint - The blueprint to modify
* @param InGraph - The graph to use for the new node
*/
static UEdGraphNode* CreatePostBeginPlayEvent(UBlueprint* InBlueprint, UEdGraph* InGraph)
{
UEdGraph* TempOuter = NewObject<UEdGraph>((UObject*)InBlueprint);
TempOuter->SetFlags(RF_Transient);
// Make an add component node
UK2Node_Event* NewEventNode = NewObject<UK2Node_Event>(TempOuter);
NewEventNode->EventReference.SetExternalMember(FName(TEXT("ReceiveBeginPlay")), AActor::StaticClass());
NewEventNode->bOverrideFunction = true;
//Check for existing events
UK2Node_Event* ExistingEvent = FBlueprintEditorUtils::FindOverrideForFunction(InBlueprint, NewEventNode->EventReference.GetMemberParentClass(NewEventNode->GetBlueprintClassFromNode()), NewEventNode->EventReference.GetMemberName());
if (!ExistingEvent)
{
return CreateNewGraphNodeFromTemplate(NewEventNode, InGraph, FVector2f(200.0f, 0.0f));
}
return ExistingEvent;
}
/**
* Creates a custom event node
*
* @param InBlueprint - The blueprint to modify
* @param InGraph - The graph to use for the new node
* @param EventName - The name of the event
*/
static UEdGraphNode* CreateCustomEvent(UBlueprint* InBlueprint, UEdGraph* InGraph, const FString& EventName)
{
UEdGraph* TempOuter = NewObject<UEdGraph>((UObject*)InBlueprint);
TempOuter->SetFlags(RF_Transient);
// Make an add component node
UK2Node_CustomEvent* NewEventNode = NewObject<UK2Node_CustomEvent>(TempOuter);
NewEventNode->CustomFunctionName = "EventName";
return CreateNewGraphNodeFromTemplate(NewEventNode, InGraph, FVector2f(1200.0f, 0.0f));
}
/**
* Creates a node template for a UKismetSystemLibrary function
*
* @param NodeOuter - The outer to use for the template
* @param InGraph - The function to use for the node
*/
static UK2Node* CreateKismetFunctionTemplate(UObject* NodeOuter, const FName& FunctionName)
{
// Make a call function template
UK2Node_CallFunction* CallFuncNode = NewObject<UK2Node_CallFunction>(NodeOuter);
UFunction* Function = FindFieldChecked<UFunction>(UKismetSystemLibrary::StaticClass(), FunctionName);
CallFuncNode->FunctionReference.SetFromField<UFunction>(Function, false);
return CallFuncNode;
}
/**
* Creates a delay node
*
* @param InBlueprint - The blueprint to modify
* @param InGraph - The graph to use for the new node
* @param ConnectPin - The pin to connect the new node to
*/
static UEdGraphNode* AddDelayNode(UBlueprint* InBlueprint, UEdGraph* InGraph, UEdGraphPin* ConnectPin = NULL)
{
UEdGraph* TempOuter = NewObject<UEdGraph>((UObject*)InBlueprint);
TempOuter->SetFlags(RF_Transient);
const FScopedTransaction PropertyChanged(LOCTEXT("AddedGraphNode", "Added a graph node"));
InGraph->Modify();
// Make a call function template
static FName DelayFunctionName(TEXT("Delay"));
UK2Node* CallFuncNode = CreateKismetFunctionTemplate(TempOuter, DelayFunctionName);
//Create the node
return CreateNewGraphNodeFromTemplate(CallFuncNode, InGraph, FVector2f(400.0f, 0.0f), ConnectPin);
}
/**
* Creates a PrintString node
*
* @param InBlueprint - The blueprint to modify
* @param InGraph - The graph to use for the new node
* @param ConnectPin - The pin to connect the new node to
*/
static UEdGraphNode* AddPrintStringNode(UBlueprint* InBlueprint, UEdGraph* InGraph, UEdGraphPin* ConnectPin = NULL)
{
UEdGraph* TempOuter = NewObject<UEdGraph>((UObject*)InBlueprint);
TempOuter->SetFlags(RF_Transient);
// Make a call function template
static FName PrintStringFunctionName(TEXT("PrintString"));
UK2Node* CallFuncNode = CreateKismetFunctionTemplate(TempOuter, PrintStringFunctionName);
return CreateNewGraphNodeFromTemplate(CallFuncNode, InGraph, FVector2f(680.0f, 0.0f), ConnectPin);
}
/**
* Creates a call function node
*
* @param InBlueprint - The blueprint to modify
* @param InGraph - The graph to use for the new node
* @param FunctionName - The name of the function to call
* @param ConnectPin - The pin to connect the new node to
*/
static UEdGraphNode* AddCallFunctionGraphNode(UBlueprint* InBlueprint, UEdGraph* InGraph, const FName& FunctionName, UEdGraphPin* ConnectPin = NULL)
{
UEdGraph* TempOuter = NewObject<UEdGraph>((UObject*)InBlueprint);
TempOuter->SetFlags(RF_Transient);
// Make a call function template
UK2Node_CallFunction* CallFuncNode = NewObject<UK2Node_CallFunction>(TempOuter);
CallFuncNode->FunctionReference.SetSelfMember(FunctionName);
return CreateNewGraphNodeFromTemplate(CallFuncNode, InGraph, FVector2f(1200.0f, 0.0f), ConnectPin);
}
/**
* Creates Get or Set node
*
* @param InBlueprint - The blueprint to modify
* @param InGraph - The graph to use for the new node
* @param VarName - The name of the variable to use
* @param bGet - If true, create a Get node. If false, create a Set node.
* @param XOffset - How far to offset the node in the graph
*/
static UEdGraphNode* AddGetSetNode(UBlueprint* InBlueprint, UEdGraph* InGraph, const FString& VarName, bool bGet, float XOffset = 0.f)
{
const FScopedTransaction PropertyChanged(LOCTEXT("AddedGraphNode", "Added a graph node"));
InGraph->Modify();
FEdGraphSchemaAction_K2NewNode NodeInfo;
// Create get or set node, depending on whether we clicked on an input or output pin
UK2Node_Variable* TemplateNode = NULL;
if (bGet)
{
TemplateNode = NewObject<UK2Node_VariableGet>();
}
else
{
TemplateNode = NewObject<UK2Node_VariableSet>();
}
TemplateNode->VariableReference.SetSelfMember(FName(*VarName));
NodeInfo.NodeTemplate = TemplateNode;
return NodeInfo.PerformAction(InGraph, NULL, FVector2f(XOffset, 130.0f), true);
}
/**
* Sets the default value for a pin
*
* @param Node - The node that owns the pin to set
* @param PinName - The name of the pin
* @param PinValue - The new default value
*/
static void SetPinDefaultValue(UEdGraphNode* Node, const FName PinName, const FString& PinValue)
{
UEdGraphPin* Pin = Node->FindPin(PinName);
Pin->DefaultValue = PinValue;
}
/**
* Sets the default object for a pin
*
* @param Node - The node that owns the pin to set
* @param PinName - The name of the pin
* @param PinObject - The new default object
*/
static void SetPinDefaultObject(UEdGraphNode* Node, const FName PinName, UObject* PinObject)
{
UEdGraphPin* Pin = Node->FindPin(PinName);
Pin->DefaultObject = PinObject;
}
/**
* Adds a string member variable to a blueprint
*
* @param InBlueprint - The blueprint to modify
* @param VariableName - The name of the new string variable
*/
static void AddStringMemberValue(UBlueprint* InBlueprint, const FName& VariableName)
{
FEdGraphPinType StringPinType(UEdGraphSchema_K2::PC_String, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType());
FBlueprintEditorUtils::AddMemberVariable(InBlueprint, VariableName, StringPinType);
}
/**
* Creates a new function graph
*
* @param InBlueprint - The blueprint to modify
* @param FunctionName - The function name to use for the new graph
*/
static UEdGraph* CreateNewFunctionGraph(UBlueprint* InBlueprint, const FName& FunctionName)
{
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(InBlueprint, FunctionName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
FBlueprintEditorUtils::AddFunctionGraph<UClass>(InBlueprint, NewGraph, /*bIsUserCreated=*/ true, NULL);
return NewGraph;
}
}
/**
* Container for items related to the blueprint editor test
*/
namespace BlueprintEditorPromotionTestHelper
{
/**
* The main build promotion test class
*/
struct FBlueprintEditorPromotionTestHelper
{
/** Pointer to running automation test instance */
FBlueprintEditorPromotionTest* Test;
/** Function definition for the test stage functions */
typedef bool(BlueprintEditorPromotionTestHelper::FBlueprintEditorPromotionTestHelper::*TestStageFn)();
/** List of test stage functions */
TArray<TestStageFn> TestStages;
TArray<FString> StageNames;
/** The index of the test stage we are on */
int32 CurrentStage;
/** Pointer to the active editor world */
UWorld* CurrentWorld;
/** Particle System loaded from Automation Settings for Blueprint Pass */
UParticleSystem* LoadedParticleSystem;
/** Meshes to use for the blueprint */
UStaticMesh* FirstBlueprintMesh;
UStaticMesh* SecondBlueprintMesh;
/** Objects created by the Blueprint stages */
UBlueprint* BlueprintObject;
UPackage* BlueprintPackage;
UEdGraph* CustomGraph;
USCS_Node* MeshNode;
USCS_Node* OtherMeshNode;
USCS_Node* PSNode;
UEdGraphNode* AddMeshNode;
UEdGraphNode* PostBeginPlayEventNode;
UEdGraphNode* DelayNode;
UEdGraphNode* SetNode;
UEdGraphNode* GetNode;
UEdGraphNode* PrintNode;
UEdGraphNode* SetStaticMeshNode;
UEdGraphNode* CustomEventNode;
UEdGraphNode* AddParticleSystemNode;
UEdGraphNode* CallFunctionNode;
/** Delay helper */
BlueprintEditorPromotionUtils::FDelayHelper DelayHelper;
/** List of skipped tests */
TArray<FString> SkippedTests;
/** summary logs to display at the end */
TArray<FString> SummaryLines;
/** Keeps track of errors generated by building the map */
int32 BuildStartErrorCount;
int32 SectionSuccessCount;
int32 SectionTestCount;
int32 SectionErrorCount;
int32 SectionWarningCount;
int32 SectionLogCount;
#define ADD_TEST_STAGE(FuncName,StageName) \
TestStages.Add(&BlueprintEditorPromotionTestHelper::FBlueprintEditorPromotionTestHelper::FuncName); \
StageNames.Add(StageName);
/**
* Constructor
*/
FBlueprintEditorPromotionTestHelper()
: CurrentStage(0)
{
FMemory::Memzero(this, sizeof(FBlueprintEditorPromotionTestHelper));
ADD_TEST_STAGE(Cleanup, TEXT("Pre-start cleanup"));
ADD_TEST_STAGE(Setup, TEXT("Setup"));
ADD_TEST_STAGE(Blueprint_CreateNewBlueprint_Part1, TEXT("Create a new Blueprint"));
ADD_TEST_STAGE(Blueprint_CreateNewBlueprint_Part2, TEXT("Create a new Blueprint"));
ADD_TEST_STAGE(Blueprint_DataOnlyBlueprint_Part1, TEXT("Data-only Blueprint"));
ADD_TEST_STAGE(Blueprint_DataOnlyBlueprint_Part2, TEXT("Data-only Blueprint"));
ADD_TEST_STAGE(Blueprint_DataOnlyBlueprint_Part3, TEXT("Data-only Blueprint"));
ADD_TEST_STAGE(Blueprint_ComponentsMode_Part1, TEXT("Components Mode"));
ADD_TEST_STAGE(Blueprint_ComponentsMode_Part2, TEXT("Components Mode"));
ADD_TEST_STAGE(Blueprint_ConstructionScript, TEXT("Construction Script"));
ADD_TEST_STAGE(Blueprint_PromoteVariable_Part1, TEXT("Variable from Component Mode 1"));
ADD_TEST_STAGE(Blueprint_PromoteVariable_Part2, TEXT("Variable from Component Mode 2"));
//ADD_TEST_STAGE(Blueprint_PromoteVariable_Part3, TEXT("Variable from Component Mode 3"));
ADD_TEST_STAGE(Blueprint_EventGraph, TEXT("Event Graph"));
ADD_TEST_STAGE(Blueprint_CustomVariable, TEXT("Custom Variables"));
ADD_TEST_STAGE(Blueprint_UsingVariables, TEXT("Using Variables"));
ADD_TEST_STAGE(Blueprint_RenameCustomEvent, TEXT("Renaming Custom Event"));
ADD_TEST_STAGE(Blueprint_NewFunctions, TEXT("New Function"));
ADD_TEST_STAGE(Blueprint_CompleteBlueprint, TEXT("Completing the Blueprint"));
ADD_TEST_STAGE(Cleanup, TEXT("Teardown"));
}
/**
* Runs the current test stage
*/
bool Update()
{
Test->PushContext(StageNames[CurrentStage]);
bool bStageComplete = (this->*TestStages[CurrentStage])();
Test->PopContext();
if (bStageComplete)
{
CurrentStage++;
}
return CurrentStage >= TestStages.Num();
}
private:
bool Cleanup()
{
//Make sure no editors are open
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->CloseAllAssetEditors();
//remove all assets in the build package
// Load the asset registry module
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
// Form a filter from the paths
FARFilter Filter;
Filter.bRecursivePaths = true;
new (Filter.PackagePaths) FName(*FEditorPromotionTestUtilities::GetGamePath());
// Query for a list of assets in the selected paths
TArray<FAssetData> AssetList;
AssetRegistry.GetAssets(Filter, AssetList);
// Clear and try to delete all assets
for (int32 AssetIdx = 0; AssetIdx < AssetList.Num(); ++AssetIdx)
{
Test->AddInfo(*FString::Printf(TEXT("Removing asset: %s"), *AssetList[AssetIdx].AssetName.ToString()));
if (AssetList[AssetIdx].IsAssetLoaded())
{
UObject* LoadedAsset = AssetList[AssetIdx].GetAsset();
AssetRegistry.AssetDeleted(LoadedAsset);
bool bSuccessful = ObjectTools::DeleteSingleObject(LoadedAsset, false);
//If we failed to delete this object manually clear any references and try again
if (!bSuccessful)
{
//Clear references to the object so we can delete it
FAutomationEditorCommonUtils::NullReferencesToObject(LoadedAsset);
bSuccessful = ObjectTools::DeleteSingleObject(LoadedAsset, false);
}
}
}
Test->AddInfo(*FString::Printf(TEXT("Clearing Path: %s"), *FEditorPromotionTestUtilities::GetGamePath()));
AssetRegistry.RemovePath(FEditorPromotionTestUtilities::GetGamePath());
//Remove the directory
bool bEnsureExists = false;
bool bDeleteEntireTree = true;
FString PackageDirectory = FPaths::ProjectContentDir() / TEXT("BuildPromotionTest");
IFileManager::Get().DeleteDirectory(*PackageDirectory, bEnsureExists, bDeleteEntireTree);
Test->AddInfo(*FString::Printf(TEXT("Deleting Folder: %s"), *PackageDirectory));
return true;
}
bool Setup()
{
//Make sure we have the required assets
UAutomationTestSettings const* AutomationTestSettings = GetDefault<UAutomationTestSettings>();
check(AutomationTestSettings);
FAssetData AssetData;
const FString FirstMeshPath = AutomationTestSettings->BlueprintEditorPromotionTest.FirstMeshPath.FilePath;
if (FirstMeshPath.Len() > 0)
{
AssetData = FAutomationEditorCommonUtils::GetAssetDataFromPackagePath(FirstMeshPath);
FirstBlueprintMesh = Cast<UStaticMesh>(AssetData.GetAsset());
}
const FString SecondMeshPath = AutomationTestSettings->BlueprintEditorPromotionTest.SecondMeshPath.FilePath;
if (SecondMeshPath.Len() > 0)
{
AssetData = FAutomationEditorCommonUtils::GetAssetDataFromPackagePath(SecondMeshPath);
SecondBlueprintMesh = Cast<UStaticMesh>(AssetData.GetAsset());
}
const FString ParticleSystemPath = AutomationTestSettings->BlueprintEditorPromotionTest.DefaultParticleAsset.FilePath;
if (ParticleSystemPath.Len() > 0)
{
AssetData = FAutomationEditorCommonUtils::GetAssetDataFromPackagePath(ParticleSystemPath);
LoadedParticleSystem = Cast<UParticleSystem>(AssetData.GetAsset());
}
if (!(FirstBlueprintMesh && SecondBlueprintMesh && LoadedParticleSystem))
{
SkippedTests.Add(TEXT("All Blueprint tests. (Missing a required mesh or particle system)"));
if (FirstMeshPath.IsEmpty() || SecondMeshPath.IsEmpty())
{
Test->AddInfo(TEXT("SKIPPING BLUEPRINT TESTS. FirstMeshPath or SecondMeshPath not configured in AutomationTestSettings."));
}
else
{
Test->AddWarning(TEXT("SKIPPING BLUEPRINT TESTS. Invalid FirstMeshPath or SecondMeshPath in AutomationTestSettings, or particle system was not created."));
}
}
return true;
}
/**
* Blueprint Test Stage: Create a new Blueprint (Part 1)
* Creates a new actor based blueprint and opens the editor
*/
bool Blueprint_CreateNewBlueprint_Part1()
{
// Exit early if any of the required assets are missing
if (!(FirstBlueprintMesh && SecondBlueprintMesh && LoadedParticleSystem))
{
return true;
}
UBlueprintFactory* Factory = NewObject<UBlueprintFactory>();
Factory->ParentClass = AActor::StaticClass();
const FString PackageName = FEditorPromotionTestUtilities::GetGamePath() + TEXT("/") + BlueprintEditorPromotionUtils::BlueprintNameString;
BlueprintPackage = CreatePackage( *PackageName);
EObjectFlags Flags = RF_Public | RF_Standalone;
UObject* ExistingBlueprint = FindObject<UBlueprint>(BlueprintPackage, *BlueprintEditorPromotionUtils::BlueprintNameString);
Test->TestNull(TEXT("Blueprint asset does not already exist (delete blueprint and restart editor)"), ExistingBlueprint);
// Exit early since test will not be valid with pre-existing assets
if (ExistingBlueprint)
{
return true;
}
BlueprintObject = Cast<UBlueprint>(Factory->FactoryCreateNew(UBlueprint::StaticClass(), BlueprintPackage, FName(*BlueprintEditorPromotionUtils::BlueprintNameString), Flags, NULL, GWarn));
Test->TestNotNull(TEXT("Created new Actor-based blueprint"), BlueprintObject);
if (BlueprintObject)
{
// Update asset registry and mark package dirty
FAssetRegistryModule::AssetCreated(BlueprintObject);
BlueprintPackage->MarkPackageDirty();
Test->AddInfo(TEXT("Opening the blueprint editor for the first time"));
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(BlueprintObject);
}
return true;
}
/**
* Blueprint Test Stage: Create a new Blueprint (Part 2)
* Checks that the blueprint editor opened in the correct mode
*/
bool Blueprint_CreateNewBlueprint_Part2()
{
if (BlueprintObject)
{
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(BlueprintObject, true);
IBlueprintEditor* BlueprintEditor = (IBlueprintEditor*)AssetEditor;
Test->TestTrue(TEXT("Opened correct initial editor"), BlueprintEditor->GetCurrentMode() != FBlueprintEditorApplicationModes::BlueprintDefaultsMode);
}
return true;
}
/**
* Blueprint Test Stage: Data-only Blueprint (Part 1)
* Closes the blueprint editor
*/
bool Blueprint_DataOnlyBlueprint_Part1()
{
if (BlueprintObject)
{
Test->AddInfo(TEXT("Closing the blueprint editor"));
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->CloseAllAssetEditors();
}
return true;
}
/**
* Blueprint Test Stage: Data-only Blueprint (Part 2)
* Re opens the blueprint editor
*/
bool Blueprint_DataOnlyBlueprint_Part2()
{
if (BlueprintObject)
{
Test->AddInfo(TEXT("Opening the blueprint editor for the second time"));
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(BlueprintObject);
}
return true;
}
/**
* Blueprint Test Stage: Data-only Blueprint (Part 3)
* Checks that the editor opened in the Defaults mode
*/
bool Blueprint_DataOnlyBlueprint_Part3()
{
if (BlueprintObject)
{
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(BlueprintObject, true);
IBlueprintEditor* BlueprintEditor = (IBlueprintEditor*)AssetEditor;
bool bCorrectEditorOpened = BlueprintEditor->GetCurrentMode() == FBlueprintEditorApplicationModes::BlueprintDefaultsMode;
Test->TestTrue(TEXT("Correct defaults editor opened"), bCorrectEditorOpened);
if (bCorrectEditorOpened)
{
Test->AddInfo(TEXT("Switching to components mode"));
BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintComponentsMode);
}
}
return true;
}
/**
* Blueprint Test Stage: Components Mode (Part 1)
* Adds 3 new components to the blueprint, changes the root component, renames the components, and compiles the blueprint
*/
bool Blueprint_ComponentsMode_Part1()
{
if (BlueprintObject)
{
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(BlueprintObject, true);
IBlueprintEditor* BlueprintEditor = (IBlueprintEditor*)AssetEditor;
MeshNode = BlueprintEditorPromotionUtils::CreateBlueprintComponent(BlueprintObject, FirstBlueprintMesh);
Test->TestNotNull(TEXT("First mesh component added"), MeshNode);
OtherMeshNode = BlueprintEditorPromotionUtils::CreateBlueprintComponent(BlueprintObject, SecondBlueprintMesh);
Test->TestNotNull(TEXT("Second mesh component added"), OtherMeshNode);
PSNode = BlueprintEditorPromotionUtils::CreateBlueprintComponent(BlueprintObject, LoadedParticleSystem);
Test->TestNotNull(TEXT("Particle system component added"), PSNode);
//Set the Particle System as the root
BlueprintEditorPromotionUtils::SetComponentAsRoot(BlueprintObject, PSNode);
Test->TestTrue(TEXT("Particle system set as root"), PSNode->IsRootNode());
//Set Names
const FName MeshName(TEXT("FirstMesh"));
FBlueprintEditorUtils::RenameComponentMemberVariable(BlueprintObject, MeshNode, MeshName);
Test->AddInfo(TEXT("Renamed the first mesh component to FirstMesh"));
const FName OtherMeshName(TEXT("SecondMesh"));
FBlueprintEditorUtils::RenameComponentMemberVariable(BlueprintObject, OtherMeshNode, OtherMeshName);
Test->AddInfo(TEXT("Renamed the second mesh component to SecondMesh"));
const FName PSName(TEXT("ParticleSys"));
FBlueprintEditorUtils::RenameComponentMemberVariable(BlueprintObject, PSNode, PSName);
Test->AddInfo(TEXT("Renamed the particle system component to ParticleSys"));
BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject);
Test->AddInfo(TEXT("Switched to graph editing mode"));
BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode);
}
return true;
}
/**
* Blueprint Test Stage: Components Mode (Part 2)
* Removes the 3 components added before and compiles the blueprint
*/
bool Blueprint_ComponentsMode_Part2()
{
if (BlueprintObject)
{
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(BlueprintObject, true);
IBlueprintEditor* BlueprintEditor = (IBlueprintEditor*)AssetEditor;
//FEditorPromotionTestUtilities::TakeScreenshot(TEXT("BlueprintComponentVariables"), true);
Test->AddInfo(TEXT("Switched to components mode"));
BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintComponentsMode);
BlueprintEditorPromotionUtils::RemoveBlueprintComponent(BlueprintObject, MeshNode);
BlueprintEditorPromotionUtils::RemoveBlueprintComponent(BlueprintObject, OtherMeshNode);
BlueprintEditorPromotionUtils::RemoveBlueprintComponent(BlueprintObject, PSNode);
//There should only be the scene component left
Test->TestTrue(TEXT("Successfully removed blueprint components"), BlueprintObject->SimpleConstructionScript->GetAllNodes().Num() == 1);
MeshNode = NULL;
OtherMeshNode = NULL;
PSNode = NULL;
Test->AddInfo(TEXT("Switched to graph mode"));
BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode);
BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject);
}
return true;
}
/**
* Blueprint Test Stage: Construction Script
* Adds an AddStaticMeshComponent to the construction graph and links it to the entry node
*/
bool Blueprint_ConstructionScript()
{
if (BlueprintObject)
{
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(BlueprintObject, true);
FBlueprintEditor* BlueprintEditor = (FBlueprintEditor*)AssetEditor;
UEdGraph* ConstructionGraph = FBlueprintEditorUtils::FindUserConstructionScript(BlueprintObject);
BlueprintEditor->OpenGraphAndBringToFront(ConstructionGraph);
AddMeshNode = BlueprintEditorPromotionUtils::CreateAddComponentActionNode(BlueprintObject, ConstructionGraph, FirstBlueprintMesh);
Test->TestNotNull(TEXT("Add Static Mesh Component node created"), AddMeshNode);
GEditor->UndoTransaction();
Test->TestTrue(TEXT("Undo add component node completed"), ConstructionGraph->Nodes.Num() == 0 || ConstructionGraph->Nodes[ConstructionGraph->Nodes.Num() - 1] != AddMeshNode);
GEditor->RedoTransaction();
Test->TestTrue(TEXT("Redo add component node completed"), ConstructionGraph->Nodes.Num() > 0 && ConstructionGraph->Nodes[ConstructionGraph->Nodes.Num() - 1] == AddMeshNode);
TArray<UK2Node_FunctionEntry*> EntryNodes;
ConstructionGraph->GetNodesOfClass(EntryNodes);
UEdGraphNode* EntryNode = EntryNodes.Num() > 0 ? EntryNodes[0] : NULL;
Test->TestNotNull(TEXT("Found entry node to connect Add Static Mesh to"), EntryNode);
if (EntryNode)
{
BlueprintEditorPromotionUtils::ConnectGraphNodes(AddMeshNode, UEdGraphSchema_K2::PN_Execute, EntryNode, UEdGraphSchema_K2::PN_Then);
UEdGraphPin* EntryOutPin = EntryNode->FindPin(UEdGraphSchema_K2::PN_Then);
UEdGraphPin* AddStaticMeshInPin = AddMeshNode->FindPin(UEdGraphSchema_K2::PN_Execute);
Test->TestTrue(TEXT("Connected entry node to Add Static Mesh node"), EntryOutPin->LinkedTo.Contains(AddStaticMeshInPin));
GEditor->UndoTransaction();
Test->TestTrue(TEXT("Undo connection to Add Static Mesh Node succeeded"), EntryOutPin->LinkedTo.Num() == 0);
GEditor->RedoTransaction();
Test->TestTrue(TEXT("Redo connection to Add Static Mesh Node succeeded"), EntryOutPin->LinkedTo.Contains(AddStaticMeshInPin));
}
BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject);
}
return true;
}
/**
* Saves the blueprint stored in BlueprintObject
*/
void SaveBlueprint()
{
if (BlueprintObject && BlueprintPackage)
{
BlueprintPackage->SetDirtyFlag(true);
BlueprintPackage->FullyLoad();
const FString PackagePath = FEditorPromotionTestUtilities::GetGamePath() + TEXT("/") + BlueprintEditorPromotionUtils::BlueprintNameString;
FSavePackageArgs SaveArgs;
SaveArgs.TopLevelFlags = RF_Standalone;
SaveArgs.Error = GLog;
bool bBlueprintSaved = UPackage::SavePackage(BlueprintPackage, nullptr,
*FPackageName::LongPackageNameToFilename(PackagePath, FPackageName::GetAssetPackageExtension()),
SaveArgs);
Test->TestTrue(*FString::Printf(TEXT("Blueprint saved successfully (%s)"), *BlueprintObject->GetName()), bBlueprintSaved);
}
}
/**
* Blueprint Test Stage: Variable from component mode (Part 1)
* Promotes the return pin of the AddStaticMeshNode to a variable and then renames it
*/
bool Blueprint_PromoteVariable_Part1()
{
if (BlueprintObject)
{
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(BlueprintObject, true);
FBlueprintEditor* BlueprintEditor = (FBlueprintEditor*)AssetEditor;
BlueprintEditorPromotionUtils::PromotePinToVariable(BlueprintObject, AddMeshNode, UEdGraphSchema_K2::PN_ReturnValue);
Test->AddInfo(TEXT("Promoted the return pin on the add mesh node to a variable"));
const FName OldVarName(TEXT("NewVar")); // Default variable name
const FName NewVarName(TEXT("MyMesh"));
FBlueprintEditorUtils::RenameMemberVariable(BlueprintObject, OldVarName, NewVarName);
Test->TestNotEqual(TEXT("New variable was renamed"), FBlueprintEditorUtils::FindMemberVariableGuidByName(BlueprintObject, OldVarName), FBlueprintEditorUtils::FindMemberVariableGuidByName(BlueprintObject, NewVarName));
BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject);
Test->AddInfo(TEXT("Switched to graph mode"));
BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode);
}
return true;
}
bool Blueprint_PromoteVariable_Part2()
{
if (BlueprintObject)
{
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(BlueprintObject, true);
FBlueprintEditor* BlueprintEditor = (FBlueprintEditor*)AssetEditor;
//FEditorPromotionTestUtilities::TakeScreenshot(TEXT("BlueprintMeshVariable"), true);
Test->AddInfo(TEXT("Switched to components mode"));
BlueprintEditor->SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintComponentsMode);
BlueprintEditorPromotionUtils::SendBlueprintResetViewCommand();
}
return true;
}
///**
//* Blueprint Test Stage: Variable from component mode (Part 3)
//* Takes a screenshot of the mesh variable
//*/
//bool Blueprint_PromoteVariable_Part3()
//{
// if (BlueprintObject)
// {
// FEditorPromotionTestUtilities::TakeScreenshot(TEXT("BlueprintComponent"), true);
// }
// return true;
//}
/**
* Blueprint Test Stage: Event Graph
* Adds a ReceiveBeginPlay and Delay node to the event graph
*/
bool Blueprint_EventGraph()
{
if (BlueprintObject)
{
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(BlueprintObject, true);
FBlueprintEditor* BlueprintEditor = (FBlueprintEditor*)AssetEditor;
UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObject);
BlueprintEditor->OpenGraphAndBringToFront(EventGraph);
Test->AddInfo(TEXT("Opened the event graph"));
PostBeginPlayEventNode = BlueprintEditorPromotionUtils::CreatePostBeginPlayEvent(BlueprintObject, EventGraph);
Test->TestNotNull(TEXT("Created EventBeginPlay node"), PostBeginPlayEventNode);
UEdGraphPin* PlayThenPin = PostBeginPlayEventNode->FindPin(UEdGraphSchema_K2::PN_Then);
DelayNode = BlueprintEditorPromotionUtils::AddDelayNode(BlueprintObject, EventGraph, PlayThenPin);
Test->TestNotNull(TEXT("Created Delay node"), DelayNode);
GEditor->UndoTransaction();
Test->TestTrue(TEXT("Undo adding Delay node succeeded"), EventGraph->Nodes.Num() == 0 || EventGraph->Nodes[EventGraph->Nodes.Num() - 1] != DelayNode);
GEditor->RedoTransaction();
Test->TestTrue(TEXT("Redo adding Delay node succeeded"), EventGraph->Nodes.Num() > 0 && EventGraph->Nodes[EventGraph->Nodes.Num() - 1] == DelayNode);
// Update Delay node's Duration pin with new default value
const FName DelayDurationPinName = TEXT("Duration");
const FString NewDurationDefaultValue = TEXT("2.0");
BlueprintEditorPromotionUtils::SetPinDefaultValue(DelayNode, DelayDurationPinName, NewDurationDefaultValue);
Test->TestEqual(TEXT("Delay node default duration set to 2.0 seconds"), DelayNode->FindPin(DelayDurationPinName)->DefaultValue, NewDurationDefaultValue);
BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject);
}
return true;
}
/**
* Blueprint Test Stage: Custom Variable
* Creates a custom string variable and adds Get/Set nodes for it
*/
bool Blueprint_CustomVariable()
{
if (BlueprintObject)
{
UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObject);
Test->AddInfo(TEXT("Added a string member variable"));
BlueprintEditorPromotionUtils::AddStringMemberValue(BlueprintObject, BlueprintEditorPromotionUtils::BlueprintStringVariableName);
SetNode = BlueprintEditorPromotionUtils::AddGetSetNode(BlueprintObject, EventGraph, BlueprintEditorPromotionUtils::BlueprintStringVariableName.ToString(), false);
Test->TestNotNull(TEXT("Added Set node for string variable"), SetNode);
GEditor->UndoTransaction();
Test->TestTrue(TEXT("Undo adding Set node succeeded"), EventGraph->Nodes.Num() == 0 || EventGraph->Nodes[EventGraph->Nodes.Num() - 1] != SetNode);
GEditor->RedoTransaction();
Test->TestTrue(TEXT("Redo adding Set node succeeded"), EventGraph->Nodes.Num() > 0 && EventGraph->Nodes[EventGraph->Nodes.Num() - 1] == SetNode);
GetNode = BlueprintEditorPromotionUtils::AddGetSetNode(BlueprintObject, EventGraph, BlueprintEditorPromotionUtils::BlueprintStringVariableName.ToString(), true, 400);
Test->TestNotNull(TEXT("Added Get node for string variable"), GetNode);
GEditor->UndoTransaction();
Test->TestTrue(TEXT("Undo adding Get node succeeded"), EventGraph->Nodes.Num() == 0 || EventGraph->Nodes[EventGraph->Nodes.Num() - 1] != GetNode);
GEditor->RedoTransaction();
Test->TestTrue(TEXT("Redo adding Get node succeeded"), EventGraph->Nodes.Num() > 0 && EventGraph->Nodes[EventGraph->Nodes.Num() - 1] == GetNode);
EventGraph->RemoveNode(SetNode);
Test->TestFalse(TEXT("Set node removed from EventGraph"), EventGraph->Nodes.Contains(SetNode));
SetNode = NULL;
}
return true;
}
/**
* Blueprint Test Stage: Using Variables
* Adds a PrintString and SetStaticMesh then connects all the existing nodes
*/
bool Blueprint_UsingVariables()
{
if (BlueprintObject)
{
const bool bVariableIsHidden = false;
FBlueprintEditorUtils::SetBlueprintOnlyEditableFlag(BlueprintObject, BlueprintEditorPromotionUtils::BlueprintStringVariableName, bVariableIsHidden);
Test->AddInfo(TEXT("Exposed the blueprint string variable"));
UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObject);
PrintNode = BlueprintEditorPromotionUtils::AddPrintStringNode(BlueprintObject, EventGraph);
Test->TestNotNull(TEXT("Added Print String node"), PrintNode);
//Connect Get to printstring
UEdGraphPin* GetVarPin = GetNode->FindPin(BlueprintEditorPromotionUtils::BlueprintStringVariableName);
UEdGraphPin* InStringPin = PrintNode->FindPin(TEXT("InString"));
GetVarPin->MakeLinkTo(InStringPin);
Test->TestTrue(TEXT("Connected string variable Get node to the Print String node"), GetVarPin->LinkedTo.Contains(InStringPin));
//Connect Delay to PrintString
UEdGraphPin* DelayExecPin = DelayNode->FindPin(UEdGraphSchema_K2::PN_Then);
UEdGraphPin* PrintStringPin = PrintNode->FindPin(UEdGraphSchema_K2::PN_Execute);
DelayExecPin->MakeLinkTo(PrintStringPin);
Test->TestTrue(TEXT("Connected Delay nod to Print String node"), DelayExecPin->LinkedTo.Contains(PrintStringPin));
const FName MyMeshVarName(TEXT("MyMesh"));
GetNode = BlueprintEditorPromotionUtils::AddGetSetNode(BlueprintObject, EventGraph, MyMeshVarName.ToString(), true, 680);
Test->TestNotNull(TEXT("Added Get node for MyMesh variable"), GetNode);
SetStaticMeshNode = BlueprintEditorPromotionUtils::AddSetStaticMeshNode(BlueprintObject, EventGraph);
Test->TestNotNull(TEXT("Added Set Static Mesh node"), SetStaticMeshNode);
UEdGraphPin* GetExecPin = GetNode->FindPin(TEXT("MyMesh"));
UEdGraphPin* SetStaticMeshSelfPin = SetStaticMeshNode->FindPin(UEdGraphSchema_K2::PN_Self);
GetExecPin->MakeLinkTo(SetStaticMeshSelfPin);
Test->TestTrue(TEXT("Connected Get MyMesh node to Set Static Mesh node"), GetExecPin->LinkedTo.Contains(SetStaticMeshSelfPin));
UEdGraphPin* SetStaticMeshMeshPin = SetStaticMeshNode->FindPin(TEXT("NewMesh"));
SetStaticMeshMeshPin->DefaultObject = SecondBlueprintMesh;
Test->TestEqual(*FString::Printf(TEXT("Set Static Mesh default mesh updated to %s"), *SecondBlueprintMesh->GetName()), Cast<UStaticMesh>(SetStaticMeshMeshPin->DefaultObject), SecondBlueprintMesh);
//Connect SetStaticMeshMesh to PrintString
UEdGraphPin* PrintStringThenPin = PrintNode->FindPin(UEdGraphSchema_K2::PN_Then);
UEdGraphPin* SetStaticMeshExecPin = SetStaticMeshNode->FindPin(UEdGraphSchema_K2::PN_Execute);
PrintStringThenPin->MakeLinkTo(SetStaticMeshExecPin);
Test->TestTrue(TEXT("Connected Print String node to Set Static Mesh node"), PrintStringThenPin->LinkedTo.Contains(SetStaticMeshExecPin));
}
return true;
}
/**
* Blueprint Test Stage: Renaming custom event
* Creates, renames, and then removes a custom event node
*/
bool Blueprint_RenameCustomEvent()
{
if (BlueprintObject)
{
UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObject);
CustomEventNode = BlueprintEditorPromotionUtils::CreateCustomEvent(BlueprintObject, EventGraph, TEXT("NewEvent"));
Test->TestNotNull(TEXT("Custom event node created"), CustomEventNode);
if (CustomEventNode)
{
//Rename the event
const FString NewEventNodeName = TEXT("RenamedEvent");
CustomEventNode->Rename(*NewEventNodeName);
Test->TestEqual(TEXT("Custom event rename succeeded"), CustomEventNode->GetName(), NewEventNodeName);
EventGraph->RemoveNode(CustomEventNode);
Test->TestFalse(TEXT("Blueprint EventGraph does not contain removed custom event node"), EventGraph->Nodes.Contains(CustomEventNode));
CustomEventNode = NULL;
}
}
return true;
}
/**
* Blueprint Test Stage: New function
* Creates a new function graph and then hooks up a new AddParticleSystem inside it
*/
bool Blueprint_NewFunctions()
{
if (BlueprintObject)
{
CustomGraph = BlueprintEditorPromotionUtils::CreateNewFunctionGraph(BlueprintObject, TEXT("NewFunction"));
Test->TestNotNull(TEXT("Created new function graph"), CustomGraph);
AddParticleSystemNode = BlueprintEditorPromotionUtils::CreateAddComponentActionNode(BlueprintObject, CustomGraph, LoadedParticleSystem);
Test->TestNotNull(TEXT("Created Add Particle System node"), AddParticleSystemNode);
UEdGraphPin* ExecutePin = AddParticleSystemNode ? AddParticleSystemNode->FindPin(UEdGraphSchema_K2::PN_Execute) : NULL;
//Find the input for the function graph
TArray<UK2Node_FunctionEntry*> EntryNodes;
CustomGraph->GetNodesOfClass(EntryNodes);
UEdGraphNode* EntryNode = EntryNodes.Num() > 0 ? EntryNodes[0] : NULL;
if (EntryNode && ExecutePin)
{
UEdGraphPin* EntryPin = EntryNode->FindPin(UEdGraphSchema_K2::PN_Then);
EntryPin->MakeLinkTo(ExecutePin);
Test->TestTrue(TEXT("Connected Add Particle System node to entry node"), EntryPin->LinkedTo.Contains(ExecutePin));
}
BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject);
}
return true;
}
/**
* Blueprint Test Stage: Completing the blueprint
* Adds a CallFunction node to call the custom function created in the previous step.
*/
bool Blueprint_CompleteBlueprint()
{
if (BlueprintObject)
{
UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObject);
UEdGraphPin* SetStaticMeshThenPin = SetStaticMeshNode->FindPin(UEdGraphSchema_K2::PN_Then);
CallFunctionNode = BlueprintEditorPromotionUtils::AddCallFunctionGraphNode(BlueprintObject, EventGraph, TEXT("NewFunction"), SetStaticMeshThenPin);
Test->TestNotNull(TEXT("Created Call Function node"), CallFunctionNode);
if (CallFunctionNode)
{
Test->TestTrue(TEXT("Connected Set Static Mesh node to Call Function node"), SetStaticMeshThenPin->LinkedTo.Contains(CallFunctionNode->FindPin(UEdGraphSchema_K2::PN_Execute)));
}
BlueprintEditorPromotionUtils::CompileBlueprint(BlueprintObject);
SaveBlueprint();
}
return true;
}
};
}
/**
* Latent command to run the main build promotion test
*/
DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FRunPromotionTestCommand, TSharedPtr<BlueprintEditorPromotionTestHelper::FBlueprintEditorPromotionTestHelper>, BlueprintEditorTestInfo);
bool FRunPromotionTestCommand::Update()
{
return BlueprintEditorTestInfo->Update();
}
/**
* Automation test that handles the blueprint editor promotion process
*/
bool FBlueprintEditorPromotionTest::RunTest(const FString& Parameters)
{
TSharedPtr<BlueprintEditorPromotionTestHelper::FBlueprintEditorPromotionTestHelper> BuildPromotionTest = MakeShareable(new BlueprintEditorPromotionTestHelper::FBlueprintEditorPromotionTestHelper());
BuildPromotionTest->Test = this;
ADD_LATENT_AUTOMATION_COMMAND(FRunPromotionTestCommand(BuildPromotionTest));
return true;
}
#undef LOCTEXT_NAMESPACE
#endif //WITH_DEV_AUTOMATION_TESTS