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

368 lines
15 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 "UObject/ObjectMacros.h"
#include "UObject/Class.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Builders/CubeBuilder.h"
#include "GameFramework/PlayerStart.h"
#include "Editor.h"
#include "LevelEditorViewport.h"
#include "FileHelpers.h"
#include "ProjectDescriptor.h"
#include "GameProjectUtils.h"
#include "SProjectDialog.h"
#include "DesktopPlatformModule.h"
#include "Tests/AutomationTestSettings.h"
#include "Tests/AutomationEditorCommon.h"
#include "TemplateCategory.h"
#include "TemplateItem.h"
#if WITH_DEV_AUTOMATION_TESTS
DEFINE_LOG_CATEGORY_STATIC(LogGameProjectGenerationTests, Log, All);
namespace GameProjectAutomationUtils
{
/**
* Generates the desired project file name
*/
static FString GetDesiredProjectFilename()
{
UAutomationTestSettings const* AutomationTestSettings = GetDefault<UAutomationTestSettings>();
check(AutomationTestSettings);
FString ProjectName;
const FString ProjectNameOverride = AutomationTestSettings->BuildPromotionTest.NewProjectSettings.NewProjectNameOverride;
if (ProjectNameOverride.Len())
{
ProjectName = ProjectNameOverride;
}
else
{
ProjectName = TEXT("NewTestProject");
}
FString ProjectPath;
const FString ProjectPathOverride = AutomationTestSettings->BuildPromotionTest.NewProjectSettings.NewProjectFolderOverride.Path;
if (ProjectPathOverride.Len())
{
ProjectPath = ProjectPathOverride;
}
else
{
ProjectPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*FAutomationTestFramework::Get().GetUserAutomationDirectory());
}
const FString Filename = ProjectName + TEXT(".") + FProjectDescriptor::GetExtension();
FString ProjectFilename = FPaths::Combine(*ProjectPath, *ProjectName, *Filename);
FPaths::MakePlatformFilename(ProjectFilename);
return ProjectFilename;
}
/*
* Create a project from a template with a given criteria
* @param InTemplates List of available project templates
* @param InTargetedHardware Target hardware (EHardwareClass)
* @param InGraphicPreset Graphics preset (EGraphicsPreset)
* @param InCategory Target category (EContentSourceCategory)
* @param OutMatchedProjects Total projects matching criteria
* @param OutCreatedProjects Total projects succesfully created
*/
static void CreateProjectSet(TMap<FName, TArray<TSharedPtr<FTemplateItem>> >& InTemplates, EHardwareClass InTargetedHardware, EGraphicsPreset InGraphicPreset, FName InCategory, int32 &OutCreatedProjects, int32 &OutMatchedProjects)
{
// If this is empty, it will use the same name for each project, otherwise it will create a project based on target platform and source template
FString TestRootFolder;// = "ProjectTests";
// This has the code remove the projects once created
bool bRemoveCreatedProjects = true;
OutCreatedProjects = 0;
OutMatchedProjects = 0;
// Iterate all templates and try to create those that match the required criteria
for (auto& EachTemplate : InTemplates)
{
// Check this is the correct category
if (EachTemplate.Key == InCategory)
{
// Now iterate each template in the category
for (TSharedPtr<FTemplateItem> OneTemplate : EachTemplate.Value)
{
TSharedPtr<FTemplateItem> Item = OneTemplate;
// loop over the code project file and the blueprint file
FString ProjectFile = Item->BlueprintProjectFile;
for (int i = 0; i < 2; ++i)
{
if (ProjectFile.IsEmpty())
continue;
FString DesiredProjectFilename;
if (TestRootFolder.IsEmpty() == true)
{
// Same name for all
DesiredProjectFilename = GameProjectAutomationUtils::GetDesiredProjectFilename();
}
else
{
// Unique names
FString Hardware;
if (InTargetedHardware == EHardwareClass::Desktop)
{
Hardware = TEXT("Dsk");
}
else
{
Hardware = TEXT("Mob");
}
FString ProjectName = FPaths::GetCleanFilename(ProjectFile).Replace(TEXT("TP_"), TEXT(""));
FString ProjectDirName = FPaths::GetBaseFilename(ProjectFile).Replace(TEXT("TP_"), TEXT(""));
FString BasePath = FPaths::RootDir();
DesiredProjectFilename = FString::Printf(TEXT("%s/%s/%s%s/%s%s"), *BasePath, *TestRootFolder, *Hardware, *ProjectDirName, *Hardware, *ProjectName);
}
// If the project already exists, delete it just in case things were left in a bad state.
if (IFileManager::Get().DirectoryExists(*FPaths::GetPath(DesiredProjectFilename)))
{
IFileManager::Get().DeleteDirectory(*FPaths::GetPath(DesiredProjectFilename), /*RequireExists=*/false, /*Tree=*/true);
}
// Setup creation parameters
FText FailReason, FailLog;
FProjectInformation ProjectInfo;
ProjectInfo.ProjectFilename = DesiredProjectFilename;
ProjectInfo.bShouldGenerateCode = ProjectFile == Item->CodeProjectFile;
ProjectInfo.TemplateFile = ProjectFile;
ProjectInfo.TargetedHardware = InTargetedHardware;
ProjectInfo.DefaultGraphicsPerformance = InGraphicPreset;
TArray<FString> CreatedFiles;
OutMatchedProjects++;
// Finally try to create the project
if (!GameProjectUtils::CreateProject(ProjectInfo, FailReason, FailLog, &CreatedFiles))
{
// Failed, report the reason
UE_LOG(LogGameProjectGenerationTests, Error, TEXT("Failed to create %s project %s based on %s. Reason: %s\nProject Creation Failure Log:\n%s"), *InCategory.ToString(), *DesiredProjectFilename, *Item->Name.ToString(), *FailReason.ToString(), *FailLog.ToString());
}
else
{
// Created ok
OutCreatedProjects++;
// Now remove the files we just created (if required)
if (bRemoveCreatedProjects == true) //-V547
{
FString RootFolder = FPaths::GetPath(DesiredProjectFilename);
GameProjectUtils::DeleteCreatedFiles(RootFolder, CreatedFiles);
}
}
}
ProjectFile = Item->CodeProjectFile;
}
}
}
return;
}
}
/**
* Automation test to clean up old test project files
*/
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBuildPromotionNewProjectCleanupTest, "System.Promotion.Project Promotion Pass.Step 1 Blank Project Creation.Cleanup Potential Project Location", /*EAutomationTestFlags::Disabled |*/ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter );
bool FBuildPromotionNewProjectCleanupTest::RunTest(const FString& Parameters)
{
FString DesiredProjectFilename = GameProjectAutomationUtils::GetDesiredProjectFilename();
if (FPaths::FileExists(DesiredProjectFilename))
{
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Found an old project file at %s"), *DesiredProjectFilename);
if (FPaths::IsProjectFilePathSet())
{
FString CurrentProjectPath = FPaths::GetProjectFilePath();
if (CurrentProjectPath == DesiredProjectFilename)
{
UE_LOG(LogGameProjectGenerationTests, Warning, TEXT("Can not clean up the target project location because it is the current active project."));
}
else
{
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Removing files from old project path: %s"), *FPaths::GetPath(DesiredProjectFilename));
bool bEnsureExists = false;
bool bDeleteEntireTree = true;
IFileManager::Get().DeleteDirectory(*FPaths::GetPath(DesiredProjectFilename), bEnsureExists, bDeleteEntireTree);
}
}
}
else
{
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Target project location is clear"));
}
return true;
}
/**
* Automation test to create a new project
*/
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBuildPromotionNewProjectCreateTest, "System.Promotion.Project Promotion Pass.Step 1 Blank Project Creation.Create Project", /*EAutomationTestFlags::Disabled |*/ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter | EAutomationTestFlags::RequiresUser);
bool FBuildPromotionNewProjectCreateTest::RunTest(const FString& Parameters)
{
FString DesiredProjectFilename = GameProjectAutomationUtils::GetDesiredProjectFilename();
if (FPaths::FileExists(DesiredProjectFilename))
{
UE_LOG(LogGameProjectGenerationTests, Warning, TEXT("A project already exists at the target location: %s"), *DesiredProjectFilename);
const FString OldProjectFolder = FPaths::GetPath(DesiredProjectFilename);
const FString OldProjectName = FPaths::GetBaseFilename(DesiredProjectFilename);
const FString RootFolder = FPaths::GetPath(OldProjectFolder);
//Add a number to the end
for (uint32 i = 2;; ++i)
{
const FString PossibleProjectName = FString::Printf(TEXT("%s%i"), *OldProjectName, i);
const FString PossibleProjectFilename = FString::Printf(TEXT("%s/%s/%s.%s"), *RootFolder, *PossibleProjectName, *PossibleProjectName, *FProjectDescriptor::GetExtension());
if (!FPaths::FileExists(PossibleProjectFilename))
{
DesiredProjectFilename = PossibleProjectFilename;
UE_LOG(LogGameProjectGenerationTests, Warning, TEXT("Changing the target project name to: %s"), *FPaths::GetBaseFilename(DesiredProjectFilename));
break;
}
}
}
FText FailReason, FailLog;
FProjectInformation ProjectInfo;
ProjectInfo.ProjectFilename = DesiredProjectFilename;
if (GameProjectUtils::CreateProject(ProjectInfo, FailReason, FailLog))
{
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Generated a new project: %s"), *DesiredProjectFilename);
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Test successful!"));
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("\nPlease switch to the new project and continue to Step 2."));
}
else
{
UE_LOG(LogGameProjectGenerationTests, Error, TEXT("Could not generate new project: %s - %s"), *FailReason.ToString(), *FailLog.ToString());
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Test failed!"));
}
return true;
}
/**
* Automation test to create a simple level and save it
*/
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBuildPromotionNewProjectMapTest, "System.Promotion.Project Promotion Pass.Step 2 Basic Level Creation.Create Basic Level", /*EAutomationTestFlags::Disabled |*/ EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter);
bool FBuildPromotionNewProjectMapTest::RunTest(const FString& Parameters)
{
//New level
UWorld* CurrentWorld = FAutomationEditorCommonUtils::CreateNewMap();
if (!CurrentWorld)
{
UE_LOG(LogGameProjectGenerationTests, Error, TEXT("Failed to create an empty level"));
return false;
}
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Adding Level Geometry"));
//Add some bsp and a player start
GEditor->Exec(CurrentWorld, TEXT("BRUSH Scale 1 1 1"));
for(FLevelEditorViewportClient* ViewportClient : GEditor->GetLevelViewportClients())
{
if (!ViewportClient->IsOrtho())
{
ViewportClient->SetViewLocation(FVector(176, 2625, 2075));
ViewportClient->SetViewRotation(FRotator(319, 269, 1));
}
}
ULevel* CurrentLevel = CurrentWorld->GetCurrentLevel();
//Cube Additive Brush
UCubeBuilder* CubeAdditiveBrushBuilder = Cast<UCubeBuilder>(GEditor->FindBrushBuilder(UCubeBuilder::StaticClass()));
CubeAdditiveBrushBuilder->X = 4096.0f;
CubeAdditiveBrushBuilder->Y = 4096.0f;
CubeAdditiveBrushBuilder->Z = 128.0f;
CubeAdditiveBrushBuilder->Build(CurrentWorld);
GEditor->Exec(CurrentWorld, TEXT("BRUSH MOVETO X=0 Y=0 Z=0"));
GEditor->Exec(CurrentWorld, TEXT("BRUSH ADD"));
//Add a playerstart
const FTransform Transform(FRotator(-16384, 0, 0), FVector(0.f, 1750.f, 166.f));
AActor* PlayerStart = GEditor->AddActor(CurrentWorld->GetCurrentLevel(), APlayerStart::StaticClass(), Transform);
if (PlayerStart)
{
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Added a player start"));
}
else
{
UE_LOG(LogGameProjectGenerationTests, Error, TEXT("Failed to add a player start"));
}
// Save the map
FString PathToSave = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir() + TEXT("Maps/NewProjectTest.umap"));
FEditorFileUtils::SaveLevel(CurrentLevel, PathToSave);
UE_LOG(LogGameProjectGenerationTests, Display, TEXT("Saved map"));
return true;
}
/*
* Template project creation test
*/
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FCreateBPTemplateProjectAutomationTests, "System.Promotion.Project Promotion Pass.Step 3 NewProjectCreationTests.CreateBlueprintProjects", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter /*| EAutomationTestFlags::Disabled*/)
/**
* Uses the new project wizard to locate all templates available for new blueprint project creation and verifies creation succeeds.
*
* @param Parameters - Unused for this test
* @return TRUE if the test was successful, FALSE otherwise
*/
bool FCreateBPTemplateProjectAutomationTests::RunTest(const FString& Parameters)
{
TMap<FName, TArray<TSharedPtr<FTemplateItem>> > Templates = SProjectDialog::FindTemplateProjects();
int32 OutMatchedProjectsDesk = 0;
int32 OutCreatedProjectsDesk = 0;
GameProjectAutomationUtils::CreateProjectSet(Templates, EHardwareClass::Desktop, EGraphicsPreset::Maximum, "Game", OutMatchedProjectsDesk, OutCreatedProjectsDesk);
int32 OutMatchedProjectsMob = 0;
int32 OutCreatedProjectsMob = 0;
GameProjectAutomationUtils::CreateProjectSet(Templates, EHardwareClass::Mobile, EGraphicsPreset::Maximum, "Visualization", OutMatchedProjectsMob, OutCreatedProjectsMob);
return ( OutMatchedProjectsDesk == OutCreatedProjectsDesk ) && ( OutMatchedProjectsMob == OutCreatedProjectsMob );
}
/*
* Template project creation test
*/
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FCreateCPPTemplateProjectAutomationTests, "System.Promotion.Project Promotion Pass.Step 3 NewProjectCreationTests.CreateCodeProjects", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter /*| EAutomationTestFlags::Disabled*/)
/**
* Uses the new project wizard to locate all templates available for new code project creation and verifies creation succeeds.
*
* @param Parameters - Unused for this test
* @return TRUE if the test was successful, FALSE otherwise
*/
bool FCreateCPPTemplateProjectAutomationTests::RunTest(const FString& Parameters)
{
TMap<FName, TArray<TSharedPtr<FTemplateItem>> > Templates = SProjectDialog::FindTemplateProjects();
int32 OutMatchedProjectsDesk = 0;
int32 OutCreatedProjectsDesk = 0;
GameProjectAutomationUtils::CreateProjectSet(Templates, EHardwareClass::Desktop, EGraphicsPreset::Maximum, "Game", OutMatchedProjectsDesk, OutCreatedProjectsDesk);
int32 OutMatchedProjectsMob = 0;
int32 OutCreatedProjectsMob = 0;
GameProjectAutomationUtils::CreateProjectSet(Templates, EHardwareClass::Mobile, EGraphicsPreset::Maximum, "Game", OutMatchedProjectsMob, OutCreatedProjectsMob);
return (OutMatchedProjectsDesk == OutCreatedProjectsDesk) && (OutMatchedProjectsMob == OutCreatedProjectsMob);
}
#endif //WITH_DEV_AUTOMATION_TESTS