You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Tools/RC/ResourceCompilerPC/Tests/test_Main.cpp

306 lines
14 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ResourceCompilerPC_precompiled.h"
#include <AzTest/AzTest.h>
#include <AzTest/Utils.h>
#include <AzCore/IO/Path/Path.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include "../CryEngine/Cry3DEngine/CGF/CGFLoader.h"
#include "StatCGFCompiler.h"
#include "MultiplatformConfig.h"
#include <QTemporaryDir>
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
AZStd::string GetTestAssetRootPath()
{
char resolvedDir[AZ_MAX_PATH_LEN];
AZ::IO::FileIOBase::GetInstance()->ResolvePath(
"@engroot@/Code/Tools/RC/ResourceCompilerPC/Tests/TestAssets/SamplesProject/",
resolvedDir, sizeof(resolvedDir));
return AZStd::string(resolvedDir);
}
// This is a dummy ResourceCompiler object overload.
// The CGF compiler is heavily dependent upon a ResourceCompiler object existing, so this overload
// just does the minimum amount of work to allow the CGF compiler to query it for default values and
// specific values needed for our test cases.
class ResourceCompilerForTesting
: public IResourceCompiler
, public IConfigKeyRegistry
{
public:
ResourceCompilerForTesting()
{
platformInfo.SetName(0, "pc");
platformInfo.bBigEndian = false;
}
virtual ~ResourceCompilerForTesting() {}
//IConfigKeyRegistry
virtual void VerifyKeyRegistration([[maybe_unused]] const char* szKey) const {}
virtual bool HasKeyRegistered([[maybe_unused]] const char* szKey) const { return false; }
//IResourceCompiler
virtual void RegisterConvertor([[maybe_unused]] const char* name, [[maybe_unused]] IConvertor* conv) {}
virtual IPakSystem* GetPakSystem() { return nullptr; }
virtual const ICfgFile* GetIniFile() const { return nullptr; }
virtual int GetPlatformCount() const { return 1; }
virtual const PlatformInfo* GetPlatformInfo([[maybe_unused]] int index) const { return &platformInfo; }
virtual int FindPlatform([[maybe_unused]] const char* name) const { return 0; }
virtual void AddInputOutputFilePair([[maybe_unused]] const char* inputFilename, [[maybe_unused]] const char* outputFilename) {}
virtual void MarkOutputFileForRemoval([[maybe_unused]] const char* sOutputFilename) {}
virtual void AddExitObserver([[maybe_unused]] IExitObserver* p) {}
virtual void RemoveExitObserver([[maybe_unused]] IExitObserver* p) {}
virtual IRCLog* GetIRCLog() { return nullptr; }
virtual int GetVerbosityLevel() const { return 0; }
virtual const SFileVersion& GetFileVersion() const { return fileVersionInfo; }
virtual const void GetGenericInfo([[maybe_unused]] char* buffer, [[maybe_unused]] size_t bufferSize, [[maybe_unused]] const char* rowSeparator) const {}
virtual void RegisterKey([[maybe_unused]] const char* key, [[maybe_unused]] const char* helptxt) {}
virtual const char* GetExePath() const { return nullptr; }
virtual const char* GetTmpPath() const { return nullptr; }
virtual const char* GetInitialCurrentDir() const { return nullptr; }
virtual XmlNodeRef LoadXml([[maybe_unused]] const char* filename) { XmlNodeRef tmp; return tmp; }
virtual XmlNodeRef CreateXml([[maybe_unused]] const char* tag) { XmlNodeRef tmp; return tmp; }
virtual bool CompileSingleFileBySingleProcess([[maybe_unused]] const char* filename) { return true; }
virtual void SetAssetWriter([[maybe_unused]] IAssetWriter* pAssetWriter) {}
virtual IAssetWriter* GetAssetWriter() const { return nullptr; }
virtual const char* GetAppRoot() const { return nullptr; }
SFileVersion fileVersionInfo;
PlatformInfo platformInfo;
MultiplatformConfig localMultiConfig;
};
class CGFBuilderTest
: public ::testing::Test
{
protected:
AZStd::string GetTemporaryDirectory()
{
QString tempPath = m_temporaryDirectory.path();
return AZStd::string(tempPath.toUtf8().constData());
}
void SetUp() override
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
AZ::AllocatorInstance<AZ::LegacyAllocator>::Create();
AZ::AllocatorInstance<CryStringAllocator>::Create();
m_app.reset(aznew AzToolsFramework::ToolsApplication());
AZ::ComponentApplication::Descriptor desc;
desc.m_useExistingAllocator = true;
m_app->Start(desc);
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
AssetBuilderSDK::InitializeSerializationContext();
const AZStd::string engroot = AZ::Test::GetEngineRootPath();
AZ::IO::FileIOBase::GetInstance()->SetAlias("@engroot@", engroot.c_str());
AZ::IO::Path assetRoot(AZ::Utils::GetProjectPath());
assetRoot /= "Cache";
AZ::IO::FileIOBase::GetInstance()->SetAlias("@root@", assetRoot.c_str());
m_rc = new ResourceCompilerForTesting();
m_compiler = new CStatCGFCompiler();
m_rc->localMultiConfig.init(1, 0, m_rc);
m_rc->localMultiConfig.setActivePlatform(0);
}
void TearDown() override
{
delete m_compiler;
delete m_rc;
m_compiler = nullptr;
m_rc = nullptr;
m_app->Stop();
m_app = nullptr;
AZ::AllocatorInstance<CryStringAllocator>::Destroy();
AZ::AllocatorInstance<AZ::LegacyAllocator>::Destroy();
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
void LoadCompileAndValidateCGF(const AZStd::string& cgfName, const AZStd::string& cgfPath, const AZStd::string& outputPath, AssetBuilderSDK::ProcessJobResponse& response);
AZStd::unique_ptr<AzToolsFramework::ToolsApplication> m_app;
QTemporaryDir m_temporaryDirectory;
CStatCGFCompiler* m_compiler = nullptr;
ResourceCompilerForTesting* m_rc = nullptr;
};
void CGFBuilderTest::LoadCompileAndValidateCGF(const AZStd::string& cgfName, const AZStd::string& cgfPath, const AZStd::string& outputPath, AssetBuilderSDK::ProcessJobResponse& response)
{
// Pass in a custom asset root to the CGF compiler because our test assets are not going through AP
AZStd::string assetRoot(GetTestAssetRootPath());
// Our test assets came from SamplesProject, so emulate that being our game project
AZStd::string gameFolder = "SamplesProject";
IConvertContext* const pCC = m_compiler->GetConvertContext();
pCC->SetMultiplatformConfig(&m_rc->localMultiConfig);
pCC->SetRC(m_rc);
pCC->SetForceRecompiling(true);
pCC->SetConvertorExtension(".cgf");
pCC->SetSourceFileNameOnly(cgfName.c_str());
pCC->SetSourceFolder(cgfPath.c_str());
pCC->SetOutputFolder(outputPath.c_str());
bool compileSuccess = m_compiler->CompileCGF(response, assetRoot, gameFolder);
EXPECT_TRUE(compileSuccess);
bool responseSuccess = m_compiler->WriteResponse(outputPath.c_str(), response, compileSuccess);
EXPECT_TRUE(responseSuccess);
}
// In this case, we load a basic CGF where the material path is just the name of the material file,
// so it is assumed that material is in the same folder as the CGF
TEST_F(CGFBuilderTest, CGF_MaterialInSameFolder)
{
AZStd::string cgfName = "cube_material_same_folder.cgf";
AZStd::string cgfPath(PathHelpers::Join(GetTestAssetRootPath().c_str(),"Objects/Primitives/"));
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::Bus::Events::NormalizePathKeepCase, cgfPath);
AZStd::string outputPath = GetTemporaryDirectory();
AZStd::string expectedMaterialPath;
AzFramework::StringFunc::AssetDatabasePath::Join(cgfPath.c_str(), "primitives_001_mg.mtl", expectedMaterialPath);
AssetBuilderSDK::ProcessJobResponse response;
LoadCompileAndValidateCGF(cgfName, cgfPath, outputPath, response);
ASSERT_EQ(response.m_outputProducts.size(), 1);
AZStd::string productPath = response.m_outputProducts[0].m_productFileName;
EXPECT_TRUE(productPath == "cube_material_same_folder.cgf"); // Path would be relative to the AP temp directory, so just the file name itself
ASSERT_EQ(response.m_outputProducts[0].m_pathDependencies.size(), 1);
AssetBuilderSDK::ProductPathDependency& dependency = *response.m_outputProducts[0].m_pathDependencies.begin();
AZStd::string materialPath = dependency.m_dependencyPath;
EXPECT_TRUE(materialPath == expectedMaterialPath);
EXPECT_EQ(dependency.m_dependencyType, AssetBuilderSDK::ProductPathDependencyType::SourceFile);
}
// In this case, we load a basic CGF where the material path is absolute from the dev/ folder,
// for example "automatedtesting/materials/test.mtl".
TEST_F(CGFBuilderTest, CGF_MaterialInDifferentFolder)
{
AZStd::string cgfName = "gs_block.cgf";
AZStd::string cgfPath(PathHelpers::Join(GetTestAssetRootPath().c_str(), "Objects/GettingStartedAssets/"));
AZStd::string outputPath = GetTemporaryDirectory();
AssetBuilderSDK::ProcessJobResponse response;
LoadCompileAndValidateCGF(cgfName, cgfPath, outputPath, response);
ASSERT_EQ(response.m_outputProducts.size(), 1);
AZStd::string productPath = response.m_outputProducts[0].m_productFileName;
EXPECT_TRUE(productPath == "gs_block.cgf"); // Path would be relative to the AP temp directory, so just the file name itself
ASSERT_EQ(response.m_outputProducts[0].m_pathDependencies.size(), 1);
AssetBuilderSDK::ProductPathDependency& dependency = *response.m_outputProducts[0].m_pathDependencies.begin();
AZStd::string materialPath = dependency.m_dependencyPath;
EXPECT_TRUE(materialPath == "materials/gettingstartedmaterials/gs_block.mtl");
EXPECT_EQ(dependency.m_dependencyType, AssetBuilderSDK::ProductPathDependencyType::ProductFile);
}
// A simple CGF with 2 lods, outputting to just 1 compiled CGF file
TEST_F(CGFBuilderTest, CGF_WithLOD_NoSplit)
{
AZStd::string cgfName = "CGF_LOD_Test.cgf";
AZStd::string cgfPath(PathHelpers::Join(GetTestAssetRootPath().c_str(), "Objects/"));
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::Bus::Events::NormalizePathKeepCase, cgfPath);
AZStd::string outputPath = GetTemporaryDirectory();
AZStd::string expectedMaterialPath;
AzFramework::StringFunc::AssetDatabasePath::Join(cgfPath.c_str(), "Grass_Atlas_matGroup.mtl", expectedMaterialPath);
m_rc->localMultiConfig.setKeyValue(eCP_PriorityLowest, "SplitLODs", "false");
AssetBuilderSDK::ProcessJobResponse response;
LoadCompileAndValidateCGF(cgfName, cgfPath, outputPath, response);
ASSERT_EQ(response.m_outputProducts.size(), 1);
AZStd::string productPath = response.m_outputProducts[0].m_productFileName;
EXPECT_TRUE(productPath == "CGF_LOD_Test.cgf"); // Path would be relative to the AP temp directory, so just the file name itself
ASSERT_EQ(response.m_outputProducts[0].m_pathDependencies.size(), 1);
AssetBuilderSDK::ProductPathDependency& dependency = *response.m_outputProducts[0].m_pathDependencies.begin();
AZStd::string materialPath = dependency.m_dependencyPath;
EXPECT_TRUE(materialPath == expectedMaterialPath);
}
// A simple CGF with 2 lods, outputting to a unique CGF per lod
TEST_F(CGFBuilderTest, CGF_WithLOD_Split)
{
AZStd::string cgfName = "CGF_LOD_Test.cgf";
AZStd::string cgfPath(PathHelpers::Join(GetTestAssetRootPath().c_str(), "Objects/"));
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::Bus::Events::NormalizePathKeepCase, cgfPath);
AZStd::string outputPath = GetTemporaryDirectory();
m_rc->localMultiConfig.setKeyValue(eCP_PriorityLowest, "SplitLODs", "true");
AssetBuilderSDK::ProcessJobResponse response;
LoadCompileAndValidateCGF(cgfName, cgfPath, outputPath, response);
ASSERT_EQ(response.m_outputProducts.size(), 3);
AZStd::string productPath = response.m_outputProducts[0].m_productFileName;
EXPECT_TRUE(productPath == "CGF_LOD_Test.cgf");
productPath = response.m_outputProducts[1].m_productFileName;
EXPECT_TRUE(productPath == "CGF_LOD_Test_lod1.cgf");
productPath = response.m_outputProducts[2].m_productFileName;
EXPECT_TRUE(productPath == "CGF_LOD_Test_lod2.cgf");
EXPECT_TRUE(response.m_outputProducts[0].m_pathDependencies.size() == 3);
AZStd::vector<AZStd::string> expectedDependencyPaths = {
"Grass_Atlas_matGroup.mtl",
"CGF_LOD_Test_lod1.cgf",
"CGF_LOD_Test_lod2.cgf"
};
for (AZStd::string& expectedDependency : expectedDependencyPaths)
{
const char* fileName = expectedDependency.c_str();
AzFramework::StringFunc::AssetDatabasePath::Join(cgfPath.c_str(), fileName, expectedDependency);
}
for (const AssetBuilderSDK::ProductPathDependency& dependency : response.m_outputProducts[0].m_pathDependencies)
{
ASSERT_TRUE(AZStd::find(expectedDependencyPaths.begin(), expectedDependencyPaths.end(), dependency.m_dependencyPath) != expectedDependencyPaths.end());
}
}