Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,200 @@
/*
* 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 thistoolsApp
* 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 <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <Tests/AZTestShared/Utils/Utils.h>
#include <AzToolsFramework/Archive/ArchiveAPI.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/Archive/ArchiveAPI.h>
#include <AzToolsFramework/AssetBundle/AssetBundleAPI.h>
#include <QString>
#include <QDir>
#include <QFileInfo>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QTextStream>
namespace UnitTest
{
namespace
{
bool CreateDummyFile(const QString& fullPathToFile, const QString& tempStr = {})
{
QFileInfo fi(fullPathToFile);
QDir fp(fi.path());
fp.mkpath(".");
QFile writer(fullPathToFile);
if (!writer.open(QFile::ReadWrite))
{
return false;
}
{
QTextStream stream(&writer);
stream << tempStr << Qt::endl;
}
writer.close();
return true;
}
class ArchiveTest :
public ::testing::Test
{
public:
QStringList CreateArchiveFileList()
{
QStringList returnList;
returnList.append("basicfile.txt");
returnList.append("basicfile2.txt");
returnList.append("testfolder/folderfile.txt");
returnList.append("testfolder2/sharedfolderfile.txt");
returnList.append("testfolder2/sharedfolderfile2.txt");
returnList.append("testfolder3/testfolder4/depthfile.bat");
return returnList;
}
QString GetArchiveFolderName()
{
return "Archive";
}
void CreateArchiveFolder( QString archiveFolderName, QStringList fileList )
{
QDir tempPath = QDir(m_tempDir.path()).filePath(archiveFolderName);
for (const auto& thisFile : fileList)
{
QString absoluteTestFilePath = tempPath.absoluteFilePath(thisFile);
EXPECT_TRUE(CreateDummyFile(absoluteTestFilePath));
}
}
void CreateArchiveFolder()
{
CreateArchiveFolder(GetArchiveFolderName(), CreateArchiveFileList());
}
QString GetArchivePath()
{
return QDir(m_tempDir.path()).filePath("TestArchive.pak");
}
QString GetArchiveFolder()
{
return QDir(m_tempDir.path()).filePath(GetArchiveFolderName());
}
bool CreateArchive()
{
bool createResult{ false };
AzToolsFramework::ArchiveCommandsBus::BroadcastResult(createResult, &AzToolsFramework::ArchiveCommandsBus::Events::CreateArchiveBlocking, GetArchivePath().toStdString().c_str(), GetArchiveFolder().toStdString().c_str());
return createResult;
}
void SetUp() override
{
m_app.reset(aznew AzToolsFramework::ToolsApplication);
m_app->Start(AzFramework::Application::Descriptor());
// 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);
}
void TearDown() override
{
m_app->Stop();
m_app.reset();
}
AZStd::unique_ptr<AzToolsFramework::ToolsApplication> m_app;
QTemporaryDir m_tempDir {QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).filePath("ArchiveTests-")};
};
TEST_F(ArchiveTest, CreateArchiveBlocking_FilesAtThreeDepths_ArchiveCreated)
{
EXPECT_TRUE(m_tempDir.isValid());
CreateArchiveFolder();
bool createResult = CreateArchive();
EXPECT_EQ(createResult, true);
}
TEST_F(ArchiveTest, ListFilesInArchiveBlocking_FilesAtThreeDepths_FilesFound)
{
EXPECT_TRUE(m_tempDir.isValid());
CreateArchiveFolder();
EXPECT_EQ(CreateArchive(), true);
AZStd::vector<AZStd::string> fileList;
bool listResult{ false };
AzToolsFramework::ArchiveCommandsBus::BroadcastResult(listResult, &AzToolsFramework::ArchiveCommandsBus::Events::ListFilesInArchiveBlocking, GetArchivePath().toStdString().c_str(), fileList);
EXPECT_EQ(fileList.size(), 6);
}
TEST_F(ArchiveTest, CreateDeltaCatalog_AssetsNotRegistered_Failure)
{
QStringList fileList = CreateArchiveFileList();
CreateArchiveFolder(GetArchiveFolderName(), fileList);
bool createResult = CreateArchive();
EXPECT_EQ(createResult, true);
bool catalogCreated{ true };
AZ::Test::AssertAbsorber assertAbsorber;
AzToolsFramework::AssetBundleCommandsBus::BroadcastResult(catalogCreated, &AzToolsFramework::AssetBundleCommandsBus::Events::CreateDeltaCatalog, GetArchivePath().toStdString().c_str(), true);
EXPECT_EQ(catalogCreated, false);
}
TEST_F(ArchiveTest, CreateDeltaCatalog_ArchiveWithoutCatalogAssetsRegistered_Success)
{
QStringList fileList = CreateArchiveFileList();
CreateArchiveFolder(GetArchiveFolderName(), fileList);
bool createResult = CreateArchive();
EXPECT_EQ(createResult, true);
for (const auto& thisPath : fileList)
{
AZ::Data::AssetInfo newInfo;
newInfo.m_relativePath = thisPath.toStdString().c_str();
newInfo.m_assetType = AZ::Uuid::CreateRandom();
newInfo.m_sizeBytes = 100; // Arbitrary
AZ::Data::AssetId generatedID(AZ::Uuid::CreateRandom());
newInfo.m_assetId = generatedID;
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, generatedID, newInfo);
}
bool catalogCreated{ false };
AzToolsFramework::AssetBundleCommandsBus::BroadcastResult(catalogCreated, &AzToolsFramework::AssetBundleCommandsBus::Events::CreateDeltaCatalog, GetArchivePath().toStdString().c_str(), true);
EXPECT_EQ(catalogCreated, true);
}
}
}
@@ -0,0 +1,790 @@
/*
* 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 <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/Asset/AssetSeedManager.h>
#include <AzFramework/Asset/AssetRegistry.h>
#include <AzCore/IO/FileIO.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Asset/AssetCatalog.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <Tests/AZTestShared/Utils/Utils.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Asset/AssetBundler.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzToolsFramework/AssetCatalog/PlatformAddressedAssetCatalog.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
namespace // anonymous
{
constexpr int TotalAssets = 6;
constexpr int TotalTempFiles = 3;
constexpr char TempFiles[TotalTempFiles][AZ_MAX_PATH_LEN] = { "firstAssetFileInfoList.assetlist", "secondAssetFileInfoList.assetlist", "assetFileInfoList.assetlist" };
enum FileIndex
{
FirstAssetFileInfoList,
SecondAssetFileInfoList,
ResultAssetFileInfoList
};
}
namespace UnitTest
{
class AssetFileInfoListComparisonTest
: public AllocatorsFixture
, public AZ::Data::AssetCatalogRequestBus::Handler
{
public:
void SetUp() override
{
using namespace AZ::Data;
m_application = new AzToolsFramework::ToolsApplication();
AzToolsFramework::AssetSeedManager assetSeedManager;
AzFramework::AssetRegistry assetRegistry;
m_localFileIO = aznew AZ::IO::LocalFileIO();
m_priorFileIO = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FileIOBase::SetInstance(m_localFileIO);
AZ::IO::FileIOBase::GetInstance()->SetAlias("@assets@", GetTestFolderPath().c_str());
AZStd::string assetRoot = AzToolsFramework::PlatformAddressedAssetCatalog::GetAssetRootForPlatform(AzFramework::PlatformId::PC);
for (int idx = 0; idx < TotalAssets; idx++)
{
m_assets[idx] = AssetId(AZ::Uuid::CreateRandom(), 0);
AZ::Data::AssetInfo info;
info.m_relativePath = AZStd::string::format("Asset%d.txt", idx);
info.m_assetId = m_assets[idx];
assetRegistry.RegisterAsset(m_assets[idx], info);
AzFramework::StringFunc::Path::Join(assetRoot.c_str(), info.m_relativePath.c_str(), m_assetsPath[idx]);
if (m_fileStreams[idx].Open(m_assetsPath[idx].c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary | AZ::IO::OpenMode::ModeCreatePath))
{
m_fileStreams[idx].Write(info.m_relativePath.size(), info.m_relativePath.data());
}
else
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Unable to create temporary file ( %s ) in AssetSeedManager unit tests.\n", m_assetsPath[idx].c_str()).c_str());
}
}
// asset1 -> asset2
assetRegistry.RegisterAssetDependency(m_assets[1], AZ::Data::ProductDependency(m_assets[2], 0));
// asset2 -> asset3
assetRegistry.RegisterAssetDependency(m_assets[2], AZ::Data::ProductDependency(m_assets[3], 0));
// asset3 -> asset4
assetRegistry.RegisterAssetDependency(m_assets[3], AZ::Data::ProductDependency(m_assets[4], 0));
m_application->Start(AzFramework::Application::Descriptor());
// 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);
AZ::SerializeContext* context;
AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
ASSERT_TRUE(context) << "No serialize context.\n";
AzToolsFramework::AssetSeedManager::Reflect(context);
// Asset Catalog does not expose its internal asset registry and the only way to set it is through LoadCatalog API
// Currently I am serializing the asset registry to disk
// and invoking the LoadCatalog API to populate the asset catalog created by the azframework app.
AZStd::string pcCatalogFile = AzToolsFramework::PlatformAddressedAssetCatalog::GetCatalogRegistryPathForPlatform(AzFramework::PlatformId::PC);
ASSERT_TRUE(AzFramework::AssetCatalog::SaveCatalog(pcCatalogFile.c_str(), &assetRegistry)) << "Unable to save the asset catalog file.\n";
m_pcCatalog = new AzToolsFramework::PlatformAddressedAssetCatalog(AzFramework::PlatformId::PC);
assetSeedManager.AddSeedAsset(m_assets[0], AzFramework::PlatformFlags::Platform_PC);
assetSeedManager.AddSeedAsset(m_assets[1], AzFramework::PlatformFlags::Platform_PC);
assetSeedManager.SaveAssetFileInfo(TempFiles[FileIndex::FirstAssetFileInfoList], AzFramework::PlatformFlags::Platform_PC, {});
// Modify contents of asset2
int fileIndex = 2;
if (m_fileStreams[fileIndex].Open(m_assetsPath[fileIndex].c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary | AZ::IO::OpenMode::ModeCreatePath))
{
AZStd::string fileContent = AZStd::string::format("new Asset%d.txt", fileIndex);// changing file content
m_fileStreams[fileIndex].Write(fileContent.size(), fileContent.c_str());
}
else
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Unable to open asset file.\n").c_str());
}
// Modify contents of asset 4
fileIndex = 4;
if (m_fileStreams[fileIndex].Open(m_assetsPath[fileIndex].c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary | AZ::IO::OpenMode::ModeCreatePath))
{
AZStd::string fileContent = AZStd::string::format("new Asset%d.txt", fileIndex);// changing file content
m_fileStreams[fileIndex].Write(fileContent.size(), fileContent.c_str());
}
else
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Unable to open asset file.\n").c_str());
}
assetSeedManager.RemoveSeedAsset(m_assets[0], AzFramework::PlatformFlags::Platform_PC);
assetSeedManager.AddSeedAsset(m_assets[5], AzFramework::PlatformFlags::Platform_PC);
assetSeedManager.SaveAssetFileInfo(TempFiles[FileIndex::SecondAssetFileInfoList], AzFramework::PlatformFlags::Platform_PC, {});
}
void TearDown() override
{
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
// Delete all temporary files
for (int idx = 0; idx < TotalTempFiles; idx++)
{
if (fileIO->Exists(TempFiles[idx]))
{
fileIO->Remove(TempFiles[idx]);
}
}
// Deleting all temporary assets files
for (int idx = 0; idx < TotalAssets; idx++)
{
// we need to close the handle before we try to remove the file
m_fileStreams[idx].Close();
if (fileIO->Exists(m_assetsPath[idx].c_str()))
{
fileIO->Remove(m_assetsPath[idx].c_str());
}
}
auto pcCatalogFile = AzToolsFramework::PlatformAddressedAssetCatalog::GetCatalogRegistryPathForPlatform(AzFramework::PlatformId::PC);
if (fileIO->Exists(pcCatalogFile.c_str()))
{
fileIO->Remove(pcCatalogFile.c_str());
}
delete m_pcCatalog;
delete m_localFileIO;
m_localFileIO = nullptr;
AZ::IO::FileIOBase::SetInstance(m_priorFileIO);
m_application->Stop();
delete m_application;
}
void AssetFileInfoValidation_DeltaComparison_Valid()
{
// First AssetFileInfoList {0,1,2,3,4} , Second AssetFileInfoList {1,2*,3,4*,5} where * indicate that hash has changed for that asset
AzToolsFramework::AssetFileInfoListComparison assetFileInfoListComparison;
AzToolsFramework::AssetFileInfoListComparison::ComparisonData comparisonData(AzToolsFramework::AssetFileInfoListComparison::ComparisonType::Delta, TempFiles[FileIndex::ResultAssetFileInfoList]);
comparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
comparisonData.m_secondInput = TempFiles[FileIndex::SecondAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(comparisonData);
ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "Delta operation failed.\n";
// AssetFileInfo should contain {2*, 4*, 5}
AzToolsFramework::AssetFileInfoList assetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n";
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 3);
// Verifying that the hash of the file are correct. They must be from the second AssetFileInfoList.
AzToolsFramework::AssetFileInfoList secondAssetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::SecondAssetFileInfoList], secondAssetFileInfoList)) << "Unable to read the asset file info list.\n";
AZStd::unordered_map<AZ::Data::AssetId, AzToolsFramework::AssetFileInfo> assetIdToAssetFileInfoMap;
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : secondAssetFileInfoList.m_fileInfoList)
{
assetIdToAssetFileInfoMap[assetFileInfo.m_assetId] = AZStd::move(assetFileInfo);
}
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = assetIdToAssetFileInfoMap.find(assetFileInfo.m_assetId);
if (found != assetIdToAssetFileInfoMap.end())
{
// checking the file hash
for (int idx = 0; idx < AzToolsFramework::AssetFileInfo::s_arraySize; idx++)
{
if (found->second.m_hash[idx] != assetFileInfo.m_hash[idx])
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Invalid file hash.\n").c_str());
break;
}
}
}
}
// Verifying that correct assetId are present in the assetFileInfo list
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[2], m_assets[4], m_assets[5] };
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = expectedAssetIds.find(assetFileInfo.m_assetId);
if (found != expectedAssetIds.end())
{
expectedAssetIds.erase(found);
}
}
EXPECT_EQ(expectedAssetIds.size(), 0);
}
void AssetFileInfoValidation_UnionComparison_Valid()
{
// First AssetFileInfoList {0,1,2,3,4} , Second AssetFileInfoList {1,2*,3,4*,5} where * indicate that hash has changed for that asset
AzToolsFramework::AssetFileInfoListComparison assetFileInfoListComparison;
AzToolsFramework::AssetFileInfoListComparison::ComparisonData comparisonData(AzToolsFramework::AssetFileInfoListComparison::ComparisonType::Union, TempFiles[FileIndex::ResultAssetFileInfoList]);
comparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
comparisonData.m_secondInput = TempFiles[FileIndex::SecondAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(comparisonData);
ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "Union operation failed.\n";
// AssetFileInfo should contain {0, 1, 2*, 3, 4*, 5}
AzToolsFramework::AssetFileInfoList assetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n";
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 6);
//Verifying that the hash of the files are correct.
AzToolsFramework::AssetFileInfoList firstAssetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::FirstAssetFileInfoList], firstAssetFileInfoList)) << "Unable to read the asset file info list.\n";
AZStd::unordered_map<AZ::Data::AssetId, AzToolsFramework::AssetFileInfo> firstAssetIdToAssetFileInfoMap;
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : firstAssetFileInfoList.m_fileInfoList)
{
firstAssetIdToAssetFileInfoMap[assetFileInfo.m_assetId] = AZStd::move(assetFileInfo);
}
AzToolsFramework::AssetFileInfoList secondAssetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::SecondAssetFileInfoList], secondAssetFileInfoList)) << "Unable to read the asset file info list.\n";
AZStd::unordered_map<AZ::Data::AssetId, AzToolsFramework::AssetFileInfo> secondAssetIdToAssetFileInfoMap;
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : secondAssetFileInfoList.m_fileInfoList)
{
secondAssetIdToAssetFileInfoMap[assetFileInfo.m_assetId] = AZStd::move(assetFileInfo);
}
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto foundFirst = firstAssetIdToAssetFileInfoMap.find(assetFileInfo.m_assetId);
auto foundSecond = secondAssetIdToAssetFileInfoMap.find(assetFileInfo.m_assetId);
if (foundSecond != secondAssetIdToAssetFileInfoMap.end())
{
// Even if the asset Id is present in both the AssetFileInfo List, it should match the file hash from the second AssetFileInfo list
for (int idx = 0; idx < AzToolsFramework::AssetFileInfo::s_arraySize; idx++)
{
if (foundSecond->second.m_hash[idx] != assetFileInfo.m_hash[idx])
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Invalid file hash.\n").c_str());
break;
}
}
}
else if (foundFirst != firstAssetIdToAssetFileInfoMap.end())
{
// checking the file hash
for (int idx = 0; idx < AzToolsFramework::AssetFileInfo::s_arraySize; idx++)
{
if (foundFirst->second.m_hash[idx] != assetFileInfo.m_hash[idx])
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Invalid file hash.\n").c_str());
break;
}
}
}
else
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Invalid file hash.\n").c_str());
}
}
// Verifying that correct assetId are present in the assetFileInfo list
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[0], m_assets[1], m_assets[2], m_assets[3], m_assets[4], m_assets[5] };
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = expectedAssetIds.find(assetFileInfo.m_assetId);
if (found != expectedAssetIds.end())
{
expectedAssetIds.erase(found);
}
}
EXPECT_EQ(expectedAssetIds.size(), 0);
}
void AssetFileInfoValidation_IntersectionComparison_Valid()
{
// First AssetFileInfoList {0,1,2,3,4} , Second AssetFileInfoList {1,2*,3,4*,5} where * indicate that hash has changed for that asset
AzToolsFramework::AssetFileInfoListComparison assetFileInfoListComparison;
AzToolsFramework::AssetFileInfoListComparison::ComparisonData comparisonData(AzToolsFramework::AssetFileInfoListComparison::ComparisonType::Intersection, TempFiles[FileIndex::ResultAssetFileInfoList]);
comparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
comparisonData.m_secondInput = TempFiles[FileIndex::SecondAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(comparisonData);
ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "Intersection operation failed.\n";
// AssetFileInfo should contain {1,2*,3,4*}
AzToolsFramework::AssetFileInfoList assetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n";
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 4);
// Verifying that the hash of the file are correct. They must be from the second AssetFileInfoList.
AzToolsFramework::AssetFileInfoList secondAssetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::SecondAssetFileInfoList], secondAssetFileInfoList)) << "Unable to read the asset file info list.\n";
AZStd::unordered_map<AZ::Data::AssetId, AzToolsFramework::AssetFileInfo> assetIdToAssetFileInfoMap;
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : secondAssetFileInfoList.m_fileInfoList)
{
assetIdToAssetFileInfoMap[assetFileInfo.m_assetId] = AZStd::move(assetFileInfo);
}
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = assetIdToAssetFileInfoMap.find(assetFileInfo.m_assetId);
if (found != assetIdToAssetFileInfoMap.end())
{
// checking the file hash
for (int idx = 0; idx < AzToolsFramework::AssetFileInfo::s_arraySize; idx++)
{
if (found->second.m_hash[idx] != assetFileInfo.m_hash[idx])
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Invalid file hash.\n").c_str());
break;
}
}
}
}
// Verifying that correct assetId are present in the assetFileInfo list
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[1], m_assets[2], m_assets[3], m_assets[4] };
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = expectedAssetIds.find(assetFileInfo.m_assetId);
if (found != expectedAssetIds.end())
{
expectedAssetIds.erase(found);
}
}
EXPECT_EQ(expectedAssetIds.size(), 0);
}
void AssetFileInfoValidation_ComplementComparison_Valid()
{
using namespace AzToolsFramework;
// First AssetFileInfoList {0,1,2,3,4} , Second AssetFileInfoList {1,2*,3,4*,5} where * indicate that hash has changed for that asset
AssetFileInfoListComparison assetFileInfoListComparison;
AzToolsFramework::AssetFileInfoListComparison::ComparisonData comparisonData(AssetFileInfoListComparison::ComparisonType::Complement, TempFiles[FileIndex::ResultAssetFileInfoList]);
comparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
comparisonData.m_secondInput = TempFiles[FileIndex::SecondAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(comparisonData);
ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "Complement comparison failed.\n";
// AssetFileInfo should contain {5}
AssetFileInfoList assetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n";
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 1);
// Verifying that the hash of the file are correct. They must be from the second AssetFileInfoList.
AssetFileInfoList secondAssetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::SecondAssetFileInfoList], secondAssetFileInfoList)) << "Unable to read the asset file info list.\n";
AZStd::unordered_map<AZ::Data::AssetId, AssetFileInfo> assetIdToAssetFileInfoMap;
for (const AssetFileInfo& assetFileInfo : secondAssetFileInfoList.m_fileInfoList)
{
assetIdToAssetFileInfoMap[assetFileInfo.m_assetId] = AZStd::move(assetFileInfo);
}
for (const AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = assetIdToAssetFileInfoMap.find(assetFileInfo.m_assetId);
if (found != assetIdToAssetFileInfoMap.end())
{
// checking the file hash
for (int idx = 0; idx < AssetFileInfo::s_arraySize; idx++)
{
if (found->second.m_hash[idx] != assetFileInfo.m_hash[idx])
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Invalid file hash.\n").c_str());
break;
}
}
}
}
// Verifying that correct assetId are present in the assetFileInfo list
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[5] };
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = expectedAssetIds.find(assetFileInfo.m_assetId);
if (found != expectedAssetIds.end())
{
expectedAssetIds.erase(found);
}
}
EXPECT_EQ(expectedAssetIds.size(), 0);
}
void AssetFileInfoValidation_FilePatternWildcardComparisonAll_Valid()
{
using namespace AzToolsFramework;
// First AssetFileInfoList {0,1,2,3,4} , Second AssetFileInfoList {1,2*,3,4*,5} where * indicate that hash has changed for that asset
AssetFileInfoListComparison assetFileInfoListComparison;
AssetFileInfoListComparison::ComparisonData comparisonData(AssetFileInfoListComparison::ComparisonType::FilePattern, TempFiles[FileIndex::ResultAssetFileInfoList], "Asset*.txt", AssetFileInfoListComparison::FilePatternType::Wildcard);
comparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(comparisonData);
ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "File pattern match failed.\n";
// AssetFileInfo should contain {0,1,2,3,4}
AssetFileInfoList assetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n";
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 5);
// Verifying that correct assetId are present in the assetFileInfo list
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[0], m_assets[1], m_assets[2], m_assets[3], m_assets[4] };
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = expectedAssetIds.find(assetFileInfo.m_assetId);
if (found != expectedAssetIds.end())
{
expectedAssetIds.erase(found);
}
}
EXPECT_EQ(expectedAssetIds.size(), 0);
}
void AssetFileInfoValidation_FilePatternWildcardComparisonNone_ExpectFailure()
{
using namespace AzToolsFramework;
// First AssetFileInfoList {0,1,2,3,4} , Second AssetFileInfoList {1,2*,3,4*,5} where * indicate that hash has changed for that asset
AssetFileInfoListComparison assetFileInfoListComparison;
AssetFileInfoListComparison::ComparisonData comparisonData(AssetFileInfoListComparison::ComparisonType::FilePattern, TempFiles[FileIndex::ResultAssetFileInfoList], "Foo*.txt", AssetFileInfoListComparison::FilePatternType::Wildcard);
comparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(comparisonData);
ASSERT_FALSE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "File pattern match should not have produced any output.\n";
// AssetFileInfo should not exist on-disk
ASSERT_FALSE(AZ::IO::FileIOBase::GetInstance()->Exists(TempFiles[FileIndex::ResultAssetFileInfoList])) << "Asset List file should not exist on-disk.\n";
}
void AssetFileInfoValidation_FilePatternRegexComparisonPartial_Valid()
{
using namespace AzToolsFramework;
// First AssetFileInfoList {0,1,2,3,4} , Second AssetFileInfoList {1,2*,3,4*,5} where * indicate that hash has changed for that asset
AssetFileInfoListComparison assetFileInfoListComparison;
AssetFileInfoListComparison::ComparisonData comparisonData(AssetFileInfoListComparison::ComparisonType::FilePattern, TempFiles[FileIndex::ResultAssetFileInfoList], "Asset[0-3].txt", AssetFileInfoListComparison::FilePatternType::Regex);
comparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(comparisonData);
ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "File pattern match failed.\n";
// AssetFileInfo should be {0,1,2,3}
AssetFileInfoList assetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n";
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 4);
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[0], m_assets[1], m_assets[2], m_assets[3]};
for (const AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = expectedAssetIds.find(assetFileInfo.m_assetId);
if (found != expectedAssetIds.end())
{
expectedAssetIds.erase(found);
}
}
EXPECT_EQ(expectedAssetIds.size(), 0);
}
void AssetFileInfoValidation_DeltaFilePatternComparisonOperation_Valid()
{
using namespace AzToolsFramework;
// First AssetFileInfoList {0,1,2,3,4} , Second AssetFileInfoList {1,2*,3,4*,5} where * indicate that hash has changed for that asset
AssetFileInfoListComparison assetFileInfoListComparison;
AzToolsFramework::AssetFileInfoListComparison::ComparisonData deltaComparisonData(AzToolsFramework::AssetFileInfoListComparison::ComparisonType::Delta, "$1");
deltaComparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
deltaComparisonData.m_secondInput = TempFiles[FileIndex::SecondAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(deltaComparisonData);
AssetFileInfoListComparison::ComparisonData filePatternComparisonData(AssetFileInfoListComparison::ComparisonType::FilePattern, TempFiles[FileIndex::ResultAssetFileInfoList], "Asset[0-3].txt", AssetFileInfoListComparison::FilePatternType::Regex);
filePatternComparisonData.m_firstInput = "$1";
assetFileInfoListComparison.AddComparisonStep(filePatternComparisonData);
ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "Multiple Comparison Operation( Delta + FilePattern ) failed.\n";
// Output of the Delta Operation should be {2*, 4*, 5}
// Output of the FilePattern Operation should be {2*}
AzToolsFramework::AssetFileInfoList assetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n";
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 1);
AZStd::unordered_map<AZ::Data::AssetId, AssetFileInfo> assetIdToAssetFileInfoMap;
AzToolsFramework::AssetFileInfoList secondAssetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::SecondAssetFileInfoList], secondAssetFileInfoList)) << "Unable to read the asset file info list.\n";
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : secondAssetFileInfoList.m_fileInfoList)
{
assetIdToAssetFileInfoMap[assetFileInfo.m_assetId] = AZStd::move(assetFileInfo);
}
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = assetIdToAssetFileInfoMap.find(assetFileInfo.m_assetId);
if (found != assetIdToAssetFileInfoMap.end())
{
// checking the file hash
for (int idx = 0; idx < AzToolsFramework::AssetFileInfo::s_arraySize; idx++)
{
if (found->second.m_hash[idx] != assetFileInfo.m_hash[idx])
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Invalid file hash.\n").c_str());
break;
}
}
}
}
// Verifying that correct assetId are present in the assetFileInfo list
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[2] };
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = expectedAssetIds.find(assetFileInfo.m_assetId);
if (found != expectedAssetIds.end())
{
expectedAssetIds.erase(found);
}
}
EXPECT_EQ(expectedAssetIds.size(), 0);
}
void AssetFileInfoValidation_FilePatternDeltaComparisonOperation_Valid()
{
using namespace AzToolsFramework;
// First AssetFileInfoList {0,1,2,3,4} , Second AssetFileInfoList {1,2*,3,4*,5} where * indicate that hash has changed for that asset
AssetFileInfoListComparison assetFileInfoListComparison;
AssetFileInfoListComparison::ComparisonData filePatternComparisonData(AssetFileInfoListComparison::ComparisonType::FilePattern,"$1", "Asset[0-3].txt", AssetFileInfoListComparison::FilePatternType::Regex);
filePatternComparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(filePatternComparisonData);
AzToolsFramework::AssetFileInfoListComparison::ComparisonData deltaComparisonData(AzToolsFramework::AssetFileInfoListComparison::ComparisonType::Delta, TempFiles[FileIndex::ResultAssetFileInfoList]);
deltaComparisonData.m_firstInput = "$1";
deltaComparisonData.m_secondInput = TempFiles[FileIndex::SecondAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(deltaComparisonData);
ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "Multiple Comparison Operation( FilePattern + Delta ) failed.\n";
// Output of the FilePattern Operation should be {0,1,2,3}
// Output of the Delta Operation should be {2*,4*,5}
AzToolsFramework::AssetFileInfoList assetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n";
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 3);
AZStd::unordered_map<AZ::Data::AssetId, AssetFileInfo> assetIdToAssetFileInfoMap;
AzToolsFramework::AssetFileInfoList secondAssetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::SecondAssetFileInfoList], secondAssetFileInfoList)) << "Unable to read the asset file info list.\n";
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : secondAssetFileInfoList.m_fileInfoList)
{
assetIdToAssetFileInfoMap[assetFileInfo.m_assetId] = AZStd::move(assetFileInfo);
}
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = assetIdToAssetFileInfoMap.find(assetFileInfo.m_assetId);
if (found != assetIdToAssetFileInfoMap.end())
{
// checking the file hash
for (int idx = 0; idx < AzToolsFramework::AssetFileInfo::s_arraySize; idx++)
{
if (found->second.m_hash[idx] != assetFileInfo.m_hash[idx])
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Invalid file hash.\n").c_str());
break;
}
}
}
}
// Verifying that correct assetId are present in the assetFileInfo list
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[2], m_assets[4], m_assets[5] };
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = expectedAssetIds.find(assetFileInfo.m_assetId);
if (found != expectedAssetIds.end())
{
expectedAssetIds.erase(found);
}
}
EXPECT_EQ(expectedAssetIds.size(), 0);
}
void AssetFileInfoValidation_DeltaUnionFilePatternComparisonOperation_Valid()
{
using namespace AzToolsFramework;
// First AssetFileInfoList {0,1,2,3,4} , Second AssetFileInfoList {1,2*,3,4*,5} where * indicate that hash has changed for that asset
AssetFileInfoListComparison assetFileInfoListComparison;
AzToolsFramework::AssetFileInfoListComparison::ComparisonData deltaComparisonData(AzToolsFramework::AssetFileInfoListComparison::ComparisonType::Delta, "$1");
deltaComparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
deltaComparisonData.m_secondInput = TempFiles[FileIndex::SecondAssetFileInfoList];
assetFileInfoListComparison.AddComparisonStep(deltaComparisonData);
AssetFileInfoListComparison::ComparisonData unionComparisonData(AssetFileInfoListComparison::ComparisonType::Union, "$2");
unionComparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
unionComparisonData.m_secondInput = "$1";
assetFileInfoListComparison.AddComparisonStep(unionComparisonData);
AssetFileInfoListComparison::ComparisonData filePatternComparisonData(AssetFileInfoListComparison::ComparisonType::FilePattern, TempFiles[FileIndex::ResultAssetFileInfoList], "Asset[4-5].txt", AssetFileInfoListComparison::FilePatternType::Regex);
filePatternComparisonData.m_firstInput = "$2";
assetFileInfoListComparison.AddComparisonStep(filePatternComparisonData);
ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "Multiple Comparison Operation( Delta + Union + FilePattern ) failed.\n";
// Output of the Delta Operation should be {2*, 4*, 5}
// Putput of the Union Operation should be {0, 1, 2*, 3, 4*, 5}
// Output of the FilePattern Operation should be {4*, 5}
AzToolsFramework::AssetFileInfoList assetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n";
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 2);
AZStd::unordered_map<AZ::Data::AssetId, AssetFileInfo> assetIdToAssetFileInfoMap;
AzToolsFramework::AssetFileInfoList secondAssetFileInfoList;
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::SecondAssetFileInfoList], secondAssetFileInfoList)) << "Unable to read the asset file info list.\n";
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : secondAssetFileInfoList.m_fileInfoList)
{
assetIdToAssetFileInfoMap[assetFileInfo.m_assetId] = AZStd::move(assetFileInfo);
}
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = assetIdToAssetFileInfoMap.find(assetFileInfo.m_assetId);
if (found != assetIdToAssetFileInfoMap.end())
{
// checking the file hash
for (int idx = 0; idx < AzToolsFramework::AssetFileInfo::s_arraySize; idx++)
{
if (found->second.m_hash[idx] != assetFileInfo.m_hash[idx])
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Invalid file hash.\n").c_str());
break;
}
}
}
}
// Verifying that correct assetId are present in the assetFileInfo list
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[4], m_assets[5] };
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
{
auto found = expectedAssetIds.find(assetFileInfo.m_assetId);
if (found != expectedAssetIds.end())
{
expectedAssetIds.erase(found);
}
}
EXPECT_EQ(expectedAssetIds.size(), 0);
}
AzToolsFramework::ToolsApplication* m_application;
AzToolsFramework::PlatformAddressedAssetCatalog* m_pcCatalog;
AZ::IO::FileIOBase* m_priorFileIO = nullptr;
AZ::IO::FileIOBase* m_localFileIO = nullptr;
AZ::IO::FileIOStream m_fileStreams[TotalAssets];
AZ::Data::AssetId m_assets[TotalAssets];
AZStd::string m_assetsPath[TotalAssets];
};
TEST_F(AssetFileInfoListComparisonTest, AssetFileInfoValidation_DeltaComparison_Valid)
{
AssetFileInfoValidation_DeltaComparison_Valid();
}
TEST_F(AssetFileInfoListComparisonTest, AssetFileInfoValidation_UnionComparison_Valid)
{
AssetFileInfoValidation_UnionComparison_Valid();
}
TEST_F(AssetFileInfoListComparisonTest, AssetFileInfoValidation_IntersectionComparison_Valid)
{
AssetFileInfoValidation_IntersectionComparison_Valid();
}
TEST_F(AssetFileInfoListComparisonTest, AssetFileInfoValidation_ComplementComparison_Valid)
{
AssetFileInfoValidation_ComplementComparison_Valid();
}
TEST_F(AssetFileInfoListComparisonTest, AssetFileInfoValidation_FilePatternWildcardComparisonAll_Valid)
{
AssetFileInfoValidation_FilePatternWildcardComparisonAll_Valid();
}
TEST_F(AssetFileInfoListComparisonTest, AssetFileInfoValidation_FilePatternWildcardComparisonNone_ExpectFailure)
{
AssetFileInfoValidation_FilePatternWildcardComparisonNone_ExpectFailure();
}
TEST_F(AssetFileInfoListComparisonTest, AssetFileInfoValidation_FilePatternRegexComparisonPartial_Valid)
{
AssetFileInfoValidation_FilePatternRegexComparisonPartial_Valid();
}
TEST_F(AssetFileInfoListComparisonTest, AssetFileInfoValidation_DeltaFilePatternComparisonOperation_Valid)
{
AssetFileInfoValidation_DeltaFilePatternComparisonOperation_Valid();
}
TEST_F(AssetFileInfoListComparisonTest, AssetFileInfoValidation_FilePatternDeltaComparisonOperation_Valid)
{
AssetFileInfoValidation_FilePatternDeltaComparisonOperation_Valid();
}
TEST_F(AssetFileInfoListComparisonTest, AssetFileInfoValidation_DeltaUnionFilePatternComparisonOperation_Valid)
{
AssetFileInfoValidation_DeltaUnionFilePatternComparisonOperation_Valid();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
namespace UnitTests
{
class MockAssetSystemRequest : public AzToolsFramework::AssetSystemRequestBus::Handler
{
public:
MOCK_METHOD1(GetAbsoluteAssetDatabaseLocation, bool(AZStd::string&));
MOCK_METHOD0(GetAbsoluteDevGameFolderPath, const char* ());
MOCK_METHOD0(GetAbsoluteDevRootFolderPath, const char* ());
MOCK_METHOD2(GetRelativeProductPathFromFullSourceOrProductPath, bool(const AZStd::string& fullPath, AZStd::string& relativeProductPath));
MOCK_METHOD2(GetFullSourcePathFromRelativeProductPath, bool(const AZStd::string& relPath, AZStd::string& fullSourcePath));
MOCK_METHOD5(GetAssetInfoById, bool(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType, const AZStd::string& platformName, AZ::Data::AssetInfo& assetInfo, AZStd::string& rootFilePath));
MOCK_METHOD3(GetSourceInfoBySourcePath, bool(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder));
MOCK_METHOD3(GetSourceInfoBySourceUUID, bool(const AZ::Uuid& sourceUuid, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder));
MOCK_METHOD1(GetScanFolders, bool(AZStd::vector<AZStd::string>& scanFolders));
MOCK_METHOD1(GetAssetSafeFolders, bool(AZStd::vector<AZStd::string>& assetSafeFolders));
MOCK_METHOD1(IsAssetPlatformEnabled, bool(const char* platform));
MOCK_METHOD1(GetPendingAssetsForPlatform, int(const char* platform));
MOCK_METHOD2(GetAssetsProducedBySourceUUID, bool(const AZ::Uuid& sourceUuid, AZStd::vector<AZ::Data::AssetInfo>& productsAssetInfo));
};
}
@@ -0,0 +1,296 @@
/*
* 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 <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/utils.h>
#include <AzCore/UnitTest/Mocks/MockFileIOBase.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Asset/AssetUtils.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
namespace //anonymous
{
const char DummyProjectName[] = "DummyProject";
const char GemsFolder[] = "Gems";
const char GemAName[] = "GemA";
const char GemBName[] = "GemB";
const char GemCName[] = "GemC";
constexpr int TotalNumberFiles = 5;
const char* FileNames[TotalNumberFiles] = { "gems.json" , "project.json" , "gem.json", "gem.json" , "gem.json" };
const int FileHandles[TotalNumberFiles] = { 1111, 2222, 3333, 4444, 5555 };
const int GemsIdx = 0;
const int ProjectIdx = 1;
const int GemAGemIdx = 2;
const int GemBGemIdx = 3;
const int GemCGemIdx = 4;
const char GemsFileContent[] = R"({
"GemListFormatVersion": 2,
"Gems" : [
{
"Path": "Gems/GemA",
"Uuid" : "044a63ea67d04479aa5daf62ded9d9cb",
"Version" : "0.1.0",
"_comment" : "GemA"
},
{
"Path": "Gems/GemB",
"Uuid" : "07375b61b1a2424bb03088bbdf28b2c9",
"Version" : "0.1.0",
"_comment" : "GemB"
},
{
"Path": "Gems/GemC",
"Uuid" : "0945e21b7ae848ac80b4ec1f34c459cd",
"Version" : "0.1.0",
"_comment" : "GemC"
}
]
})";
const char ProjectFileContent[] = R"({
"project_name": "DummyProject",
"product_name": "DummyProject",
"executable_name": "DummyProjectLauncher",
"modules" : [],
"project_id": "{91FB81A1-072C-4A80-8FCC-7E2C4C767B4D}",
"xenia_settings" : {
},
"android_settings" : {
"package_name" : "com.lumberyard.yourgame",
"version_number" : 1,
"version_name" : "1.0.0.0",
"orientation" : "landscape"
},
"provo_settings": {
}
})";
const char GemAGemFileContent[] = R"({
"GemFormatVersion": 4,
"Uuid": "044A63EA67D04479AA5DAF62DED9D9CB",
"Name": "GemA",
"DisplayName": "GemA",
"Version": "0.1.0",
"Summary": "Only for unit test purposes.",
"Tags": ["Foo"],
"IconPath": "preview.png",
"EditorModule": true
})";
const char GemBGemFileContent[] = R"({
"GemFormatVersion": 4,
"Uuid": "07375B61B1A2424BB03088BBDF28B2C9",
"Name": "GemB",
"DisplayName": "GemB",
"Version": "0.1.0",
"Summary": "Only for unit test purposes.",
"Tags": ["Foo"],
"IconPath": "preview.png",
"EditorModule": true
})";
const char GemCGemFileContent[] = R"({
"GemFormatVersion": 4,
"Uuid" : "0945E21B7AE848AC80B4EC1F34C459CD",
"Name" : "GemC",
"DisplayName" : "GemC",
"Version" : "0.1.0",
"Summary" : "Only for unit test purposes.",
"Tags" : ["Foo"],
"IconPath" : "preview.png",
"EditorModule" : true
})";
const char* FileContents[TotalNumberFiles] = { GemsFileContent , ProjectFileContent, GemAGemFileContent, GemBGemFileContent, GemCGemFileContent };
}
namespace UnitTest
{
using ::testing::NiceMock;
using ::testing::_;
using ::testing::Return;
class MockFileIO
: public AZ::IO::MockFileIOBase
{
public:
MockFileIO()
{
PopulateData();
SetupMocks();
}
void PopulateData()
{
AZStd::string gemsSettingsFilePath;
AzFramework::StringFunc::Path::Join(DummyProjectName, FileNames[GemsIdx], gemsSettingsFilePath);
m_fileHandleContentMap[AZ::Uuid::CreateName(gemsSettingsFilePath.c_str())] = AZStd::make_pair(FileHandles[GemsIdx], FileContents[GemsIdx]);
AZStd::string projectSettingsFilePath;
AzFramework::StringFunc::Path::Join(DummyProjectName, FileNames[ProjectIdx], projectSettingsFilePath);
m_fileHandleContentMap[AZ::Uuid::CreateName(projectSettingsFilePath.c_str())] = AZStd::make_pair(FileHandles[ProjectIdx], FileContents[ProjectIdx]);
AZStd::string gemAGemFilePath;
AzFramework::StringFunc::Path::Join(GemsFolder, GemAName, gemAGemFilePath);
AzFramework::StringFunc::Path::Join(gemAGemFilePath.c_str(), FileNames[GemAGemIdx], gemAGemFilePath);
m_fileHandleContentMap[AZ::Uuid::CreateName(gemAGemFilePath.c_str())] = AZStd::make_pair(FileHandles[GemAGemIdx], FileContents[GemAGemIdx]);
AZStd::string gemBGemFilePath;
AzFramework::StringFunc::Path::Join(GemsFolder, GemBName, gemBGemFilePath);
AzFramework::StringFunc::Path::Join(gemBGemFilePath.c_str(), FileNames[GemBGemIdx], gemBGemFilePath);
m_fileHandleContentMap[AZ::Uuid::CreateName(gemBGemFilePath.c_str())] = AZStd::make_pair(FileHandles[GemBGemIdx], FileContents[GemBGemIdx]);
AZStd::string gemCGemFilePath;
AzFramework::StringFunc::Path::Join(GemsFolder, GemCName, gemCGemFilePath);
AzFramework::StringFunc::Path::Join(gemCGemFilePath.c_str(), FileNames[GemCGemIdx], gemCGemFilePath);
m_fileHandleContentMap[AZ::Uuid::CreateName(gemCGemFilePath.c_str())] = AZStd::make_pair(FileHandles[GemCGemIdx], FileContents[GemCGemIdx]);
}
void SetupMocks()
{
ON_CALL(*this, Open(_, _, _)).WillByDefault(testing::Invoke(
[&](const char* filePath, AZ::IO::OpenMode mode, AZ::IO::HandleType& fileHandle)
{
auto found = m_fileHandleContentMap.find(AZ::Uuid::CreateName(filePath));
if (found == m_fileHandleContentMap.end())
{
return AZ::IO::Result(AZ::IO::ResultCode::Error);
}
fileHandle = found->second.first;
return AZ::IO::Result(AZ::IO::ResultCode::Success);
}
));
ON_CALL(*this, Read(_, _, _, _, _)).WillByDefault(testing::Invoke(
[&](AZ::IO::HandleType fileHandle, void* buffer, AZ::u64 size, bool failOnFewerThanSizeBytesRead, AZ::u64* bytesRead)
{
for (auto iter = m_fileHandleContentMap.begin(); iter != m_fileHandleContentMap.end(); iter++)
{
if (iter->second.first == fileHandle)
{
memcpy(buffer, iter->second.second.c_str(), iter->second.second.length() + 1);
return AZ::IO::Result(AZ::IO::ResultCode::Success);
}
}
return AZ::IO::Result(AZ::IO::ResultCode::Error);
}
));
ON_CALL(*this, Size(testing::Matcher<AZ::IO::HandleType>(_), testing::Matcher<AZ::u64&>(_))).WillByDefault(testing::Invoke(
[&](AZ::IO::HandleType fileHandle, AZ::u64& size)
{
for (auto iter = m_fileHandleContentMap.begin(); iter != m_fileHandleContentMap.end(); iter++)
{
if (iter->second.first == fileHandle)
{
size = iter->second.second.length();
return AZ::IO::Result(AZ::IO::ResultCode::Success);
}
}
return AZ::IO::Result(AZ::IO::ResultCode::Error);
}
));
ON_CALL(*this, Exists(_)).WillByDefault(testing::Invoke(
[&](const char* filePath)
{
auto found = m_fileHandleContentMap.find(AZ::Uuid::CreateName(filePath));
if (found == m_fileHandleContentMap.end())
{
return false;
}
return true;
}
));
ON_CALL(*this, Close(_))
.WillByDefault(
Return(AZ::IO::Result(AZ::IO::ResultCode::Success)));
}
AZStd::unordered_map<AZ::Uuid, AZStd::pair<AZ::IO::HandleType, AZStd::string>> m_fileHandleContentMap;
};
class AssetUtilitiesGemsTest
: public ::testing::Test
{
public:
void SetUp() override
{
m_data = AZStd::make_unique<StaticData>();
m_data->m_priorFileIO = AZ::IO::FileIOBase::GetInstance();
m_data->m_application.reset(aznew AzToolsFramework::ToolsApplication);
m_data->m_application->Start(AzFramework::Application::Descriptor());
m_data->m_localFileIO = new ::testing::NiceMock<MockFileIO>();
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_data->m_localFileIO);
}
void TearDown() override
{
delete m_data->m_localFileIO;
AZ::IO::FileIOBase::SetInstance(m_data->m_priorFileIO);
m_data->m_application->Stop();
m_data->m_application.reset();
m_data.reset();
}
struct StaticData
{
AZStd::unique_ptr<MockFileIO> m_fileIO;
AZStd::string m_testEngineRoot;
AZStd::unique_ptr<AzToolsFramework::ToolsApplication> m_application = {};
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> m_gemInfoList;
AZ::IO::FileIOBase* m_priorFileIO = nullptr;
AZ::IO::FileIOBase* m_localFileIO = nullptr;
};
AZStd::unique_ptr<StaticData> m_data;
};
TEST_F(AssetUtilitiesGemsTest, GemSystem_RetreiveGemsList_OK)
{
AzToolsFramework::AssetUtils::GetGemsInfo(m_data->m_testEngineRoot.c_str(), m_data->m_testEngineRoot.c_str(), DummyProjectName, m_data->m_gemInfoList);
EXPECT_EQ(m_data->m_gemInfoList.size(), 3);
AZStd::unordered_set<AZStd::string> gemsNameMap{ "GemA", "GemB", "GemC" };
for (const AzToolsFramework::AssetUtils::GemInfo& gemInfo : m_data->m_gemInfoList)
{
gemsNameMap.erase(gemInfo.m_gemName);
}
ASSERT_EQ(gemsNameMap.size(), 0);
}
}
@@ -0,0 +1,237 @@
/*
* 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 "ComponentModeTestDoubles.h"
#include <AzCore/Serialization/SerializeContext.h>
namespace AzToolsFramework
{
namespace ComponentModeFramework
{
AZ_CLASS_ALLOCATOR_IMPL(PlaceHolderComponentMode, AZ::SystemAllocator, 0)
AZ_CLASS_ALLOCATOR_IMPL(AnotherPlaceHolderComponentMode, AZ::SystemAllocator, 0)
AZ_CLASS_ALLOCATOR_IMPL(OverrideMouseInteractionComponentMode, AZ::SystemAllocator, 0)
void PlaceholderEditorComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<PlaceholderEditorComponent>()
->Version(0)
->Field("ComponentMode", &PlaceholderEditorComponent::m_componentModeDelegate);
}
}
void PlaceholderEditorComponent::Activate()
{
EditorComponentBase::Activate();
m_componentModeDelegate.ConnectWithSingleComponentMode<
PlaceholderEditorComponent, PlaceHolderComponentMode>(
AZ::EntityComponentIdPair(GetEntityId(), GetId()), nullptr);
}
void PlaceholderEditorComponent::Deactivate()
{
EditorComponentBase::Deactivate();
m_componentModeDelegate.Disconnect();
}
void AnotherPlaceholderEditorComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AnotherPlaceholderEditorComponent>()
->Version(0)
->Field("ComponentMode", &AnotherPlaceholderEditorComponent::m_componentModeDelegate);
}
}
void AnotherPlaceholderEditorComponent::GetProvidedServices(
AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("InterestingService"));
}
void AnotherPlaceholderEditorComponent::GetIncompatibleServices(
AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("InterestingService"));
}
void AnotherPlaceholderEditorComponent::Activate()
{
EditorComponentBase::Activate();
m_componentModeDelegate.ConnectWithSingleComponentMode<
AnotherPlaceholderEditorComponent, PlaceHolderComponentMode>(
AZ::EntityComponentIdPair(GetEntityId(), GetId()), nullptr);
}
void AnotherPlaceholderEditorComponent::Deactivate()
{
EditorComponentBase::Deactivate();
m_componentModeDelegate.Disconnect();
}
void DependentPlaceholderEditorComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<DependentPlaceholderEditorComponent>()
->Version(0)
->Field("ComponentMode", &DependentPlaceholderEditorComponent::m_componentModeDelegate);
}
}
void DependentPlaceholderEditorComponent::Activate()
{
EditorComponentBase::Activate();
// connect the ComponentMode delegate to this entity/component id pair
m_componentModeDelegate.Connect<DependentPlaceholderEditorComponent>(AZ::EntityComponentIdPair(GetEntityId(), GetId()), nullptr);
// setup the ComponentMode(s) to add for the editing of this Component (in this case Spline and Tube ComponentModes)
m_componentModeDelegate.SetAddComponentModeCallback([this](const AZ::EntityComponentIdPair& entityComponentIdPair)
{
using namespace AzToolsFramework::ComponentModeFramework;
// builder for PlaceHolderComponentMode for DependentPlaceholderEditorComponent
const auto placeholdComponentModeBuilder =
CreateComponentModeBuilder<DependentPlaceholderEditorComponent, PlaceHolderComponentMode>(
entityComponentIdPair);
// must have AnotherPlaceholderEditorComponent when using DependentPlaceholderEditorComponent
const auto componentId = GetEntity()->FindComponent<AnotherPlaceholderEditorComponent>()->GetId();
const auto anotherPlaceholdComponentModeBuilder =
CreateComponentModeBuilder<AnotherPlaceholderEditorComponent, AnotherPlaceHolderComponentMode>(
AZ::EntityComponentIdPair(GetEntityId(), componentId));
// aggregate builders
const auto entityAndComponentModeBuilder =
EntityAndComponentModeBuilders(
GetEntityId(), { placeholdComponentModeBuilder, anotherPlaceholdComponentModeBuilder });
// updates modes to add when entering ComponentMode
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::AddComponentModes, entityAndComponentModeBuilder);
});
}
void DependentPlaceholderEditorComponent::Deactivate()
{
EditorComponentBase::Deactivate();
m_componentModeDelegate.Disconnect();
}
void IncompatiblePlaceholderEditorComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<IncompatiblePlaceholderEditorComponent>()
->Version(0)
->Field("ComponentMode", &IncompatiblePlaceholderEditorComponent::m_componentModeDelegate);
}
}
void IncompatiblePlaceholderEditorComponent::GetProvidedServices(
AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("InterestingService"));
}
void IncompatiblePlaceholderEditorComponent::GetIncompatibleServices(
AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("InterestingService"));
}
void IncompatiblePlaceholderEditorComponent::Activate()
{
EditorComponentBase::Activate();
m_componentModeDelegate.ConnectWithSingleComponentMode<
IncompatiblePlaceholderEditorComponent, AnotherPlaceHolderComponentMode>(
AZ::EntityComponentIdPair(GetEntityId(), GetId()), nullptr);
}
void IncompatiblePlaceholderEditorComponent::Deactivate()
{
EditorComponentBase::Deactivate();
m_componentModeDelegate.Disconnect();
}
ComponentModeActionSignalNotificationChecker::ComponentModeActionSignalNotificationChecker(int busId)
{
ComponentModeActionSignalNotificationBus::Handler::BusConnect(busId);
}
ComponentModeActionSignalNotificationChecker::~ComponentModeActionSignalNotificationChecker()
{
ComponentModeActionSignalNotificationBus::Handler::BusDisconnect();
}
void ComponentModeActionSignalNotificationChecker::OnActionTriggered()
{
m_counter++;
}
PlaceHolderComponentMode::PlaceHolderComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, AZ::Uuid componentType)
: EditorBaseComponentMode(entityComponentIdPair, componentType)
{
ComponentModeActionSignalRequestBus::Handler::BusConnect(entityComponentIdPair);
}
PlaceHolderComponentMode::~PlaceHolderComponentMode()
{
ComponentModeActionSignalRequestBus::Handler::BusDisconnect();
}
AZStd::vector<AzToolsFramework::ActionOverride> PlaceHolderComponentMode::PopulateActionsImpl()
{
const AZ::Crc32 placeHolderComponentModeAction = AZ_CRC_CE("com.amazon.action.placeholder.test");
return AZStd::vector<AzToolsFramework::ActionOverride>
{
// setup an event to notify us when an action fires
AzToolsFramework::ActionOverride()
.SetUri(placeHolderComponentModeAction)
.SetKeySequence(QKeySequence(Qt::Key_Space))
.SetTitle("Test action")
.SetTip("This is a test action")
.SetEntityComponentIdPair(AZ::EntityComponentIdPair(GetEntityId(), GetComponentId()))
.SetCallback([this]()
{
ComponentModeActionSignalNotificationBus::Event(
m_componentModeActionSignalNotificationBusId, &ComponentModeActionSignalNotifications::OnActionTriggered);
})
};
}
void PlaceHolderComponentMode::SetComponentModeActionNotificationBusToNotify(const int busId)
{
m_componentModeActionSignalNotificationBusId = busId;
}
AnotherPlaceHolderComponentMode::AnotherPlaceHolderComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, AZ::Uuid componentType)
: EditorBaseComponentMode(entityComponentIdPair, componentType) {}
OverrideMouseInteractionComponentMode::OverrideMouseInteractionComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, AZ::Uuid componentType)
: EditorBaseComponentMode(entityComponentIdPair, componentType) {}
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,263 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/ComponentMode/ComponentModeDelegate.h>
#include <AzToolsFramework/ComponentMode/EditorBaseComponentMode.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
namespace AzToolsFramework
{
namespace ComponentModeFramework
{
/// @file
/// A collection of placeholder Components to use in Component Mode tests.
/// These Components do nothing useful in and of themselves but help verify Component Mode behavior.
class PlaceholderEditorComponent
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_EDITOR_COMPONENT(
PlaceholderEditorComponent, "{A246ABC8-B5AF-4302-BFE7-F1927EE0203F}",
AzToolsFramework::Components::EditorComponentBase);
static void Reflect(AZ::ReflectContext* context);
// AZ::Component ...
void Activate() override;
void Deactivate() override;
private:
ComponentModeDelegate m_componentModeDelegate; ///< Responsible for detecting ComponentMode activation
///< and creating a concrete ComponentMode.
};
class AnotherPlaceholderEditorComponent
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_EDITOR_COMPONENT(
AnotherPlaceholderEditorComponent, "{3CF10B26-461C-40F8-8E03-2F6BD3E093DA}",
AzToolsFramework::Components::EditorComponentBase);
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
// AZ::Component ...
void Activate() override;
void Deactivate() override;
private:
ComponentModeDelegate m_componentModeDelegate; ///< Responsible for detecting ComponentMode activation
///< and creating a concrete ComponentMode.
};
class DependentPlaceholderEditorComponent
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_EDITOR_COMPONENT(
DependentPlaceholderEditorComponent, "{A5093BD0-5585-4DA5-92B8-408F67B147C0}",
AzToolsFramework::Components::EditorComponentBase);
static void Reflect(AZ::ReflectContext* context);
// AZ::Component ...
void Activate() override;
void Deactivate() override;
private:
ComponentModeDelegate m_componentModeDelegate; ///< Responsible for detecting ComponentMode activation
///< and creating a concrete ComponentMode.
};
class IncompatiblePlaceholderEditorComponent
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_EDITOR_COMPONENT(
IncompatiblePlaceholderEditorComponent, "{284C7965-87C2-41C7-909B-1345061B3DC7}",
AzToolsFramework::Components::EditorComponentBase);
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
// AZ::Component ...
void Activate() override;
void Deactivate() override;
private:
ComponentModeDelegate m_componentModeDelegate; ///< Responsible for detecting ComponentMode activation
///< and creating a concrete ComponentMode.
};
// Simple component for testing that can be supplied a component mode
// type via template argument.
template<typename ComponentModeT>
class TestComponentModeComponent
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_EDITOR_COMPONENT(
TestComponentModeComponent, "{57B53B5D-D51B-4CCB-A875-9CF630282667}",
AzToolsFramework::Components::EditorComponentBase);
static void Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<TestComponentModeComponent>()
->Version(0)
->Field("ComponentMode", &TestComponentModeComponent::m_componentModeDelegate);
}
}
// AZ::Component ...
void Activate() override
{
EditorComponentBase::Activate();
m_componentModeDelegate.ConnectWithSingleComponentMode<
TestComponentModeComponent, ComponentModeT>(
AZ::EntityComponentIdPair(GetEntityId(), GetId()), nullptr);
}
void Deactivate() override
{
EditorComponentBase::Deactivate();
m_componentModeDelegate.Disconnect();
}
private:
ComponentModeDelegate m_componentModeDelegate; ///< Responsible for detecting ComponentMode activation
///< and creating a concrete ComponentMode.
};
/// A simple request bus to let us notify an entity component Id pair what address
/// to listen on for ComponentModeActionSignalNotifications.
class ComponentModeActionSignalRequests
: public AZ::EntityComponentBus
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
virtual void SetComponentModeActionNotificationBusToNotify(int busId) = 0;
};
using ComponentModeActionSignalRequestBus = AZ::EBus<ComponentModeActionSignalRequests>;
/// A simple bus to raise an event when a particular action has occurred.
class ComponentModeActionSignalNotifications
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = int;
virtual void OnActionTriggered() {}
};
using ComponentModeActionSignalNotificationBus = AZ::EBus<ComponentModeActionSignalNotifications>;
/// Implements ComponentModeActionSignalNotificationBus and increments a counter
/// each time OnActionTriggered is called.
class ComponentModeActionSignalNotificationChecker
: private ComponentModeActionSignalNotificationBus::Handler
{
public:
explicit ComponentModeActionSignalNotificationChecker(int busId);
~ComponentModeActionSignalNotificationChecker();
int GetCount() const { return m_counter; }
private:
// ComponentModeActionSignalNotificationBus ...
void OnActionTriggered() override;
int m_counter = 0; ///< Counter to be incremented in OnActionTriggered.
};
class PlaceHolderComponentMode
: public EditorBaseComponentMode
, private ComponentModeActionSignalRequestBus::Handler
{
public:
AZ_CLASS_ALLOCATOR_DECL
PlaceHolderComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, AZ::Uuid componentType);
PlaceHolderComponentMode(const PlaceHolderComponentMode&) = delete;
PlaceHolderComponentMode& operator=(const PlaceHolderComponentMode&) = delete;
PlaceHolderComponentMode(PlaceHolderComponentMode&&) = delete;
PlaceHolderComponentMode& operator=(PlaceHolderComponentMode&&) = delete;
~PlaceHolderComponentMode();
// EditorBaseComponentMode ...
void Refresh() override {}
AZStd::vector<AzToolsFramework::ActionOverride> PopulateActionsImpl() override;
// ComponentModeActionSignalRequestBus ...
void SetComponentModeActionNotificationBusToNotify(int busId) override;
private:
int m_componentModeActionSignalNotificationBusId = 0; ///< This is the busId to send the action notification to.
};
class AnotherPlaceHolderComponentMode
: public EditorBaseComponentMode
{
public:
AZ_CLASS_ALLOCATOR_DECL
AnotherPlaceHolderComponentMode(
const AZ::EntityComponentIdPair& entityComponentIdPair, AZ::Uuid componentType);
AnotherPlaceHolderComponentMode(const AnotherPlaceHolderComponentMode&) = delete;
AnotherPlaceHolderComponentMode& operator=(const AnotherPlaceHolderComponentMode&) = delete;
AnotherPlaceHolderComponentMode(AnotherPlaceHolderComponentMode&&) = delete;
AnotherPlaceHolderComponentMode& operator=(AnotherPlaceHolderComponentMode&&) = delete;
~AnotherPlaceHolderComponentMode() = default;
// EditorBaseComponentMode ...
void Refresh() override {}
};
// ComponentMode which overrides mouse events
class OverrideMouseInteractionComponentMode
: public EditorBaseComponentMode
, public AzToolsFramework::ViewportInteraction::ViewportSelectionRequests
{
public:
AZ_CLASS_ALLOCATOR_DECL
OverrideMouseInteractionComponentMode(const AZ::EntityComponentIdPair& entityComponentIdPair, AZ::Uuid componentType);
// EditorBaseComponentMode ...
void Refresh() override {}
private:
/// AzToolsFramework::ViewportInteraction::ViewportSelectionRequests ...
bool HandleMouseInteraction(const AzToolsFramework::ViewportInteraction::MouseInteractionEvent& mouseInteraction) override
{
// Pretend like we are handling some mouse interaction
AZ_UNUSED(mouseInteraction);
return true;
}
};
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -0,0 +1,35 @@
/*
* 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 "ComponentModeTestDoubles.h"
#include "ComponentModeTestFixture.h"
#include <AzCore/UserSettings/UserSettingsComponent.h>
namespace UnitTest
{
void ComponentModeTestFixture::SetUpEditorFixtureImpl()
{
using namespace AzToolsFramework;
using namespace AzToolsFramework::ComponentModeFramework;
auto* app = GetApplication();
ASSERT_TRUE(app);
app->RegisterComponentDescriptor(PlaceholderEditorComponent::CreateDescriptor());
app->RegisterComponentDescriptor(AnotherPlaceholderEditorComponent::CreateDescriptor());
app->RegisterComponentDescriptor(DependentPlaceholderEditorComponent::CreateDescriptor());
app->RegisterComponentDescriptor(
TestComponentModeComponent<OverrideMouseInteractionComponentMode>::CreateDescriptor());
app->RegisterComponentDescriptor(IncompatiblePlaceholderEditorComponent::CreateDescriptor());
}
} // namespace UnitTest
@@ -0,0 +1,27 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
class ComponentModeTestFixture
: public ToolsApplicationFixture
{
protected:
void SetUpEditorFixtureImpl() override;
};
} // namespace UnitTest
@@ -0,0 +1,599 @@
/*
* 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 "ComponentModeTestDoubles.h"
#include "ComponentModeTestFixture.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <AzToolsFramework/ComponentMode/ComponentModeCollection.h>
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorPendingCompositionBus.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/Viewport/ActionBus.h>
#include <AzToolsFramework/ViewportSelection/EditorDefaultSelection.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
#include <AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <QApplication>
namespace UnitTest
{
using namespace AzToolsFramework;
using namespace AzToolsFramework::ComponentModeFramework;
TEST_F(ComponentModeTestFixture, BeginEndComponentMode)
{
using ::testing::Eq;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
QWidget rootWidget;
ActionOverrideRequestBus::Event(
GetEntityContextId(), &ActionOverrideRequests::SetupActionOverrideHandler, &rootWidget);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::BeginComponentMode,
AZStd::vector<EntityAndComponentModeBuilders>{});
bool inComponentMode = false;
ComponentModeSystemRequestBus::BroadcastResult(
inComponentMode, &ComponentModeSystemRequests::InComponentMode);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
EXPECT_TRUE(inComponentMode);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::EndComponentMode);
ComponentModeSystemRequestBus::BroadcastResult(
inComponentMode, &ComponentModeSystemRequests::InComponentMode);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
EXPECT_FALSE(inComponentMode);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
ActionOverrideRequestBus::Event(
GetEntityContextId(), &ActionOverrideRequests::TeardownActionOverrideHandler);
}
TEST_F(ComponentModeTestFixture, TwoComponentsOnSingleEntityWithSameComponentModeBothBegin)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
// setup default editor interaction model
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity("ComponentModeEntity", &entity);
entity->Deactivate();
// add two placeholder Components (each with their own Component Mode)
const AZ::Component* placeholder1 = entity->CreateComponent<PlaceholderEditorComponent>();
const AZ::Component* placeholder2 = entity->CreateComponent<PlaceholderEditorComponent>();
entity->Activate();
// mimic selecting the entity in the viewport (after selection the ComponentModeDelegate
// connects to the ComponentModeDelegateRequestBus on the entity/component pair address)
const AzToolsFramework::EntityIdList entityIds = { entityId };
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, entityIds);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// move all selected components into ComponentMode
// (mimic pressing the 'Edit' button to begin Component Mode)
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::AddSelectedComponentModesOfType,
AZ::AzTypeInfo<PlaceholderEditorComponent>::Uuid());
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
bool firstComponentModeInstantiated = false;
ComponentModeSystemRequestBus::BroadcastResult(
firstComponentModeInstantiated, &ComponentModeSystemRequests::ComponentModeInstantiated,
AZ::EntityComponentIdPair(entityId, placeholder1->GetId()));
bool secondComponentModeInstantiated = false;
ComponentModeSystemRequestBus::BroadcastResult(
secondComponentModeInstantiated, &ComponentModeSystemRequests::ComponentModeInstantiated,
AZ::EntityComponentIdPair(entityId, placeholder2->GetId()));
EXPECT_TRUE(firstComponentModeInstantiated && secondComponentModeInstantiated);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(ComponentModeTestFixture, OneComponentModeBeginsWithTwoComponentsOnSingleEntityEachWithDifferentComponentModes)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
// setup default editor interaction model
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity("ComponentModeEntity", &entity);
entity->Deactivate();
// add two placeholder Components (each with their own Component Mode)
const AZ::Component* placeholder1 = entity->CreateComponent<PlaceholderEditorComponent>();
const AZ::Component* placeholder2 = entity->CreateComponent<AnotherPlaceholderEditorComponent>();
entity->Activate();
// mimic selecting the entity in the viewport (after selection the ComponentModeDelegate
// connects to the ComponentModeDelegateRequestBus on the entity/component pair address)
const AzToolsFramework::EntityIdList entityIds = { entityId };
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, entityIds);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// move all selected components into ComponentMode
// (mimic pressing the 'Edit' button to begin Component Mode)
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::AddSelectedComponentModesOfType,
AZ::AzTypeInfo<PlaceholderEditorComponent>::Uuid());
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
bool firstComponentModeInstantiated = false;
ComponentModeSystemRequestBus::BroadcastResult(
firstComponentModeInstantiated, &ComponentModeSystemRequests::ComponentModeInstantiated,
AZ::EntityComponentIdPair(entityId, placeholder1->GetId()));
bool secondComponentModeInstantiated = true;
ComponentModeSystemRequestBus::BroadcastResult(
secondComponentModeInstantiated, &ComponentModeSystemRequests::ComponentModeInstantiated,
AZ::EntityComponentIdPair(entityId, placeholder2->GetId()));
EXPECT_TRUE(firstComponentModeInstantiated && !secondComponentModeInstantiated);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(ComponentModeTestFixture, TwoComponentsOnSingleEntityWithSameComponentModeDoNotCycle)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
// setup default editor interaction model
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity("ComponentModeEntity", &entity);
entity->Deactivate();
// add two placeholder Components (each with their own Component Mode)
const AZ::Component* placeholder1 = entity->CreateComponent<PlaceholderEditorComponent>();
AZ_UNUSED(placeholder1);
const AZ::Component* placeholder2 = entity->CreateComponent<PlaceholderEditorComponent>();
AZ_UNUSED(placeholder2);
entity->Activate();
// mimic selecting the entity in the viewport (after selection the ComponentModeDelegate
// connects to the ComponentModeDelegateRequestBus on the entity/component pair address)
const AzToolsFramework::EntityIdList entityIds = { entityId };
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, entityIds);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// move all selected components into ComponentMode
// (mimic pressing the 'Edit' button to begin Component Mode)
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::AddSelectedComponentModesOfType,
AZ::AzTypeInfo<PlaceholderEditorComponent>::Uuid());
bool nextModeCycled = true;
ComponentModeSystemRequestBus::BroadcastResult(
nextModeCycled, &ComponentModeSystemRequests::SelectNextActiveComponentMode);
bool previousModeCycled = true;
ComponentModeSystemRequestBus::BroadcastResult(
previousModeCycled, &ComponentModeSystemRequests::SelectPreviousActiveComponentMode);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
EXPECT_TRUE(!nextModeCycled && !previousModeCycled);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(ComponentModeTestFixture, TwoComponentsOnSingleEntityWithSameComponentModeHasOnlyOneType)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
// setup default editor interaction model
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity("ComponentModeEntity", &entity);
entity->Deactivate();
// add two placeholder Components (each with their own Component Mode)
const AZ::Component* placeholder1 = entity->CreateComponent<PlaceholderEditorComponent>();
AZ_UNUSED(placeholder1);
const AZ::Component* placeholder2 = entity->CreateComponent<PlaceholderEditorComponent>();
AZ_UNUSED(placeholder2);
entity->Activate();
// mimic selecting the entity in the viewport (after selection the ComponentModeDelegate
// connects to the ComponentModeDelegateRequestBus on the entity/component pair address)
const AzToolsFramework::EntityIdList entityIds = { entityId };
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, entityIds);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// move all selected components into ComponentMode
// (mimic pressing the 'Edit' button to begin Component Mode)
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::AddSelectedComponentModesOfType,
AZ::AzTypeInfo<PlaceholderEditorComponent>::Uuid());
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
bool multipleComponentModeTypes = true;
ComponentModeSystemRequestBus::BroadcastResult(
multipleComponentModeTypes, &ComponentModeSystemRequests::HasMultipleComponentTypes);
EXPECT_FALSE(multipleComponentModeTypes);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(ComponentModeTestFixture, TwoComponentsOnSingleEntityWithDifferentComponentModeHasOnlyOneType)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
// setup default editor interaction model
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity("ComponentModeEntity", &entity);
entity->Deactivate();
// add two placeholder Components (each with their own Component Mode)
const AZ::Component* placeholder1 = entity->CreateComponent<PlaceholderEditorComponent>();
AZ_UNUSED(placeholder1);
const AZ::Component* placeholder2 = entity->CreateComponent<AnotherPlaceholderEditorComponent>();
AZ_UNUSED(placeholder2);
entity->Activate();
// mimic selecting the entity in the viewport (after selection the ComponentModeDelegate
// connects to the ComponentModeDelegateRequestBus on the entity/component pair address)
const AzToolsFramework::EntityIdList entityIds = { entityId };
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, entityIds);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// move all selected components into ComponentMode
// (mimic pressing the 'Edit' button to begin Component Mode)
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::AddSelectedComponentModesOfType,
AZ::AzTypeInfo<AnotherPlaceholderEditorComponent>::Uuid());
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
bool multipleComponentModeTypes = true;
ComponentModeSystemRequestBus::BroadcastResult(
multipleComponentModeTypes, &ComponentModeSystemRequests::HasMultipleComponentTypes);
EXPECT_FALSE(multipleComponentModeTypes);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(ComponentModeTestFixture, TwoComponentsOnSingleEntityWithDependentComponentModesHasTwoTypes)
{
using testing::Eq;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
// setup default editor interaction model
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity("ComponentModeEntity", &entity);
entity->Deactivate();
// add two placeholder Components (each with their own Component Mode)
const AZ::Component* placeholder1 = entity->CreateComponent<AnotherPlaceholderEditorComponent>();
AZ_UNUSED(placeholder1);
// DependentPlaceholderEditorComponent has a Component Mode dependent on AnotherPlaceholderEditorComponent
const AZ::Component* placeholder2 = entity->CreateComponent<DependentPlaceholderEditorComponent>();
AZ_UNUSED(placeholder2);
entity->Activate();
// mimic selecting the entity in the viewport (after selection the ComponentModeDelegate
// connects to the ComponentModeDelegateRequestBus on the entity/component pair address)
const AzToolsFramework::EntityIdList entityIds = { entityId };
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, entityIds);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// move all selected components into ComponentMode
// (mimic pressing the 'Edit' button to begin Component Mode)
ComponentModeSystemRequestBus::Broadcast(
&ComponentModeSystemRequests::AddSelectedComponentModesOfType,
AZ::AzTypeInfo<DependentPlaceholderEditorComponent>::Uuid());
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
bool multipleComponentModeTypes = false;
ComponentModeSystemRequestBus::BroadcastResult(
multipleComponentModeTypes, &ComponentModeSystemRequests::HasMultipleComponentTypes);
bool secondComponentModeInstantiated = false;
ComponentModeSystemRequestBus::BroadcastResult(
secondComponentModeInstantiated, &ComponentModeSystemRequests::ComponentModeInstantiated,
AZ::EntityComponentIdPair(entityId, placeholder2->GetId()));
AZ::Uuid activeComponentType = AZ::Uuid::CreateNull();
ComponentModeSystemRequestBus::BroadcastResult(
activeComponentType, &ComponentModeSystemRequests::ActiveComponentMode);
EXPECT_TRUE(multipleComponentModeTypes);
EXPECT_TRUE(secondComponentModeInstantiated);
EXPECT_THAT(activeComponentType, Eq(AZ::AzTypeInfo<DependentPlaceholderEditorComponent>::Uuid()));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(ComponentModeTestFixture, TwoComponentsOnSingleEntityWithSameComponentModeBothTriggerSameAction)
{
using testing::Eq;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity("ComponentModeEntity", &entity);
entity->Deactivate();
// add two placeholder Components (each with their own Component Mode)
const AZ::Component* placeholder1 = entity->CreateComponent<PlaceholderEditorComponent>();
const AZ::Component* placeholder2 = entity->CreateComponent<PlaceholderEditorComponent>();
entity->Activate();
// mimic selecting the entity in the viewport (after selection the ComponentModeDelegate
// connects to the ComponentModeDelegateRequestBus on the entity/component pair address)
AzToolsFramework::SelectEntity(entityId);
// move all selected components into ComponentMode
// (mimic pressing the 'Edit' button to begin Component Mode)
EnterComponentMode<PlaceholderEditorComponent>();
// Component Modes are now instantiated
// create a simple signal checker type which implements the ComponentModeActionSignalNotificationBus
const int checkerBusId = 1234;
ComponentModeActionSignalNotificationChecker checker(checkerBusId);
// when a shortcut action happens, we want to send a message to the checker bus
// internally PlaceHolderComponentMode sets up an action to send an event to
// ComponentModeActionSignalNotifications::OnActionTriggered - we make sure each
// Component Mode is will sent the notification to the correct address.
ComponentModeActionSignalRequestBus::Event(
AZ::EntityComponentIdPair(entityId, placeholder1->GetId()),
&ComponentModeActionSignalRequests::SetComponentModeActionNotificationBusToNotify,
checkerBusId);
ComponentModeActionSignalRequestBus::Event(
AZ::EntityComponentIdPair(entityId, placeholder2->GetId()),
&ComponentModeActionSignalRequests::SetComponentModeActionNotificationBusToNotify,
checkerBusId);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// trigger the shortcut for this Component Mode
QTest::keyPress(&m_editorActions.m_componentModeWidget, Qt::Key_Space);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
// ensure the checker count is what we expect (both Component Modes will notify the
// ComponentModeActionSignalNotificationChecker connected at the address specified)
EXPECT_THAT(checker.GetCount(), Eq(2));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(ComponentModeTestFixture, ShouldIgnoreMouseEventWhenOverridenByComponentMode)
{
using OverrideMouseInteractionComponent = TestComponentModeComponent<OverrideMouseInteractionComponentMode>;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
// Setup default editor interaction model.
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity("ComponentModeEntity", &entity);
entity->Deactivate();
// Add placeholder component which implements component mode.
const AZ::Component* placeholder1 = entity->CreateComponent<OverrideMouseInteractionComponent>();
entity->Activate();
// Mimic selecting the entity in the viewport (after selection the ComponentModeDelegate
// connects to the ComponentModeDelegateRequestBus on the entity/component pair address)
AzToolsFramework::SelectEntity(entityId);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// Move all selected components into ComponentMode
// (mimic pressing the 'Edit' button to begin Component Mode)
EnterComponentMode<OverrideMouseInteractionComponent>();
ViewportInteraction::MouseInteractionEvent interactionEvent;
interactionEvent.m_mouseEvent = ViewportInteraction::MouseEvent::Move;
// Simulate a mouse event
using MouseInteractionResult = AzToolsFramework::ViewportInteraction::MouseInteractionResult;
MouseInteractionResult handled = MouseInteractionResult::None;
EditorInteractionSystemViewportSelectionRequestBus::BroadcastResult(
handled, &EditorInteractionSystemViewportSelectionRequestBus::Events::InternalHandleAllMouseInteractions,
interactionEvent);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
// Check it was handled by the component mode.
EXPECT_EQ(handled, MouseInteractionResult::Viewport);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
// Test version of EntityPropertyEditor to detect/ensure certain functions were called
class TestEntityPropertyEditor
: public AzToolsFramework::EntityPropertyEditor
{
public:
void InvalidatePropertyDisplay(PropertyModificationRefreshLevel level) override;
bool m_invalidatePropertyDisplayCalled = false;
};
void TestEntityPropertyEditor::InvalidatePropertyDisplay([[maybe_unused]] PropertyModificationRefreshLevel level)
{
m_invalidatePropertyDisplayCalled = true;
}
// Simple fixture to encapsulate a TestEntityPropertyEditor
class ComponentModePinnedSelectionFixture
: public ToolsApplicationFixture
{
public:
void SetUpEditorFixtureImpl() override
{
m_testEntityPropertyEditor = AZStd::make_unique<TestEntityPropertyEditor>();
}
void TearDownEditorFixtureImpl() override
{
m_testEntityPropertyEditor.reset();
}
AZStd::unique_ptr<TestEntityPropertyEditor> m_testEntityPropertyEditor;
};
TEST_F(ComponentModePinnedSelectionFixture, CannotEnterComponentModeWhenEntityIsPinnedButNotSelected)
{
using PlaceHolderComponent = TestComponentModeComponent<PlaceHolderComponentMode>;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity("ComponentModeEntity", &entity);
entity->Deactivate();
// Add placeholder component which implements component mode.
entity->CreateComponent<PlaceHolderComponent>();
entity->Activate();
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// select entity
const auto selectedEntities = AzToolsFramework::EntityIdList { entityId };
SelectEntities(selectedEntities);
// pin entity
AzToolsFramework::EntityIdSet selectedSet(selectedEntities.begin(), selectedEntities.end());
m_testEntityPropertyEditor->SetOverrideEntityIds(selectedSet);
// deselect entity
SelectEntities(AzToolsFramework::EntityIdList{});
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
EXPECT_TRUE(m_testEntityPropertyEditor->IsLockedToSpecificEntities());
EXPECT_TRUE(m_testEntityPropertyEditor->m_invalidatePropertyDisplayCalled);
bool couldBeginComponentMode =
AzToolsFramework::ComponentModeFramework::CouldBeginComponentModeWithEntity(entityId);
EXPECT_FALSE(couldBeginComponentMode);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(ComponentModeTestFixture, CannotEnterComponentModeWhenThereArePendingComponents)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity("ComponentModeEntity", &entity);
entity->Deactivate();
AzToolsFramework::EntityCompositionRequestBus::Broadcast(
&AzToolsFramework::EntityCompositionRequestBus::Events::AddComponentsToEntities,
AzToolsFramework::EntityIdList{entityId},
AZ::ComponentTypeList{ AZ::AzTypeInfo<AnotherPlaceholderEditorComponent>::Uuid() });
AzToolsFramework::EntityCompositionRequestBus::Broadcast(
&AzToolsFramework::EntityCompositionRequestBus::Events::AddComponentsToEntities,
AzToolsFramework::EntityIdList{entityId},
AZ::ComponentTypeList{AZ::AzTypeInfo<IncompatiblePlaceholderEditorComponent>::Uuid()});
entity->Activate();
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
SelectEntities(AzToolsFramework::EntityIdList{entityId});
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
AZ::Entity::ComponentArrayType pendingComponents;
AzToolsFramework::EditorPendingCompositionRequestBus::Event(
entityId, &AzToolsFramework::EditorPendingCompositionRequestBus::Events::GetPendingComponents,
pendingComponents);
// ensure we do have pending components
EXPECT_EQ(pendingComponents.size(), 1);
// cannot enter Component Mode with pending components
EXPECT_FALSE(AzToolsFramework::ComponentModeFramework::CouldBeginComponentModeWithEntity(entityId));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
} // namespace UnitTest
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,260 @@
/*
* 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 <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Manipulators/EditorVertexSelection.h>
#include <AzToolsFramework/Manipulators/HoverSelection.h>
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
#include <AzToolsFramework/Manipulators/TranslationManipulators.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFramework.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h>
#include <AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h>
#include <AzManipulatorTestFramework/ImmediateModeActionDispatcher.h>
using namespace AzToolsFramework;
namespace UnitTest
{
// test implementation of variable/fixed vertex request buses
// (to be used in place of spline/polygon prism etc)
class TestVariableVerticesVertexContainer
: public AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler
, public AZ::VariableVerticesRequestBus<AZ::Vector3>::Handler
{
public:
void Connect(AZ::EntityId entityId);
void Disconnect();
// FixedVerticesRequestBus/VariableVerticesRequestBus ...
bool GetVertex(size_t index, AZ::Vector3& vertex) const override { return m_vertexContainer.GetVertex(index, vertex); }
bool UpdateVertex(size_t index, const AZ::Vector3& vertex) override { return m_vertexContainer.UpdateVertex(index, vertex); };
void AddVertex(const AZ::Vector3& vertex) override { m_vertexContainer.AddVertex(vertex); }
bool InsertVertex(size_t index, const AZ::Vector3& vertex) override { return m_vertexContainer.InsertVertex(index, vertex); }
bool RemoveVertex(size_t index) override { return m_vertexContainer.RemoveVertex(index); }
void SetVertices(const AZStd::vector<AZ::Vector3>& vertices) override { m_vertexContainer.SetVertices(vertices); };
void ClearVertices() override { m_vertexContainer.Clear(); }
size_t Size() const override { return m_vertexContainer.Size(); }
bool Empty() const override { return m_vertexContainer.Empty(); }
private:
AZ::VertexContainer<AZ::Vector3> m_vertexContainer;
};
void TestVariableVerticesVertexContainer::Connect(const AZ::EntityId entityId)
{
AZ::VariableVerticesRequestBus<AZ::Vector3>::Handler::BusConnect(entityId);
AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::BusConnect(entityId);
}
void TestVariableVerticesVertexContainer::Disconnect()
{
AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::BusDisconnect();
AZ::VariableVerticesRequestBus<AZ::Vector3>::Handler::BusDisconnect();
}
class TestEditorVertexSelectionVariable
: public EditorVertexSelectionVariable<AZ::Vector3>
{
public:
AZ_CLASS_ALLOCATOR(TestEditorVertexSelectionVariable, AZ::SystemAllocator, 0)
void ShowVertexDeletionWarning() override { /*noop*/ }
};
class EditorVertexSelectionFixture
: public ToolsApplicationFixture
{
public:
void SetUpEditorFixtureImpl() override
{
m_entityId = CreateDefaultEditorEntity("Default");
m_vertexContainer.Connect(m_entityId);
RecreateVertexSelection();
}
void TearDownEditorFixtureImpl() override
{
m_vertexContainer.Disconnect();
m_vertexSelection.Destroy();
}
void RecreateVertexSelection();
void PopulateVertices();
void ClearVertices();
static const AZ::u32 VertexCount = 4;
AZ::EntityId m_entityId;
TestEditorVertexSelectionVariable m_vertexSelection;
TestVariableVerticesVertexContainer m_vertexContainer;
};
const AZ::u32 EditorVertexSelectionFixture::VertexCount;
void EditorVertexSelectionFixture::RecreateVertexSelection()
{
m_vertexSelection.Create(
AZ::EntityComponentIdPair(m_entityId, AZ::InvalidComponentId),
g_mainManipulatorManagerId, AZStd::make_unique<NullHoverSelection>(),
TranslationManipulators::Dimensions::Three, ConfigureTranslationManipulatorAppearance3d);
}
void EditorVertexSelectionFixture::PopulateVertices()
{
for (size_t vertIndex = 0; vertIndex < EditorVertexSelectionFixture::VertexCount; ++vertIndex)
{
InsertVertexAfter(
AZ::EntityComponentIdPair(m_entityId, AZ::InvalidComponentId), 0, AZ::Vector3::CreateZero());
}
}
void EditorVertexSelectionFixture::ClearVertices()
{
for (size_t vertIndex = 0; vertIndex < EditorVertexSelectionFixture::VertexCount; ++vertIndex)
{
SafeRemoveVertex<AZ::Vector3>(
AZ::EntityComponentIdPair(m_entityId, AZ::InvalidComponentId), 0);
}
}
TEST_F(EditorVertexSelectionFixture, PropertyEditorEntityChangeAfterVertexAdded)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
// connect before insert vertex
EditorEntityComponentChangeDetector editorEntityComponentChangeDetector(m_entityId);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
PopulateVertices();
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
EXPECT_TRUE(editorEntityComponentChangeDetector.ChangeDetected());
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(EditorVertexSelectionFixture, PropertyEditorEntityChangeAfterVertexRemoved)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
PopulateVertices();
// connect after insert vertex
EditorEntityComponentChangeDetector editorEntityComponentChangeDetector(m_entityId);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
ClearVertices();
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
EXPECT_TRUE(editorEntityComponentChangeDetector.ChangeDetected());
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(EditorVertexSelectionFixture, PropertyEditorEntityChangeAfterTerrainSnap)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
PopulateVertices();
// connect after insert vertex
EditorEntityComponentChangeDetector editorEntityComponentChangeDetector(m_entityId);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// just provide a placeholder mouse interaction event in this case
m_vertexSelection.SnapVerticesToTerrain(ViewportInteraction::MouseInteractionEvent{});
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
EXPECT_TRUE(editorEntityComponentChangeDetector.ChangeDetected());
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
using EditorVertexSelectionManipulatorFixture =
IndirectCallManipulatorViewportInteractionFixtureMixin<EditorVertexSelectionFixture>;
TEST_F(EditorVertexSelectionManipulatorFixture, CannotDeleteAllVertices)
{
using ::testing::Eq;
const auto entityComponentIdPair = AZ::EntityComponentIdPair(m_entityId, AZ::InvalidComponentId);
const float horizontalPositions[] = {-1.5f, -0.5f, 0.5f, 1.5f};
for (size_t vertIndex = 0; vertIndex < std::size(horizontalPositions); ++vertIndex)
{
InsertVertexAfter(
entityComponentIdPair, vertIndex, AZ::Vector3(horizontalPositions[vertIndex], 5.0f, 0.0f));
}
// rebuild the vertex selection after adding the new verts
RecreateVertexSelection();
// build a vector of the vertex positions in screen space
AZStd::vector<AzFramework::ScreenPoint> vertexScreenPositions;
for (size_t vertIndex = 0; vertIndex < std::size(horizontalPositions); ++vertIndex)
{
AZ::Vector3 localVertex;
bool found = false;
AZ::FixedVerticesRequestBus<AZ::Vector3>::EventResult(
found, m_entityId, &AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::GetVertex,
vertIndex, localVertex);
if (found)
{
// note: entity position is at the origin so localVertex position is equivalent to world
vertexScreenPositions.push_back(AzFramework::WorldToScreen(localVertex, m_cameraState));
}
}
// select each vertex (by holding ctrl)
m_actionDispatcher->CameraState(m_cameraState)
->MousePosition(vertexScreenPositions[0])
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->MouseLButtonDown()
->MouseLButtonUp()
->MousePosition(vertexScreenPositions[1])
->MouseLButtonDown()
->MouseLButtonUp()
->MousePosition(vertexScreenPositions[2])
->MouseLButtonDown()
->MouseLButtonUp()
->MousePosition(vertexScreenPositions[3])
->MouseLButtonDown()
->MouseLButtonUp();
// and then attempt to delete them
m_vertexSelection.DestroySelected();
size_t vertexCountAfter = 0;
AZ::VariableVerticesRequestBus<AZ::Vector3>::EventResult(
vertexCountAfter, m_entityId, &AZ::VariableVerticesRequestBus<AZ::Vector3>::Events::Size);
// deleting all vertices is disallowed - size should remain the same
EXPECT_THAT(vertexCountAfter, Eq(EditorVertexSelectionFixture::VertexCount));
}
} // namespace UnitTest
@@ -0,0 +1,116 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
namespace AzToolsFramework
{
class EditorEntityContextComponentTests
: public ::testing::Test
{
protected:
void SetUp() override
{
m_app.Start(m_descriptor);
// 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);
}
void TearDown() override
{
m_app.Stop();
}
AzToolsFramework::ToolsApplication m_app;
AZ::ComponentApplication::Descriptor m_descriptor;
};
TEST_F(EditorEntityContextComponentTests, EditorEntityContextTests_CreateEditorEntity_CreatesValidEntity)
{
AZStd::string entityName("TestName");
AZ::EntityId createdEntityId;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
createdEntityId,
&AzToolsFramework::EditorEntityContextRequests::CreateNewEditorEntity,
entityName.c_str());
EXPECT_TRUE(createdEntityId.IsValid());
AZ::Entity* createdEntity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(createdEntity, &AZ::ComponentApplicationRequests::FindEntity, createdEntityId);
EXPECT_NE(createdEntity, nullptr);
EXPECT_EQ(entityName.compare(createdEntity->GetName()), 0);
EXPECT_EQ(createdEntity->GetId(), createdEntityId);
}
TEST_F(EditorEntityContextComponentTests, EditorEntityContextTests_CreateEditorEntityWithValidId_CreatesValidEntity)
{
AZ::EntityId validId(AZ::Entity::MakeId());
EXPECT_TRUE(validId.IsValid());
AZStd::string entityName("TestName");
AZ::EntityId createdEntityId;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
createdEntityId,
&AzToolsFramework::EditorEntityContextRequestBus::Events::CreateNewEditorEntityWithId,
entityName.c_str(),
validId);
EXPECT_TRUE(createdEntityId.IsValid());
EXPECT_EQ(createdEntityId, validId);
AZ::Entity* createdEntity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(createdEntity, &AZ::ComponentApplicationRequests::FindEntity, createdEntityId);
EXPECT_NE(createdEntity, nullptr);
EXPECT_EQ(entityName.compare(createdEntity->GetName()), 0);
EXPECT_EQ(createdEntity->GetId(), validId);
}
TEST_F(EditorEntityContextComponentTests, EditorEntityContextTests_CreateEditorEntityWithInvalidId_NoEntityCreated)
{
AZ::EntityId invalidId;
EXPECT_FALSE(invalidId.IsValid());
AZStd::string entityName("TestName");
AZ::EntityId createdEntityId;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
createdEntityId,
&AzToolsFramework::EditorEntityContextRequestBus::Events::CreateNewEditorEntityWithId,
entityName.c_str(),
invalidId);
EXPECT_FALSE(createdEntityId.IsValid());
AZ::Entity* createdEntity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(createdEntity, &AZ::ComponentApplicationRequests::FindEntity, createdEntityId);
EXPECT_EQ(createdEntity, nullptr);
}
TEST_F(EditorEntityContextComponentTests, EditorEntityContextTests_CreateEditorEntityWithInUseId_NoEntityCreated)
{
// Create an entity so we can grab an in-use entity ID.
AZStd::string entityName("TestName");
AZ::EntityId createdEntityId;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
createdEntityId,
&AzToolsFramework::EditorEntityContextRequestBus::Events::CreateNewEditorEntity,
entityName.c_str());
EXPECT_TRUE(createdEntityId.IsValid());
// Attempt to create another entity with the same ID, and verify this call fails.
AZ::EntityId secondEntityId;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
secondEntityId,
&AzToolsFramework::EditorEntityContextRequestBus::Events::CreateNewEditorEntityWithId,
entityName.c_str(),
createdEntityId);
EXPECT_FALSE(secondEntityId.IsValid());
}
}
@@ -0,0 +1,957 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Component/TransformBus.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Component/EditorComponentAPIBus.h>
#include <AzToolsFramework/Entity/EditorEntitySearchComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityComponent.h>
namespace AzToolsFramework
{
// Test components used to test component filters
class EntitySearch_TestComponent1
: public AZ::Component
{
public:
AZ_COMPONENT(EntitySearch_TestComponent1, "{D8ABC8F6-E43B-4ED9-AABE-BA8905D4099D}", AZ::Component);
static void Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EntitySearch_TestComponent1, AZ::Component>()
->Version(1)
->Field("Bool Value", &EntitySearch_TestComponent1::m_boolValue)
->Field("Int Value", &EntitySearch_TestComponent1::m_intValue)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<EntitySearch_TestComponent1>("SearchTestComponent1", "Component 1 for Entity Search Unit Tests")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AddableByUser, true)
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"))
->Attribute(AZ::Edit::Attributes::Category, "Entity Search Test Components")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/Tag.png")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/Tag.png")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://www.amazongames.com/")
->DataElement(AZ::Edit::UIHandlers::Default, &EntitySearch_TestComponent1::m_boolValue, "Bool", "")
->DataElement(AZ::Edit::UIHandlers::Default, &EntitySearch_TestComponent1::m_intValue, "Int", "")
;
}
}
}
static constexpr bool DefaultBoolValue = true;
EntitySearch_TestComponent1() = default;
EntitySearch_TestComponent1(int intValue, bool boolValue)
: m_boolValue(boolValue)
, m_intValue(intValue)
{
}
virtual ~EntitySearch_TestComponent1() override
{}
private:
void Init() override
{}
void Activate() override
{}
void Deactivate() override
{}
int m_intValue = 0;
bool m_boolValue = DefaultBoolValue;
};
class EntitySearch_TestComponent2
: public AZ::Component
{
public:
AZ_COMPONENT(EntitySearch_TestComponent2, "{E50A848D-64C3-4445-A21B-D8F9C96972FE}", AZ::Component);
static void Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EntitySearch_TestComponent2, AZ::Component>()
->Version(1)
->Field("Float Value", &EntitySearch_TestComponent2::m_floatValue)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<EntitySearch_TestComponent2>("SearchTestComponent2", "Component 2 for Entity Search Unit Tests")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AddableByUser, true)
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"))
->Attribute(AZ::Edit::Attributes::Category, "Entity Search Test Components")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/Tag.png")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/Tag.png")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://www.amazongames.com/")
->DataElement(AZ::Edit::UIHandlers::Default, &EntitySearch_TestComponent2::m_floatValue, "Float", "")
;
}
}
}
const static float DefaultFloatValue;
EntitySearch_TestComponent2() = default;
EntitySearch_TestComponent2(float floatValue)
: m_floatValue(floatValue)
{
}
virtual ~EntitySearch_TestComponent2() override
{}
private:
void Init() override
{}
void Activate() override
{}
void Deactivate() override
{}
float m_floatValue = DefaultFloatValue;
};
const float EntitySearch_TestComponent2::DefaultFloatValue = 5.0f;
class EditorEntitySearchComponentTests
: public ::testing::Test
{
protected:
void SetUp() override
{
m_app.Start(m_descriptor);
// 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);
RegisterComponents();
GenerateTestHierarchy();
}
void RegisterComponents()
{
// Register our test components (This process also reflects them to the appropriate contexts)
auto* EntitySearch_TestComponent1Descriptor = EntitySearch_TestComponent1::CreateDescriptor();
auto* EntitySearch_TestComponent2Descriptor = EntitySearch_TestComponent2::CreateDescriptor();
m_app.RegisterComponentDescriptor(EntitySearch_TestComponent1Descriptor);
m_app.RegisterComponentDescriptor(EntitySearch_TestComponent2Descriptor);
m_testComponentType1 = azrtti_typeid<EntitySearch_TestComponent1>();
m_testComponentType2 = azrtti_typeid<EntitySearch_TestComponent2>();
}
void GenerateTestHierarchy()
{
/*
* City
* |_ Street (Test Component 2)
* |_ Car
* | |_ Passenger (Test Component 1, Test Component 2)
* | |_ Passenger
* |_ Car (Test Component 1)
* | |_ Passenger
* |_ SportsCar
* |_ Passenger (Test Component 2)
* |_ Passenger
*/
m_testComponentType1Count = 0;
m_entityMap["cityId"] = CreateEditorEntity("City", AZ::EntityId());
m_entityMap["streetId"] = CreateEditorEntity("Street", m_entityMap["cityId"], false, true);
m_entityMap["carId1"] = CreateEditorEntity("Car", m_entityMap["streetId"]);
m_entityMap["passengerId1"] = CreateEditorEntity("Passenger", m_entityMap["carId1"], true, true);
m_entityMap["passengerId2"] = CreateEditorEntity("Passenger", m_entityMap["carId1"]);
m_entityMap["carId2"] = CreateEditorEntity("Car", m_entityMap["streetId"], true);
m_entityMap["passengerId3"] = CreateEditorEntity("Passenger", m_entityMap["carId2"]);
m_entityMap["sportsCarId"] = CreateEditorEntity("SportsCar", m_entityMap["streetId"]);
m_entityMap["passengerId4"] = CreateEditorEntity("Passenger", m_entityMap["sportsCarId"], false, true);
m_entityMap["passengerId5"] = CreateEditorEntity("Passenger", m_entityMap["sportsCarId"]);
// Add some Components
}
AZ::EntityId CreateEditorEntity(const char* name, AZ::EntityId parentId, bool addTestComponent1 = false, bool addTestComponent2 = false)
{
AZ::Entity* entity = nullptr;
UnitTest::CreateDefaultEditorEntity(name, &entity);
entity->Deactivate();
if (addTestComponent1)
{
entity->CreateComponent<EntitySearch_TestComponent1>(m_testComponentType1Count++, EntitySearch_TestComponent1::DefaultBoolValue);
}
if (addTestComponent2)
{
entity->CreateComponent<EntitySearch_TestComponent2>(EntitySearch_TestComponent2::DefaultFloatValue);
}
entity->Activate();
// Parent
AZ::TransformBus::Event(entity->GetId(), &AZ::TransformInterface::SetParent, parentId);
return entity->GetId();
}
void TearDown() override
{
m_app.Stop();
}
AzToolsFramework::ToolsApplication m_app;
AZ::ComponentApplication::Descriptor m_descriptor;
AZStd::unordered_map<AZStd::string, AZ::EntityId> m_entityMap;
AZ::Uuid m_testComponentType1;
AZ::Uuid m_testComponentType2;
int m_testComponentType1Count;
};
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_RootEntities)
{
{
EntityIdList rootEntities;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(rootEntities, &AzToolsFramework::EditorEntitySearchRequests::GetRootEditorEntities);
EXPECT_EQ(rootEntities.size(), 1);
EXPECT_EQ(rootEntities[0], m_entityMap["cityId"]);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByName_Base)
{
{
// No filters - return all entities
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, EntitySearchFilter());
EXPECT_EQ(searchResults.size(), m_entityMap.size());
}
{
// Filter by name - single entity
EntitySearchFilter filter;
filter.m_names.push_back("Street");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["streetId"]);
}
{
// Filter by name - multiple entities
EntitySearchFilter filter;
filter.m_names.push_back("Passenger");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 5);
}
{
// Filter by name - multiple names
EntitySearchFilter filter;
filter.m_names.push_back("Passenger");
filter.m_names.push_back("Street");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 6);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByName_Wildcard)
{
{
EntitySearchFilter filter;
filter.m_names.push_back("Str*et");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["streetId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("St*t");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["streetId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("Str?et");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["streetId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("Str?t");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("C*");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 3);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("*");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), m_entityMap.size());
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByName_CaseSensitive)
{
{
EntitySearchFilter filter;
filter.m_names.push_back("Street");
filter.m_namesCaseSensitive = false; // Default
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["streetId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("street");
filter.m_namesCaseSensitive = false; // Default
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["streetId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("Street");
filter.m_namesCaseSensitive = true;
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["streetId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("street");
filter.m_namesCaseSensitive = true;
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByPath_Base)
{
{
EntitySearchFilter filter;
filter.m_names.push_back("City|Street|SportsCar");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["sportsCarId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("City|Street|Car|Passenger");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 3);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByPath_Wildcard)
{
{
EntitySearchFilter filter;
filter.m_names.push_back("City|*|SportsCar");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["sportsCarId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("City|Street|*|Passenger");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 5);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("City|Street|*Car|Passenger");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 5);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("City|Street|Sport*|Passenger");
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 2);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByPath_CaseSensitive)
{
{
EntitySearchFilter filter;
filter.m_names.push_back("City|Street");
filter.m_namesCaseSensitive = false; // Default
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["streetId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("city|street");
filter.m_namesCaseSensitive = false; // Default
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["streetId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("City|Street");
filter.m_namesCaseSensitive = true;
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["streetId"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("city|street");
filter.m_namesCaseSensitive = true;
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByComponent_Base)
{
{
EntitySearchFilter filter;
filter.m_components.emplace(m_testComponentType1);
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 2);
}
{
EntitySearchFilter filter;
filter.m_components.emplace(m_testComponentType2);
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 3);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByComponent_Multiple)
{
{
EntitySearchFilter filter;
filter.m_components.emplace(m_testComponentType1);
filter.m_components.emplace(m_testComponentType2);
filter.m_mustMatchAllComponents = false; // Default
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 4);
}
{
EntitySearchFilter filter;
filter.m_components.emplace(m_testComponentType1);
filter.m_components.emplace(m_testComponentType2);
filter.m_mustMatchAllComponents = true;
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["passengerId1"]);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByComponent_MatchProperty)
{
{
EntitySearchFilter filter;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Bool", EntitySearch_TestComponent1::DefaultBoolValue } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 2);
}
{
EntitySearchFilter filter;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Int", 0 } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
}
{
EntitySearchFilter filter;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Bool", !EntitySearch_TestComponent1::DefaultBoolValue } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
{
EntitySearchFilter filter;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Int", m_entityMap.size() } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
{
EntitySearchFilter filter;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Float", 0.0f } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
{
EntitySearchFilter filter;
filter.m_components.emplace(m_testComponentType2, EntitySearchFilter::ComponentProperties{ { "Bool", EntitySearch_TestComponent1::DefaultBoolValue } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByComponent_MatchMultipleProperties)
{
{
EntitySearchFilter filter;
filter.m_mustMatchAllComponents = true;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Bool", EntitySearch_TestComponent1::DefaultBoolValue } });
filter.m_components.emplace(m_testComponentType2, EntitySearchFilter::ComponentProperties{ { "Float", EntitySearch_TestComponent2::DefaultFloatValue } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
}
{
EntitySearchFilter filter;
filter.m_mustMatchAllComponents = false;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Bool", EntitySearch_TestComponent1::DefaultBoolValue } });
filter.m_components.emplace(m_testComponentType2, EntitySearchFilter::ComponentProperties{ { "Float", EntitySearch_TestComponent2::DefaultFloatValue } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 4);
}
{
EntitySearchFilter filter;
filter.m_mustMatchAllComponents = true;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Bool", EntitySearch_TestComponent1::DefaultBoolValue }, { "Int", 0 } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
}
{
EntitySearchFilter filter;
filter.m_mustMatchAllComponents = false;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Bool", EntitySearch_TestComponent1::DefaultBoolValue }, { "Int", 0 } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 2);
}
{
EntitySearchFilter filter;
filter.m_mustMatchAllComponents = false;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Bool", EntitySearch_TestComponent1::DefaultBoolValue }, { "Int", 0 } });
filter.m_components.emplace(m_testComponentType2, EntitySearchFilter::ComponentProperties{ { "Float", EntitySearch_TestComponent2::DefaultFloatValue } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 4);
}
{
EntitySearchFilter filter;
filter.m_mustMatchAllComponents = true;
filter.m_components.emplace(m_testComponentType1, EntitySearchFilter::ComponentProperties{ { "Bool", EntitySearch_TestComponent1::DefaultBoolValue }, { "Int", 0 } });
filter.m_components.emplace(m_testComponentType2, EntitySearchFilter::ComponentProperties{ { "Float", EntitySearch_TestComponent2::DefaultFloatValue } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
}
{
EntitySearchFilter filter;
filter.m_mustMatchAllComponents = false;
filter.m_components.emplace(m_testComponentType1);
filter.m_components.emplace(m_testComponentType2, EntitySearchFilter::ComponentProperties{ { "Float", EntitySearch_TestComponent2::DefaultFloatValue } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 4);
}
{
EntitySearchFilter filter;
filter.m_mustMatchAllComponents = true;
filter.m_components.emplace(m_testComponentType1);
filter.m_components.emplace(m_testComponentType2, EntitySearchFilter::ComponentProperties{ { "Float", EntitySearch_TestComponent2::DefaultFloatValue } });
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_SearchByAabb_Base)
{
{
// No filters - return all entities
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, EntitySearchFilter());
EXPECT_EQ(searchResults.size(), m_entityMap.size());
}
{
// Filter by huge AABB - return all entities
EntitySearchFilter filter;
filter.m_aabb = AZ::Aabb::CreateCenterRadius(AZ::Vector3::CreateZero(), 1000.0f);
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), m_entityMap.size());
}
{
// Filter by small AABB - return no entity
EntitySearchFilter filter;
filter.m_aabb = AZ::Aabb::CreateCenterRadius(AZ::Vector3::CreateOne(), 0.1f);
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_Search_Roots_Base)
{
{
EntitySearchFilter filter;
filter.m_names.push_back("Passenger");
filter.m_roots.push_back(m_entityMap["carId1"]);
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 2);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("Passenger");
filter.m_roots.push_back(m_entityMap["carId2"]);
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["passengerId3"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("SportsCar");
filter.m_roots.push_back(m_entityMap["carId1"]);
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("City|Street|SportsCar|Passenger");
filter.m_roots.push_back(m_entityMap["carId1"]);
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("Car|Passenger");
filter.m_roots.push_back(m_entityMap["carId1"]);
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 2);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_Search_Roots_NamesAreRootBased)
{
{
EntitySearchFilter filter;
filter.m_names.push_back("Car|Passenger");
// No root - Default
filter.m_namesAreRootBased = false; // Default
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 3);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("Car|Passenger");
// No root - Default
filter.m_namesAreRootBased = true;
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("Car|Passenger");
filter.m_roots.push_back(m_entityMap["streetId"]);
filter.m_namesAreRootBased = false; // Default
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 3);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("Car|Passenger");
filter.m_roots.push_back(m_entityMap["streetId"]);
filter.m_namesAreRootBased = true;
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 3);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("Car|Passenger");
filter.m_roots.push_back(m_entityMap["carId2"]);
filter.m_namesAreRootBased = true;
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 0);
}
}
TEST_F(EditorEntitySearchComponentTests, EditorEntitySearchTests_Search_MultipleFilters)
{
{
EntitySearchFilter filter;
filter.m_names.push_back("Car");
filter.m_aabb = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-1.0f), AZ::Vector3(1.0f));
filter.m_components.emplace(m_testComponentType1);
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["carId2"]);
}
{
EntitySearchFilter filter;
filter.m_names.push_back("Pass*");
filter.m_roots.push_back(m_entityMap["sportsCarId"]);
filter.m_components.emplace(m_testComponentType2, EntitySearchFilter::ComponentProperties{ { "Float", EntitySearch_TestComponent2::DefaultFloatValue } });
filter.m_namesAreRootBased = true;
filter.m_namesCaseSensitive = true;
EntityIdList searchResults;
AzToolsFramework::EditorEntitySearchBus::BroadcastResult(searchResults, &AzToolsFramework::EditorEntitySearchRequests::SearchEntities, filter);
EXPECT_EQ(searchResults.size(), 1);
EXPECT_EQ(searchResults[0], m_entityMap["passengerId4"]);
}
}
}
@@ -0,0 +1,178 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace UnitTest
{
using namespace AZ;
using namespace AzToolsFramework;
class EditorEntitySelectionTest
: public ToolsApplicationFixture
{
void SetUpEditorFixtureImpl() override
{
m_entity1 = CreateDefaultEditorEntity("Entity1");
m_entity2 = CreateDefaultEditorEntity("Entity2");
m_entity3 = CreateDefaultEditorEntity("Entity3");
m_entity4 = CreateDefaultEditorEntity("Entity4");
}
public:
AZ::EntityId m_entity1;
AZ::EntityId m_entity2;
AZ::EntityId m_entity3;
AZ::EntityId m_entity4;
};
TEST_F(EditorEntitySelectionTest, EditorEntitySelectionTests_SetAndGetSelectedEntities)
{
// Set entity1 and entity4 as selected
EntityIdList testEntityIds{ m_entity1, m_entity4 };
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, testEntityIds);
EntityIdList selectedEntityIds;
ToolsApplicationRequestBus::BroadcastResult(
selectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities);
EXPECT_EQ(selectedEntityIds.size(), testEntityIds.size());
for (auto& id : testEntityIds)
{
EXPECT_TRUE(AZStd::find(selectedEntityIds.begin(), selectedEntityIds.end(), id) != selectedEntityIds.end());
}
// Clear all selected entities
testEntityIds.clear();
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, testEntityIds);
ToolsApplicationRequestBus::BroadcastResult(
selectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities);
EXPECT_TRUE(selectedEntityIds.empty());
}
TEST_F(EditorEntitySelectionTest, EditorEntitySelectionTests_MarkEntitySelectedAndDeselected)
{
// Mark testEntityId as selected
AZ::EntityId testEntityId = m_entity1;
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::MarkEntitySelected, testEntityId);
bool testEntitySelected = false;
ToolsApplicationRequestBus::BroadcastResult(
testEntitySelected, &ToolsApplicationRequests::IsSelected, testEntityId);
bool anyEntitySelected = false;
ToolsApplicationRequestBus::BroadcastResult(
anyEntitySelected, &ToolsApplicationRequests::AreAnyEntitiesSelected);
EntityIdList selectedEntityIds;
ToolsApplicationRequestBus::BroadcastResult(
selectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities);
EXPECT_TRUE(testEntitySelected);
EXPECT_TRUE(anyEntitySelected);
EXPECT_EQ(selectedEntityIds.size(), 1);
EXPECT_EQ(selectedEntityIds.front(), testEntityId);
// Mark testEntityId as deselected
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::MarkEntityDeselected, testEntityId);
ToolsApplicationRequestBus::BroadcastResult(
testEntitySelected, &ToolsApplicationRequests::IsSelected, testEntityId);
ToolsApplicationRequestBus::BroadcastResult(
anyEntitySelected, &ToolsApplicationRequests::AreAnyEntitiesSelected);
ToolsApplicationRequestBus::BroadcastResult(
selectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities);
EXPECT_FALSE(testEntitySelected);
EXPECT_FALSE(anyEntitySelected);
EXPECT_TRUE(selectedEntityIds.empty());
}
TEST_F(EditorEntitySelectionTest, EditorEntitySelectionTests_MarkEntitiesDeselectedAndSelected)
{
// Set all entities as selected
EntityIdList testEntityIds{ m_entity1, m_entity2, m_entity3, m_entity4 };
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, testEntityIds);
// Deselect first half of entities
EntityIdList deselctedEntityIds{ testEntityIds.begin(), testEntityIds.begin() + testEntityIds.size() / 2 };
EntityIdList expectedSelectedEntityIds{ testEntityIds.begin() + testEntityIds.size() / 2, testEntityIds.end() };
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::MarkEntitiesDeselected, deselctedEntityIds);
bool targetSelected = false;
for (auto& id : expectedSelectedEntityIds)
{
ToolsApplicationRequestBus::BroadcastResult(
targetSelected, &ToolsApplicationRequests::IsSelected, id);
EXPECT_TRUE(targetSelected);
}
for (auto& id : deselctedEntityIds)
{
ToolsApplicationRequestBus::BroadcastResult(
targetSelected, &ToolsApplicationRequests::IsSelected, id);
EXPECT_FALSE(targetSelected);
}
bool anyEntitySelected = false;
ToolsApplicationRequestBus::BroadcastResult(
anyEntitySelected, &ToolsApplicationRequests::AreAnyEntitiesSelected);
EntityIdList actualSelectedEntityIds;
ToolsApplicationRequestBus::BroadcastResult(
actualSelectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities);
EXPECT_TRUE(anyEntitySelected);
EXPECT_EQ(actualSelectedEntityIds.size(), expectedSelectedEntityIds.size());
for (auto& id : expectedSelectedEntityIds)
{
EXPECT_TRUE(AZStd::find(actualSelectedEntityIds.begin(), actualSelectedEntityIds.end(), id) != actualSelectedEntityIds.end());
}
// Re-select first half of entities so that all entities got selected again
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::MarkEntitiesSelected, deselctedEntityIds);
expectedSelectedEntityIds = testEntityIds;
ToolsApplicationRequestBus::BroadcastResult(
anyEntitySelected, &ToolsApplicationRequests::AreAnyEntitiesSelected);
ToolsApplicationRequestBus::BroadcastResult(
actualSelectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities);
EXPECT_TRUE(anyEntitySelected);
EXPECT_EQ(actualSelectedEntityIds.size(), expectedSelectedEntityIds.size());
for (auto& id : expectedSelectedEntityIds)
{
EXPECT_TRUE(AZStd::find(actualSelectedEntityIds.begin(), actualSelectedEntityIds.end(), id) != actualSelectedEntityIds.end());
ToolsApplicationRequestBus::BroadcastResult(
targetSelected, &ToolsApplicationRequests::IsSelected, id);
EXPECT_TRUE(targetSelected);
}
}
}
@@ -0,0 +1,148 @@
/*
* 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 <AzCore/Component/Entity.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/UI/PropertyEditor/EntityIdQLabel.hxx>
#include <AzToolsFramework/Viewport/ActionBus.h>
#include <QtTest/QtTest>
using namespace AzToolsFramework;
namespace UnitTest
{
// Test widget to store a EntityIdQLabel
class EntityIdQLabel_TestWidget
: public QWidget
{
public:
explicit EntityIdQLabel_TestWidget(QWidget* parent = nullptr)
: QWidget(nullptr)
{
AZ_UNUSED(parent);
// ensure EntityIdQLabel_TestWidget can intercept and filter any incoming events itself
installEventFilter(this);
m_testLabel = new EntityIdQLabel(this);
}
EntityIdQLabel* m_testLabel = nullptr;
};
// Used to simulate a system implementing the EditorRequests bus to validate that the double click will
// result in a GoToSelectedEntitiesInViewports event
class EditorRequestHandlerTest : AzToolsFramework::EditorRequests::Bus::Handler
{
public:
EditorRequestHandlerTest()
{
AzToolsFramework::EditorRequests::Bus::Handler::BusConnect();
}
~EditorRequestHandlerTest()
{
AzToolsFramework::EditorRequests::Bus::Handler::BusDisconnect();
}
void BrowseForAssets(AssetBrowser::AssetSelectionModel& /*selection*/) override {}
int GetIconTextureIdFromEntityIconPath(const AZStd::string& entityIconPath) override { AZ_UNUSED(entityIconPath); return 0; }
bool DisplayHelpersVisible() override { return false; }
void GoToSelectedEntitiesInViewports() override
{
m_wentToSelectedEntitiesInViewport = true;
}
bool m_wentToSelectedEntitiesInViewport = false;
};
// Fixture to support testing EntityIdQLabel functionality
class EntityIdQLabelTest
: public AllocatorsTestFixture
{
public:
void SetUp() override
{
m_app.Start(AzFramework::Application::Descriptor());
// 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);
}
void TearDown() override
{
m_app.Stop();
}
EntityIdQLabel_TestWidget* m_widget = nullptr;
private:
ToolsApplication m_app;
};
TEST_F(EntityIdQLabelTest, DoubleClickEntitySelectionTest)
{
AZ::Entity* entity = aznew AZ::Entity();
ASSERT_TRUE(entity != nullptr);
entity->Init();
entity->Activate();
AZ::EntityId entityId = entity->GetId();
ASSERT_TRUE(entityId.IsValid());
EntityIdQLabel_TestWidget* widget = new EntityIdQLabel_TestWidget(nullptr);
ASSERT_TRUE(widget != nullptr);
ASSERT_TRUE(widget->m_testLabel != nullptr);
widget->m_testLabel->setFocus();
widget->m_testLabel->SetEntityId(entityId, {});
EditorRequestHandlerTest editorRequestHandler;
// Simulate double clicking the label
QTest::mouseDClick(widget->m_testLabel, Qt::LeftButton);
// If successful we expect the label's entity to be selected.
EntityIdList selectedEntities;
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
EXPECT_FALSE(selectedEntities.empty()) << "Double clicking on an EntityIdQLabel should select the entity";
EXPECT_TRUE(selectedEntities[0] == entityId) << "The selected entity is not the one that was double clicked";
selectedEntities.clear();
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selectedEntities);
widget->m_testLabel->SetEntityId(AZ::EntityId(), {});
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
EXPECT_TRUE(selectedEntities.empty()) << "Double clicking on an EntityIdQLabel with an invalid entity ID shouldn't change anything";
EXPECT_TRUE(editorRequestHandler.m_wentToSelectedEntitiesInViewport) << "Double clicking an EntityIdQLabel should result in a GoToSelectedEntitiesInViewports call";
delete entity;
delete widget;
}
}
@@ -0,0 +1,379 @@
/*
* 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.
*
*/
/*
* 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.
*
*/
// Test Environment
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
// Inspector Test Includes
#include <AzToolsFramework/UI/ComponentPalette/ComponentPaletteUtil.hxx>
namespace UnitTest
{
// Test component that is NOT available for a user to interact with
// It does not appear in the Add Component menu in the Editor
// It is not a system or game component
class Inspector_TestComponent1
: public AZ::Component
{
public:
AZ_COMPONENT(Inspector_TestComponent1, "{BD25A077-DF38-4B67-BEA5-F4587A747A36}", AZ::Component);
static void Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<Inspector_TestComponent1, AZ::Component>()
->Field("Data", &Inspector_TestComponent1::m_data)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<Inspector_TestComponent1>("InspectorTestComponent1", "Component 1 for AZ Tools Framework Unit Tests")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AddableByUser, false)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide)
->Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::NotPushable)
->Attribute(AZ::Edit::Attributes::HideIcon, true);
}
}
}
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("InspectorTestService1"));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("InspectorTestService1"));
}
virtual ~Inspector_TestComponent1() override
{
}
void SetData(int data)
{
m_data = data;
};
int GetData()
{
return m_data;
}
private:
void Init() override
{}
void Activate() override
{}
void Deactivate() override
{}
/// Whether this entity is locked
int m_data = 0;
};
// Test component that IS available for a user to interact with
// It does appear in the Add Component menu in the editor and is a game component
class Inspector_TestComponent2
: public AZ::Component
{
public:
AZ_COMPONENT(Inspector_TestComponent2, "{57D1C818-FD31-4FCD-A4DB-705EABF4E98B}", AZ::Component);
static void Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<Inspector_TestComponent2, AZ::Component>()
->Field("Data", &Inspector_TestComponent2::m_data)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<Inspector_TestComponent2>("InspectorTestComponent2", "Component 2 for AZ Tools Framework Unit Tests")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AddableByUser, true)
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"))
->Attribute(AZ::Edit::Attributes::Category, "Inspector Test Components")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/Tag.png")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/Tag.png")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://www.amazongames.com/")
->DataElement(AZ::Edit::UIHandlers::Default, &Inspector_TestComponent2::m_data, "Data", "The component's Data");
}
}
}
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("InspectorTestService2"));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("InspectorTestService2"));
}
virtual ~Inspector_TestComponent2() override
{
}
void SetData(int data)
{
m_data = data;
};
int GetData()
{
return m_data;
}
private:
void Init() override
{}
void Activate() override
{}
void Deactivate() override
{}
/// Whether this entity is locked
int m_data = 0;
};
// Test component that IS available for a user to interact with
// It does appear in an Add Component menu and is a system component
class Inspector_TestComponent3
: public AZ::Component
{
public:
AZ_COMPONENT(Inspector_TestComponent3, "{552CCFB1-135E-4B02-A492-25A3BBDFA381}", AZ::Component);
static void Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<Inspector_TestComponent3, AZ::Component>()
->Field("Data", &Inspector_TestComponent3::m_data)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<Inspector_TestComponent3>("InspectorTestComponent3", "Component 3 for AZ Tools Framework Unit Tests")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AddableByUser, true)
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
->Attribute(AZ::Edit::Attributes::Category, "Inspector Test Components")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/Tag.png")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/Tag.png")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://www.amazongames.com/")
->DataElement(AZ::Edit::UIHandlers::Default, &Inspector_TestComponent3::m_data, "Data", "The component's Data");
}
}
}
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("InspectorTestService3"));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("InspectorTestService3"));
}
virtual ~Inspector_TestComponent3() override
{
}
void SetData(int data)
{
m_data = data;
};
int GetData()
{
return m_data;
}
private:
void Init() override
{}
void Activate() override
{}
void Deactivate() override
{}
/// Whether this entity is locked
int m_data = 0;
};
// Component Filters for Testing
bool Filter_IsTestComponent1(const AZ::SerializeContext::ClassData& classData)
{
AZ::Uuid testComponent1_typeId = azrtti_typeid<Inspector_TestComponent1>();
return classData.m_typeId == testComponent1_typeId;
}
// Component Filters for Testing
bool Filter_IsTestComponent2(const AZ::SerializeContext::ClassData& classData)
{
AZ::Uuid testComponent2_typeId = azrtti_typeid<Inspector_TestComponent2>();
return classData.m_typeId == testComponent2_typeId;
}
// Component Filters for Testing
bool Filter_IsTestComponent3(const AZ::SerializeContext::ClassData& classData)
{
AZ::Uuid testComponent3_typeId = azrtti_typeid<Inspector_TestComponent2>();
return classData.m_typeId == testComponent3_typeId;
}
class ComponentPaletteTests
: public AllocatorsTestFixture
{
public:
ComponentPaletteTests()
: AllocatorsTestFixture()
{ }
void SetUp() override
{
AZ::ComponentApplication::Descriptor componentApplicationDesc;
componentApplicationDesc.m_useExistingAllocator = true;
m_application = aznew AzToolsFramework::ToolsApplication();
m_application->Start(componentApplicationDesc);
// 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);
}
void TearDown() override
{
// Release all slice asset references, so AssetManager doens't complain.
delete m_application;
}
public:
AzToolsFramework::ToolsApplication* m_application = nullptr;
};
// Test pushing slices to create news slices that could result in cyclic
// dependency, e.g. push slice1 => slice2 and slice2 => slice1 at the same
// time.
TEST_F(ComponentPaletteTests, TestComponentPalleteUtilities)
{
AZ::SerializeContext* context = m_application->GetSerializeContext();
// Register our test components (This process also reflects them to the appropriate contexts)
auto* Inspector_TestComponent1Descriptor = Inspector_TestComponent1::CreateDescriptor();
auto* Inspector_TestComponent2Descriptor = Inspector_TestComponent2::CreateDescriptor();
auto* Inspector_TestComponent3Descriptor = Inspector_TestComponent3::CreateDescriptor();
m_application->RegisterComponentDescriptor(Inspector_TestComponent1Descriptor);
m_application->RegisterComponentDescriptor(Inspector_TestComponent2Descriptor);
m_application->RegisterComponentDescriptor(Inspector_TestComponent3Descriptor);
AZ::Uuid testComponent1_typeId = azrtti_typeid<Inspector_TestComponent1>();
AZ::Uuid testComponent2_typeId = azrtti_typeid<Inspector_TestComponent2>();
//////////////////////////////////////////////////////////////////////////
// TEST OffersRequiredServices()
//////////////////////////////////////////////////////////////////////////
// Verify that OffersRequiredServices returns true with the services provided by the component.
AZ::ComponentDescriptor::DependencyArrayType testComponent1_ProvidedServices;
Inspector_TestComponent1::GetProvidedServices(testComponent1_ProvidedServices);
AZ_TEST_ASSERT(testComponent1_ProvidedServices.size() == 1);
const AZ::SerializeContext::ClassData* testComponent1_ClassData = context->FindClassData(testComponent1_typeId);
EXPECT_TRUE(AzToolsFramework::ComponentPaletteUtil::OffersRequiredServices(testComponent1_ClassData, testComponent1_ProvidedServices));
// Verify that OffersRequiredServices returns when given services provided by a different component
AZ::ComponentDescriptor::DependencyArrayType testComponent2_ProvidedServices;
Inspector_TestComponent2::GetProvidedServices(testComponent2_ProvidedServices);
AZ_TEST_ASSERT(testComponent2_ProvidedServices.size() == 1);
AZ_TEST_ASSERT(testComponent1_ProvidedServices != testComponent2_ProvidedServices);
EXPECT_FALSE(AzToolsFramework::ComponentPaletteUtil::OffersRequiredServices(testComponent1_ClassData, testComponent2_ProvidedServices));
// verify that OffersRequiredServices returns true when provided with an empty list of services
EXPECT_TRUE(AzToolsFramework::ComponentPaletteUtil::OffersRequiredServices(testComponent1_ClassData, AZ::ComponentDescriptor::DependencyArrayType()));
//////////////////////////////////////////////////////////////////////////
// TEST IsAddableByUser()
//////////////////////////////////////////////////////////////////////////
// Verify that IsAddableByUser returns false when given a component that is not editable or viewable by the user
EXPECT_FALSE(AzToolsFramework::ComponentPaletteUtil::IsAddableByUser(testComponent1_ClassData));
// Verify that IsAddableByUser returns true when given a component that has the appropriate edit context reflection
const AZ::SerializeContext::ClassData* testComponent2_ClassData = context->FindClassData(testComponent2_typeId);
EXPECT_TRUE(AzToolsFramework::ComponentPaletteUtil::IsAddableByUser(testComponent2_ClassData));
//////////////////////////////////////////////////////////////////////////
// TEST ContainsEditableComponents()
//////////////////////////////////////////////////////////////////////////
// Remove reflection of Test Component 2 for the first test
m_application->UnregisterComponentDescriptor(Inspector_TestComponent2Descriptor);
context->EnableRemoveReflection();
Inspector_TestComponent2::Reflect(context);
context->DisableRemoveReflection();
// Verify that there are no components that satisfy the AppearsInGameComponentMenu filter without service dependency conditions
EXPECT_FALSE(AzToolsFramework::ComponentPaletteUtil::ContainsEditableComponents(context, &Filter_IsTestComponent2, AZ::ComponentDescriptor::DependencyArrayType()));
// Reflect Test Component 2 for subsequent tests
Inspector_TestComponent2::Reflect(context);
m_application->RegisterComponentDescriptor(Inspector_TestComponent2Descriptor);
// Verify that there is now a component that satisfies the AppearsInGameComponentMenu filter without service dependency conditions
EXPECT_TRUE(AzToolsFramework::ComponentPaletteUtil::ContainsEditableComponents(context, &Filter_IsTestComponent2, AZ::ComponentDescriptor::DependencyArrayType()));
// Verify that true is returned here because test component 2 is editable and provides test component 2 services
EXPECT_TRUE(AzToolsFramework::ComponentPaletteUtil::ContainsEditableComponents(context, &Filter_IsTestComponent2, testComponent2_ProvidedServices));
// Verify that false is returned here because test component 2 does not provide any of the required services
EXPECT_FALSE(AzToolsFramework::ComponentPaletteUtil::ContainsEditableComponents(context, &Filter_IsTestComponent2, testComponent1_ProvidedServices));
// Verify that even though Test Component 1 exists and is returned by the filter and there are no services to match, false is returned
// because Test Component 1 is not editable.
EXPECT_FALSE(AzToolsFramework::ComponentPaletteUtil::ContainsEditableComponents(context, &Filter_IsTestComponent1, AZ::ComponentDescriptor::DependencyArrayType()));
// Verify that true is returned here when a system component is editable
EXPECT_TRUE(AzToolsFramework::ComponentPaletteUtil::ContainsEditableComponents(context, &Filter_IsTestComponent3, AZ::ComponentDescriptor::DependencyArrayType()));
}
}
@@ -0,0 +1,300 @@
/*
* 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 thistoolsApp
* 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 <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/Fingerprinting/TypeFingerprinter.h>
using namespace AzToolsFramework;
using namespace AzToolsFramework::Fingerprinting;
namespace UnitTest
{
class ReflectedTestClass
{
public:
AZ_TYPE_INFO(ReflectedTestClass, "{AE55A3D4-845B-457F-94BA-A708BBDD6307}");
AZ_CLASS_ALLOCATOR(ReflectedTestClass, AZ::SystemAllocator, 0);
~ReflectedTestClass()
{
delete m_property1AsPointer;
}
int m_property1;
bool m_property1AsBool;
int *m_property1AsPointer = nullptr;
int m_property2;
static void ReflectDefault(AZ::SerializeContext& context)
{
context.Class<ReflectedTestClass>()
->Field("Property1", &ReflectedTestClass::m_property1);
}
static void ReflectHigherVersion(AZ::SerializeContext& context)
{
context.Class<ReflectedTestClass>()
->Version(2)
->Field("Property1", &ReflectedTestClass::m_property1);
}
static void ReflectRenamedProperty(AZ::SerializeContext& context)
{
context.Class<ReflectedTestClass>()
->Field("Property1Renamed", &ReflectedTestClass::m_property1);
}
static void ReflectPropertyWithDifferentType(AZ::SerializeContext& context)
{
context.Class<ReflectedTestClass>()
->Field("Property", &ReflectedTestClass::m_property1AsBool);
}
static void ReflectPropertyAsPointer(AZ::SerializeContext& context)
{
context.Class<ReflectedTestClass>()
->Field("Property1", &ReflectedTestClass::m_property1AsPointer);
}
static void ReflectTwoProperties(AZ::SerializeContext& context)
{
context.Class<ReflectedTestClass>()
->Field("Property1", &ReflectedTestClass::m_property1)
->Field("Property2", &ReflectedTestClass::m_property2);
}
};
class ReflectedBaseClass
{
public:
AZ_TYPE_INFO(ReflectedBaseClass, "{B53DC61E-6E8A-4F0A-82E4-864FA50326E5}");
virtual ~ReflectedBaseClass() = default;
static void ReflectDefault(AZ::SerializeContext& context)
{
context.Class<ReflectedBaseClass>();
}
};
class ReflectedSubClass : public ReflectedBaseClass
{
public:
AZ_TYPE_INFO(ReflectedSubClass, "{B95E143C-D97E-44F3-8F38-BAB6F317A03C}");
static void ReflectWithInheritance(AZ::SerializeContext& context)
{
context.Class<ReflectedSubClass, ReflectedBaseClass>();
}
static void ReflectWithoutInheritance(AZ::SerializeContext& context)
{
context.Class<ReflectedSubClass>();
}
};
class ReflectedClassWithPointer
{
public:
AZ_TYPE_INFO(ReflectedClassWithPointer, "{03DE24B9-288B-41B5-952D-4749F8F400D2}");
~ReflectedClassWithPointer()
{
delete m_pointer;
}
ReflectedTestClass* m_pointer = nullptr;
static void Reflect(AZ::SerializeContext& context)
{
context.Class<ReflectedClassWithPointer>()
->Field("Pointer", &ReflectedClassWithPointer::m_pointer);
}
};
TEST(FingerprintTests, IntFingerprint_IsValid)
{
AZ::SerializeContext serializeContext;
TypeFingerprinter fingerprinter{ serializeContext };
EXPECT_NE(InvalidTypeFingerprint, fingerprinter.GetFingerprint<int>());
}
TEST(FingerprintTests, ClassFingerprint_IsValid)
{
AZ::SerializeContext serializeContext;
ReflectedTestClass::ReflectDefault(serializeContext);
TypeFingerprinter fingerprinter{ serializeContext };
EXPECT_NE(InvalidTypeFingerprint, fingerprinter.GetFingerprint<ReflectedTestClass>());
}
TEST(FingerprintTests, ClassWithNewVersionNumber_ChangesFingerprint)
{
AZ::SerializeContext serializeContext1;
ReflectedTestClass::ReflectDefault(serializeContext1);
TypeFingerprinter fingerprinter1{ serializeContext1 };
AZ::SerializeContext serializeContext2;
ReflectedTestClass::ReflectHigherVersion(serializeContext2);
TypeFingerprinter fingerprinter2{ serializeContext2 };
EXPECT_NE(fingerprinter1.GetFingerprint<ReflectedTestClass>(), fingerprinter2.GetFingerprint<ReflectedTestClass>());
}
TEST(FingerprintTests, ClassWithRenamedProperty_ChangesFingerprint)
{
AZ::SerializeContext serializeContext1;
ReflectedTestClass::ReflectDefault(serializeContext1);
TypeFingerprinter fingerprinter1{ serializeContext1 };
AZ::SerializeContext serializeContext2;
ReflectedTestClass::ReflectRenamedProperty(serializeContext2);
TypeFingerprinter fingerprinter2{ serializeContext2 };
EXPECT_NE(fingerprinter1.GetFingerprint<ReflectedTestClass>(), fingerprinter2.GetFingerprint<ReflectedTestClass>());
}
TEST(FingerprintTests, ClassWithPropertyThatChangesType_ChangesFingerprint)
{
AZ::SerializeContext serializeContext1;
ReflectedTestClass::ReflectDefault(serializeContext1);
TypeFingerprinter fingerprinter1{ serializeContext1 };
AZ::SerializeContext serializeContext2;
ReflectedTestClass::ReflectPropertyWithDifferentType(serializeContext2);
TypeFingerprinter fingerprinter2{ serializeContext2 };
EXPECT_NE(fingerprinter1.GetFingerprint<ReflectedTestClass>(), fingerprinter2.GetFingerprint<ReflectedTestClass>());
}
TEST(FingerprintTests, ClassWithPropertyThatChangesToPointer_ChangesFingerprint)
{
AZ::SerializeContext serializeContext1;
ReflectedTestClass::ReflectDefault(serializeContext1);
TypeFingerprinter fingerprinter1{ serializeContext1 };
AZ::SerializeContext serializeContext2;
ReflectedTestClass::ReflectPropertyAsPointer(serializeContext2);
TypeFingerprinter fingerprinter2{ serializeContext2 };
EXPECT_NE(fingerprinter1.GetFingerprint<ReflectedTestClass>(), fingerprinter2.GetFingerprint<ReflectedTestClass>());
}
TEST(FingerprintTests, ClassWithNewProperty_ChangesFingerprint)
{
AZ::SerializeContext serializeContext1;
ReflectedTestClass::ReflectDefault(serializeContext1);
TypeFingerprinter fingerprinter1{ serializeContext1 };
AZ::SerializeContext serializeContext2;
ReflectedTestClass::ReflectTwoProperties(serializeContext2);
TypeFingerprinter fingerprinter2{ serializeContext2 };
EXPECT_NE(fingerprinter1.GetFingerprint<ReflectedTestClass>(), fingerprinter2.GetFingerprint<ReflectedTestClass>());
}
TEST(FingerprintTests, ClassGainingBaseClass_ChangesFingerprint)
{
AZ::SerializeContext serializeContext1;
ReflectedBaseClass::ReflectDefault(serializeContext1);
ReflectedSubClass::ReflectWithoutInheritance(serializeContext1);
TypeFingerprinter fingerprinter1{ serializeContext1 };
AZ::SerializeContext serializeContext2;
ReflectedBaseClass::ReflectDefault(serializeContext2);
ReflectedSubClass::ReflectWithInheritance(serializeContext2);
TypeFingerprinter fingerprinter2{ serializeContext2 };
EXPECT_NE(fingerprinter1.GetFingerprint<ReflectedSubClass>(), fingerprinter2.GetFingerprint<ReflectedSubClass>());
}
TEST(FingerprintTests, GatherAllTypesInObject_FindsCorrectTypes)
{
AZ::SerializeContext serializeContext;
ReflectedTestClass::ReflectDefault(serializeContext);
TypeFingerprinter fingerprinter{ serializeContext };
ReflectedTestClass object;
TypeCollection typesInObject;
fingerprinter.GatherAllTypesInObject(&object, typesInObject);
EXPECT_EQ(2, typesInObject.size());
EXPECT_EQ(1, typesInObject.count(AZ::SerializeTypeInfo<int>::GetUuid()));
EXPECT_EQ(1, typesInObject.count(AZ::SerializeTypeInfo<ReflectedTestClass>::GetUuid()));
}
TEST(FingerprintTests, GatherAllTypesInObjectWithBaseClass_FindsCorrectTypes)
{
AZ::SerializeContext serializeContext;
ReflectedBaseClass::ReflectDefault(serializeContext);
ReflectedSubClass::ReflectWithInheritance(serializeContext);
TypeFingerprinter fingerprinter{ serializeContext };
ReflectedSubClass object;
TypeCollection typesInObject;
fingerprinter.GatherAllTypesInObject(&object, typesInObject);
EXPECT_EQ(2, typesInObject.size());
EXPECT_EQ(1, typesInObject.count(AZ::SerializeTypeInfo<ReflectedSubClass>::GetUuid()));
EXPECT_EQ(1, typesInObject.count(AZ::SerializeTypeInfo<ReflectedBaseClass>::GetUuid()));
}
TEST(FingerprintTests, GatherTypesInObjectWithNullPointer_FindsCorrectTypes)
{
AZ::SerializeContext serializeContext;
ReflectedClassWithPointer::Reflect(serializeContext);
ReflectedTestClass::ReflectDefault(serializeContext);
TypeFingerprinter fingerprinter{ serializeContext };
ReflectedClassWithPointer classWithPointer;
classWithPointer.m_pointer = nullptr;
TypeCollection typesInObject;
fingerprinter.GatherAllTypesInObject(&classWithPointer, typesInObject);
// shouldn't gather types from ReflectedTestClass, since m_pointer is null
EXPECT_EQ(1, typesInObject.size());
EXPECT_EQ(1, typesInObject.count(AZ::SerializeTypeInfo<ReflectedClassWithPointer>::GetUuid()));
}
TEST(FingerprintTests, GatherTypesInObjectWithValidPointer_FindsCorrectTypes)
{
AZ::SerializeContext serializeContext;
ReflectedClassWithPointer::Reflect(serializeContext);
ReflectedTestClass::ReflectDefault(serializeContext);
TypeFingerprinter fingerprinter{ serializeContext };
ReflectedClassWithPointer classWithPointer;
classWithPointer.m_pointer = aznew ReflectedTestClass();
TypeCollection typesInObject;
fingerprinter.GatherAllTypesInObject(&classWithPointer, typesInObject);
// should have followed m_pointer and gathered types from ReflectedTestClass
EXPECT_EQ(3, typesInObject.size());
EXPECT_EQ(1, typesInObject.count(AZ::SerializeTypeInfo<ReflectedClassWithPointer>::GetUuid()));
EXPECT_EQ(1, typesInObject.count(AZ::SerializeTypeInfo<ReflectedTestClass>::GetUuid()));
EXPECT_EQ(1, typesInObject.count(AZ::SerializeTypeInfo<int>::GetUuid()));
}
TEST(FingerprintTests, GenerateFingerprintForAllTypesInObject_Works)
{
AZ::SerializeContext serializeContext;
ReflectedTestClass::ReflectDefault(serializeContext);
TypeFingerprinter fingerprinter{ serializeContext };
ReflectedTestClass object;
EXPECT_NE(InvalidTypeFingerprint, fingerprinter.GenerateFingerprintForAllTypesInObject(&object));
}
} // namespace UnitTest
@@ -0,0 +1,30 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace UnitTest
{
// Supported integer types for spinbox control
using IntegerPrimtitiveTestConfigs = ::testing::Types
<
AZ::s8,
AZ::u8,
AZ::s16,
AZ::u16,
AZ::s32,
AZ::u32,
AZ::s64,
AZ::u64
>;
} // namespace UnitTest
@@ -0,0 +1,225 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzToolsFramework/UI/Logging/LogLine.h>
using namespace AZ;
using namespace AzToolsFramework;
using namespace AzToolsFramework::Logging;
namespace UnitTest
{
static const char* s_logPrefix = "~~1541632104059~~1~~8240~~RC Builder~~";
static const char* s_logWindow = "RC Builder";
TEST(LogLines, BasicTest)
{
const char* messages[] = {
R"X(Executing RC.EXE: '"E:\lyengine\dev\windows\bin\profile\rc.exe" "E:/Directory/File.tga")X",
R"X(Executing RC.EXE with working directory : '')X",
R"X(ResourceCompiler 64 - bit DEBUG)X",
R"X(Platform support : PC, PowerVR, etc2Comp)X",
R"X(Version 1.1.8.6 Nov 5 2018 13 : 28 : 28)X"
};
AZStd::string textBuffer;
for (const char* message : messages)
{
if (textBuffer.size() > 0)
{
textBuffer.append("\n");
}
textBuffer.append(s_logPrefix);
textBuffer.append(message);
}
AZStd::list<LogLine> lines;
LogLine::ParseLog(lines, textBuffer.c_str(), textBuffer.size() + 1);
EXPECT_EQ(lines.size(), sizeof(messages) / sizeof(messages[0]));
size_t index = 0;
for (LogLine& line : lines)
{
EXPECT_STREQ(s_logWindow, line.GetLogWindow().c_str());
EXPECT_STREQ(line.GetLogMessage().c_str(), messages[index]);
EXPECT_EQ(line.GetLogType(), LogLine::TYPE_MESSAGE);
index++;
}
}
TEST(LogLines, Junk)
{
const char* messages[] = {
R"X(small string)X",
R"X(tiny)X",
R"X(unformatted string)X",
};
AZStd::string textBuffer;
for (const char* message : messages)
{
if (textBuffer.size() > 0)
{
textBuffer.append("\n");
}
textBuffer.append(s_logPrefix);
textBuffer.append(message);
}
AZStd::list<LogLine> lines;
LogLine::ParseLog(lines, textBuffer.c_str(), textBuffer.size() + 1);
EXPECT_EQ(lines.size(), sizeof(messages) / sizeof(messages[0]));
size_t index = 0;
for (LogLine& line : lines)
{
EXPECT_STREQ(s_logWindow, line.GetLogWindow().c_str());
EXPECT_STREQ(line.GetLogMessage().c_str(), messages[index]);
EXPECT_EQ(line.GetLogType(), LogLine::TYPE_MESSAGE);
index++;
}
}
TEST(LogLines, RCParsingWithoutType)
{
const char* message = "Memory: working set 15.6Mb (peak 15.6Mb), pagefile 35.9Mb (peak 35.9Mb)";
const char* timeStampWithProperRCSpacing = " 0:00 "; // <-- exact number of spaces specified by RC
const char* timeStampWithWrongRCSpacing = " 0:00 "; // <-- a different number of spaces
AZStd::string textBuffer;
textBuffer.append(AZStd::string::format("%s%s%s", s_logPrefix, timeStampWithProperRCSpacing, message));
textBuffer.append("\n");
AZStd::string messageWithTimeStampNotParsed = AZStd::string::format("%s%s", timeStampWithWrongRCSpacing, message);
textBuffer.append(AZStd::string::format("%s%s", s_logPrefix, messageWithTimeStampNotParsed.c_str()));
AZStd::list<LogLine> lines;
LogLine::ParseLog(lines, textBuffer.c_str(), textBuffer.size() + 1);
EXPECT_EQ(lines.size(), 2);
LogLine& lineWithRCFormatting = lines.front();
EXPECT_STREQ(s_logWindow, lineWithRCFormatting.GetLogWindow().c_str());
EXPECT_STREQ(lineWithRCFormatting.GetLogMessage().c_str(), message);
EXPECT_EQ(lineWithRCFormatting.GetLogType(), LogLine::TYPE_MESSAGE);
LogLine& lineWithoutRCFormatting = lines.back();
EXPECT_STREQ(s_logWindow, lineWithoutRCFormatting.GetLogWindow().c_str());
EXPECT_STREQ(lineWithoutRCFormatting.GetLogMessage().c_str(), messageWithTimeStampNotParsed.c_str());
EXPECT_EQ(lineWithoutRCFormatting.GetLogType(), LogLine::TYPE_MESSAGE);
}
TEST(LogLines, RCParsingToEmptyLine)
{
const char* timeStampWithProperRCSpacing = " 0:00"; // <-- exact number of spaces specified by RC, but no space on the end
AZStd::string textBuffer;
textBuffer.append(AZStd::string::format("%s%s", s_logPrefix, timeStampWithProperRCSpacing));
AZStd::list<LogLine> lines;
LogLine::ParseLog(lines, textBuffer.c_str(), textBuffer.size() + 1);
EXPECT_EQ(lines.size(), 1);
LogLine& lineWithRCFormatting = lines.front();
EXPECT_STREQ(s_logWindow, lineWithRCFormatting.GetLogWindow().c_str());
EXPECT_STREQ(lineWithRCFormatting.GetLogMessage().c_str(), "");
EXPECT_EQ(lineWithRCFormatting.GetLogType(), LogLine::TYPE_MESSAGE);
}
TEST(LogLines, RCParsingWithType)
{
const char* rcPrefix = "E: 0:00 ";
const char* message = R"X(CImageCompiler::ProcessImplementation: LoadInput(file:'E:\Directory\File.tga', ext:'tga') failed)X";
AZStd::string textBuffer;
textBuffer.append(AZStd::string::format("%s%s%s", s_logPrefix, rcPrefix, message));
AZStd::list<LogLine> lines;
LogLine::ParseLog(lines, textBuffer.c_str(), textBuffer.size() + 1);
EXPECT_EQ(lines.size(), 1);
LogLine& line = lines.front();
EXPECT_STREQ(s_logWindow, line.GetLogWindow().c_str());
EXPECT_STREQ(line.GetLogMessage().c_str(), message);
EXPECT_EQ(line.GetLogType(), LogLine::TYPE_ERROR);
}
static AZStd::string CreateContextLine(const char* context, const char* data)
{
return AZStd::string::format("C: [%s] = %s", context, data);
}
TEST(LogLines, ContextParsing)
{
std::pair<const char*, const char*> contextInfos[] = {
std::make_pair("Source", "scriptcanvas / AntiAlias.scriptcanvas"),
std::make_pair("Platforms", "pc")
};
const char* messages[] = {
R"X(C: [Source] = scriptcanvas / AntiAlias.scriptcanvas)X",
R"X(C: [Platforms] = pc)X"
};
AZStd::string textBuffer;
for (auto& contextInfo : contextInfos)
{
if (textBuffer.size() > 0)
{
textBuffer.append("\n");
}
textBuffer.append(s_logPrefix);
textBuffer.append(CreateContextLine(contextInfo.first, contextInfo.second));
}
AZStd::list<LogLine> lines;
LogLine::ParseLog(lines, textBuffer.c_str(), textBuffer.size() + 1);
EXPECT_EQ(lines.size(), sizeof(messages) / sizeof(messages[0]));
size_t index = 0;
for (LogLine& line : lines)
{
EXPECT_EQ(AZStd::string(s_logWindow), line.GetLogWindow());
AZStd::string message = CreateContextLine(contextInfos[index].first, contextInfos[index].second);
EXPECT_STREQ(line.GetLogMessage().c_str(), message.c_str());
EXPECT_EQ(line.GetLogType(), LogLine::TYPE_CONTEXT);
std::pair<QString, QString> result;
bool contextLineParsed = LogLine::ParseContextLogLine(line, result);
EXPECT_TRUE(contextLineParsed);
const char* expectedContext = contextInfos[index].first;
const char* expectedData = contextInfos[index].second;
QByteArray parsedContext = result.first.toUtf8();
QByteArray parsedData = result.second.toUtf8();
EXPECT_STREQ(expectedContext, parsedContext.data());
EXPECT_STREQ(expectedData, parsedData.data());
index++;
}
}
}
@@ -0,0 +1,55 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzQtComponents/Components/StyleManager.h>
#include <QApplication>
using namespace AZ;
// Handle asserts
class ToolsFrameworkHook
: public AZ::Test::ITestEnvironment
{
public:
void SetupEnvironment() override
{
AllocatorInstance<SystemAllocator>::Create();
}
void TeardownEnvironment() override
{
AllocatorInstance<SystemAllocator>::Destroy();
}
};
AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv)
{
::testing::InitGoogleMock(&argc, argv);
QApplication app(argc, argv);
auto styleManager = AZStd::make_unique< AzQtComponents::StyleManager>(&app);
styleManager->initialize(&app);
AZ::Test::printUnusedParametersWarning(argc, argv);
AZ::Test::addTestEnvironments({ new ToolsFrameworkHook });
int result = RUN_ALL_TESTS();
styleManager.release();
return result;
}
#if defined(HAVE_BENCHMARK)
AZ_BENCHMARK_HOOK();
#else
IMPLEMENT_TEST_EXECUTABLE_MAIN();
#endif
@@ -0,0 +1,194 @@
/*
* 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 <AzCore/Math/Quaternion.h>
#include <AzCore/Math/Spline.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Picking/Manipulators/ManipulatorBounds.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
const float g_epsilon = 1e-4f;
using namespace AzToolsFramework::Picking;
TEST(ManipulatorBounds, Sphere)
{
ManipulatorBoundSphere manipulatorBoundSphere(RegisteredBoundId{});
manipulatorBoundSphere.m_center = AZ::Vector3::CreateZero();
manipulatorBoundSphere.m_radius = 2.0f;
float intersectionDistance;
bool intersection = manipulatorBoundSphere.IntersectRay(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(), intersectionDistance);
EXPECT_NEAR(intersectionDistance, 8.0f, g_epsilon);
EXPECT_TRUE(intersection);
}
TEST(ManipulatorBounds, Box)
{
ManipulatorBoundBox manipulatorBoundBox(RegisteredBoundId{});
manipulatorBoundBox.m_center = AZ::Vector3::CreateZero();
manipulatorBoundBox.m_halfExtents = AZ::Vector3(1.0f);
manipulatorBoundBox.m_axis1 = AZ::Vector3::CreateAxisX();
manipulatorBoundBox.m_axis2 = AZ::Vector3::CreateAxisY();
manipulatorBoundBox.m_axis3 = AZ::Vector3::CreateAxisZ();
float intersectionDistance;
bool intersection = manipulatorBoundBox.IntersectRay(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(), intersectionDistance);
EXPECT_NEAR(intersectionDistance, 9.0f, g_epsilon);
EXPECT_TRUE(intersection);
}
TEST(ManipulatorBounds, Cylinder)
{
ManipulatorBoundCylinder manipulatorCylinder(RegisteredBoundId{});
manipulatorCylinder.m_base = AZ::Vector3::CreateAxisZ(-5.0f);
manipulatorCylinder.m_axis = AZ::Vector3::CreateAxisZ();
manipulatorCylinder.m_height = 10.0f;
manipulatorCylinder.m_radius = 2.0f;
float intersectionDistance;
bool intersection = manipulatorCylinder.IntersectRay(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(), intersectionDistance);
EXPECT_NEAR(intersectionDistance, 8.0f, g_epsilon);
EXPECT_TRUE(intersection);
}
TEST(ManipulatorBounds, Cone)
{
ManipulatorBoundCone manipulatorCone(RegisteredBoundId{});
manipulatorCone.m_apexPosition = AZ::Vector3::CreateAxisZ(-5.0f);
manipulatorCone.m_height = 10.0f;
manipulatorCone.m_dir = AZ::Vector3::CreateAxisZ();
manipulatorCone.m_radius = 4.0f;
float intersectionDistance;
bool intersection = manipulatorCone.IntersectRay(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(), intersectionDistance);
EXPECT_NEAR(intersectionDistance, 8.0f, g_epsilon);
EXPECT_TRUE(intersection);
}
TEST(ManipulatorBounds, Quad)
{
ManipulatorBoundQuad manipulatorQuad(RegisteredBoundId{});
manipulatorQuad.m_corner1 = AZ::Vector3(-1.0f, 0.0f, 1.0f);
manipulatorQuad.m_corner2 = AZ::Vector3(1.0f, 0.0f, 1.0f);
manipulatorQuad.m_corner3 = AZ::Vector3(1.0f, 0.0f, -1.0f);
manipulatorQuad.m_corner4 = AZ::Vector3(-1.0f, 0.0f, -1.0f);
float intersectionDistance;
bool intersection = manipulatorQuad.IntersectRay(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(), intersectionDistance);
EXPECT_NEAR(intersectionDistance, 10.0f, g_epsilon);
EXPECT_TRUE(intersection);
}
TEST(ManipulatorBounds, Torus)
{
ManipulatorBoundTorus manipulatorTorus(RegisteredBoundId{});
manipulatorTorus.m_axis = AZ::Vector3::CreateAxisY();
manipulatorTorus.m_center = AZ::Vector3::CreateZero();
manipulatorTorus.m_majorRadius = 5.0f;
manipulatorTorus.m_minorRadius = 0.5f;
float _;
bool intersectionCenter = manipulatorTorus.IntersectRay(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(), _);
// miss - through center
EXPECT_TRUE(!intersectionCenter);
float intersectionDistance;
bool intersectionOutside = manipulatorTorus.IntersectRay(
AZ::Vector3(5.0f, 10.0f, 0.0f), -AZ::Vector3::CreateAxisY(), intersectionDistance);
EXPECT_NEAR(intersectionDistance, 9.5f, g_epsilon);
EXPECT_TRUE(intersectionOutside);
}
TEST(ManipulatorBounds, RayIntersectsTorusAtAcuteAngle)
{
// torus approximation is side-on to ray
ManipulatorBoundTorus manipulatorTorus(RegisteredBoundId{});
manipulatorTorus.m_axis = AZ::Vector3::CreateAxisX();
manipulatorTorus.m_center = AZ::Vector3::CreateZero();
manipulatorTorus.m_majorRadius = 5.0f;
manipulatorTorus.m_minorRadius = 0.5f;
// calculation used to orientate the ray to hit
// the inside edge of the cylinder
//
// tan @ = opp / adj
// tan @ = 0.5 / 5.0 = 0.1
// @ = atan(0.1) = 5.71 degrees
//
// tan 5.71 = x / 15
// x = 15 * tan 5.71 = ~1.5
const AZ::Vector3 orientatedPickRay =
AZ::Quaternion::CreateRotationZ(AZ::DegToRad(5.7f)).TransformVector(-AZ::Vector3::CreateAxisY());
float _;
bool intersection = manipulatorTorus.IntersectRay(
AZ::Vector3(-1.5f, 10.0f, 0.0f), orientatedPickRay, _);
// ensure we get a valid intersection (even if the first hit
// might have happened in the 'hollow' part of the cylinder)
EXPECT_TRUE(intersection);
}
TEST(ManipulatorBounds, Line)
{
ManipulatorBoundLineSegment manipulatorLine(RegisteredBoundId{});
manipulatorLine.m_worldStart = AZ::Vector3(-5.0f, 0.0f, 0.0f);
manipulatorLine.m_worldEnd = AZ::Vector3(5.0f, 0.0f, 0.0f);
manipulatorLine.m_width = 0.2f;
float intersectionDistance;
bool intersection = manipulatorLine.IntersectRay(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(), intersectionDistance);
EXPECT_NEAR(intersectionDistance, 10.0f, g_epsilon);
EXPECT_TRUE(intersection);
}
TEST(ManipulatorBounds, Spline)
{
AZStd::shared_ptr<AZ::BezierSpline> bezierSpline = AZStd::make_shared<AZ::BezierSpline>();
bezierSpline->m_vertexContainer.AddVertex(AZ::Vector3(-10.0f, 0.0f, 0.0f));
bezierSpline->m_vertexContainer.AddVertex(AZ::Vector3(-5.0f, 0.0f, 0.0f));
bezierSpline->m_vertexContainer.AddVertex(AZ::Vector3(5.0f, 0.0f, 0.0f));
bezierSpline->m_vertexContainer.AddVertex(AZ::Vector3(10.0f, 0.0f, 0.0f));
ManipulatorBoundSpline manipulatorSpline(RegisteredBoundId{});
manipulatorSpline.m_spline = bezierSpline;
manipulatorSpline.m_transform = AZ::Transform::CreateIdentity();
manipulatorSpline.m_width = 0.2f;
float intersectionDistance;
bool intersection = manipulatorSpline.IntersectRay(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(), intersectionDistance);
EXPECT_NEAR(intersectionDistance, 10.0f, g_epsilon);
EXPECT_TRUE(intersection);
}
} // namespace UnitTest
@@ -0,0 +1,141 @@
/*
* 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 <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Manipulators/LinearManipulator.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityComponent.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
using namespace AzToolsFramework;
namespace UnitTest
{
class ManipulatorCoreFixture
: public ToolsApplicationFixture
{
public:
void SetUpEditorFixtureImpl() override
{
m_linearManipulator = LinearManipulator::MakeShared(AZ::Transform::CreateIdentity());
m_entityId = CreateDefaultEditorEntity("Default", &m_entity);
if (const auto* transformComponent = m_entity->FindComponent<Components::TransformComponent>())
{
m_transformComponentId = transformComponent->GetId();
m_linearManipulator->AddEntityComponentIdPair(
AZ::EntityComponentIdPair{ m_entityId, m_transformComponentId });
}
if (const auto* lockComponent = m_entity->FindComponent<Components::EditorLockComponent>())
{
m_lockComponentId = lockComponent->GetId();
m_linearManipulator->AddEntityComponentIdPair(
AZ::EntityComponentIdPair{ m_entityId, m_lockComponentId });
}
if (const auto* visibilityComponent = m_entity->FindComponent<Components::EditorVisibilityComponent>())
{
m_visibiltyComponentId = visibilityComponent->GetId();
m_linearManipulator->AddEntityComponentIdPair(
AZ::EntityComponentIdPair{ m_entityId, m_visibiltyComponentId });
}
m_editorEntityComponentChangeDetector
= AZStd::make_unique<EditorEntityComponentChangeDetector>(m_entityId);
}
void TearDownEditorFixtureImpl() override
{
}
AZStd::shared_ptr<LinearManipulator> m_linearManipulator;
AZ::Entity* m_entity = nullptr;
AZ::EntityId m_entityId;
AZStd::unique_ptr<EditorEntityComponentChangeDetector> m_editorEntityComponentChangeDetector;
AZ::ComponentId m_transformComponentId;
AZ::ComponentId m_lockComponentId;
AZ::ComponentId m_visibiltyComponentId;
};
TEST_F(ManipulatorCoreFixture, AllEntityIdComponentPairsRemovedFromManipulatorAfterRemoveEntity)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
// handled in Fixture::SetUp()
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
m_linearManipulator->RemoveEntityId(m_entityId);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
EXPECT_FALSE(m_linearManipulator->HasEntityId(m_entityId));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(ManipulatorCoreFixture, EntityIdComponentPairRemovedFromManipulatorAfterRemoveEntityComponentId)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
const auto entityLockComponentIdPair = AZ::EntityComponentIdPair{ m_entityId, m_lockComponentId };
const auto entityVisibiltyComponentIdPair = AZ::EntityComponentIdPair{ m_entityId, m_visibiltyComponentId };
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
m_linearManipulator->RemoveEntityComponentIdPair(entityLockComponentIdPair);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
EXPECT_FALSE(m_linearManipulator->HasEntityComponentIdPair(entityLockComponentIdPair));
EXPECT_TRUE(m_linearManipulator->HasEntityComponentIdPair(entityVisibiltyComponentIdPair));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST_F(ManipulatorCoreFixture, EntityComponentsNotifiedAfterManipulatorAction)
{
using testing::UnorderedElementsAre;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
// handled in Fixture::SetUp()
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
m_linearManipulator->OnLeftMouseDown(
AzToolsFramework::ViewportInteraction::MouseInteraction{}, 0.0f);
m_linearManipulator->OnLeftMouseUp(
AzToolsFramework::ViewportInteraction::MouseInteraction{});
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
EXPECT_THAT(
m_editorEntityComponentChangeDetector->m_componentIds,
UnorderedElementsAre(m_transformComponentId, m_lockComponentId, m_visibiltyComponentId));
EXPECT_TRUE(m_editorEntityComponentChangeDetector->PropertyDisplayInvalidated());
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
} // namespace UnitTest
@@ -0,0 +1,107 @@
/*
* 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 <AzCore/Serialization/SerializeContext.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Manipulators/RotationManipulators.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
namespace UnitTest
{
using namespace AzToolsFramework;
class ManipulatorViewTest
: public AllocatorsTestFixture
{
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
public:
void SetUp() override
{
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
m_app.Start(AzFramework::Application::Descriptor());
// 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);
}
void TearDown() override
{
m_app.Stop();
m_serializeContext.reset();
}
ToolsApplication m_app;
};
TEST_F(ManipulatorViewTest, ViewDirectionForCameraAlignedManipulatorFacesCameraInManipulatorSpace)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Given
const AZ::Transform orientation =
AZ::Transform::CreateFromQuaternion(
AZ::Quaternion::CreateFromAxisAngle(
AZ::Vector3::CreateAxisX(), AZ::DegToRad(-90.0f)));
const AZ::Transform translation =
AZ::Transform::CreateTranslation(AZ::Vector3(5.0f, 0.0f, 10.0f));
const AZ::Transform manipulatorSpace = translation * orientation;
// create a rotation manipulator in an arbitrary space
RotationManipulators rotationManipulators(manipulatorSpace);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
const AZ::Vector3 worldCameraPosition = AZ::Vector3(5.0f, -10.0f, 10.0f);
// transform the view direction to the space of the manipulator (space + local transform)
const AZ::Vector3 viewDirection =
CalculateViewDirection(rotationManipulators, worldCameraPosition);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
// check the view direction is in the same space as the manipulator (space + local transform)
EXPECT_THAT(viewDirection, IsClose(AZ::Vector3::CreateAxisZ()));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST(Manipulator, ScaleBasedOnCameraDistanceInFront)
{
AzFramework::CameraState cameraState{};
cameraState.m_position = AZ::Vector3::CreateAxisY(20.0f);
cameraState.m_forward = -AZ::Vector3::CreateAxisY();
const float scale =
AzToolsFramework::CalculateScreenToWorldMultiplier(AZ::Vector3::CreateZero(), cameraState);
EXPECT_NEAR(scale, 2.0f, std::numeric_limits<float>::epsilon());
}
TEST(Manipulator, ScaleBasedOnCameraDistanceToTheSide)
{
AzFramework::CameraState cameraState{};
cameraState.m_position = AZ::Vector3::CreateAxisY(20.0f);
cameraState.m_forward = -AZ::Vector3::CreateAxisY();
const float scale =
AzToolsFramework::CalculateScreenToWorldMultiplier(AZ::Vector3::CreateAxisX(-10.0f), cameraState);
EXPECT_NEAR(scale, 2.0f, std::numeric_limits<float>::epsilon());
}
} // namespace UnitTest
@@ -0,0 +1,736 @@
/*
* 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 <AzCore/Jobs/JobContext.h>
#include <AzCore/Jobs/JobManager.h>
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <SourceControl/PerforceComponent.h>
#include <SourceControl/PerforceConnection.h>
#include <QTemporaryDir>
namespace UnitTest
{
struct MockPerforceComponent
: AzToolsFramework::PerforceComponent
{
friend struct PerforceComponentFixture;
};
struct PerforceComponentFixture
: ::testing::Test
, TraceBusRedirector
, SourceControlTest
{
void SetUp() override
{
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
AZ::JobManagerDesc jobDesc;
AZ::JobManagerThreadDesc threadDesc;
jobDesc.m_workerThreads.push_back(threadDesc);
jobDesc.m_workerThreads.push_back(threadDesc);
jobDesc.m_workerThreads.push_back(threadDesc);
m_jobManager = aznew AZ::JobManager(jobDesc);
m_jobContext = aznew AZ::JobContext(*m_jobManager);
AZ::JobContext::SetGlobalContext(m_jobContext);
AZ::Debug::TraceMessageBus::Handler::BusConnect();
AZ::TickBus::AllowFunctionQueuing(true);
m_perforceComponent = AZStd::make_unique<MockPerforceComponent>();
m_perforceComponent->Activate();
m_perforceComponent->SetConnection(new MockPerforceConnection(m_command));
EnableSourceControl();
}
void TearDown() override
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
AZ::TickBus::AllowFunctionQueuing(false);
AZ::TickBus::ClearQueuedEvents();
m_perforceComponent->Deactivate();
m_perforceComponent = nullptr;
AZ::JobContext::SetGlobalContext(nullptr);
delete m_jobContext;
delete m_jobManager;
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
}
AZStd::unique_ptr<MockPerforceComponent> m_perforceComponent;
AZ::JobManager* m_jobManager = nullptr;
AZ::JobContext* m_jobContext = nullptr;
};
TEST_F(PerforceComponentFixture, TestGetBulkFileInfo_MultipleFiles_Succeeds)
{
static constexpr char FileAPath[] = R"(C:\depot\dev\default.font)";
static constexpr char FileBPath[] = R"(C:\depot\dev\default.xml)";
m_command.m_fstatResponse =
R"(... depotFile //depot/dev/default.xml)" "\r\n"
R"(... clientFile C:\depot\dev\default.xml)" "\r\n"
R"(... isMapped)" "\r\n"
R"(... headAction integrate)" "\r\n"
R"(... headType text)" "\r\n"
R"(... headTime 1454346715)" "\r\n"
R"(... headRev 3)" "\r\n"
R"(... headChange 147109)" "\r\n"
R"(... headModTime 1452731919)" "\r\n"
R"(... haveRev 3)" "\r\n"
"\r\n"
R"(... depotFile //depot/dev/default.font)" "\r\n"
R"(... clientFile C:\depot\dev\default.font)" "\r\n"
R"(... isMapped)" "\r\n"
R"(... headAction branch)" "\r\n"
R"(... headType text)" "\r\n"
R"(... headTime 1479280355)" "\r\n"
R"(... headRev 1)" "\r\n"
R"(... headChange 317116)" "\r\n"
R"(... headModTime 1478804078)" "\r\n"
R"(... haveRev 1)" "\r\n"
"\r\n";
AZStd::binary_semaphore callbackSignal;
bool result = false;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
AZStd::unordered_set<AZStd::string> requestFiles = { FileAPath, FileBPath };
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::GetBulkFileInfo, requestFiles, bulkCallback);
WaitForSourceControl(callbackSignal);
ASSERT_TRUE(result);
ASSERT_EQ(fileInfo.size(), 2);
for (int i = 0; i < 2; ++i)
{
ASSERT_EQ(fileInfo[i].m_status, AzToolsFramework::SourceControlStatus::SCS_OpSuccess);
ASSERT_TRUE(fileInfo[i].IsManaged());
}
}
TEST_F(PerforceComponentFixture, TestGetBulkFileInfo_MissingFile_Succeeds)
{
static constexpr char FileAPath[] = R"(C:\depot\dev\does-not-exist.txt)";
static constexpr char FileBPath[] = R"(C:\depot\dev\does-not-exist-two.txt)";
m_command.m_fstatErrorResponse =
R"(C:\depot\dev\does-not-exist.txt - no such file(s).)" "\r\n"
R"(C:\depot\dev\does-not-exist-two.txt - no such file(s).)" "\r\n"
"\r\n";
AZStd::binary_semaphore callbackSignal;
bool result = false;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
AZStd::unordered_set<AZStd::string> requestFiles = { FileAPath, FileBPath };
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::GetBulkFileInfo, requestFiles, bulkCallback);
WaitForSourceControl(callbackSignal);
ASSERT_TRUE(result);
ASSERT_EQ(fileInfo.size(), 2);
for (int i = 0; i < 2; ++i)
{
ASSERT_EQ(fileInfo[i].m_status, AzToolsFramework::SourceControlStatus::SCS_OpSuccess);
ASSERT_EQ(fileInfo[i].m_flags, AzToolsFramework::SourceControlFlags::SCF_Writeable); // Writable should be the only flag
}
}
TEST_F(PerforceComponentFixture, TestGetBulkFileInfo_CompareWithGetFileInfo_ResultMatches)
{
static constexpr char FileAPath[] = R"(C:\depot\dev\default.font)";
static constexpr char FstatResponse[] =
R"(... depotFile //depot/dev/default.font)" "\r\n"
R"(... clientFile C:\depot\dev\default.font)" "\r\n"
R"(... isMapped)" "\r\n"
R"(... headAction branch)" "\r\n"
R"(... headType text)" "\r\n"
R"(... headTime 1479280355)" "\r\n"
R"(... headRev 1)" "\r\n"
R"(... headChange 317116)" "\r\n"
R"(... headModTime 1478804078)" "\r\n"
R"(... haveRev 1)" "\r\n"
"\r\n";
AZStd::binary_semaphore callbackSignal;
bool result = false;
AzToolsFramework::SourceControlFileInfo fileInfoSingle;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto singleCallback = [&callbackSignal, &result, &fileInfoSingle](bool success, AzToolsFramework::SourceControlFileInfo info)
{
result = success;
fileInfoSingle = info;
callbackSignal.release();
};
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
AZStd::unordered_set<AZStd::string> requestFiles = { FileAPath };
m_command.m_fstatResponse = FstatResponse;
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::GetBulkFileInfo, requestFiles, bulkCallback);
WaitForSourceControl(callbackSignal);
ASSERT_TRUE(result);
m_command.m_fstatResponse = FstatResponse;
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::GetFileInfo, FileAPath, singleCallback);
WaitForSourceControl(callbackSignal);
ASSERT_TRUE(result);
ASSERT_FALSE(fileInfo.empty());
ASSERT_EQ(fileInfoSingle.m_flags, fileInfo[0].m_flags);
}
TEST_F(PerforceComponentFixture, Test_ExecuteEditBulk)
{
static constexpr char FileAPath[] = R"(C:\depot\dev\does-not-exist.txt)";
static constexpr char FileBPath[] = R"(C:\depot\dev\default.font)";
m_command.m_fstatErrorResponse =
R"(C:\depot\dev\does-not-exist.txt - no such file(s).)" "\r\n"
"\r\n";
m_command.m_fstatResponse =
R"(... depotFile //depot/dev/default.font)" "\r\n"
R"(... clientFile C:\depot\dev\default.font)" "\r\n"
R"(... isMapped)" "\r\n"
R"(... headAction branch)" "\r\n"
R"(... headType text)" "\r\n"
R"(... headTime 1479280355)" "\r\n"
R"(... headRev 1)" "\r\n"
R"(... headChange 317116)" "\r\n"
R"(... headModTime 1478804078)" "\r\n"
R"(... otherOpen)" "\r\n"
R"(... haveRev 1)" "\r\n"
"\r\n";
bool addCalled = false;
bool editCalled = false;
m_command.m_addCallback = [&addCalled]([[maybe_unused]] const AZStd::string& args)
{
addCalled = true;
};
m_command.m_editCallback = [this, &editCalled]([[maybe_unused]] const AZStd::string& args)
{
editCalled = true;
m_command.m_fstatResponse =
R"(... depotFile //depot/dev/does-not-exist.txt)" "\r\n"
R"(... clientFile C:\depot\dev\does-not-exist.txt)" "\r\n"
R"(... isMapped)" "\r\n"
R"(... action add)" "\r\n"
R"(... change default)" "\r\n"
R"(... type text)" "\r\n"
R"(... actionOwner unittest)" "\r\n"
R"(... workRev 1)" "\r\n"
"\r\n"
R"(... depotFile //depot/dev/default.font)" "\r\n"
R"(... clientFile C:\depot\dev\default.font)" "\r\n"
R"(... isMapped)" "\r\n"
R"(... headAction add)" "\r\n"
R"(... headType text)" "\r\n"
R"(... headTime 1557439413)" "\r\n"
R"(... headRev 1)" "\r\n"
R"(... headChange 902209)" "\r\n"
R"(... headModTime 1556296348)" "\r\n"
R"(... haveRev 1)" "\r\n"
R"(... action edit)" "\r\n"
R"(... change default)" "\r\n"
R"(... type text)" "\r\n"
R"(... actionOwner unittest)" "\r\n"
R"(... workRev 1)" "\r\n"
"\r\n";
};
AZStd::binary_semaphore callbackSignal;
bool result = false;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
AZStd::unordered_set<AZStd::string> requestFiles = { FileAPath, FileBPath };
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestEditBulk, requestFiles, true, bulkCallback);
WaitForSourceControl(callbackSignal);
ASSERT_TRUE(result);
ASSERT_TRUE(addCalled);
ASSERT_TRUE(editCalled);
ASSERT_EQ(fileInfo.size(), 2);
for (int i = 0; i < 2; ++i)
{
ASSERT_EQ(fileInfo[i].m_status, AzToolsFramework::SourceControlStatus::SCS_OpSuccess);
}
}
TEST_F(PerforceComponentFixture, Test_ExecuteEditBulk_CheckedOutByOther_Failure)
{
static constexpr char FileBPath[] = R"(C:\depot\dev\default.font)";
m_command.m_fstatResponse =
R"(... depotFile //depot/dev/default.font)" "\r\n"
R"(... clientFile C:\depot\dev\default.font)" "\r\n"
R"(... isMapped)" "\r\n"
R"(... headAction branch)" "\r\n"
R"(... headType text)" "\r\n"
R"(... headTime 1479280355)" "\r\n"
R"(... headRev 1)" "\r\n"
R"(... headChange 317116)" "\r\n"
R"(... headModTime 1478804078)" "\r\n"
R"(... otherOpen)" "\r\n"
R"(... haveRev 1)" "\r\n"
"\r\n";
bool addCalled = false;
bool editCalled = false;
m_command.m_addCallback = [&addCalled]([[maybe_unused]] const AZStd::string& args)
{
addCalled = true;
};
m_command.m_editCallback = [&editCalled]([[maybe_unused]] const AZStd::string& args)
{
editCalled = true;
};
AZStd::binary_semaphore callbackSignal;
bool result = false;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
AZStd::unordered_set<AZStd::string> requestFiles = { FileBPath };
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestEditBulk, requestFiles, false, bulkCallback);
WaitForSourceControl(callbackSignal);
ASSERT_FALSE(result);
ASSERT_FALSE(addCalled);
ASSERT_FALSE(editCalled);
}
bool CreateDummyFile(const QString& fullPathToFile, QString contents = "")
{
QFileInfo fi(fullPathToFile);
QDir fp(fi.path());
fp.mkpath(".");
QFile writer(fullPathToFile);
if (!writer.open(QFile::WriteOnly))
{
return false;
}
{
QTextStream ts(&writer);
ts.setCodec("UTF-8");
ts << contents;
}
return true;
}
TEST_F(PerforceComponentFixture, Test_ExecuteEditBulk_Local_Succeeds)
{
AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, false);
QTemporaryDir tempDir;
AZStd::string fullPathA = tempDir.filePath("fileA.txt").toUtf8().constData();
AZStd::string fullPathB = tempDir.filePath("fileB.txt").toUtf8().constData();
AZStd::vector<const char*> testPaths = { fullPathA.c_str(), fullPathB.c_str() };
for (const char* path : testPaths)
{
ASSERT_TRUE(CreateDummyFile(path));
ASSERT_TRUE(AZ::IO::SystemFile::Exists(path));
AZ::IO::SystemFile::SetWritable(path, false);
ASSERT_FALSE(AZ::IO::SystemFile::IsWritable(path));
}
AZStd::binary_semaphore callbackSignal;
bool result = false;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
AZStd::unordered_set<AZStd::string> requestFiles = { testPaths.begin(), testPaths.end() };
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestEditBulk, requestFiles, false, bulkCallback);
WaitForSourceControl(callbackSignal);
ASSERT_TRUE(result);
ASSERT_EQ(fileInfo.size(), testPaths.size());
for (int i = 0; i < testPaths.size(); ++i)
{
ASSERT_EQ(fileInfo[i].m_status, AzToolsFramework::SourceControlStatus::SCS_OpSuccess);
ASSERT_TRUE(fileInfo[i].HasFlag(AzToolsFramework::SourceControlFlags::SCF_Writeable));
ASSERT_TRUE(AZ::IO::SystemFile::IsWritable(testPaths[i]));
}
}
TEST_F(PerforceComponentFixture, Test_ExecuteRenameBulk_Local_Succeeds)
{
AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, false);
QTemporaryDir tempDir;
AZStd::string fullPathA = tempDir.filePath("one/two/three/fileA.txt").toUtf8().constData();
AZStd::string fullPathB = tempDir.filePath("one/two/three/fileB.txt").toUtf8().constData();
AZStd::vector<const char*> testPaths = { fullPathA.c_str(), fullPathB.c_str() };
for (const char* path : testPaths)
{
ASSERT_TRUE(CreateDummyFile(path));
ASSERT_TRUE(AZ::IO::SystemFile::Exists(path));
}
AZStd::binary_semaphore callbackSignal;
bool result = false;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
auto from = tempDir.filePath("o*e/*o/three/file*.txt");
auto to = tempDir.filePath("o*e/*o/three/fileRenamed*.png");
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestRenameBulk, from.toUtf8().constData(), to.toUtf8().constData(), bulkCallback);
WaitForSourceControl(callbackSignal);
ASSERT_TRUE(result);
ASSERT_EQ(fileInfo.size(), testPaths.size());
ASSERT_FALSE(AZ::IO::SystemFile::Exists(fullPathA.c_str()));
ASSERT_FALSE(AZ::IO::SystemFile::Exists(fullPathB.c_str()));
ASSERT_TRUE(AZ::IO::SystemFile::Exists(tempDir.filePath("one/two/three/fileRenamedA.png").toUtf8().constData()));
ASSERT_TRUE(AZ::IO::SystemFile::Exists(tempDir.filePath("one/two/three/fileRenamedB.png").toUtf8().constData()));
for (int i = 0; i < testPaths.size(); ++i)
{
ASSERT_EQ(fileInfo[i].m_status, AzToolsFramework::SourceControlStatus::SCS_OpSuccess);
ASSERT_TRUE(fileInfo[i].HasFlag(AzToolsFramework::SourceControlFlags::SCF_Tracked));
}
}
TEST_F(PerforceComponentFixture, Test_ExecuteRenameBulk_Local_MismatchedWildcards_Fails)
{
AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, false);
QTemporaryDir tempDir;
AZStd::string fullPathA = tempDir.filePath("one/two/three/fileA.txt").toUtf8().constData();
AZStd::string fullPathB = tempDir.filePath("one/two/three/fileB.txt").toUtf8().constData();
AZStd::vector<const char*> testPaths = { fullPathA.c_str(), fullPathB.c_str() };
for (const char* path : testPaths)
{
ASSERT_TRUE(CreateDummyFile(path));
ASSERT_TRUE(AZ::IO::SystemFile::Exists(path));
}
AZStd::binary_semaphore callbackSignal;
bool result = false;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
auto from = tempDir.filePath("o*e/*o/three/file*.txt");
auto to = tempDir.filePath("o*e/two/three/fileRenamed*.png");
AZ_TEST_START_TRACE_SUPPRESSION;
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestRenameBulk, from.toUtf8().constData(), to.toUtf8().constData(), bulkCallback);
WaitForSourceControl(callbackSignal);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_FALSE(result);
ASSERT_EQ(fileInfo.size(), 0);
ASSERT_TRUE(AZ::IO::SystemFile::Exists(fullPathA.c_str()));
ASSERT_TRUE(AZ::IO::SystemFile::Exists(fullPathB.c_str()));
ASSERT_FALSE(AZ::IO::SystemFile::Exists(tempDir.filePath("one/two/three/fileRenamedA.png").toUtf8().constData()));
ASSERT_FALSE(AZ::IO::SystemFile::Exists(tempDir.filePath("one/two/three/fileRenamedB.png").toUtf8().constData()));
}
TEST_F(PerforceComponentFixture, Test_ExecuteDeleteBulk_Local_Succeeds)
{
AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, false);
QTemporaryDir tempDir;
AZStd::string fullPathA = tempDir.filePath("one/two/three/fileA.txt").toUtf8().constData();
AZStd::string fullPathB = tempDir.filePath("one/two/three/fileB.txt").toUtf8().constData();
AZStd::vector<const char*> testPaths = { fullPathA.c_str(), fullPathB.c_str() };
for (const char* path : testPaths)
{
ASSERT_TRUE(CreateDummyFile(path));
ASSERT_TRUE(AZ::IO::SystemFile::Exists(path));
}
AZStd::binary_semaphore callbackSignal;
bool result = false;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
auto from = tempDir.filePath("o*e/*o/three/file*.txt");
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestDeleteBulk, from.toUtf8().constData(), bulkCallback);
WaitForSourceControl(callbackSignal);
ASSERT_TRUE(result);
ASSERT_EQ(fileInfo.size(), testPaths.size());
ASSERT_FALSE(AZ::IO::SystemFile::Exists(fullPathA.c_str()));
ASSERT_FALSE(AZ::IO::SystemFile::Exists(fullPathB.c_str()));
for (int i = 0; i < testPaths.size(); ++i)
{
ASSERT_EQ(fileInfo[i].m_status, AzToolsFramework::SourceControlStatus::SCS_OpSuccess);
ASSERT_FALSE(fileInfo[i].HasFlag(AzToolsFramework::SourceControlFlags::SCF_Tracked));
}
}
TEST_F(PerforceComponentFixture, Test_GetFiles_Succeeds)
{
AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, false);
QTemporaryDir tempDir;
AZStd::string fullPathA = tempDir.filePath("one/two/three/fileA.txt").toUtf8().constData();
AZStd::string fullPathB = tempDir.filePath("one/two/three/fileB.txt").toUtf8().constData();
AZStd::vector<const char*> testPaths = { fullPathA.c_str(), fullPathB.c_str() };
for (const char* path : testPaths)
{
ASSERT_TRUE(CreateDummyFile(path));
ASSERT_TRUE(AZ::IO::SystemFile::Exists(path));
}
auto result = AzToolsFramework::LocalFileSCComponent::GetFiles(tempDir.filePath("one/tw*/fileA.txt").toUtf8().constData());
ASSERT_EQ(result.size(), 0);
result = AzToolsFramework::LocalFileSCComponent::GetFiles(tempDir.filePath("on...").toUtf8().constData());
ASSERT_EQ(result.size(), 2);
}
TEST_F(PerforceComponentFixture, Test_GetFiles_StarWildcardAtEnd_OnlyReturnsFirstFile)
{
AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, false);
QTemporaryDir tempDir;
AZStd::string fullPathA = tempDir.filePath("one/file1.txt").toUtf8().constData();
AZStd::string fullPathB = tempDir.filePath("one/folder/file1.txt").toUtf8().constData();
AZStd::vector<const char*> testPaths = { fullPathA.c_str(), fullPathB.c_str() };
for (const char* path : testPaths)
{
ASSERT_TRUE(CreateDummyFile(path));
ASSERT_TRUE(AZ::IO::SystemFile::Exists(path));
}
auto result = AzToolsFramework::LocalFileSCComponent::GetFiles(tempDir.filePath("one/f*").toUtf8().constData());
ASSERT_EQ(result.size(), 1);
}
TEST_F(PerforceComponentFixture, Test_GetFiles_MultipleWildcardsAndWildcardAtEnd_Succeeds)
{
AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, false);
QTemporaryDir tempDir;
AZStd::string fullPathA = tempDir.filePath("one/two/three/fileA.txt").toUtf8().constData();
AZStd::string fullPathB = tempDir.filePath("one/two/three/fileB.txt").toUtf8().constData();
AZStd::vector<const char*> testPaths = { fullPathA.c_str(), fullPathB.c_str() };
for (const char* path : testPaths)
{
ASSERT_TRUE(CreateDummyFile(path));
ASSERT_TRUE(AZ::IO::SystemFile::Exists(path));
}
auto result = AzToolsFramework::LocalFileSCComponent::GetFiles(tempDir.filePath("o*e/tw*/...").toUtf8().constData());
ASSERT_EQ(result.size(), 2);
}
TEST_F(PerforceComponentFixture, Test_GetBulkFileInfo_Wildcard_Succeeds)
{
AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, false);
QTemporaryDir tempDir;
AZStd::string fullPathA = tempDir.filePath("one/two/three/fileA.txt").toUtf8().constData();
AZStd::string fullPathB = tempDir.filePath("one/two/three/fileB.txt").toUtf8().constData();
AZStd::vector<const char*> testPaths = { fullPathA.c_str(), fullPathB.c_str() };
for (const char* path : testPaths)
{
ASSERT_TRUE(CreateDummyFile(path));
}
AZStd::binary_semaphore callbackSignal;
bool result = false;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
AZStd::unordered_set<AZStd::string> paths = { tempDir.filePath("o*e/*o/three/file*.txt").toUtf8().constData() };
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::GetBulkFileInfo, paths, bulkCallback);
WaitForSourceControl(callbackSignal);
ASSERT_TRUE(result);
ASSERT_EQ(fileInfo.size(), testPaths.size());
using namespace AzToolsFramework;
for (int i = 0; i < testPaths.size(); ++i)
{
ASSERT_EQ(fileInfo[i].m_status, SCS_OpSuccess);
ASSERT_EQ(fileInfo[i].m_flags, SCF_Writeable | SCF_OpenByUser | SCF_Tracked);
}
}
TEST_F(PerforceComponentFixture, Test_GetBulkFileInfo_MultipleFiles_Succeeds)
{
AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, false);
QTemporaryDir tempDir;
AZStd::string fullPathA = tempDir.filePath("one/two/three/fileA.txt").toUtf8().constData();
AZStd::string fullPathB = tempDir.filePath("one/two/three/fileB.txt").toUtf8().constData();
AZStd::vector<const char*> testPaths = { fullPathA.c_str(), fullPathB.c_str() };
for (const char* path : testPaths)
{
ASSERT_TRUE(CreateDummyFile(path));
}
AZStd::binary_semaphore callbackSignal;
bool result = false;
AZStd::vector<AzToolsFramework::SourceControlFileInfo> fileInfo;
auto bulkCallback = [&callbackSignal, &result, &fileInfo](bool success, AZStd::vector<AzToolsFramework::SourceControlFileInfo> info)
{
result = success;
fileInfo = info;
callbackSignal.release();
};
AZStd::unordered_set<AZStd::string> paths = { fullPathA, fullPathB };
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::GetBulkFileInfo, paths, bulkCallback);
WaitForSourceControl(callbackSignal);
ASSERT_TRUE(result);
ASSERT_EQ(fileInfo.size(), testPaths.size());
using namespace AzToolsFramework;
for (int i = 0; i < testPaths.size(); ++i)
{
ASSERT_EQ(fileInfo[i].m_status, SCS_OpSuccess);
ASSERT_EQ(fileInfo[i].m_flags, SCF_Writeable | SCF_OpenByUser | SCF_Tracked);
}
}
}
@@ -0,0 +1,267 @@
/*
* 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 <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/AssetCatalog/PlatformAddressedAssetCatalogManager.h>
#include <AzFramework/Asset/AssetRegistry.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/IO/FileIO.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <Tests/AZTestShared/Utils/Utils.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzTest/AzTest.h>
#include <QTemporaryDir>
#include <QDir>
#include <AzCore/UserSettings/UserSettingsComponent.h>
namespace
{
static const int s_totalAssets = 12;
}
namespace UnitTest
{
class PlatformAddressedAssetCatalogManagerTest
: public AllocatorsFixture
{
public:
AZStd::string GetTempFolder()
{
QTemporaryDir dir;
QDir tempPath(dir.path());
return tempPath.absolutePath().toUtf8().data();
}
void SetUp() override
{
using namespace AZ::Data;
m_application = new AzToolsFramework::ToolsApplication();
m_application->Start(AzFramework::Application::Descriptor());
// 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);
AZStd::string cacheFolder;
AzFramework::StringFunc::Path::Join(GetTempFolder().c_str(), "testplatform", cacheFolder);
AzFramework::StringFunc::Path::Join(cacheFolder.c_str(), "testproject", cacheFolder);
AZ::IO::FileIOBase::GetInstance()->SetAlias("@assets@", cacheFolder.c_str());
for (int platformNum = AzFramework::PlatformId::PC; platformNum < AzFramework::PlatformId::NumPlatformIds; ++platformNum)
{
AZStd::string platformName{ AzFramework::PlatformHelper::GetPlatformName(static_cast<AzFramework::PlatformId>(platformNum)) };
if (!platformName.length())
{
// Do not test disabled platforms
continue;
}
AZStd::unique_ptr<AzFramework::AssetRegistry> assetRegistry = AZStd::make_unique<AzFramework::AssetRegistry>();
for (int idx = 0; idx < s_totalAssets; idx++)
{
m_assets[platformNum][idx] = AssetId(AZ::Uuid::CreateRandom(), 0);
AZ::Data::AssetInfo info;
info.m_relativePath = AZStd::string::format("%s%sAsset%d_%s.txt", cacheFolder.c_str(), AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING, idx, platformName.c_str());
info.m_assetId = m_assets[platformNum][idx];
assetRegistry->RegisterAsset(m_assets[platformNum][idx], info);
m_assetsPath[platformNum][idx] = info.m_relativePath;
if (m_fileStreams[platformNum][idx].Open(m_assetsPath[platformNum][idx].c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary | AZ::IO::OpenMode::ModeCreatePath))
{
m_fileStreams[platformNum][idx].Write(info.m_relativePath.size(), info.m_relativePath.data());
}
else
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Unable to create temporary file ( %s ) in PlatformAddressedAssetCatalogManagerTest unit tests.\n", m_assetsPath[platformNum][idx].c_str()).c_str());
}
}
bool useRequestBus = false;
AzFramework::AssetCatalog assetCatalog(useRequestBus);
AZStd::string catalogPath = AzToolsFramework::PlatformAddressedAssetCatalog::GetCatalogRegistryPathForPlatform(static_cast<AzFramework::PlatformId>(platformNum));
if (!assetCatalog.SaveCatalog(catalogPath.c_str(), assetRegistry.get()))
{
GTEST_FATAL_FAILURE_(AZStd::string::format("Unable to save the asset catalog file.\n").c_str());
}
}
m_PlatformAddressedAssetCatalogManager = new AzToolsFramework::PlatformAddressedAssetCatalogManager();
}
void TearDown() override
{
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
for (int platformNum = AzFramework::PlatformId::PC; platformNum < AzFramework::PlatformId::NumPlatformIds; ++platformNum)
{
AZStd::string platformName{ AzFramework::PlatformHelper::GetPlatformName(static_cast<AzFramework::PlatformId>(platformNum)) };
if (!platformName.length())
{
// Do not test disabled platforms
continue;
}
AZStd::string catalogPath = AzToolsFramework::PlatformAddressedAssetCatalog::GetCatalogRegistryPathForPlatform(static_cast<AzFramework::PlatformId>(platformNum));
if (fileIO->Exists(catalogPath.c_str()))
{
fileIO->Remove(catalogPath.c_str());
}
// Deleting all the temporary files
for (int idx = 0; idx < s_totalAssets; idx++)
{
// we need to close the handle before we try to remove the file
m_fileStreams[platformNum][idx].Close();
if (fileIO->Exists(m_assetsPath[platformNum][idx].c_str()))
{
fileIO->Remove(m_assetsPath[platformNum][idx].c_str());
}
}
}
delete m_localFileIO;
m_localFileIO = nullptr;
AZ::IO::FileIOBase::SetInstance(m_priorFileIO);
delete m_PlatformAddressedAssetCatalogManager;
m_application->Stop();
delete m_application;
}
AzToolsFramework::PlatformAddressedAssetCatalogManager* m_PlatformAddressedAssetCatalogManager;
AzToolsFramework::ToolsApplication* m_application;
AZ::IO::FileIOBase* m_priorFileIO = nullptr;
AZ::IO::FileIOBase* m_localFileIO = nullptr;
AZ::IO::FileIOStream m_fileStreams[AzFramework::PlatformId::NumPlatformIds][s_totalAssets];
AZ::Data::AssetId m_assets[AzFramework::PlatformId::NumPlatformIds][s_totalAssets];
AZStd::string m_assetsPath[AzFramework::PlatformId::NumPlatformIds][s_totalAssets];
};
TEST_F(PlatformAddressedAssetCatalogManagerTest, PlatformAddressedAssetCatalogManager_AllCatalogsLoaded_Success)
{
for (int platformNum = AzFramework::PlatformId::PC; platformNum < AzFramework::PlatformId::NumPlatformIds; ++platformNum)
{
AZStd::string platformName{ AzFramework::PlatformHelper::GetPlatformName(static_cast<AzFramework::PlatformId>(platformNum)) };
if (!platformName.length())
{
// Do not test disabled platforms
continue;
}
for (int assetNum = 0; assetNum < s_totalAssets; ++assetNum)
{
AZ::Data::AssetInfo assetInfo;
AzToolsFramework::AssetCatalog::PlatformAddressedAssetCatalogRequestBus::EventResult(assetInfo, static_cast<AzFramework::PlatformId>(platformNum), &AZ::Data::AssetCatalogRequests::GetAssetInfoById, m_assets[platformNum][assetNum]);
EXPECT_EQ(m_assets[platformNum][assetNum], assetInfo.m_assetId);
}
}
}
TEST_F(PlatformAddressedAssetCatalogManagerTest, PlatformAddressedAssetCatalogManager_CatalogExistsChecks_Success)
{
EXPECT_EQ(AzToolsFramework::PlatformAddressedAssetCatalog::CatalogExists(AzFramework::PlatformId::ES3), true);
AZStd::string es3CatalogPath = AzToolsFramework::PlatformAddressedAssetCatalog::GetCatalogRegistryPathForPlatform(AzFramework::PlatformId::ES3);
if (AZ::IO::FileIOBase::GetInstance()->Exists(es3CatalogPath.c_str()))
{
AZ::IO::FileIOBase::GetInstance()->Remove(es3CatalogPath.c_str());
}
EXPECT_EQ(AzToolsFramework::PlatformAddressedAssetCatalog::CatalogExists(AzFramework::PlatformId::ES3), false);
}
class PlatformAddressedAssetCatalogMessageTest : public AzToolsFramework::PlatformAddressedAssetCatalog
{
public:
PlatformAddressedAssetCatalogMessageTest(AzFramework::PlatformId platformId) : AzToolsFramework::PlatformAddressedAssetCatalog(platformId)
{
}
MOCK_METHOD1(AssetChanged, void(AzFramework::AssetSystem::AssetNotificationMessage message));
MOCK_METHOD1(AssetRemoved, void(AzFramework::AssetSystem::AssetNotificationMessage message));
};
class PlatformAddressedAssetCatalogManagerMessageTest : public AzToolsFramework::PlatformAddressedAssetCatalogManager
{
public:
PlatformAddressedAssetCatalogManagerMessageTest(AzFramework::PlatformId platformId) :
AzToolsFramework::PlatformAddressedAssetCatalogManager(AzFramework::PlatformId::Invalid)
{
TakeSingleCatalog(AZStd::make_unique<PlatformAddressedAssetCatalogMessageTest>(platformId));
}
};
class MessageTest
: public AllocatorsFixture
{
public:
AZStd::string GetTempFolder()
{
QTemporaryDir dir;
QDir tempPath(dir.path());
return tempPath.absolutePath().toUtf8().data();
}
void SetUp() override
{
AZ::IO::FileIOBase::SetInstance(nullptr); // The API requires the old instance to be destroyed first
AZ::IO::FileIOBase::SetInstance(new AZ::IO::LocalFileIO());
AZStd::string cacheFolder;
AzFramework::StringFunc::Path::Join(GetTempFolder().c_str(), "testplatform", cacheFolder);
AzFramework::StringFunc::Path::Join(cacheFolder.c_str(), "testproject", cacheFolder);
AZ::IO::FileIOBase::GetInstance()->SetAlias("@assets@", cacheFolder.c_str());
m_platformAddressedAssetCatalogManager = AZStd::make_unique<AzToolsFramework::PlatformAddressedAssetCatalogManager>(AzFramework::PlatformId::Invalid);
}
void TearDown() override
{
m_platformAddressedAssetCatalogManager.reset();
}
AZStd::unique_ptr<AzToolsFramework::PlatformAddressedAssetCatalogManager> m_platformAddressedAssetCatalogManager;
};
TEST_F(MessageTest, PlatformAddressedAssetCatalogManagerMessageTest_MessagesForwarded_CountsMatch)
{
AzFramework::AssetSystem::AssetNotificationMessage testMessage;
AzFramework::AssetSystem::NetworkAssetUpdateInterface* notificationInterface = AZ::Interface<AzFramework::AssetSystem::NetworkAssetUpdateInterface>::Get();
EXPECT_NE(notificationInterface, nullptr);
auto* mockCatalog = new ::testing::NiceMock<PlatformAddressedAssetCatalogMessageTest>(AzFramework::PlatformId::ES3);
AZStd::unique_ptr< ::testing::NiceMock<PlatformAddressedAssetCatalogMessageTest>> catalogHolder;
catalogHolder.reset(mockCatalog);
m_platformAddressedAssetCatalogManager->TakeSingleCatalog(AZStd::move(catalogHolder));
EXPECT_CALL(*mockCatalog, AssetChanged(testing::_)).Times(0);
notificationInterface->AssetChanged(testMessage);
testMessage.m_platform = "es3";
EXPECT_CALL(*mockCatalog, AssetChanged(testing::_)).Times(1);
notificationInterface->AssetChanged(testMessage);
testMessage.m_platform = "pc";
EXPECT_CALL(*mockCatalog, AssetChanged(testing::_)).Times(0);
notificationInterface->AssetChanged(testMessage);
EXPECT_CALL(*mockCatalog, AssetRemoved(testing::_)).Times(0);
notificationInterface->AssetRemoved(testMessage);
testMessage.m_platform = "es3";
EXPECT_CALL(*mockCatalog, AssetRemoved(testing::_)).Times(1);
notificationInterface->AssetRemoved(testMessage);
}
}
@@ -0,0 +1,136 @@
/*
* 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.
*
*/
#if defined(HAVE_BENCHMARK)
#include <Prefab/Benchmark/PrefabBenchmarkFixture.h>
#include <AzCore/Component/TransformBus.h> //for create entity
#include <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestDataUtils.h>
namespace Benchmark
{
void BM_Prefab::SetupPrefabSystem()
{
m_app = AZStd::make_unique<AzToolsFramework::ToolsApplication>();
ASSERT_TRUE(m_app != nullptr);
m_app->Start(AzFramework::Application::Descriptor());
AZ::Entity* systemEntity = m_app->FindEntity(AZ::SystemEntityId);
ASSERT_TRUE(systemEntity != nullptr);
m_prefabSystemComponent = systemEntity->FindComponent<AzToolsFramework::Prefab::PrefabSystemComponent>();
ASSERT_TRUE(m_prefabSystemComponent != nullptr);
m_mockIOActionValidator = AZStd::make_unique<UnitTest::MockPrefabFileIOActionValidator>();
ASSERT_TRUE(m_mockIOActionValidator != nullptr);
m_instanceUpdateExecutorInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get();
ASSERT_TRUE(m_instanceUpdateExecutorInterface != nullptr);
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
}
void BM_Prefab::TearDownPrefabSystem()
{
m_mockIOActionValidator.reset();
m_app.reset();
}
void BM_Prefab::ResetPrefabSystem()
{
TearDownPrefabSystem();
SetupPrefabSystem();
}
void BM_Prefab::SetUp(::benchmark::State & state)
{
AZ::Debug::TraceMessageBus::Handler::BusConnect();
UnitTest::AllocatorsBenchmarkFixture::SetUp(state);
SetupPrefabSystem();
}
void BM_Prefab::TearDown(::benchmark::State & state)
{
m_paths = {};
TearDownPrefabSystem();
UnitTest::AllocatorsBenchmarkFixture::TearDown(state);
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
}
AZ::Entity* BM_Prefab::CreateEntity(const char* entityName, const AZ::EntityId& parentId)
{
// Circumvent the EntityContext system and generate a new entity with a transformcomponent
AZ::Entity* newEntity = aznew AZ::Entity(entityName);
newEntity->CreateComponent(AZ::TransformComponentTypeId);
newEntity->Init();
newEntity->Activate();
SetEntityParent(newEntity->GetId(), parentId);
return newEntity;
}
void BM_Prefab::CreateEntities(const unsigned int entityCount, AZStd::vector<AZ::Entity*>& entities)
{
for (int entityIndex = 0; entityIndex < entityCount; ++entityIndex)
{
AZStd::string entityName = "TestEntity";
entityName = entityName + AZStd::to_string(entityIndex);
entities.emplace_back(CreateEntity(entityName.c_str()));
}
}
void BM_Prefab::SetEntityParent(const AZ::EntityId& entityId, const AZ::EntityId& parentId)
{
AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetParent, parentId);
}
void BM_Prefab::CreateFakePaths(const unsigned int pathCount)
{
//setup fake paths
for (int number = 0; number < pathCount; ++number)
{
AZStd::string path = m_pathString;
m_paths.push_back(path + AZStd::to_string(number) + "_" + AZStd::to_string(pathCount));
}
}
void BM_Prefab::SetUpMockValidatorForReadPrefab()
{
int pathCount = m_paths.size();
for (int number = 0; number < pathCount; ++number)
{
m_mockIOActionValidator->ReadPrefabDom(
m_paths[number], UnitTest::PrefabTestDomUtils::CreatePrefabDom());
}
}
void BM_Prefab::DeleteInstances(const AzToolsFramework::Prefab::InstanceList& instancesToDelete)
{
for (AzToolsFramework::Prefab::Instance* instanceToDelete : instancesToDelete)
{
ASSERT_TRUE(instanceToDelete);
delete instanceToDelete;
instanceToDelete = nullptr;
}
}
}
#endif
@@ -0,0 +1,67 @@
/*
* 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.
*
*/
#if defined(HAVE_BENCHMARK)
#pragma once
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <Prefab/MockPrefabFileIOActionValidator.h>
#include <Prefab/PrefabSystemComponent.h>
#include <Prefab/PrefabTestData.h>
#include <Prefab/PrefabTestUtils.h>
namespace Benchmark
{
using namespace UnitTest::PrefabTestUtils;
class BM_Prefab
: public UnitTest::AllocatorsBenchmarkFixture
, public UnitTest::TraceBusRedirector
{
protected:
using ::benchmark::Fixture::SetUp;
using ::benchmark::Fixture::TearDown;
void SetUp(::benchmark::State& state) override;
void TearDown(::benchmark::State& state) override;
AZ::Entity* CreateEntity(
const char* entityName,
const AZ::EntityId& parentId = AZ::EntityId());
void CreateEntities(const unsigned int entityCount, AZStd::vector<AZ::Entity*>& entities);
void SetEntityParent(const AZ::EntityId& entityId, const AZ::EntityId& parentId);
void CreateFakePaths(const unsigned int pathCount);
void SetUpMockValidatorForReadPrefab();
void DeleteInstances(const AzToolsFramework::Prefab::InstanceList& instances);
void SetupPrefabSystem();
void TearDownPrefabSystem();
void ResetPrefabSystem();
//prefab specific
AZStd::unique_ptr<AzToolsFramework::ToolsApplication> m_app;
AzToolsFramework::Prefab::PrefabSystemComponent* m_prefabSystemComponent = nullptr;
AzToolsFramework::Prefab::PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
AzToolsFramework::Prefab::InstanceUpdateExecutorInterface* m_instanceUpdateExecutorInterface = nullptr;
const char* m_pathString = "path/to/template";
AZStd::vector<AZStd::string> m_paths;
AZStd::unique_ptr <UnitTest::MockPrefabFileIOActionValidator> m_mockIOActionValidator;
};
}
#endif
@@ -0,0 +1,188 @@
/*
* 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.
*
*/
#if defined(HAVE_BENCHMARK)
#include <Prefab/Benchmark/PrefabBenchmarkFixture.h>
namespace Benchmark
{
using BM_PrefabCreate = BM_Prefab;
using namespace AzToolsFramework::Prefab;
BENCHMARK_DEFINE_F(BM_PrefabCreate, CreatePrefabs_SingleEntityEach)(::benchmark::State& state)
{
const unsigned int numEntities = state.range();
const unsigned int numInstances = numEntities;
CreateFakePaths(numInstances);
for (auto _ : state)
{
state.PauseTiming();
AZStd::vector<AZ::Entity*> entities;
CreateEntities(numEntities, entities);
AZStd::vector<AZStd::unique_ptr<Instance>> newInstances;
state.ResumeTiming();
for (int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter)
{
newInstances.push_back(m_prefabSystemComponent->CreatePrefab(
{ entities[instanceCounter] },
{},
m_paths[instanceCounter]));
}
state.PauseTiming();
newInstances.clear();
ResetPrefabSystem();
state.ResumeTiming();
}
state.SetComplexityN(numInstances);
}
BENCHMARK_REGISTER_F(BM_PrefabCreate, CreatePrefabs_SingleEntityEach)
->RangeMultiplier(10)
->Range(100, 10000)
->Unit(benchmark::kMillisecond)
->Complexity();
BENCHMARK_DEFINE_F(BM_PrefabCreate, CreatePrefab_FromEntities)(::benchmark::State& state)
{
const unsigned int numEntities = state.range();
for (auto _ : state)
{
state.PauseTiming();
AZStd::vector<AZ::Entity*> entities;
CreateEntities(numEntities, entities);
state.ResumeTiming();
AZStd::unique_ptr<Instance> instance = m_prefabSystemComponent->CreatePrefab(
entities
, {}
, m_pathString);
state.PauseTiming();
instance.reset();
ResetPrefabSystem();
state.ResumeTiming();
}
state.SetComplexityN(numEntities);
}
BENCHMARK_REGISTER_F(BM_PrefabCreate, CreatePrefab_FromEntities)
->RangeMultiplier(10)
->Range(100, 10000)
->Unit(benchmark::kMillisecond)
->Complexity();
BENCHMARK_DEFINE_F(BM_PrefabCreate, CreatePrefab_FromSingleDepthInstances)(::benchmark::State& state)
{
const unsigned int numInstancesToAdd = state.range();
const unsigned int numEntities = numInstancesToAdd;
// Create fake paths for all the nested instances
// plus the instance receiving them
CreateFakePaths(numInstancesToAdd + 1);
for (auto _ : state)
{
state.PauseTiming();
AZStd::vector<AZ::Entity*> entities;
CreateEntities(numEntities, entities);
AZStd::vector<AZStd::unique_ptr<Instance>> testInstances;
testInstances.resize(numInstancesToAdd);
for (int instanceCounter = 0; instanceCounter < numInstancesToAdd; ++instanceCounter)
{
testInstances[instanceCounter] = (m_prefabSystemComponent->CreatePrefab(
{ entities[instanceCounter] }
, {}
, m_paths[instanceCounter]));
}
state.ResumeTiming();
AZStd::unique_ptr<Instance> nestedInstance = m_prefabSystemComponent->CreatePrefab(
{}
, AZStd::move(testInstances)
, m_paths.back());
state.PauseTiming();
nestedInstance.reset();
ResetPrefabSystem();
state.ResumeTiming();
}
state.SetComplexityN(numInstancesToAdd);
}
BENCHMARK_REGISTER_F(BM_PrefabCreate, CreatePrefab_FromSingleDepthInstances)
->RangeMultiplier(10)
->Range(100, 10000)
->Unit(benchmark::kMillisecond)
->Complexity();
BENCHMARK_DEFINE_F(BM_PrefabCreate, CreatePrefab_FromLinearNestingOfInstances)(::benchmark::State& state)
{
const unsigned int numInstances = state.range();
// Create fake paths for all the nested instances
// plus the root instance
CreateFakePaths(numInstances + 1);
for (auto _ : state)
{
state.PauseTiming();
AZStd::unique_ptr<Instance> nestedInstanceRoot = m_prefabSystemComponent->CreatePrefab(
{ CreateEntity("Entity1") },
{},
m_paths.back());
state.ResumeTiming();
for (int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter)
{
nestedInstanceRoot = m_prefabSystemComponent->CreatePrefab(
{},
MakeInstanceList( AZStd::move(nestedInstanceRoot) ),
m_paths[instanceCounter]);
}
state.PauseTiming();
nestedInstanceRoot.reset();
ResetPrefabSystem();
state.ResumeTiming();
}
state.SetComplexityN(numInstances);
}
}
#endif
@@ -0,0 +1,55 @@
/*
* 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.
*
*/
#if defined(HAVE_BENCHMARK)
#include <Prefab/Benchmark/PrefabBenchmarkFixture.h>
namespace Benchmark
{
using BM_PrefabInstantiate = BM_Prefab;
using namespace AzToolsFramework::Prefab;
BENCHMARK_DEFINE_F(BM_PrefabInstantiate, InstantiatePrefab_SingleEntityInstance)(::benchmark::State& state)
{
const unsigned int numInstances = state.range();
AZStd::unique_ptr<Instance> firstInstance = m_prefabSystemComponent->CreatePrefab(
{ CreateEntity("Entity1") },
{},
m_pathString);
TemplateId templateToInstantiateId = firstInstance->GetTemplateId();
for (auto _ : state)
{
state.PauseTiming();
AZStd::vector<AZStd::unique_ptr<Instance>> newInstances;
newInstances.resize(numInstances);
state.ResumeTiming();
for (int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter)
{
newInstances[instanceCounter] = m_prefabSystemComponent->InstantiatePrefab(templateToInstantiateId);
}
}
state.SetComplexityN(numInstances);
}
BENCHMARK_REGISTER_F(BM_PrefabInstantiate, InstantiatePrefab_SingleEntityInstance)
->RangeMultiplier(10)
->Range(100, 10000)
->Unit(benchmark::kMillisecond)
->Complexity();
}
#endif
@@ -0,0 +1,57 @@
/*
* 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.
*
*/
#if defined(HAVE_BENCHMARK)
#include <Prefab/Benchmark/PrefabBenchmarkFixture.h>
namespace Benchmark
{
using BM_PrefabLoad = BM_Prefab;
using namespace AzToolsFramework::Prefab;
BENCHMARK_DEFINE_F(BM_PrefabLoad, LoadPrefab_Basic)(::benchmark::State& state)
{
const unsigned int numTemplates = state.range();
CreateFakePaths(numTemplates);
for (auto _ : state)
{
state.PauseTiming();
SetUpMockValidatorForReadPrefab();
m_prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
state.ResumeTiming();
for (int templateCounter = 0; templateCounter < numTemplates; ++templateCounter)
{
m_prefabLoaderInterface->LoadTemplate(m_paths[templateCounter]);
}
state.PauseTiming();
ResetPrefabSystem();
state.ResumeTiming();
}
state.SetComplexityN(numTemplates);
}
BENCHMARK_REGISTER_F(BM_PrefabLoad, LoadPrefab_Basic)
->RangeMultiplier(10)
->Range(100, 1000)
->Unit(benchmark::kMillisecond)
->Complexity();
}
#endif
@@ -0,0 +1,256 @@
/*
* 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.
*
*/
#if defined(HAVE_BENCHMARK)
#include <Prefab/Benchmark/PrefabBenchmarkFixture.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
namespace Benchmark
{
using BM_PrefabUpdateInstances = BM_Prefab;
using namespace AzToolsFramework::Prefab;
BENCHMARK_DEFINE_F(BM_PrefabUpdateInstances, UpdateInstances_SingeEntityInstances)(::benchmark::State& state)
{
const unsigned int numInstances = state.range();
CreateFakePaths(2);
const auto& nestedTemplatePath = m_paths.front();
const auto& enclosingTemplatePath = m_paths.back();
for (auto _ : state)
{
state.PauseTiming();
AZ::Entity* entity = CreateEntity("Entity");
AZStd::unique_ptr<Instance> nestedInstance = m_prefabSystemComponent->CreatePrefab(
{ entity },
{},
nestedTemplatePath);
AZStd::unique_ptr<Instance> enclosingInstance = m_prefabSystemComponent->CreatePrefab(
{},
MakeInstanceList( AZStd::move(nestedInstance) ),
enclosingTemplatePath);
TemplateId templateToInstantiateId = enclosingInstance->GetTemplateId();
{
AZStd::vector<AZStd::unique_ptr<Instance>> newInstances;
newInstances.resize(numInstances);
for (unsigned int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter)
{
newInstances[instanceCounter] = m_prefabSystemComponent->InstantiatePrefab(templateToInstantiateId);
}
entity->SetName("Updated Entity");
PrefabDom updatedPrefabDom;
PrefabDomUtils::StoreInstanceInPrefabDom(*enclosingInstance, updatedPrefabDom);
PrefabDom& enclosingTemplatePrefabDom = m_prefabSystemComponent->FindTemplateDom(templateToInstantiateId);
enclosingTemplatePrefabDom.CopyFrom(updatedPrefabDom, enclosingTemplatePrefabDom.GetAllocator());
state.ResumeTiming();
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(templateToInstantiateId);
m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
state.PauseTiming();
}
enclosingInstance.reset();
ResetPrefabSystem();
state.ResumeTiming();
}
state.SetComplexityN(numInstances);
}
BENCHMARK_REGISTER_F(BM_PrefabUpdateInstances, UpdateInstances_SingeEntityInstances)
->RangeMultiplier(10)
->Range(100, 10000)
->Unit(benchmark::kMillisecond)
->Complexity();
BENCHMARK_DEFINE_F(BM_PrefabUpdateInstances, UpdateInstances_SingleLinearNestingOfInstances)(::benchmark::State& state)
{
const unsigned int maxDepth = state.range();
CreateFakePaths(maxDepth);
const unsigned int numInstances = maxDepth;
for (auto _ : state)
{
state.PauseTiming();
AZ::Entity* entity = CreateEntity("Entity");
AZStd::unique_ptr<Instance> currentInstanceRoot = m_prefabSystemComponent->CreatePrefab(
{ entity },
{},
m_paths.back());
for (unsigned int currentDepth = 1; currentDepth < maxDepth; ++currentDepth)
{
currentInstanceRoot = m_prefabSystemComponent->CreatePrefab(
{},
MakeInstanceList( AZStd::move(currentInstanceRoot) ),
m_paths[currentDepth - 1]);
}
entity->SetName("Updated Entity");
PrefabDom updatedPrefabDom;
PrefabDomUtils::StoreInstanceInPrefabDom(*currentInstanceRoot, updatedPrefabDom);
const TemplateId rootTemplateId = currentInstanceRoot->GetTemplateId();
PrefabDom& rootTemplatePrefabDom = m_prefabSystemComponent->FindTemplateDom(rootTemplateId);
rootTemplatePrefabDom.CopyFrom(updatedPrefabDom, rootTemplatePrefabDom.GetAllocator());
state.ResumeTiming();
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(rootTemplateId);
m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
state.PauseTiming();
currentInstanceRoot.reset();
ResetPrefabSystem();
state.ResumeTiming();
}
state.SetComplexityN(numInstances);
}
BENCHMARK_DEFINE_F(BM_PrefabUpdateInstances, UpdateInstances_MultipleLinearNestingOfInstances)(::benchmark::State& state)
{
const unsigned int numRootInstances = state.range();
const unsigned int maxDepth = state.range();
CreateFakePaths(maxDepth);
const unsigned int numInstances = numRootInstances * maxDepth;
for (auto _ : state)
{
state.PauseTiming();
AZ::Entity* entity = CreateEntity("Entity");
AZStd::unique_ptr<Instance> currentInstanceRoot = m_prefabSystemComponent->CreatePrefab(
{ entity },
{},
m_paths.back());
for (unsigned int currentDepth = 0; currentDepth < maxDepth - 1; ++currentDepth)
{
currentInstanceRoot = m_prefabSystemComponent->CreatePrefab(
{},
MakeInstanceList( AZStd::move(currentInstanceRoot) ),
m_paths[currentDepth]);
}
const TemplateId rootTemplateId = currentInstanceRoot->GetTemplateId();
{
AZStd::vector<AZStd::unique_ptr<Instance>> newInstances;
newInstances.resize(numRootInstances - 1);
for (unsigned int instanceCounter = 0; instanceCounter < numRootInstances - 1; ++instanceCounter)
{
newInstances[instanceCounter] = m_prefabSystemComponent->InstantiatePrefab(rootTemplateId);
}
entity->SetName("Updated Entity");
PrefabDom updatedPrefabDom;
PrefabDomUtils::StoreInstanceInPrefabDom(*currentInstanceRoot, updatedPrefabDom);
PrefabDom& rootTemplatePrefabDom = m_prefabSystemComponent->FindTemplateDom(rootTemplateId);
rootTemplatePrefabDom.CopyFrom(updatedPrefabDom, rootTemplatePrefabDom.GetAllocator());
state.ResumeTiming();
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(rootTemplateId);
m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
state.PauseTiming();
}
currentInstanceRoot.reset();
ResetPrefabSystem();
state.ResumeTiming();
}
state.SetComplexityN(numInstances);
}
BENCHMARK_DEFINE_F(BM_PrefabUpdateInstances, UpdateInstances_BinaryTreeNestedInstanceHierarchy)(::benchmark::State& state)
{
const unsigned int maxDepth = state.range();
CreateFakePaths(maxDepth);
const unsigned int numInstances = (1 << maxDepth) - 1;
for (auto _ : state)
{
state.PauseTiming();
AZ::Entity* entity = CreateEntity("Entity");
AZStd::unique_ptr<Instance> currentInstanceRoot = m_prefabSystemComponent->CreatePrefab(
{ entity },
{},
m_paths.back());
for (unsigned int currentDepth = 0; currentDepth < maxDepth - 1; ++currentDepth)
{
AZStd::unique_ptr<Instance> extraNestedInstance =
m_prefabSystemComponent->InstantiatePrefab(currentInstanceRoot->GetTemplateId());
currentInstanceRoot = m_prefabSystemComponent->CreatePrefab(
{},
MakeInstanceList( AZStd::move(currentInstanceRoot), AZStd::move(extraNestedInstance) ),
m_paths[currentDepth]);
}
entity->SetName("Updated Entity");
PrefabDom updatedPrefabDom;
PrefabDomUtils::StoreInstanceInPrefabDom(*currentInstanceRoot, updatedPrefabDom);
const TemplateId rootTemplateId = currentInstanceRoot->GetTemplateId();
PrefabDom& rootTemplatePrefabDom = m_prefabSystemComponent->FindTemplateDom(rootTemplateId);
rootTemplatePrefabDom.CopyFrom(updatedPrefabDom, rootTemplatePrefabDom.GetAllocator());
state.ResumeTiming();
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(rootTemplateId);
m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
state.PauseTiming();
currentInstanceRoot.reset();
ResetPrefabSystem();
state.ResumeTiming();
}
state.SetComplexityN(numInstances);
}
BENCHMARK_REGISTER_F(BM_PrefabUpdateInstances, UpdateInstances_BinaryTreeNestedInstanceHierarchy)
->DenseRange(8, 12, 2)
->Unit(benchmark::kMillisecond)
->Complexity();
}
#endif
@@ -0,0 +1,93 @@
/*
* 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 <Prefab/MockPrefabFileIOActionValidator.h>
#include <AzCore/JSON/prettywriter.h>
namespace UnitTest
{
MockPrefabFileIOActionValidator::MockPrefabFileIOActionValidator()
{
// Cache the existing file io instance and build our mock file io
m_priorFileIO = AZ::IO::FileIOBase::GetInstance();
m_fileIOMock = AZStd::make_unique<testing::NiceMock<AZ::IO::MockFileIOBase>>();
// Swap out current file io instance for our mock
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_fileIOMock.get());
// Setup the default returns for our mock file io calls
AZ::IO::MockFileIOBase::InstallDefaultReturns(*m_fileIOMock.get());
}
MockPrefabFileIOActionValidator::~MockPrefabFileIOActionValidator()
{
// Restore our original file io instance
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_priorFileIO);
}
void MockPrefabFileIOActionValidator::ReadPrefabDom(
const AZStd::string& prefabFilePath,
const AzToolsFramework::Prefab::PrefabDom& prefabFileContentDom,
AZ::IO::ResultCode expectedReadResultCode,
AZ::IO::ResultCode expectedOpenResultCode,
AZ::IO::ResultCode expectedSizeResultCode,
AZ::IO::ResultCode expectedCloseResultCode)
{
rapidjson::StringBuffer prefabFileContentBuffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(prefabFileContentBuffer);
prefabFileContentDom.Accept(writer);
AZStd::string prefabFileContent(prefabFileContentBuffer.GetString());
ReadPrefabDom(prefabFilePath, prefabFileContent,
expectedReadResultCode, expectedOpenResultCode, expectedSizeResultCode, expectedCloseResultCode);
}
void MockPrefabFileIOActionValidator::ReadPrefabDom(
const AZStd::string& prefabFilePath,
const AZStd::string& prefabFileContent,
AZ::IO::ResultCode expectedReadResultCode,
AZ::IO::ResultCode expectedOpenResultCode,
AZ::IO::ResultCode expectedSizeResultCode,
AZ::IO::ResultCode expectedCloseResultCode)
{
AZ::IO::HandleType fileHandle = m_fileHandleCounter++;
EXPECT_CALL(*m_fileIOMock.get(), Open(
testing::StrEq(prefabFilePath.c_str()), testing::_, testing::_))
.WillRepeatedly(
testing::DoAll(
testing::SetArgReferee<2>(fileHandle),
testing::Return(AZ::IO::Result(expectedOpenResultCode))));
EXPECT_CALL(*m_fileIOMock.get(), Size(fileHandle, testing::_))
.WillRepeatedly(
testing::DoAll(
testing::SetArgReferee<1>(prefabFileContent.size()),
testing::Return(AZ::IO::Result(expectedSizeResultCode))));
EXPECT_CALL(*m_fileIOMock.get(), Read(fileHandle, testing::_, prefabFileContent.size(), testing::_, testing::_))
.WillRepeatedly(testing::Invoke([prefabFileContent, expectedReadResultCode](AZ::IO::HandleType, void* buffer, AZ::u64, bool, AZ::u64* bytesRead)
{
memcpy(buffer, prefabFileContent.data(), prefabFileContent.size());
*bytesRead = prefabFileContent.size();
return AZ::IO::Result(expectedReadResultCode);
}));
EXPECT_CALL(*m_fileIOMock.get(), Close(fileHandle))
.WillRepeatedly(testing::Return(AZ::IO::Result(expectedCloseResultCode)));
}
}
@@ -0,0 +1,54 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/functional.h>
#include <AzCore/UnitTest/Mocks/MockFileIOBase.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace UnitTest
{
class MockPrefabFileIOActionValidator
{
public:
MockPrefabFileIOActionValidator();
~MockPrefabFileIOActionValidator();
void ReadPrefabDom(
const AZStd::string& prefabFilePath,
const AzToolsFramework::Prefab::PrefabDom& prefabFileContentDom,
AZ::IO::ResultCode expectedReadResultCode = AZ::IO::ResultCode::Success,
AZ::IO::ResultCode expectedOpenResultCode = AZ::IO::ResultCode::Success,
AZ::IO::ResultCode expectedSizeResultCode = AZ::IO::ResultCode::Success,
AZ::IO::ResultCode expectedCloseResultCode = AZ::IO::ResultCode::Success);
void ReadPrefabDom(
const AZStd::string& prefabFilePath,
const AZStd::string& prefabFileContent,
AZ::IO::ResultCode expectedReadResultCode = AZ::IO::ResultCode::Success,
AZ::IO::ResultCode expectedOpenResultCode = AZ::IO::ResultCode::Success,
AZ::IO::ResultCode expectedSizeResultCode = AZ::IO::ResultCode::Success,
AZ::IO::ResultCode expectedCloseResultCode = AZ::IO::ResultCode::Success);
private:
// A counter for creating new file handles.
AZStd::atomic<AZ::IO::HandleType> m_fileHandleCounter = 1u;
// A mock file io for testing.
AZStd::unique_ptr<testing::NiceMock<AZ::IO::MockFileIOBase>> m_fileIOMock;
// A cache for the existing file io.
AZ::IO::FileIOBase* m_priorFileIO = nullptr;
};
}
@@ -0,0 +1,266 @@
/*
* 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 <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestFixture.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzFramework/Components/TransformComponent.h>
namespace UnitTest
{
using PrefabInstanceToTemplateTests = PrefabTestFixture;
TEST_F(PrefabInstanceToTemplateTests, PrefabUpdateTemplate_UpdateEntityOnInstance)
{
//create template with single entity
const char* newEntityName = "New Entity";
AZ::Entity* newEntity = CreateEntity(newEntityName, false);
ASSERT_TRUE(newEntity);
AZ::EntityId entityId = newEntity->GetId();
//add a transform component for testing purposes
newEntity->CreateComponent(AZ::EditorTransformComponentTypeId);
newEntity->Init();
newEntity->Activate();
AZStd::unique_ptr<Instance> firstInstance = m_prefabSystemComponent->CreatePrefab({ newEntity }, {}, "test/path");
ASSERT_TRUE(firstInstance);
//get template id
TemplateId templateId = firstInstance->GetTemplateId();
//instantiate second instance
AZStd::unique_ptr<Instance> secondInstance = m_prefabSystemComponent->InstantiatePrefab(templateId);
ASSERT_TRUE(secondInstance);
//create document with before change snapshot
PrefabDom entityDomBeforeUpdate;
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBeforeUpdate, *newEntity);
//update values on entity
const float updatedXValue = 5.0f;
AZ::TransformBus::Event(entityId, &AZ::TransformInterface::SetWorldX, updatedXValue);
//create document with after change snapshot
PrefabDom entityDomAfterUpdate;
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomAfterUpdate, *newEntity);
//generate patch
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, entityDomBeforeUpdate, entityDomAfterUpdate);
//update template
m_instanceToTemplateInterface->PatchEntityInTemplate(patch, entityId);
//activate the entity so we can access via transform bus
secondInstance->InitializeNestedEntities();
secondInstance->ActivateNestedEntities();
//get the entity id
AZStd::vector<AZ::EntityId> entityIdVector;
secondInstance->GetEntityIds([&entityIdVector](const AZ::EntityId& entityId)
{
entityIdVector.push_back(entityId);
return true;
});
EXPECT_EQ(entityIdVector.size(), 1);
AZStd::optional<AZ::EntityId> secondEntityId = entityIdVector[0];
//verify template updated correctly
//get the values from the transform on the entity
float confirmXValue = 0.0f;
AZ::TransformBus::EventResult(confirmXValue, entityId, &AZ::TransformInterface::GetWorldX);
AZ::TransformBus::EventResult(confirmXValue, secondEntityId.value(), &AZ::TransformInterface::GetWorldX);
ASSERT_TRUE(confirmXValue == updatedXValue);
}
TEST_F(PrefabInstanceToTemplateTests, PrefabUpdateTemplate_AddEntityToInstance)
{
//create template with single entity
const char* newEntityName = "New Entity";
AZ::Entity* newEntity = CreateEntity(newEntityName, false);
ASSERT_TRUE(newEntity);
//create a first instance where the entity will be added
AZStd::unique_ptr<Instance> firstInstance = m_prefabSystemComponent->CreatePrefab({}, {}, "test/path");
ASSERT_TRUE(firstInstance);
//get template id
TemplateId templateId = firstInstance->GetTemplateId();
//instantiate second instance for checking if propogation works
AZStd::unique_ptr<Instance> secondInstance = m_prefabSystemComponent->InstantiatePrefab(templateId);
ASSERT_TRUE(secondInstance);
//create document with before change snapshot
PrefabDom instanceDomBeforeUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, *firstInstance);
//add entity to instance
firstInstance->AddEntity(*newEntity);
//create document with after change snapshot
PrefabDom instanceDomAfterUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfterUpdate, *firstInstance);
//generate patch
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, instanceDomBeforeUpdate, instanceDomAfterUpdate);
//update template
m_instanceToTemplateInterface->PatchTemplate(patch, templateId);
//get the entity id
AZStd::vector<AZ::EntityId> entityIdVector;
secondInstance->GetEntityIds([&entityIdVector](const AZ::EntityId& entityId)
{
entityIdVector.push_back(entityId);
return true;
});
EXPECT_EQ(entityIdVector.size(), 1);
}
TEST_F(PrefabInstanceToTemplateTests, PrefabUpdateTemplate_RemoveEntityFromInstance)
{
//create template with single entity
const char* newEntityName = "New Entity";
AZ::Entity* newEntity = CreateEntity(newEntityName, false);
ASSERT_TRUE(newEntity);
AZ::EntityId entityId = newEntity->GetId();
//create a first instance where the entity will be removed
AZStd::unique_ptr<Instance> firstInstance = m_prefabSystemComponent->CreatePrefab({ newEntity }, {}, "test/path");
ASSERT_TRUE(firstInstance);
//get template id
TemplateId templateId = firstInstance->GetTemplateId();
//instantiate second instance for checking if propogation works
AZStd::unique_ptr<Instance> secondInstance = m_prefabSystemComponent->InstantiatePrefab(templateId);
ASSERT_TRUE(secondInstance);
//create document with before change snapshot
PrefabDom instanceDomBeforeUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, *firstInstance);
//remove entity from instance
firstInstance->DetachEntity(entityId);
//create document with after change snapshot
PrefabDom instanceDomAfterUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfterUpdate, *firstInstance);
//generate patch
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, instanceDomBeforeUpdate, instanceDomAfterUpdate);
//update template
m_instanceToTemplateInterface->PatchTemplate(patch, templateId);
//get the entity id
AZStd::vector<AZ::EntityId> entityIdVector;
secondInstance->GetEntityIds([&entityIdVector](const AZ::EntityId& entityId)
{
entityIdVector.push_back(entityId);
return true;
});
EXPECT_EQ(entityIdVector.size(), 0);
}
TEST_F(PrefabInstanceToTemplateTests, PrefabUpdateTemplate_AddInstanceToInstance)
{
//create a first instance where the instance will be added
AZStd::unique_ptr<Instance> firstInstance = m_prefabSystemComponent->CreatePrefab({}, {}, "test/path");
ASSERT_TRUE(firstInstance);
//get template id
TemplateId templateId = firstInstance->GetTemplateId();
//instantiate second instance for checking if propogation works
AZStd::unique_ptr<Instance> secondInstance = m_prefabSystemComponent->InstantiatePrefab(templateId);
ASSERT_TRUE(secondInstance);
//create document with before change snapshot
PrefabDom instanceDomBeforeUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, *firstInstance);
//create new instance and get alias
AZStd::unique_ptr<Instance> addedInstance = m_prefabSystemComponent->CreatePrefab({}, {}, "test/pathtest");
//add instance to instance
InstanceOptionalConstReference addedInstanceRef { firstInstance->AddInstance(AZStd::move(addedInstance)) };
const AzToolsFramework::Prefab::InstanceAlias addedAlias = addedInstanceRef->get().GetInstanceAlias();
//create document with after change snapshot
PrefabDom instanceDomAfterUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfterUpdate, *firstInstance);
//generate patch
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, instanceDomBeforeUpdate, instanceDomAfterUpdate);
//update template
m_instanceToTemplateInterface->PatchTemplate(patch, templateId);
EXPECT_NE(secondInstance->FindNestedInstance(addedAlias), AZStd::nullopt);
}
TEST_F(PrefabInstanceToTemplateTests, PrefabUpdateTemplate_RemoveInstanceFromInstance)
{
AZStd::unique_ptr<Instance> addedInstancePtr = m_prefabSystemComponent->CreatePrefab({}, {}, "test/pathtest");
Instance& addedInstance = *addedInstancePtr;
//create a first instance where the instance will be removed
AZStd::unique_ptr<Instance> firstInstance = m_prefabSystemComponent->CreatePrefab({}, MakeInstanceList( AZStd::move(addedInstancePtr) ), "test/path");
ASSERT_TRUE(firstInstance);
//get added instance alias
const AzToolsFramework::Prefab::InstanceAlias addedAlias = addedInstance.GetInstanceAlias();
//get template id
TemplateId templateId = firstInstance->GetTemplateId();
//instantiate second instance for checking if propogation works
AZStd::unique_ptr<Instance> secondInstance = m_prefabSystemComponent->InstantiatePrefab(templateId);
ASSERT_TRUE(secondInstance);
//create document with before change snapshot
PrefabDom instanceDomBeforeUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, *firstInstance);
//remove instance from instance
firstInstance->DetachNestedInstance(addedAlias);
//create document with after change snapshot
PrefabDom instanceDomAfterUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfterUpdate, *firstInstance);
//generate patch
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, instanceDomBeforeUpdate, instanceDomAfterUpdate);
//update template
m_instanceToTemplateInterface->PatchTemplate(patch, templateId);
EXPECT_EQ(secondInstance->FindNestedInstance(addedAlias), AZStd::nullopt);
}
}
@@ -0,0 +1,84 @@
/*
* 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 <Prefab/PrefabTestFixture.h>
namespace UnitTest
{
using PrefabInstantiateTest = PrefabTestFixture;
TEST_F(PrefabInstantiateTest, PrefabInstantiate_InstantiateInvalidTemplate_InstantiateFails)
{
EXPECT_FALSE(m_prefabSystemComponent->InstantiatePrefab(AzToolsFramework::Prefab::InvalidTemplateId));
}
TEST_F(PrefabInstantiateTest, PrefabInstantiate_NoNestingTemplate_InstantiateSucceeds)
{
AZ::Entity* newEntity = CreateEntity("New Entity");
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> firstInstance = m_prefabSystemComponent->CreatePrefab({ newEntity }, {}, "test/path");
ASSERT_TRUE(firstInstance);
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> secondInstance = m_prefabSystemComponent->InstantiatePrefab(firstInstance->GetTemplateId());
ASSERT_TRUE(secondInstance);
CompareInstances(*firstInstance, *secondInstance);
}
TEST_F(PrefabInstantiateTest, PrefabInstantiate_TripleNestingTemplate_InstantiateSucceeds)
{
AZ::Entity* newEntity = CreateEntity("New Entity");
// Build a 3 level deep nested Template
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> firstInstance = m_prefabSystemComponent->CreatePrefab({ newEntity }, {}, "test/path1");
ASSERT_TRUE(firstInstance);
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> secondInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(firstInstance) ), "test/path2");
ASSERT_TRUE(secondInstance);
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> thirdInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(secondInstance) ), "test/path3");
ASSERT_TRUE(thirdInstance);
//Instantiate it
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> fourthInstance =
m_prefabSystemComponent->InstantiatePrefab(thirdInstance->GetTemplateId());
ASSERT_TRUE(fourthInstance);
CompareInstances(*thirdInstance, *fourthInstance, false);
}
TEST_F(PrefabInstantiateTest, PrefabInstantiate_Instantiate10Times_InstantiatesSucceed)
{
AZ::Entity* newEntity = CreateEntity("New Entity");
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> firstInstance = m_prefabSystemComponent->CreatePrefab({ newEntity }, {}, "test/path");
// Store the generated instances so that the unique_ptrs are destroyed at the end of the test
// This allows us to have all the instances around at the same time
AZStd::vector<AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>> newInstances;
for (int instanceCount = 0; instanceCount < 10; ++instanceCount)
{
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>
newInstance(m_prefabSystemComponent->InstantiatePrefab(firstInstance->GetTemplateId()));
ASSERT_TRUE(newInstance);
CompareInstances(*firstInstance, *newInstance);
newInstances.push_back(AZStd::move(newInstance));
}
}
}
@@ -0,0 +1,291 @@
/*
* 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 <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <Prefab/MockPrefabFileIOActionValidator.h>
#include <Prefab/PrefabTestData.h>
#include <Prefab/PrefabTestDataUtils.h>
#include <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestFixture.h>
namespace UnitTest
{
using PrefabLoadTemplateTest = PrefabTestFixture;
TEST_F(PrefabLoadTemplateTest, LoadTemplate_TemplateWithNoNestedInstance)
{
TemplateData templateData;
templateData.m_filePath = "path/to/template/with/no/nested/instance";
MockPrefabFileIOActionValidator mockIOActionValidator;
mockIOActionValidator.ReadPrefabDom(
templateData.m_filePath, PrefabTestDomUtils::CreatePrefabDom());
templateData.m_id = m_prefabLoaderInterface->LoadTemplate(templateData.m_filePath);
PrefabTestDataUtils::ValidateTemplateLoad(templateData);
}
TEST_F(PrefabLoadTemplateTest, LoadTemplate_TemplateWithOneNestedInstance_WithNoPatches)
{
TemplateData sourceTemplateData;
sourceTemplateData.m_filePath = "path/to/template/with/no/nested/instance";
TemplateData targetTemplateData;
targetTemplateData.m_filePath = "path/to/template/with/one/nested/instance";
InstanceData targetTemplateInstanceData = PrefabTestDataUtils::CreateInstanceDataWithNoPatches(
"sourceTemplateInstance", sourceTemplateData.m_filePath);
targetTemplateData.m_instancesData[targetTemplateInstanceData.m_name] = targetTemplateInstanceData;
MockPrefabFileIOActionValidator mockIOActionValidator;
mockIOActionValidator.ReadPrefabDom(
sourceTemplateData.m_filePath, PrefabTestDomUtils::CreatePrefabDom());
mockIOActionValidator.ReadPrefabDom(
targetTemplateData.m_filePath, PrefabTestDomUtils::CreatePrefabDom({ targetTemplateInstanceData }));
targetTemplateData.m_id = m_prefabLoaderInterface->LoadTemplate(targetTemplateData.m_filePath);
sourceTemplateData.m_id = m_prefabSystemComponent->GetTemplateIdFromFilePath(sourceTemplateData.m_filePath);
LinkData linkData = PrefabTestDataUtils::CreateLinkData(
targetTemplateInstanceData, sourceTemplateData.m_id, targetTemplateData.m_id);
PrefabTestDataUtils::CheckIfTemplatesConnected(sourceTemplateData, targetTemplateData, linkData);
}
TEST_F(PrefabLoadTemplateTest, LoadTemplate_TemplateDependingOnItself_TemplateLoadedWithErrorsAdded)
{
TemplateData templateData;
templateData.m_filePath = "path/to/template/depending/on/itself";
auto templatePrefabDom = PrefabTestDomUtils::CreatePrefabDom({
PrefabTestDataUtils::CreateInstanceDataWithNoPatches("instance", templateData.m_filePath) });
MockPrefabFileIOActionValidator mockIOActionValidator;
mockIOActionValidator.ReadPrefabDom(templateData.m_filePath, templatePrefabDom);
templateData.m_id = m_prefabLoaderInterface->LoadTemplate(templateData.m_filePath);
templateData.m_isLoadedWithErrors = true;
PrefabTestDataUtils::ValidateTemplateLoad(templateData);
}
TEST_F(PrefabLoadTemplateTest, LoadTemplate_SourceTemplateDependingOnTargetTemplate_TemplatesLoadedWithErrorsAdded)
{
// Prepare two Template Data which has cyclical dependency between them.
// Set data of expected source Template.
TemplateData sourceTemplateData;
sourceTemplateData.m_filePath = "path/to/source/template";
// Set data of expected target Template.
TemplateData targetTemplateData;
targetTemplateData.m_filePath = "path/to/target/template";
// Set data of expected nested Instance in source Template.
// The Template of this Instance is target Template so that
// source Template depends on target Template.
InstanceData sourceTemplateInstanceData = PrefabTestDataUtils::CreateInstanceDataWithNoPatches(
"targetTemplateInstance", targetTemplateData.m_filePath);
// Data of expected nested Instance in target Template.
// The Template of this Instance is source Template so that
// target Template depends on source Template.
InstanceData targetTemplateInstanceData = PrefabTestDataUtils::CreateInstanceDataWithNoPatches(
"sourceTemplateInstance", sourceTemplateData.m_filePath);
// Set expected target Template's Instance data.
// There should be NO Instance data in expected source Template
// since cyclical dependency will be detected and LoadTemplate will stop.
targetTemplateData.m_instancesData[targetTemplateInstanceData.m_name] = targetTemplateInstanceData;
// Create PrefabDoms for both source/target Template.
auto sourceTemplatePrefabDom = PrefabTestDomUtils::CreatePrefabDom({ sourceTemplateInstanceData });
auto targetTemplatePrefabDom = PrefabTestDomUtils::CreatePrefabDom({ targetTemplateInstanceData });
// The mock file IO will let the PrefabSystemComponent read expected PrefabDoms while calling LoadTemplate.
MockPrefabFileIOActionValidator mockIOActionValidator;
mockIOActionValidator.ReadPrefabDom(
sourceTemplateData.m_filePath, sourceTemplatePrefabDom);
mockIOActionValidator.ReadPrefabDom(
targetTemplateData.m_filePath, targetTemplatePrefabDom);
// Load target and source Templates and get their Ids.
targetTemplateData.m_id = m_prefabLoaderInterface->LoadTemplate(targetTemplateData.m_filePath);
sourceTemplateData.m_id = m_prefabSystemComponent->GetTemplateIdFromFilePath(sourceTemplateData.m_filePath);
// Because of cyclical dependency, the two Templates should be loaded with errors.
sourceTemplateData.m_isLoadedWithErrors = true;
targetTemplateData.m_isLoadedWithErrors = true;
// Set expected data of Link from source Template to target Template.
// There should be no Link from target Template to source Template.
LinkData linkData = PrefabTestDataUtils::CreateLinkData(
targetTemplateInstanceData, sourceTemplateData.m_id, targetTemplateData.m_id);
// Verify if actual source/target Templates have the expected Template data.
// Also check if actual Link from source to target has the expected Link data.
PrefabTestDataUtils::CheckIfTemplatesConnected(sourceTemplateData, targetTemplateData, linkData);
}
TEST_F(PrefabLoadTemplateTest, LoadTemplate_InstanceWithEmptySource_TemplateLoadedWithErrorsAdded)
{
TemplateData templateData;
templateData.m_filePath = "path/to/template/with/no/instance/source";
templateData.m_isLoadedWithErrors = true;
auto templatePrefabDom = PrefabTestDomUtils::CreatePrefabDom({
PrefabTestDataUtils::CreateInstanceDataWithNoPatches("templateInstance", "") });
MockPrefabFileIOActionValidator mockIOActionValidator;
mockIOActionValidator.ReadPrefabDom(templateData.m_filePath, templatePrefabDom);
templateData.m_id = m_prefabLoaderInterface->LoadTemplate(templateData.m_filePath);
PrefabTestDataUtils::ValidateTemplateLoad(templateData);
}
TEST_F(PrefabLoadTemplateTest, LoadTemplate_InstanceWithEmptyName_TemplateLoadedWithErrorsAdded)
{
TemplateData templateData;
templateData.m_filePath = "path/to/template/with/no/instance/name";
templateData.m_isLoadedWithErrors = true;
auto templatePrefabDom = PrefabTestDomUtils::CreatePrefabDom({
PrefabTestDataUtils::CreateInstanceDataWithNoPatches("", "template/instance/source") });
MockPrefabFileIOActionValidator mockIOActionValidator;
mockIOActionValidator.ReadPrefabDom(templateData.m_filePath, templatePrefabDom);
templateData.m_id = m_prefabLoaderInterface->LoadTemplate(templateData.m_filePath);
PrefabTestDataUtils::ValidateTemplateLoad(templateData);
}
TEST_F(PrefabLoadTemplateTest, LoadTemplate_OpenSourceTemplateFileFailed_TemplateLoadedWithErrorsAdded)
{
TemplateData templateData;
templateData.m_filePath = "path/to/template";
templateData.m_isLoadedWithErrors = true;
InstanceData templateInstanceData = PrefabTestDataUtils::CreateInstanceDataWithNoPatches(
"templateInstance", "wrong/path");
MockPrefabFileIOActionValidator mockIOActionValidator;
mockIOActionValidator.ReadPrefabDom(
templateData.m_filePath,
PrefabTestDomUtils::CreatePrefabDom({ templateInstanceData }));
mockIOActionValidator.ReadPrefabDom(
templateInstanceData.m_source, PrefabTestDomUtils::CreatePrefabDom(),
AZ::IO::ResultCode::Success, AZ::IO::ResultCode::Error);
templateData.m_id = m_prefabLoaderInterface->LoadTemplate(templateData.m_filePath);
PrefabTestDataUtils::ValidateTemplateLoad(templateData);
}
TEST_F(PrefabLoadTemplateTest, LoadTemplate_MultiLevelTemplates_WithNoPatches)
{
MockPrefabFileIOActionValidator mockIOActionValidator;
AZStd::vector<TemplateData> templatesData;
const int nestedHierarchyLevel = 3;
for (int i = 0; i < nestedHierarchyLevel; i++)
{
TemplateData templateData;
templateData.m_filePath = AZStd::string::format("path/to/level/%d/template", i);
templatesData.emplace_back(templateData);
if (i != 0)
{
InstanceData templateInstanceData = PrefabTestDataUtils::CreateInstanceDataWithNoPatches(
AZStd::string::format("level%dTemplateInstance", i), templatesData[i - 1].m_filePath);
templatesData[i].m_instancesData[templateInstanceData.m_name] = templateInstanceData;
mockIOActionValidator.ReadPrefabDom(
templatesData[i].m_filePath, PrefabTestDomUtils::CreatePrefabDom({ templateInstanceData }));
}
else
{
mockIOActionValidator.ReadPrefabDom(
templatesData[i].m_filePath, PrefabTestDomUtils::CreatePrefabDom());
}
}
templatesData.back().m_id = m_prefabLoaderInterface->LoadTemplate(templatesData.back().m_filePath);
for (int i = nestedHierarchyLevel - 2; i >= 0; i--)
{
templatesData[i].m_id = m_prefabSystemComponent->GetTemplateIdFromFilePath(templatesData[i].m_filePath);
LinkData linkData = PrefabTestDataUtils::CreateLinkData(
templatesData[i + 1].m_instancesData[AZStd::string::format("level%dTemplateInstance", i + 1)],
templatesData[i].m_id, templatesData[i + 1].m_id);
PrefabTestDataUtils::CheckIfTemplatesConnected(templatesData[i], templatesData[i + 1], linkData);
}
}
TEST_F(PrefabLoadTemplateTest, LoadTemplate_TemplateWithMultiInstances_WithNoPatches)
{
MockPrefabFileIOActionValidator mockIOActionValidator;
AZStd::vector<TemplateData> sourceTemplatesData;
AZStd::vector<InstanceData> targetTemplateInstancesData;
TemplateData targetTemplateData;
targetTemplateData.m_filePath = "path/to/target/template";
const int numInstances = 3;
for (int i = 0; i < numInstances; i++)
{
TemplateData sourceTemplateData;
sourceTemplateData.m_filePath = AZStd::string::format("path/to/source/%d/template", i);
InstanceData targetTemplateInstanceData = PrefabTestDataUtils::CreateInstanceDataWithNoPatches(
AZStd::string::format("source%dTemplateInstance", i), sourceTemplateData.m_filePath);
targetTemplateData.m_instancesData[targetTemplateInstanceData.m_name] = targetTemplateInstanceData;
mockIOActionValidator.ReadPrefabDom(
sourceTemplateData.m_filePath, PrefabTestDomUtils::CreatePrefabDom());
sourceTemplatesData.emplace_back(sourceTemplateData);
targetTemplateInstancesData.emplace_back(targetTemplateInstanceData);
}
mockIOActionValidator.ReadPrefabDom(
targetTemplateData.m_filePath, PrefabTestDomUtils::CreatePrefabDom(targetTemplateInstancesData));
targetTemplateData.m_id = m_prefabLoaderInterface->LoadTemplate(targetTemplateData.m_filePath);
for (int i = 0; i < numInstances; i++)
{
sourceTemplatesData[i].m_id = m_prefabSystemComponent->GetTemplateIdFromFilePath(sourceTemplatesData[i].m_filePath);
LinkData linkFromSourceData = PrefabTestDataUtils::CreateLinkData(targetTemplateInstancesData[i],
sourceTemplatesData[i].m_id, targetTemplateData.m_id);
PrefabTestDataUtils::CheckIfTemplatesConnected(sourceTemplatesData[i], targetTemplateData, linkFromSourceData);
}
}
TEST_F(PrefabLoadTemplateTest, LoadTemplate_LoadCorruptedPrefabFileData_InvalidTemplateIdReturned)
{
const AZStd::string corruptedPrefabContent = "{ Corrupted PrefabDom";
const AZStd::string pathToCorruptedPrefab = "path/to/corrupted/prefab/file";
MockPrefabFileIOActionValidator mockIOActionValidator;
mockIOActionValidator.ReadPrefabDom(pathToCorruptedPrefab, corruptedPrefabContent);
auto tmeplateId = m_prefabLoaderInterface->LoadTemplate(pathToCorruptedPrefab);
EXPECT_EQ(tmeplateId, AzToolsFramework::Prefab::InvalidTemplateId);
}
}
@@ -0,0 +1,32 @@
/*
* 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 <Prefab/PrefabTestComponent.h>
namespace UnitTest
{
void PrefabTestComponent::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<PrefabTestComponent, AzToolsFramework::Components::EditorComponentBase>()->
Field("BoolProperty", &PrefabTestComponent::m_boolProperty);
}
}
PrefabTestComponent::PrefabTestComponent(bool boolProperty)
: m_boolProperty(boolProperty)
{
}
}
@@ -0,0 +1,32 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
namespace UnitTest
{
class PrefabTestComponent
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_EDITOR_COMPONENT(PrefabTestComponent, "{C5FCF40A-FAEC-473C-BFAF-68A66DC45B33}");
PrefabTestComponent() = default;
explicit PrefabTestComponent(bool boolProperty);
static void Reflect(AZ::ReflectContext* reflection);
bool m_boolProperty = false;
};
}
@@ -0,0 +1,32 @@
/*
* 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 <Prefab/PrefabTestData.h>
namespace UnitTest
{
InstanceData::InstanceData(const InstanceData& other)
: m_name(other.m_name)
, m_source(other.m_source)
{
m_patches.CopyFrom(other.m_patches, m_patches.GetAllocator());
}
InstanceData& InstanceData::InstanceData::operator=(
const InstanceData& other)
{
m_name = other.m_name;
m_source = other.m_source;
m_patches.CopyFrom(other.m_patches, m_patches.GetAllocator());
return *this;
}
}
@@ -0,0 +1,51 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
namespace UnitTest
{
struct InstanceData
{
InstanceData() = default;
InstanceData(const InstanceData& other);
InstanceData& operator=(const InstanceData& other);
AZStd::string m_name;
AZStd::string m_source;
AzToolsFramework::Prefab::PrefabDom m_patches;
};
struct TemplateData
{
AzToolsFramework::Prefab::TemplateId m_id = AzToolsFramework::Prefab::InvalidTemplateId;
bool m_isValid = true;
bool m_isLoadedWithErrors = false;
AZStd::string m_filePath;
AZStd::unordered_map<AZStd::string, InstanceData> m_instancesData;
};
struct LinkData
{
bool m_isValid = true;
InstanceData m_instanceData;
AzToolsFramework::Prefab::TemplateId m_sourceTemplateId = AzToolsFramework::Prefab::InvalidTemplateId;
AzToolsFramework::Prefab::TemplateId m_targetTemplateId = AzToolsFramework::Prefab::InvalidTemplateId;
};
}
@@ -0,0 +1,147 @@
/*
* 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 <Prefab/PrefabTestDataUtils.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <Prefab/PrefabTestDomUtils.h>
namespace UnitTest
{
namespace PrefabTestDataUtils
{
using namespace AzToolsFramework::Prefab;
LinkData CreateLinkData(
const InstanceData& instanceData,
const TemplateId& sourceTemplateId,
const TemplateId& targetTemplateId)
{
LinkData newLinkData;
newLinkData.m_instanceData = instanceData;
newLinkData.m_sourceTemplateId = sourceTemplateId;
newLinkData.m_targetTemplateId = targetTemplateId;
return newLinkData;
}
InstanceData CreateInstanceDataWithNoPatches(
const AZStd::string& name,
const AZStd::string& source)
{
InstanceData newInstanceData;
newInstanceData.m_name = name;
newInstanceData.m_source = source;
return newInstanceData;
}
void ValidateTemplateLoad(
const TemplateData& expectedTemplateData)
{
PrefabSystemComponentInterface* prefabSystemComponent = AZ::Interface<PrefabSystemComponentInterface>::Get();
ASSERT_TRUE(prefabSystemComponent != nullptr);
ASSERT_TRUE(expectedTemplateData.m_id != InvalidTemplateId);
auto templateReference = prefabSystemComponent->FindTemplate(expectedTemplateData.m_id);
ASSERT_TRUE(templateReference.has_value());
auto& actualTemplate = templateReference->get();
EXPECT_EQ(expectedTemplateData.m_filePath, actualTemplate.GetFilePath());
EXPECT_EQ(expectedTemplateData.m_isValid, actualTemplate.IsValid());
EXPECT_EQ(expectedTemplateData.m_isLoadedWithErrors, actualTemplate.IsLoadedWithErrors());
auto& actualInstancesLinkIds = actualTemplate.GetLinks();
EXPECT_EQ(expectedTemplateData.m_instancesData.size(), actualInstancesLinkIds.size());
for (auto& actualLinkId : actualInstancesLinkIds)
{
auto linkReference = prefabSystemComponent->FindLink(actualLinkId);
ASSERT_TRUE(linkReference.has_value());
auto& actualLink = linkReference->get();
AZStd::string actualLinkName(actualLink.GetInstanceName());
EXPECT_EQ(expectedTemplateData.m_instancesData.count(actualLinkName), 1);
auto& expectedInstanceData = expectedTemplateData.m_instancesData.find(actualLinkName)->second;
EXPECT_EQ(expectedTemplateData.m_id, actualLink.GetTargetTemplateId());
EXPECT_EQ(expectedInstanceData.m_name, actualLinkName);
EXPECT_EQ(
PrefabTestDomUtils::GetPrefabDomInstancePath(expectedInstanceData.m_name.c_str()),
actualLink.GetInstancePath());
ValidateTemplatePatches(actualLink, expectedInstanceData.m_patches);
}
}
void ValidateTemplatePatches(const Link& actualLink, const PrefabDom& expectedTemplatePatches)
{
PrefabDomValueConstReference patchesReference =
PrefabDomUtils::FindPrefabDomValue(actualLink.GetLinkDom(), PrefabDomUtils::PatchesName);
if (!expectedTemplatePatches.IsNull())
{
EXPECT_EQ(AZ::JsonSerialization::Compare(expectedTemplatePatches, patchesReference->get()),
AZ::JsonSerializerCompareResult::Equal);
}
else
{
EXPECT_FALSE(patchesReference.has_value());
}
}
void CheckIfTemplatesConnected(
const TemplateData& expectedSourceTemplateData,
const TemplateData& expectedTargetTemplateData,
const LinkData& expectedLinkData)
{
ValidateTemplateLoad(expectedSourceTemplateData);
ValidateTemplateLoad(expectedTargetTemplateData);
PrefabSystemComponentInterface* prefabSystemComponent = AZ::Interface<PrefabSystemComponentInterface>::Get();
ASSERT_TRUE(prefabSystemComponent != nullptr);
auto& actualSourceTemplate =
prefabSystemComponent->FindTemplate(expectedSourceTemplateData.m_id)->get();
auto& actualTargetTemplate =
prefabSystemComponent->FindTemplate(expectedTargetTemplateData.m_id)->get();
EXPECT_EQ(expectedLinkData.m_instanceData.m_source, actualSourceTemplate.GetFilePath());
auto& actualTargetTemplateLinkIds = actualTargetTemplate.GetLinks();
EXPECT_EQ(expectedTargetTemplateData.m_instancesData.size(), actualTargetTemplateLinkIds.size());
bool expectedLinkFound = false;
for (auto actualTargetTemplateLinkId : actualTargetTemplateLinkIds)
{
auto linkReference = prefabSystemComponent->FindLink(actualTargetTemplateLinkId);
ASSERT_TRUE(linkReference.has_value());
auto& actualLink = linkReference->get();
if (expectedLinkData.m_instanceData.m_name == actualLink.GetInstanceName())
{
EXPECT_EQ(expectedLinkData.m_isValid, actualLink.IsValid());
EXPECT_EQ(expectedLinkData.m_sourceTemplateId, actualLink.GetSourceTemplateId());
EXPECT_EQ(expectedLinkData.m_targetTemplateId, actualLink.GetTargetTemplateId());
ValidateTemplatePatches(actualLink, expectedLinkData.m_instanceData.m_patches);
EXPECT_EQ(
PrefabTestDomUtils::GetPrefabDomInstancePath(expectedLinkData.m_instanceData.m_name.c_str()),
actualLink.GetInstancePath());
expectedLinkFound = true;
break;
}
}
EXPECT_TRUE(expectedLinkFound);
}
}
}
@@ -0,0 +1,44 @@
/*
* 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.
*
*/
#pragma once
#include <Prefab/PrefabTestData.h>
#include <AzToolsFramework/Prefab/Link/Link.h>
namespace UnitTest
{
namespace PrefabTestDataUtils
{
LinkData CreateLinkData(
const InstanceData& instanceData,
const AzToolsFramework::Prefab::TemplateId& sourceTemplateId,
const AzToolsFramework::Prefab::TemplateId& targetTemplateId);
InstanceData CreateInstanceDataWithNoPatches(
const AZStd::string& name,
const AZStd::string& source);
void ValidateTemplateLoad(
const TemplateData& expectedTemplateData);
void ValidateTemplatePatches(
const AzToolsFramework::Prefab::Link& actualLink,
const AzToolsFramework::Prefab::PrefabDom& expectedTemplatePatches);
void CheckIfTemplatesConnected(
const TemplateData& expectedSourceTemplateData,
const TemplateData& expectedTargetTemplateData,
const LinkData& expectedLinkData);
}
}
@@ -0,0 +1,222 @@
/*
* 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 <Prefab/PrefabTestDomUtils.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/optional.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Prefab/Instance/TemplateInstanceMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
namespace UnitTest
{
namespace PrefabTestDomUtils
{
void SetPrefabDomInstance(
PrefabDom& prefabDom,
const char* instanceName,
const char* source,
const PrefabDomValue& patches)
{
rapidjson::SetValueByPointer(prefabDom, GetPrefabDomSourcePath(instanceName), source);
if (!patches.IsNull())
{
rapidjson::SetValueByPointer(prefabDom, GetPrefabDomPatchesPath(instanceName), patches, prefabDom.GetAllocator());
}
}
PrefabDom CreatePrefabDom()
{
PrefabDom newPrefabDom;
rapidjson::SetValueByPointer(newPrefabDom, "/Entities", rapidjson::Value());
return newPrefabDom;
}
PrefabDom CreatePrefabDom(
const AZStd::vector<InstanceData>& instancesData)
{
PrefabDom newPrefabDom = CreatePrefabDom();
for (auto& instanceData : instancesData)
{
PrefabTestDomUtils::SetPrefabDomInstance(
newPrefabDom, instanceData.m_name.c_str(),
instanceData.m_source.c_str(), instanceData.m_patches);
}
return newPrefabDom;
}
void ValidateInstances(
const TemplateId& templateId,
const PrefabDomValue& expectedContent,
const PrefabDomPath& contentPath,
bool isContentAnInstance)
{
TemplateInstanceMapperInterface* templateInstanceMapper =
AZ::Interface<TemplateInstanceMapperInterface>::Get();
ASSERT_TRUE(templateInstanceMapper != nullptr);
ASSERT_TRUE(templateId != AzToolsFramework::Prefab::InvalidTemplateId);
auto instancesReference = templateInstanceMapper->FindInstancesOwnedByTemplate(templateId);
ASSERT_TRUE(instancesReference.has_value());
auto& actualInstances = instancesReference->get();
for (auto instance : actualInstances)
{
PrefabDom instancePrefabDom;
const bool result = PrefabDomUtils::StoreInstanceInPrefabDom(*instance, instancePrefabDom);
ASSERT_TRUE(result);
auto* actualContent = contentPath.Get(instancePrefabDom);
ASSERT_TRUE(actualContent != nullptr);
if (isContentAnInstance)
{
ComparePrefabDoms(*actualContent, expectedContent, false);
}
else
{
ComparePrefabDomValues(*actualContent, expectedContent);
}
}
}
void ValidatePrefabDomEntities(const AZStd::vector<EntityAlias>& entityAliases, PrefabDom& prefabDom)
{
PrefabDomValueReference templateEntities = PrefabDomUtils::FindPrefabDomValue(prefabDom, "Entities");
ASSERT_TRUE(templateEntities.has_value());
for (EntityAlias entityAlias : entityAliases)
{
EXPECT_TRUE(PrefabDomUtils::FindPrefabDomValue(templateEntities->get(), entityAlias.c_str()).has_value());
}
}
void ValidatePrefabDomInstances(
const AZStd::vector<InstanceAlias>& instanceAliases,
const AzToolsFramework::Prefab::PrefabDom& prefabDom,
const AzToolsFramework::Prefab::PrefabDom& expectedNestedInstanceDom)
{
PrefabDomValueConstReference templateInstances = PrefabDomUtils::FindPrefabDomValue(prefabDom, "Instances");
ASSERT_TRUE(templateInstances.has_value());
for (InstanceAlias instanceAlias : instanceAliases)
{
PrefabDomValueConstReference actualNestedInstanceDom = PrefabDomUtils::FindPrefabDomValue(templateInstances->get(), instanceAlias.c_str());
ASSERT_TRUE(actualNestedInstanceDom.has_value());
ComparePrefabDoms(actualNestedInstanceDom, expectedNestedInstanceDom, false);
}
}
void ComparePrefabDoms(PrefabDomValueConstReference valueA, PrefabDomValueConstReference valueB, bool shouldCompareLinkIds)
{
ASSERT_TRUE(valueA.has_value());
ASSERT_TRUE(valueB.has_value());
const PrefabDomValue& valueADom = valueA->get();
const PrefabDomValue& valueBDom = valueB->get();
if (shouldCompareLinkIds)
{
EXPECT_EQ(AZ::JsonSerialization::Compare(valueADom, valueBDom), AZ::JsonSerializerCompareResult::Equal);
}
else
{
// Compare the source values of the two DOMs.
PrefabDomValueConstReference actualNestedInstanceDomSource =
PrefabDomUtils::FindPrefabDomValue(valueADom, PrefabDomUtils::SourceName);
PrefabDomValueConstReference expectedNestedInstanceDomSource =
PrefabDomUtils::FindPrefabDomValue(valueBDom, PrefabDomUtils::SourceName);
ComparePrefabDomValues(actualNestedInstanceDomSource, expectedNestedInstanceDomSource);
// Compare the entities values of the two DOMs.
PrefabDomValueConstReference actualNestedInstanceDomEntities =
PrefabDomUtils::FindPrefabDomValue(valueADom, PrefabTestDomUtils::EntitiesValueName);
PrefabDomValueConstReference expectedNestedInstanceDomEntities =
PrefabDomUtils::FindPrefabDomValue(valueBDom, PrefabTestDomUtils::EntitiesValueName);
ComparePrefabDomValues(actualNestedInstanceDomEntities, expectedNestedInstanceDomEntities);
// Compare the instances values of the two DOMs, which involves iterating over each expected instance and comparing it
// with its counterpart in the actual instance.
PrefabDomValueConstReference actualNestedInstanceDomInstances =
PrefabDomUtils::FindPrefabDomValue(valueADom, PrefabDomUtils::InstancesName);
PrefabDomValueConstReference expectedNestedInstanceDomInstances =
PrefabDomUtils::FindPrefabDomValue(valueBDom, PrefabDomUtils::InstancesName);
if (expectedNestedInstanceDomInstances.has_value())
{
ASSERT_TRUE(actualNestedInstanceDomInstances.has_value());
for (auto instanceIterator = expectedNestedInstanceDomInstances->get().MemberBegin();
instanceIterator != expectedNestedInstanceDomInstances->get().MemberEnd(); ++instanceIterator)
{
ComparePrefabDoms(instanceIterator->value,
PrefabDomUtils::FindPrefabDomValue(actualNestedInstanceDomInstances->get(), instanceIterator->name.GetString()),
shouldCompareLinkIds);
}
}
}
}
void ComparePrefabDomValues(PrefabDomValueConstReference valueA, PrefabDomValueConstReference valueB)
{
if (!valueA.has_value())
{
EXPECT_FALSE(valueB.has_value());
}
else
{
EXPECT_TRUE(valueB.has_value());
EXPECT_EQ(AZ::JsonSerialization::Compare(valueA->get(), valueB->get()), AZ::JsonSerializerCompareResult::Equal);
}
}
void PrintPrefabDom(const AzToolsFramework::Prefab::PrefabDomValue& prefabDom)
{
rapidjson::StringBuffer prefabBuffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(prefabBuffer);
prefabDom.Accept(writer);
std::cout << prefabBuffer.GetString() << std::endl;
}
void ValidateEntitiesOfInstances(
const AzToolsFramework::Prefab::TemplateId& templateId,
const AzToolsFramework::Prefab::PrefabDom& expectedPrefabDom,
const AZStd::vector<EntityAlias>& entityAliases)
{
for (auto& entityAlias : entityAliases)
{
PrefabDomPath entityPath = PrefabTestDomUtils::GetPrefabDomEntityPath(entityAlias);
const PrefabDomValue* expectedEntityValue = PrefabTestDomUtils::GetPrefabDomEntity(expectedPrefabDom, entityAlias);
ASSERT_TRUE(expectedEntityValue != nullptr);
PrefabTestDomUtils::ValidateInstances(templateId, *expectedEntityValue, entityPath);
}
}
void ValidateNestedInstancesOfInstances(
const AzToolsFramework::Prefab::TemplateId& templateId,
const AzToolsFramework::Prefab::PrefabDom& expectedPrefabDom,
const AZStd::vector<InstanceAlias>& nestedInstanceAliases)
{
for (auto& nestedInstanceAlias : nestedInstanceAliases)
{
PrefabDomPath nestedInstancePath = PrefabTestDomUtils::GetPrefabDomInstancePath(nestedInstanceAlias);
const PrefabDomValue* nestedInstanceValue =
PrefabTestDomUtils::GetPrefabDomInstance(expectedPrefabDom, nestedInstanceAlias);
ASSERT_TRUE(nestedInstanceValue != nullptr);
PrefabTestDomUtils::ValidateInstances(templateId, *nestedInstanceValue, nestedInstancePath, true);
}
}
}
}
@@ -0,0 +1,167 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <Prefab/PrefabTestData.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
namespace UnitTest
{
namespace PrefabTestDomUtils
{
using namespace AzToolsFramework::Prefab;
inline static const char* ComponentsValueName = "Components";
inline static const char* ComponentIdName = "Id";
inline static const char* EntitiesValueName = "Entities";
inline static const char* EntityNameValueName = "Name";
inline static const char* BoolPropertyName = "BoolProperty";
inline PrefabDomPath GetPrefabDomEntitiesPath()
{
return PrefabDomPath()
.Append(EntitiesValueName);
};
inline PrefabDomPath GetPrefabDomEntityPath(
const EntityAlias& entityAlias)
{
return GetPrefabDomEntitiesPath()
.Append(entityAlias.c_str(), entityAlias.length());
};
inline PrefabDomPath GetPrefabDomEntityNamePath(
const EntityAlias& entityAlias)
{
return GetPrefabDomEntityPath(entityAlias)
.Append(EntityNameValueName);
};
inline PrefabDomPath GetPrefabDomComponentsPath(const EntityAlias& entityAlias)
{
return GetPrefabDomEntityPath(entityAlias).Append(ComponentsValueName);
};
inline PrefabDomPath GetPrefabDomInstancesPath()
{
return PrefabDomPath()
.Append(PrefabDomUtils::InstancesName);
};
inline PrefabDomPath GetPrefabDomInstancePath(
const InstanceAlias& instanceAlias)
{
return GetPrefabDomInstancesPath().Append(instanceAlias.c_str(), instanceAlias.length());
};
inline PrefabDomPath GetPrefabDomInstancePath(
const char* instanceName)
{
return GetPrefabDomInstancesPath().Append(instanceName);
};
inline PrefabDomPath GetPrefabDomSourcePath(
const char* instanceName)
{
return GetPrefabDomInstancePath(instanceName).Append(PrefabDomUtils::SourceName);
};
inline PrefabDomPath GetPrefabDomPatchesPath(
const char* instanceName)
{
return GetPrefabDomInstancePath(instanceName).Append(PrefabDomUtils::PatchesName);
};
inline const PrefabDomValue* GetPrefabDomComponents(
const PrefabDom& prefabDom,
const EntityAlias& entityAlias)
{
return PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(prefabDom);
}
inline const PrefabDomValue* GetPrefabDomInstance(
const PrefabDom& prefabDom,
const InstanceAlias& instanceAlias)
{
return PrefabTestDomUtils::GetPrefabDomInstancePath(instanceAlias).Get(prefabDom);
}
inline const PrefabDomValue* GetPrefabDomEntity(
const PrefabDom& prefabDom,
const EntityAlias& entityAlias)
{
return PrefabTestDomUtils::GetPrefabDomEntityPath(entityAlias).Get(prefabDom);
}
inline const PrefabDomValue* GetPrefabDomEntityName(
const PrefabDom& prefabDom,
const EntityAlias& entityAlias)
{
return PrefabTestDomUtils::GetPrefabDomEntityNamePath(entityAlias).Get(prefabDom);
}
void SetPrefabDomInstance(
PrefabDom& prefabDom,
const char* instanceName,
const char* source,
const PrefabDomValue& patches);
void ValidateInstances(
const TemplateId& templateId,
const PrefabDomValue& expectedContent,
const PrefabDomPath& contentPath,
bool isContentAnInstance = false);
PrefabDom CreatePrefabDom();
PrefabDom CreatePrefabDom(const AZStd::vector<InstanceData>& instancesData);
/**
* Validates that the entities with the given entity aliases are present in the given prefab DOM.
*/
void ValidatePrefabDomEntities(const AZStd::vector<EntityAlias>& entityAliases,
PrefabDom& prefabDom);
/**
* Extracts the DOM of the instances using the given instance aliases from the prefab DOM and
* validates that they match with the expectedNestedInstanceDom.
*/
void ValidatePrefabDomInstances(const AZStd::vector<InstanceAlias>& instanceAliases,
const PrefabDom& prefabDom,
const PrefabDom& expectedNestedInstanceDom);
void ComparePrefabDoms(PrefabDomValueConstReference valueA, PrefabDomValueConstReference valueB, bool shouldCompareLinkIds = true);
void ComparePrefabDomValues(PrefabDomValueConstReference valueA, PrefabDomValueConstReference valueB);
/**
* Prints the contents of the given prefab DOM to the console in a readable format.
*/
void PrintPrefabDom(const PrefabDomValue& prefabDom);
void ValidateEntitiesOfInstances(
const AzToolsFramework::Prefab::TemplateId& templateId,
const AzToolsFramework::Prefab::PrefabDom& expectedPrefabDom,
const AZStd::vector<EntityAlias>& entityAliases);
void ValidateNestedInstancesOfInstances(
const AzToolsFramework::Prefab::TemplateId& templateId,
const AzToolsFramework::Prefab::PrefabDom& expectedPrefabDom,
const AZStd::vector<InstanceAlias>& nestedInstanceAliases);
}
}
@@ -0,0 +1,99 @@
/*
* 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 <Prefab/PrefabTestFixture.h>
#include <AzCore/Component/TransformBus.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <Prefab/PrefabTestComponent.h>
#include <Prefab/PrefabTestDomUtils.h>
namespace UnitTest
{
void PrefabTestFixture::SetUpEditorFixtureImpl()
{
// Acquire the system entity
AZ::Entity* systemEntity = GetApplication()->FindEntity(AZ::SystemEntityId);
EXPECT_TRUE(systemEntity);
// Acquire the prefab system component to gain access to its APIs for testing
m_prefabSystemComponent = systemEntity->FindComponent<AzToolsFramework::Prefab::PrefabSystemComponent>();
EXPECT_TRUE(m_prefabSystemComponent);
// Acquire the interface of PrefabLoader to gain access to its APIs for testing
m_prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
EXPECT_TRUE(m_prefabLoaderInterface);
// Acquire the interface of InstanceUpdateQueueInterface to gain access to its APIs for testing
m_instanceUpdateExecutorInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get();
EXPECT_TRUE(m_instanceUpdateExecutorInterface);
// Acquire the interface of InstanceToTemplate to gain access to its APIs for testing
m_instanceToTemplateInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceToTemplateInterface>::Get();
EXPECT_TRUE(m_instanceToTemplateInterface);
GetApplication()->RegisterComponentDescriptor(PrefabTestComponent::CreateDescriptor());
}
AZ::Entity* PrefabTestFixture::CreateEntity(const char* entityName, const bool shouldActivate)
{
// Circumvent the EntityContext system and generate a new entity with a transformcomponent
AZ::Entity* newEntity = aznew AZ::Entity(entityName);
if(shouldActivate)
{
newEntity->Init();
newEntity->Activate();
}
return newEntity;
}
void PrefabTestFixture::CompareInstances(const AzToolsFramework::Prefab::Instance& instanceA,
const AzToolsFramework::Prefab::Instance& instanceB, bool shouldCompareLinkIds)
{
AzToolsFramework::Prefab::TemplateId templateAId = instanceA.GetTemplateId();
AzToolsFramework::Prefab::TemplateId templateBId = instanceB.GetTemplateId();
ASSERT_TRUE(templateAId != AzToolsFramework::Prefab::InvalidTemplateId);
ASSERT_TRUE(templateBId != AzToolsFramework::Prefab::InvalidTemplateId);
EXPECT_EQ(templateAId, templateBId);
AzToolsFramework::Prefab::TemplateReference templateA =
m_prefabSystemComponent->FindTemplate(templateAId);
ASSERT_TRUE(templateA.has_value());
AzToolsFramework::Prefab::PrefabDom prefabDomA;
ASSERT_TRUE(AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(instanceA, prefabDomA));
AzToolsFramework::Prefab::PrefabDom prefabDomB;
ASSERT_TRUE(AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(instanceB, prefabDomB));
// Validate that both instances match when serialized
PrefabTestDomUtils::ComparePrefabDoms(prefabDomA, prefabDomB);
// Validate that the serialized instances match the shared template when serialized
PrefabTestDomUtils::ComparePrefabDoms(templateA->get().GetPrefabDom(), prefabDomB, shouldCompareLinkIds);
}
void PrefabTestFixture::DeleteInstances(const InstanceList& instancesToDelete)
{
for (Instance* instanceToDelete : instancesToDelete)
{
ASSERT_TRUE(instanceToDelete);
delete instanceToDelete;
instanceToDelete = nullptr;
}
}
}
@@ -0,0 +1,59 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <Prefab/PrefabTestData.h>
#include <Prefab/PrefabTestUtils.h>
namespace AzToolsFramework
{
namespace Prefab
{
class PrefabLoaderInterface;
}
}
namespace UnitTest
{
using namespace AzToolsFramework::Prefab;
using namespace PrefabTestUtils;
class PrefabTestFixture
: public ToolsApplicationFixture
{
protected:
inline static const char* PrefabMockFilePath = "SomePath";
inline static const char* NestedPrefabMockFilePath = "SomePathToNested";
inline static const char* WheelPrefabMockFilePath = "SomePathToWheel";
inline static const char* AxlePrefabMockFilePath = "SomePathToAxle";
inline static const char* CarPrefabMockFilePath = "SomePathToCar";
void SetUpEditorFixtureImpl() override;
AZ::Entity* CreateEntity(const char* entityName, const bool shouldActivate = true);
void CompareInstances(const Instance& instanceA,
const Instance& instanceB, bool shouldCompareLinkIds = true);
void DeleteInstances(const InstanceList& instancesToDelete);
PrefabSystemComponent* m_prefabSystemComponent = nullptr;
PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
InstanceUpdateExecutorInterface* m_instanceUpdateExecutorInterface = nullptr;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
};
}
@@ -0,0 +1,40 @@
/*
* 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.
*
*/
#pragma once
#include <vector>
#include <memory>
#include <Prefab/Instance/Instance.h>
namespace UnitTest
{
namespace PrefabTestUtils
{
using namespace AzToolsFramework::Prefab;
template<typename... InstanceArgs>
inline AZStd::vector<AZStd::unique_ptr<Instance>> MakeInstanceList(InstanceArgs&&... instances)
{
static_assert((AZStd::is_same_v<InstanceArgs, AZStd::unique_ptr<Instance>>&& ...), "All arguments must be a AZStd::unique_ptr<Instance>&&");
AZStd::vector<AZStd::unique_ptr<Instance>> instanceList;
instanceList.reserve(sizeof...(InstanceArgs));
(instanceList.emplace_back(AZStd::forward<InstanceArgs>(instances)), ...);
return instanceList;
}
inline AZStd::vector<AZStd::unique_ptr<Instance>> MakeInstanceList()
{
return {};
}
}
}
@@ -0,0 +1,444 @@
/*
* 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 <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <Prefab/PrefabTestComponent.h>
#include <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestFixture.h>
namespace UnitTest
{
using PrefabUpdateInstancesTest = PrefabTestFixture;
TEST_F(PrefabUpdateInstancesTest, PrefabUpdateInstances_UpdateEntityName_UpdateSucceeds)
{
// Create a Template from an Instance owning a single entity.
using namespace AzToolsFramework::Prefab;
const char* newEntityName = "New Entity";
AZ::Entity* newEntity = CreateEntity(newEntityName);
AZStd::unique_ptr<Instance> firstInstance = m_prefabSystemComponent->CreatePrefab({ newEntity }, {}, PrefabMockFilePath);
ASSERT_TRUE(firstInstance);
TemplateId newTemplateId = firstInstance->GetTemplateId();
EXPECT_TRUE(newTemplateId != InvalidTemplateId);
PrefabDom& templatePrefabDom = m_prefabSystemComponent->FindTemplateDom(newTemplateId);
AZStd::vector<EntityAlias> entityAliases = firstInstance->GetEntityAliases();
EXPECT_EQ(entityAliases.size(), 1);
// Instantiate Instances and validate if all entities of each Template's Instance have the given entity names.
const int numberOfInstances = 3;
AZStd::vector<AZStd::unique_ptr<Instance>> instantiatedInstances;
for (int i = 0; i < numberOfInstances; ++i)
{
instantiatedInstances.emplace_back(m_prefabSystemComponent->InstantiatePrefab(newTemplateId));
ASSERT_TRUE(instantiatedInstances.back());
EXPECT_EQ(instantiatedInstances.back()->GetTemplateId(), newTemplateId);
}
PrefabDomPath entityNamePath = PrefabTestDomUtils::GetPrefabDomEntityNamePath(entityAliases.front());
const PrefabDomValue* entityNameValue =
PrefabTestDomUtils::GetPrefabDomEntityName(templatePrefabDom, entityAliases.front());
ASSERT_TRUE(entityNameValue != nullptr);
PrefabTestDomUtils::ValidateInstances(newTemplateId, *entityNameValue, entityNamePath);
// Update Template's PrefabDom with a new entity name.
entityNamePath.Set(templatePrefabDom, "Updated Entity");
// Update Template's Instances.
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(newTemplateId);
const bool updateResult = m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
EXPECT_TRUE(updateResult);
// Validate if all entities of each Template's Instance have the updated entity names.
PrefabTestDomUtils::ValidateInstances(newTemplateId, *entityNameValue, entityNamePath);
}
TEST_F(PrefabUpdateInstancesTest, UpdatePrefabInstances_AddEntity_UpdateSucceeds)
{
// Create a Template from an Instance owning a single entity.
using namespace AzToolsFramework::Prefab;
AZ::Entity* entity1 = CreateEntity("Entity 1");
AZStd::unique_ptr<Instance> newInstance = m_prefabSystemComponent->CreatePrefab({ entity1 }, {}, PrefabMockFilePath);
TemplateId newTemplateId = newInstance->GetTemplateId();
EXPECT_TRUE(newTemplateId != InvalidTemplateId);
PrefabDom& newTemplateDom = m_prefabSystemComponent->FindTemplateDom(newTemplateId);
AZStd::vector<EntityAlias> newTemplateEntityAliases = newInstance->GetEntityAliases();
EXPECT_EQ(newTemplateEntityAliases.size(), 1);
// Instantiate Instances and validate if all Instances have the entity.
const int numberOfInstances = 3;
AZStd::vector<AZStd::unique_ptr<Instance>> instantiatedInstances;
for (int i = 0; i < numberOfInstances; ++i)
{
instantiatedInstances.emplace_back(m_prefabSystemComponent->InstantiatePrefab(newTemplateId));
ASSERT_TRUE(instantiatedInstances.back());
EXPECT_EQ(instantiatedInstances.back()->GetTemplateId(), newTemplateId);
}
PrefabTestDomUtils::ValidateEntitiesOfInstances(newTemplateId, newTemplateDom, newTemplateEntityAliases);
// Add another entity to the Instance and use it to update the PrefabDom of Template.
AZ::Entity* entity2 = CreateEntity("Entity 2");
newInstance->AddEntity(*entity2);
newTemplateEntityAliases = newInstance->GetEntityAliases();
EXPECT_EQ(newTemplateEntityAliases.size(), 2);
PrefabDom updatedTemplateDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*newInstance, updatedTemplateDom));
newTemplateDom.CopyFrom(updatedTemplateDom, newTemplateDom.GetAllocator());
// Update Template's Instances and validate if all Instances have the new entity.
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(newTemplateId);
const bool updateResult = m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
EXPECT_TRUE(updateResult);
PrefabTestDomUtils::ValidateEntitiesOfInstances(newTemplateId, newTemplateDom, newTemplateEntityAliases);
}
TEST_F(PrefabUpdateInstancesTest, UpdatePrefabInstances_AddInstance_UpdateSucceeds)
{
// Create a Template with single entity.
using namespace AzToolsFramework::Prefab;
AZ::Entity* entity = CreateEntity("Entity");
AZStd::unique_ptr<Instance> newNestedInstance = m_prefabSystemComponent->CreatePrefab({ entity }, {}, NestedPrefabMockFilePath);
TemplateId newNestedTemplateId = newNestedInstance->GetTemplateId();
EXPECT_TRUE(newNestedTemplateId != InvalidTemplateId);
EXPECT_EQ(newNestedInstance->GetEntityAliases().size(), 1);
// Create an enclosing Template with 0 entities and 1 nested Instance.
AZStd::unique_ptr<Instance> nestedInstance1 = m_prefabSystemComponent->InstantiatePrefab(newNestedTemplateId);
AZStd::unique_ptr<Instance> newEnclosingInstance = m_prefabSystemComponent->CreatePrefab({}, MakeInstanceList( AZStd::move(nestedInstance1) ), PrefabMockFilePath);
TemplateId newEnclosingTemplateId = newEnclosingInstance->GetTemplateId();
EXPECT_TRUE(newEnclosingTemplateId != InvalidTemplateId);
PrefabDom& newEnclosingTemplateDom = m_prefabSystemComponent->FindTemplateDom(newEnclosingTemplateId);
AZStd::vector<InstanceAlias> nestedInstanceAliases = newEnclosingInstance->GetNestedInstanceAliases(newNestedTemplateId);
EXPECT_EQ(nestedInstanceAliases.size(), 1);
// Instantiate enclosing Instances and validate if all enclosing Instances have the nested Instance.
const int numberOfInstances = 3;
AZStd::vector<AZStd::unique_ptr<Instance>> instantiatedInstances;
for (int i = 0; i < numberOfInstances; ++i)
{
instantiatedInstances.emplace_back(m_prefabSystemComponent->InstantiatePrefab(newEnclosingTemplateId));
ASSERT_TRUE(instantiatedInstances.back());
EXPECT_EQ(instantiatedInstances.back()->GetTemplateId(), newEnclosingTemplateId);
}
PrefabTestDomUtils::ValidateNestedInstancesOfInstances(
newEnclosingTemplateId, newEnclosingTemplateDom, nestedInstanceAliases);
// Add another nested Instance to the enclosing Instance and use it to update the PrefabDom of Template.
AZStd::unique_ptr<Instance> nestedInstance2 = m_prefabSystemComponent->InstantiatePrefab(newNestedTemplateId);
newEnclosingInstance->AddInstance(AZStd::move(nestedInstance2));
PrefabDom updatedTemplateDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*newEnclosingInstance, updatedTemplateDom));
newEnclosingTemplateDom.CopyFrom(updatedTemplateDom, newEnclosingTemplateDom.GetAllocator());
// Validate that there are 2 wheel Instances under the axle Instance
nestedInstanceAliases = newEnclosingInstance->GetNestedInstanceAliases(newNestedTemplateId);
EXPECT_EQ(nestedInstanceAliases.size(), 2);
// Update axle Template's Instances and validate if all axle Instances have the new wheel Instance.
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(newEnclosingTemplateId);
const bool updateResult = m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
EXPECT_TRUE(updateResult);
PrefabTestDomUtils::ValidateNestedInstancesOfInstances(
newEnclosingTemplateId, newEnclosingTemplateDom, nestedInstanceAliases);
}
TEST_F(PrefabUpdateInstancesTest, UpdatePrefabInstances_AddComponent_UpdateSucceeds)
{
// Create a Template from an Instance owning a single entity.
AZ::Entity* entity = CreateEntity("Entity", false);
AZStd::unique_ptr<Instance> newInstance = m_prefabSystemComponent->CreatePrefab({ entity }, {}, PrefabMockFilePath);
TemplateId newTemplateId = newInstance->GetTemplateId();
PrefabDom& newTemplateDom = m_prefabSystemComponent->FindTemplateDom(newTemplateId);
AZStd::vector<EntityAlias> newTemplateEntityAliases = newInstance->GetEntityAliases();
ASSERT_EQ(newTemplateEntityAliases.size(), 1);
// Validate that the entity doesn't have any components under it.
const PrefabDomValue* entityComponents =
PrefabTestDomUtils::GetPrefabDomComponents(newTemplateDom, newTemplateEntityAliases.front());
ASSERT_TRUE(entityComponents == nullptr);
// Instantiate Instances and validate if all Instances have the entity.
const int numberOfInstances = 3;
AZStd::vector<AZStd::unique_ptr<Instance>> instantiatedInstances;
for (int i = 0; i < numberOfInstances; ++i)
{
instantiatedInstances.emplace_back(m_prefabSystemComponent->InstantiatePrefab(newTemplateId));
ASSERT_TRUE(instantiatedInstances.back());
EXPECT_EQ(instantiatedInstances.back()->GetTemplateId(), newTemplateId);
}
PrefabTestDomUtils::ValidateEntitiesOfInstances(newTemplateId, newTemplateDom, newTemplateEntityAliases);
// Add a component to the Instance and use it to update the PrefabDom of Template.
PrefabTestComponent* prefabTestComponent = aznew PrefabTestComponent(true);
entity->AddComponent(prefabTestComponent);
auto expectedComponentId = prefabTestComponent->GetId();
PrefabDom updatedDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*newInstance, updatedDom));
newTemplateDom.CopyFrom(updatedDom, newTemplateDom.GetAllocator());
// Validate that the entity does have a component under it.
entityComponents = PrefabTestDomUtils::GetPrefabDomComponents(newTemplateDom, newTemplateEntityAliases.front());
ASSERT_TRUE(entityComponents != nullptr && entityComponents->IsArray());
EXPECT_EQ(entityComponents->GetArray().Size(), 1);
// Extract the component id of the entity in Template and verify that it matches with the component id of the Instance.
PrefabDomValueConstReference findEntityComponentIdValueResult =
PrefabDomUtils::FindPrefabDomValue(*entityComponents->Begin(), PrefabTestDomUtils::ComponentIdName);
ASSERT_TRUE(findEntityComponentIdValueResult.has_value());
EXPECT_EQ(expectedComponentId, findEntityComponentIdValueResult->get().GetUint64());
// Update Template's Instances and validate if all Instances have the new component under their entities.
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(newTemplateId);
const bool updateResult = m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
EXPECT_TRUE(updateResult);
PrefabTestDomUtils::ValidateInstances(newTemplateId, *entityComponents,
PrefabTestDomUtils::GetPrefabDomComponentsPath(newTemplateEntityAliases.front()));
}
TEST_F(PrefabUpdateInstancesTest, UpdatePrefabInstances_DetachEntity_UpdateSucceeds)
{
// Create a Template from an Instance owning 2 entities.
using namespace AzToolsFramework::Prefab;
AZ::Entity* entity1 = CreateEntity("Entity 1");
AZ::Entity* entity2 = CreateEntity("Entity 2");
AZStd::unique_ptr<Instance> newInstance = m_prefabSystemComponent->CreatePrefab(
{ entity1, entity2 },
{},
PrefabMockFilePath);
TemplateId newTemplateId = newInstance->GetTemplateId();
EXPECT_TRUE(newTemplateId != InvalidTemplateId);
PrefabDom& newTemplateDom = m_prefabSystemComponent->FindTemplateDom(newTemplateId);
AZStd::vector<EntityAlias> newTemplateEntityAliases = newInstance->GetEntityAliases();
EXPECT_EQ(newTemplateEntityAliases.size(), 2);
// Instantiate Instances and validate if all Instances have both entities.
const int numberOfInstances = 3;
AZStd::vector<AZStd::unique_ptr<Instance>> instantiatedInstances;
for (int i = 0; i < numberOfInstances; ++i)
{
instantiatedInstances.emplace_back(m_prefabSystemComponent->InstantiatePrefab(newTemplateId));
ASSERT_TRUE(instantiatedInstances.back());
EXPECT_EQ(instantiatedInstances.back()->GetTemplateId(), newTemplateId);
}
PrefabTestDomUtils::ValidateEntitiesOfInstances(newTemplateId, newTemplateDom, newTemplateEntityAliases);
// Remove an entity from the Instance and use the updated Instance to update the PrefabDom of Template.
AZStd::unique_ptr<AZ::Entity> detachedEntity = newInstance->DetachEntity(entity1->GetId());
ASSERT_TRUE(detachedEntity);
EXPECT_EQ(detachedEntity->GetId(), entity1->GetId());
newTemplateEntityAliases = newInstance->GetEntityAliases();
EXPECT_EQ(newTemplateEntityAliases.size(), 1);
PrefabDom updatedTemplateDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*newInstance, updatedTemplateDom));
newTemplateDom.CopyFrom(updatedTemplateDom, newTemplateDom.GetAllocator());
// Update Template's Instances and validate if all Instances have the remaining entity.
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(newTemplateId);
const bool updateResult = m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
EXPECT_TRUE(updateResult);
PrefabTestDomUtils::ValidateEntitiesOfInstances(newTemplateId, newTemplateDom, newTemplateEntityAliases);
}
TEST_F(PrefabUpdateInstancesTest, UpdatePrefabInstances_DetachNestedInstance_UpdateSucceeds)
{
// Create a Template with single entity.
using namespace AzToolsFramework::Prefab;
AZ::Entity* entity = CreateEntity("Entity");
AZStd::unique_ptr<Instance> newNestedInstance = m_prefabSystemComponent->CreatePrefab({ entity }, {}, NestedPrefabMockFilePath);
TemplateId newNestedTemplateId = newNestedInstance->GetTemplateId();
EXPECT_TRUE(newNestedTemplateId != InvalidTemplateId);
EXPECT_EQ(newNestedInstance->GetEntityAliases().size(), 1);
// Create an enclosing Template with 0 entities and 2 nested Instances.
AZStd::unique_ptr<Instance> nestedInstance1 = m_prefabSystemComponent->InstantiatePrefab(newNestedTemplateId);
AZStd::unique_ptr<Instance> nestedInstance2 = m_prefabSystemComponent->InstantiatePrefab(newNestedTemplateId);
AZStd::unique_ptr<Instance> newEnclosingInstance = m_prefabSystemComponent->CreatePrefab(
{},
MakeInstanceList( AZStd::move(nestedInstance1), AZStd::move(nestedInstance2) ),
PrefabMockFilePath);
TemplateId newEnclosingTemplateId = newEnclosingInstance->GetTemplateId();
EXPECT_TRUE(newEnclosingTemplateId != InvalidTemplateId);
PrefabDom& newEnclosingTemplateDom = m_prefabSystemComponent->FindTemplateDom(newEnclosingTemplateId);
AZStd::vector<InstanceAlias> nestedInstanceAliases = newEnclosingInstance->GetNestedInstanceAliases(newNestedTemplateId);
EXPECT_EQ(nestedInstanceAliases.size(), 2);
// Instantiate enclosing Instances and validate if all enclosing Instances have both nested Instances.
const int numberOfInstances = 3;
AZStd::vector<AZStd::unique_ptr<Instance>> instantiatedInstances;
for (int i = 0; i < numberOfInstances; ++i)
{
instantiatedInstances.emplace_back(m_prefabSystemComponent->InstantiatePrefab(newEnclosingTemplateId));
ASSERT_TRUE(instantiatedInstances.back());
EXPECT_EQ(instantiatedInstances.back()->GetTemplateId(), newEnclosingTemplateId);
}
PrefabTestDomUtils::ValidateNestedInstancesOfInstances(
newEnclosingTemplateId, newEnclosingTemplateDom, nestedInstanceAliases);
// Remove one nested Instance from the enclosing Instance
// and use the updated enclosing Instance to update the PrefabDom of Template.
AZStd::unique_ptr<Instance> detachedInstance = newEnclosingInstance->DetachNestedInstance(nestedInstanceAliases.front());
ASSERT_TRUE(detachedInstance);
PrefabDom updatedTemplateDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*newEnclosingInstance, updatedTemplateDom));
newEnclosingTemplateDom.CopyFrom(updatedTemplateDom, newEnclosingTemplateDom.GetAllocator());
// Validate that there is only one nested Instances under the enclosing Instance.
nestedInstanceAliases = newEnclosingInstance->GetNestedInstanceAliases(newNestedTemplateId);
EXPECT_EQ(nestedInstanceAliases.size(), 1);
// Update enclosing Template's Instances and validate if all enclosing Instances have the remaining nested Instances.
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(newEnclosingTemplateId);
const bool updateResult = m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
EXPECT_TRUE(updateResult);
PrefabTestDomUtils::ValidateNestedInstancesOfInstances(
newEnclosingTemplateId, newEnclosingTemplateDom, nestedInstanceAliases);
}
TEST_F(PrefabUpdateInstancesTest, UpdatePrefabInstances_RemoveComponent_UpdateSucceeds)
{
// Create a Template from an Instance owning a single entity with a prefabTestComponent.
AZ::Entity* entity = CreateEntity("Entity", false);
PrefabTestComponent* prefabTestComponent = aznew PrefabTestComponent(true);
entity->AddComponent(prefabTestComponent);
AZStd::unique_ptr<Instance> newInstance = m_prefabSystemComponent->CreatePrefab({ entity }, {}, PrefabMockFilePath);
TemplateId newTemplateId = newInstance->GetTemplateId();
PrefabDom& newTemplateDom = m_prefabSystemComponent->FindTemplateDom(newTemplateId);
AZStd::vector<EntityAlias> newTemplateEntityAliases = newInstance->GetEntityAliases();
ASSERT_EQ(newTemplateEntityAliases.size(), 1);
// Validate that the entity has exactly 1 component under it.
const PrefabDomValue* entityComponents =
PrefabTestDomUtils::GetPrefabDomComponents(newTemplateDom, newTemplateEntityAliases.front());
ASSERT_TRUE(entityComponents != nullptr && entityComponents->IsArray());
EXPECT_EQ(entityComponents->GetArray().Size(), 1);
// Extract the component id of the entity in the Template and verify that it matches with the component id of the entity's component.
PrefabDomValueConstReference entityComponentIdValue =
PrefabDomUtils::FindPrefabDomValue(*entityComponents->Begin(), PrefabTestDomUtils::ComponentIdName);
ASSERT_TRUE(entityComponentIdValue.has_value());
EXPECT_EQ(prefabTestComponent->GetId(), entityComponentIdValue->get().GetUint64());
// Instantiate Instances and validate if all Instances have the entity.
const int numberOfInstances = 3;
AZStd::vector<AZStd::unique_ptr<Instance>> instantiatedInstances;
for (int i = 0; i < numberOfInstances; ++i)
{
instantiatedInstances.emplace_back(m_prefabSystemComponent->InstantiatePrefab(newTemplateId));
ASSERT_TRUE(instantiatedInstances.back());
EXPECT_EQ(instantiatedInstances.back()->GetTemplateId(), newTemplateId);
}
PrefabTestDomUtils::ValidateInstances(
newTemplateId, *entityComponents, PrefabTestDomUtils::GetPrefabDomComponentsPath(newTemplateEntityAliases.front()));
// Remove a component from the Instance's entity and use the Instance to update the PrefabDom of Template.
entity->RemoveComponent(prefabTestComponent);
delete prefabTestComponent;
PrefabDom updatedDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*newInstance, updatedDom));
newTemplateDom.CopyFrom(updatedDom, newTemplateDom.GetAllocator());
// Validate that the entity does not have any component under it.
entityComponents = PrefabTestDomUtils::GetPrefabDomComponents(newTemplateDom, newTemplateEntityAliases.front());
ASSERT_TRUE(entityComponents == nullptr);
// Update Template's Instances and validate if all Instances have no component under their entities.
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(newTemplateId);
const bool updateResult = m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
EXPECT_TRUE(updateResult);
PrefabTestDomUtils::ValidateEntitiesOfInstances(newTemplateId, newTemplateDom, newTemplateEntityAliases);
}
TEST_F(PrefabUpdateInstancesTest, UpdatePrefabInstances_ChangeComponentProperty_UpdateSucceeds)
{
// Create a Template from an Instance owning a single entity with a PrefabTestComponent.
AZ::Entity* entity = CreateEntity("Entity", false);
PrefabTestComponent* prefabTestComponent = aznew PrefabTestComponent(true);
entity->AddComponent(prefabTestComponent);
AZStd::unique_ptr<Instance> newInstance = m_prefabSystemComponent->CreatePrefab({ entity }, {}, PrefabMockFilePath);
TemplateId newTemplateId = newInstance->GetTemplateId();
PrefabDom& newTemplateDom = m_prefabSystemComponent->FindTemplateDom(newTemplateId);
AZStd::vector<EntityAlias> newTemplateEntityAliases = newInstance->GetEntityAliases();
ASSERT_EQ(newTemplateEntityAliases.size(), 1);
// Validate that the entity has exactly 1 component under it.
const PrefabDomValue* entityComponents =
PrefabTestDomUtils::GetPrefabDomComponents(newTemplateDom, newTemplateEntityAliases.front());
ASSERT_TRUE(entityComponents != nullptr && entityComponents->IsArray());
EXPECT_EQ(entityComponents->GetArray().Size(), 1);
// Extract the component id of the entity in the Template and verify that it matches with the component id of the entity's component.
PrefabDomValueConstReference entityComponentIdValue =
PrefabDomUtils::FindPrefabDomValue(*entityComponents->Begin(), PrefabTestDomUtils::ComponentIdName);
ASSERT_TRUE(entityComponentIdValue.has_value());
EXPECT_EQ(prefabTestComponent->GetId(), entityComponentIdValue->get().GetUint64());
// Instantiate Instances and validate if all Instances have the entity.
const int numberOfInstances = 3;
AZStd::vector<AZStd::unique_ptr<Instance>> instantiatedInstances;
for (int i = 0; i < numberOfInstances; ++i)
{
instantiatedInstances.emplace_back(m_prefabSystemComponent->InstantiatePrefab(newTemplateId));
ASSERT_TRUE(instantiatedInstances.back());
EXPECT_EQ(instantiatedInstances.back()->GetTemplateId(), newTemplateId);
}
PrefabDomPath entityComponentsPath = PrefabTestDomUtils::GetPrefabDomComponentsPath(newTemplateEntityAliases.front());
PrefabTestDomUtils::ValidateInstances(
newTemplateId, *entityComponents, entityComponentsPath);
// Change the bool property of the component from the Instance and use the Instance to update the PrefabDom of Template.
prefabTestComponent->m_boolProperty = false;
PrefabDom updatedDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*newInstance, updatedDom));
newTemplateDom.CopyFrom(updatedDom, newTemplateDom.GetAllocator());
// Validate that the prefabTestComponent in the Template's DOM doesn't have a BoolProperty.
// Even though we changed the property to false, it won't be serialized out because it's a default value.
entityComponents = PrefabTestDomUtils::GetPrefabDomComponents(newTemplateDom, newTemplateEntityAliases.front());
ASSERT_TRUE(entityComponents != nullptr && entityComponents->IsArray());
EXPECT_EQ(entityComponents->GetArray().Size(), 1);
PrefabDomValueConstReference entityComponentBoolPropertyValue =
PrefabDomUtils::FindPrefabDomValue(*entityComponents->Begin(), PrefabTestDomUtils::BoolPropertyName);
EXPECT_FALSE(entityComponentBoolPropertyValue.has_value());
// Update Template's Instances and validate if all Instances have no BoolProperty under their prefabTestComponents in entities.
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(newTemplateId);
const bool updateResult = m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
EXPECT_TRUE(updateResult);
PrefabTestDomUtils::ValidateInstances(newTemplateId, *entityComponents, entityComponentsPath);
}
}
@@ -0,0 +1,420 @@
/*
* 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 <AzCore/Component/TransformBus.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <Prefab/PrefabTestComponent.h>
#include <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestFixture.h>
namespace UnitTest
{
using PrefabUpdateTemplateTest = PrefabTestFixture;
/*
The below tests use an example of car->axle->wheel templates to test that change propagation works correctly within templates.
The car template will have axle templates nested under it and the axle template will have wheel templates nested under it.
Because of the complexity that arises from multiple levels of prefab nesting, it's easier to write tests using an example scenario
than use generic nesting terminology.
*/
TEST_F(PrefabUpdateTemplateTest, UpdatePrefabTemplate_AddEntity_AllDependentTemplatesUpdated)
{
// Create a single entity wheel instance and create a template out of it.
AZ::Entity* wheelEntity = CreateEntity("WheelEntity1");
AZStd::unique_ptr<Instance> wheelIsolatedInstance = m_prefabSystemComponent->CreatePrefab({ wheelEntity }, {}, WheelPrefabMockFilePath);
const TemplateId wheelTemplateId = wheelIsolatedInstance->GetTemplateId();
PrefabDom& wheelTemplateDom = m_prefabSystemComponent->FindTemplateDom(wheelTemplateId);
AZStd::vector<EntityAlias> wheelTemplateEntityAliases = wheelIsolatedInstance->GetEntityAliases();
// Validate that the wheel template has the same entities(1) as the instance it was created from.
ASSERT_EQ(wheelTemplateEntityAliases.size(), 1);
PrefabTestDomUtils::ValidatePrefabDomEntities(wheelTemplateEntityAliases, wheelTemplateDom);
// Create an axle with 0 entities and 2 wheel instances.
AZStd::unique_ptr<Instance> wheel1UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
AZStd::unique_ptr<Instance> wheel2UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
AZStd::unique_ptr<Instance> axleInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(wheel1UnderAxle), AZStd::move(wheel2UnderAxle) ), AxlePrefabMockFilePath);
const TemplateId axleTemplateId = axleInstance->GetTemplateId();
const AZStd::vector<InstanceAlias> wheelInstanceAliasesUnderAxle = axleInstance->GetNestedInstanceAliases(wheelTemplateId);
PrefabDom& axleTemplateDom = m_prefabSystemComponent->FindTemplateDom(axleTemplateId);
// Create a car with 0 entities, 2 axle instances and 1 wheel instance.
AZStd::unique_ptr<Instance> axle1UnderCar = m_prefabSystemComponent->InstantiatePrefab(axleTemplateId);
AZStd::unique_ptr<Instance> axle2UnderCar = m_prefabSystemComponent->InstantiatePrefab(axleTemplateId);
AZStd::unique_ptr<Instance> spareWheelUnderCar = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
AZStd::unique_ptr<Instance> carInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(axle1UnderCar), AZStd::move(axle2UnderCar), AZStd::move(spareWheelUnderCar) ), CarPrefabMockFilePath);
const TemplateId carTemplateId = carInstance->GetTemplateId();
const AZStd::vector<InstanceAlias> axleInstanceAliasesUnderCar = carInstance->GetNestedInstanceAliases(axleTemplateId);
const AZStd::vector<InstanceAlias> wheelInstanceAliasesUnderCar = carInstance->GetNestedInstanceAliases(wheelTemplateId);
PrefabDom& carTemplateDom = m_prefabSystemComponent->FindTemplateDom(carTemplateId);
// Add another entity to a wheel instance and use it to update the wheel template.
wheelIsolatedInstance->AddEntity(*CreateEntity("WheelEntity2"));
PrefabDom updatedWheelInstance;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*wheelIsolatedInstance, updatedWheelInstance));
m_prefabSystemComponent->UpdatePrefabTemplate(wheelTemplateId, updatedWheelInstance);
// Validate that the wheel template has the same entities(2) as the updated instance.
wheelTemplateEntityAliases = wheelIsolatedInstance->GetEntityAliases();
ASSERT_EQ(wheelTemplateEntityAliases.size(), 2);
PrefabTestDomUtils::ValidatePrefabDomEntities(wheelTemplateEntityAliases, wheelTemplateDom);
// Validate that the wheels under axle are updated with 2 entities
PrefabTestDomUtils::ValidatePrefabDomInstances(wheelInstanceAliasesUnderAxle, axleTemplateDom, wheelTemplateDom);
// Validate that the wheels of axles under the car have 2 entities
PrefabTestDomUtils::ValidatePrefabDomInstances(axleInstanceAliasesUnderCar, carTemplateDom, axleTemplateDom);
// Validate that the wheel under the car has 2 entities
PrefabTestDomUtils::ValidatePrefabDomInstances(wheelInstanceAliasesUnderCar, carTemplateDom, wheelTemplateDom);
}
TEST_F(PrefabUpdateTemplateTest, UpdatePrefabTemplate_AddInstance_AllDependentTemplatesUpdated)
{
// Create a single entity wheel instance and create a template out of it.
AZ::Entity* wheelEntity = CreateEntity("WheelEntity1");
AZStd::unique_ptr<Instance> wheelIsolatedInstance = m_prefabSystemComponent->CreatePrefab({ wheelEntity },
{}, WheelPrefabMockFilePath);
const TemplateId wheelTemplateId = wheelIsolatedInstance->GetTemplateId();
PrefabDom& wheelTemplateDom = m_prefabSystemComponent->FindTemplateDom(wheelTemplateId);
// Create an axle with 0 entities and 1 wheel instance.
AZStd::unique_ptr<Instance> wheel1UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
AZStd::unique_ptr<Instance> axleInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(wheel1UnderAxle) ), AxlePrefabMockFilePath);
const TemplateId axleTemplateId = axleInstance->GetTemplateId();
PrefabDom& axleTemplateDom = m_prefabSystemComponent->FindTemplateDom(axleTemplateId);
AZStd::vector<InstanceAlias> wheelInstanceAliasesUnderAxle = axleInstance->GetNestedInstanceAliases(wheelTemplateId);
// Validate that there is only 1 wheel instance under axle.
ASSERT_EQ(wheelInstanceAliasesUnderAxle.size(), 1);
// Create a car with 0 entities and 2 axle instances.
AZStd::unique_ptr<Instance> axle1UnderCar = m_prefabSystemComponent->InstantiatePrefab(axleTemplateId);
AZStd::unique_ptr<Instance> axle2UnderCar = m_prefabSystemComponent->InstantiatePrefab(axleTemplateId);
AZStd::unique_ptr<Instance> carInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(axle1UnderCar), AZStd::move(axle2UnderCar) ), CarPrefabMockFilePath);
const TemplateId carTemplateId = carInstance->GetTemplateId();
const AZStd::vector<InstanceAlias> axleInstanceAliasesUnderCar = carInstance->GetNestedInstanceAliases(axleTemplateId);
PrefabDom& carTemplateDom = m_prefabSystemComponent->FindTemplateDom(carTemplateId);
// Add another Wheel instance to Axle instance and use it to update the Axle template.
AZStd::unique_ptr<Instance> wheel2UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
axleInstance->AddInstance(AZStd::move(wheel2UnderAxle));
PrefabDom updatedAxleInstanceDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*axleInstance, updatedAxleInstanceDom));
m_prefabSystemComponent->UpdatePrefabTemplate(axleTemplateId, updatedAxleInstanceDom);
// Validate that there are 2 wheel instances under axle
wheelInstanceAliasesUnderAxle = axleInstance->GetNestedInstanceAliases(wheelTemplateId);
ASSERT_EQ(wheelInstanceAliasesUnderAxle.size(), 2);
// Validate that the wheels under the axle have the same DOM as the wheel template.
PrefabTestDomUtils::ValidatePrefabDomInstances(wheelInstanceAliasesUnderAxle, axleTemplateDom, wheelTemplateDom);
// Validate that the axles under the car have the same DOM as the axle template.
PrefabTestDomUtils::ValidatePrefabDomInstances(axleInstanceAliasesUnderCar, carTemplateDom, axleTemplateDom);
}
TEST_F(PrefabUpdateTemplateTest, UpdatePrefabTemplate_AddComponent_AllDependentTemplatesUpdated)
{
// Create a single entity wheel instance and create a template out of it.
AZ::Entity* wheelEntity = CreateEntity("WheelEntity1", false);
AZStd::unique_ptr<Instance> wheelIsolatedInstance = m_prefabSystemComponent->CreatePrefab({ wheelEntity },
{}, WheelPrefabMockFilePath);
const TemplateId wheelTemplateId = wheelIsolatedInstance->GetTemplateId();
PrefabDom& wheelTemplateDom = m_prefabSystemComponent->FindTemplateDom(wheelTemplateId);
AZStd::vector<EntityAlias> wheelTemplateEntityAliases = wheelIsolatedInstance->GetEntityAliases();
// Validate that the wheel template has the same entities(1) as the instance it was created from.
ASSERT_EQ(wheelTemplateEntityAliases.size(), 1);
// Validate that the wheel entity doesn't have any components under it.
EntityAlias entityAlias = wheelTemplateEntityAliases.front();
PrefabDomValue* wheelEntityComponents =
PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(wheelTemplateDom);
ASSERT_TRUE(wheelEntityComponents == nullptr);
// Create an axle with 0 entities and 1 wheel instance.
AZStd::unique_ptr<Instance> wheel1UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
AZStd::unique_ptr<Instance> axleInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(wheel1UnderAxle) ), AxlePrefabMockFilePath);
const TemplateId axleTemplateId = axleInstance->GetTemplateId();
PrefabDom& axleTemplateDom = m_prefabSystemComponent->FindTemplateDom(axleTemplateId);
const AZStd::vector<InstanceAlias> wheelInstanceAliasesUnderAxle = axleInstance->GetNestedInstanceAliases(wheelTemplateId);
// Create a car with 0 entities and 1 axle instance.
AZStd::unique_ptr<Instance> axleUnderCar = m_prefabSystemComponent->InstantiatePrefab(axleTemplateId);
AZStd::unique_ptr<Instance> carInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(axleUnderCar) ), CarPrefabMockFilePath);
const TemplateId carTemplateId = carInstance->GetTemplateId();
const AZStd::vector<InstanceAlias> axleInstanceAliasesUnderCar = carInstance->GetNestedInstanceAliases(axleTemplateId);
PrefabDom& carTemplateDom = m_prefabSystemComponent->FindTemplateDom(carTemplateId);
// Add a component to Wheel instance and use it to update the wheel template.
PrefabTestComponent* prefabTestComponent = aznew PrefabTestComponent(true);
wheelEntity->AddComponent(prefabTestComponent);
auto expectedComponentId = prefabTestComponent->GetId();
PrefabDom updatedWheelInstanceDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*wheelIsolatedInstance, updatedWheelInstanceDom));
m_prefabSystemComponent->UpdatePrefabTemplate(wheelTemplateId, updatedWheelInstanceDom);
// Validate that the wheel entity does have a component under it.
wheelEntityComponents = PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(wheelTemplateDom);
ASSERT_TRUE(wheelEntityComponents != nullptr && wheelEntityComponents->IsArray());
EXPECT_EQ(wheelEntityComponents->GetArray().Size(), 1);
// Extract the component id of the entity in wheel template and verify that it matches with the component id of the wheel instance.
PrefabDomValueReference wheelEntityComponentIdValue =
PrefabDomUtils::FindPrefabDomValue(*wheelEntityComponents->Begin(), PrefabTestDomUtils::ComponentIdName);
ASSERT_TRUE(wheelEntityComponentIdValue.has_value());
EXPECT_EQ(expectedComponentId, wheelEntityComponentIdValue->get().GetUint64());
// Validate that the wheels under the axle have the same DOM as the wheel template.
PrefabTestDomUtils::ValidatePrefabDomInstances(wheelInstanceAliasesUnderAxle, axleTemplateDom, wheelTemplateDom);
// Validate that the axles under the car have the same DOM as the axle template.
PrefabTestDomUtils::ValidatePrefabDomInstances(axleInstanceAliasesUnderCar, carTemplateDom, axleTemplateDom);
}
TEST_F(PrefabUpdateTemplateTest, UpdatePrefabTemplate_DetachEntity_AllDependentTemplatesUpdated)
{
// Create wheel instance with 2 entities and create a template out of it.
AZ::Entity* wheelEntity1 = CreateEntity("WheelEntity1");
AZ::Entity* wheelEntity2 = CreateEntity("WheelEntity2");
AZStd::unique_ptr<Instance> wheelIsolatedInstance = m_prefabSystemComponent->CreatePrefab({ wheelEntity1, wheelEntity2 },
{}, WheelPrefabMockFilePath);
const TemplateId wheelTemplateId = wheelIsolatedInstance->GetTemplateId();
PrefabDom& wheelTemplateDom = m_prefabSystemComponent->FindTemplateDom(wheelTemplateId);
// Validate that the wheel template has the same entities(2) as the instance it was created from.
AZStd::vector<EntityAlias> wheelTemplateEntityAliases = wheelIsolatedInstance->GetEntityAliases();
ASSERT_EQ(wheelTemplateEntityAliases.size(), 2);
// Create an axle with 0 entities and 1 wheel instance.
AZStd::unique_ptr<Instance> wheel1UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
AZStd::unique_ptr<Instance> axleInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(wheel1UnderAxle) ), AxlePrefabMockFilePath);
const TemplateId axleTemplateId = axleInstance->GetTemplateId();
PrefabDom& axleTemplateDom = m_prefabSystemComponent->FindTemplateDom(axleTemplateId);
const AZStd::vector<InstanceAlias> wheelInstanceAliasesUnderAxle = axleInstance->GetNestedInstanceAliases(wheelTemplateId);
// Create a car with 0 entities and 1 axle instance.
AZStd::unique_ptr<Instance> axleUnderCar = m_prefabSystemComponent->InstantiatePrefab(axleTemplateId);
AZStd::unique_ptr<Instance> carInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(axleUnderCar) ), CarPrefabMockFilePath);
const TemplateId carTemplateId = carInstance->GetTemplateId();
const AZStd::vector<InstanceAlias> axleInstanceAliasesUnderCar = carInstance->GetNestedInstanceAliases(axleTemplateId);
PrefabDom& carTemplateDom = m_prefabSystemComponent->FindTemplateDom(carTemplateId);
// Detach the first entity from the Wheel instance and use it to update the wheel template.
AZStd::unique_ptr<AZ::Entity> detachedEntity = wheelIsolatedInstance->DetachEntity(wheelEntity1->GetId());
ASSERT_TRUE(detachedEntity);
PrefabDom updatedWheelInstanceDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*wheelIsolatedInstance, updatedWheelInstanceDom));
m_prefabSystemComponent->UpdatePrefabTemplate(wheelTemplateId, updatedWheelInstanceDom);
// Validate that the wheel template only has 1 entity now.
wheelTemplateEntityAliases = wheelIsolatedInstance->GetEntityAliases();
ASSERT_EQ(wheelTemplateEntityAliases.size(), 1);
PrefabTestDomUtils::ValidatePrefabDomEntities(wheelTemplateEntityAliases, wheelTemplateDom);
// Validate that the wheels under the axle have the same DOM as the wheel template.
PrefabTestDomUtils::ValidatePrefabDomInstances(wheelInstanceAliasesUnderAxle, axleTemplateDom, wheelTemplateDom);
// Validate that the axles under the car have the same DOM as the axle template.
PrefabTestDomUtils::ValidatePrefabDomInstances(axleInstanceAliasesUnderCar, carTemplateDom, axleTemplateDom);
}
TEST_F(PrefabUpdateTemplateTest, UpdatePrefabTemplate_DetachNestedInstance_AllDependentTemplatesUpdated)
{
// Create a single entity wheel instance and create a template out of it.
AZ::Entity* wheelEntity = CreateEntity("WheelEntity1");
AZStd::unique_ptr<Instance> wheelIsolatedInstance = m_prefabSystemComponent->CreatePrefab({ wheelEntity },
{}, WheelPrefabMockFilePath);
const TemplateId wheelTemplateId = wheelIsolatedInstance->GetTemplateId();
PrefabDom& wheelTemplateDom = m_prefabSystemComponent->FindTemplateDom(wheelTemplateId);
// Create an axle with 0 entities and 2 wheel instances.
AZStd::unique_ptr<Instance> wheel1UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
AZStd::unique_ptr<Instance> wheel2UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
AZStd::unique_ptr<Instance> axleInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(wheel1UnderAxle), AZStd::move(wheel2UnderAxle) ),
AxlePrefabMockFilePath);
const TemplateId axleTemplateId = axleInstance->GetTemplateId();
PrefabDom& axleTemplateDom = m_prefabSystemComponent->FindTemplateDom(axleTemplateId);
AZStd::vector<InstanceAlias> wheelInstanceAliasesUnderAxle = axleInstance->GetNestedInstanceAliases(wheelTemplateId);
// Validate that there are 2 wheel instances under axle.
ASSERT_EQ(wheelInstanceAliasesUnderAxle.size(), 2);
// Create a car with 0 entities and 1 axle instance.
AZStd::unique_ptr<Instance> axle1UnderCar = m_prefabSystemComponent->InstantiatePrefab(axleTemplateId);
AZStd::unique_ptr<Instance> carInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(axle1UnderCar) ), CarPrefabMockFilePath);
const TemplateId carTemplateId = carInstance->GetTemplateId();
const AZStd::vector<InstanceAlias> axleInstanceAliasesUnderCar = carInstance->GetNestedInstanceAliases(axleTemplateId);
PrefabDom& carTemplateDom = m_prefabSystemComponent->FindTemplateDom(carTemplateId);
// Detach second wheel instance from Axle instance and use it to update the Axle template.
InstanceAlias aliasOfWheelInstanceToRetain = wheelInstanceAliasesUnderAxle.front();
AZStd::unique_ptr<Instance> detachedInstance = axleInstance->DetachNestedInstance(wheelInstanceAliasesUnderAxle.back());
ASSERT_TRUE(detachedInstance);
PrefabDom updatedAxleInstanceDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*axleInstance, updatedAxleInstanceDom));
m_prefabSystemComponent->UpdatePrefabTemplate(axleTemplateId, updatedAxleInstanceDom);
// Validate that there is only 1 wheel instances under axle
wheelInstanceAliasesUnderAxle = axleInstance->GetNestedInstanceAliases(wheelTemplateId);
ASSERT_EQ(wheelInstanceAliasesUnderAxle.size(), 1);
EXPECT_EQ(wheelInstanceAliasesUnderAxle.front(), aliasOfWheelInstanceToRetain);
// Validate that the wheels under the axle have the same DOM as the wheel template.
PrefabTestDomUtils::ValidatePrefabDomInstances(wheelInstanceAliasesUnderAxle, axleTemplateDom, wheelTemplateDom);
// Validate that the axles under the car have the same DOM as the axle template.
PrefabTestDomUtils::ValidatePrefabDomInstances(axleInstanceAliasesUnderCar, carTemplateDom, axleTemplateDom);
}
TEST_F(PrefabUpdateTemplateTest, UpdatePrefabTemplate_RemoveComponent_AllDependentTemplatesUpdated)
{
// Create a single entity wheel instance with a PrefabTestComponent and create a template out of it.
AZ::Entity* wheelEntity = CreateEntity("WheelEntity1", false);
PrefabTestComponent* prefabTestComponent = aznew PrefabTestComponent(true);
wheelEntity->AddComponent(prefabTestComponent);
AZStd::unique_ptr<Instance> wheelIsolatedInstance = m_prefabSystemComponent->CreatePrefab({ wheelEntity },
{}, WheelPrefabMockFilePath);
const TemplateId wheelTemplateId = wheelIsolatedInstance->GetTemplateId();
PrefabDom& wheelTemplateDom = m_prefabSystemComponent->FindTemplateDom(wheelTemplateId);
AZStd::vector<EntityAlias> wheelTemplateEntityAliases = wheelIsolatedInstance->GetEntityAliases();
// Validate that the wheel template has the same entities(1) as the instance it was created from.
ASSERT_EQ(wheelTemplateEntityAliases.size(), 1);
// Validate that the wheel entity has 1 component under it.
AZStd::string entityAlias = wheelTemplateEntityAliases.front();
PrefabDomValue* wheelEntityComponents =
PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(wheelTemplateDom);
ASSERT_TRUE(wheelEntityComponents != nullptr && wheelEntityComponents->IsArray());
EXPECT_EQ(wheelEntityComponents->GetArray().Size(), 1);
// Extract the component id of the entity in wheel template and verify that it matches with the component id of the wheel instance.
PrefabDomValueReference wheelEntityComponentIdValue =
PrefabDomUtils::FindPrefabDomValue(*wheelEntityComponents->Begin(), PrefabTestDomUtils::ComponentIdName);
ASSERT_TRUE(wheelEntityComponentIdValue.has_value());
EXPECT_EQ(prefabTestComponent->GetId(), wheelEntityComponentIdValue->get().GetUint64());
// Create an axle with 0 entities and 1 wheel instance.
AZStd::unique_ptr<Instance> wheel1UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
AZStd::unique_ptr<Instance> axleInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(wheel1UnderAxle) ), AxlePrefabMockFilePath);
const TemplateId axleTemplateId = axleInstance->GetTemplateId();
PrefabDom& axleTemplateDom = m_prefabSystemComponent->FindTemplateDom(axleTemplateId);
const AZStd::vector<InstanceAlias> wheelInstanceAliasesUnderAxle = axleInstance->GetNestedInstanceAliases(wheelTemplateId);
// Create a car with 0 entities and 1 axle instance.
AZStd::unique_ptr<Instance> axleUnderCar = m_prefabSystemComponent->InstantiatePrefab(axleTemplateId);
AZStd::unique_ptr<Instance> carInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(axleUnderCar) ), CarPrefabMockFilePath);
const TemplateId carTemplateId = carInstance->GetTemplateId();
const AZStd::vector<InstanceAlias> axleInstanceAliasesUnderCar = carInstance->GetNestedInstanceAliases(axleTemplateId);
PrefabDom& carTemplateDom = m_prefabSystemComponent->FindTemplateDom(carTemplateId);
// Remove the component from Wheel instance and use it to update the wheel template.
wheelEntity->RemoveComponent(prefabTestComponent);
PrefabDom updatedWheelInstanceDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*wheelIsolatedInstance, updatedWheelInstanceDom));
m_prefabSystemComponent->UpdatePrefabTemplate(wheelTemplateId, updatedWheelInstanceDom);
// Validate that the wheel entity does not have a component under it.
wheelEntityComponents = PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(wheelTemplateDom);
ASSERT_TRUE(wheelEntityComponents == nullptr);
// Validate that the wheels under the axle have the same DOM as the wheel template.
PrefabTestDomUtils::ValidatePrefabDomInstances(wheelInstanceAliasesUnderAxle, axleTemplateDom, wheelTemplateDom);
// Validate that the axles under the car have the same DOM as the axle template.
PrefabTestDomUtils::ValidatePrefabDomInstances(axleInstanceAliasesUnderCar, carTemplateDom, axleTemplateDom);
delete prefabTestComponent;
}
TEST_F(PrefabUpdateTemplateTest, UpdatePrefabTemplate_ChangeComponentProperty_AllDependentTemplatesUpdated)
{
// Create a single entity wheel instance with a PrefabTestComponent and create a template out of it.
AZ::Entity* wheelEntity = CreateEntity("WheelEntity1", false);
PrefabTestComponent* prefabTestComponent = aznew PrefabTestComponent(true);
wheelEntity->AddComponent(prefabTestComponent);
AZStd::unique_ptr<Instance> wheelIsolatedInstance = m_prefabSystemComponent->CreatePrefab({ wheelEntity },
{}, WheelPrefabMockFilePath);
const TemplateId wheelTemplateId = wheelIsolatedInstance->GetTemplateId();
PrefabDom& wheelTemplateDom = m_prefabSystemComponent->FindTemplateDom(wheelTemplateId);
AZStd::vector<EntityAlias> wheelTemplateEntityAliases = wheelIsolatedInstance->GetEntityAliases();
// Validate that the wheel template has the same entities(1) as the instance it was created from.
ASSERT_EQ(wheelTemplateEntityAliases.size(), 1);
// Validate that the wheel entity has 1 component under it.
AZStd::string entityAlias = wheelTemplateEntityAliases.front();
PrefabDomValue* wheelEntityComponents =
PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(wheelTemplateDom);
ASSERT_TRUE(wheelEntityComponents != nullptr && wheelEntityComponents->IsArray());
EXPECT_EQ(wheelEntityComponents->GetArray().Size(), 1);
// Extract the component id of the entity in wheel template and verify that it matches with the component id of the wheel instance.
PrefabDomValueReference wheelEntityComponentIdValue =
PrefabDomUtils::FindPrefabDomValue(*wheelEntityComponents->Begin(), PrefabTestDomUtils::ComponentIdName);
ASSERT_TRUE(wheelEntityComponentIdValue.has_value());
EXPECT_EQ(prefabTestComponent->GetId(), wheelEntityComponentIdValue->get().GetUint64());
// Create an axle with 0 entities and 1 wheel instance.
AZStd::unique_ptr<Instance> wheel1UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
AZStd::unique_ptr<Instance> axleInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(wheel1UnderAxle) ), AxlePrefabMockFilePath);
const TemplateId axleTemplateId = axleInstance->GetTemplateId();
PrefabDom& axleTemplateDom = m_prefabSystemComponent->FindTemplateDom(axleTemplateId);
const AZStd::vector<InstanceAlias> wheelInstanceAliasesUnderAxle = axleInstance->GetNestedInstanceAliases(wheelTemplateId);
// Create a car with 0 entities and 1 axle instance.
AZStd::unique_ptr<Instance> axleUnderCar = m_prefabSystemComponent->InstantiatePrefab(axleTemplateId);
AZStd::unique_ptr<Instance> carInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(axleUnderCar) ), CarPrefabMockFilePath);
const TemplateId carTemplateId = carInstance->GetTemplateId();
const AZStd::vector<InstanceAlias> axleInstanceAliasesUnderCar = carInstance->GetNestedInstanceAliases(axleTemplateId);
PrefabDom& carTemplateDom = m_prefabSystemComponent->FindTemplateDom(carTemplateId);
// Change the bool property of the component from Wheel instance and use it to update the wheel template.
prefabTestComponent->m_boolProperty = false;
PrefabDom updatedWheelInstanceDom;
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*wheelIsolatedInstance, updatedWheelInstanceDom));
m_prefabSystemComponent->UpdatePrefabTemplate(wheelTemplateId, updatedWheelInstanceDom);
// Validate that the prefabTestComponent in the wheel template DOM doesn't have a BoolProperty.
// Even though we changed the property to false, it won't be serialized out because it's a default value.
wheelEntityComponents = PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(wheelTemplateDom);
ASSERT_TRUE(wheelEntityComponents != nullptr && wheelEntityComponents->IsArray());
EXPECT_EQ(wheelEntityComponents->GetArray().Size(), 1);
PrefabDomValueReference wheelEntityComponentBoolPropertyValue =
PrefabDomUtils::FindPrefabDomValue(*wheelEntityComponents->Begin(), PrefabTestDomUtils::BoolPropertyName);
ASSERT_FALSE(wheelEntityComponentBoolPropertyValue.has_value());
// Validate that the wheels under the axle have the same DOM as the wheel template.
PrefabTestDomUtils::ValidatePrefabDomInstances(wheelInstanceAliasesUnderAxle, axleTemplateDom, wheelTemplateDom);
// Validate that the axles under the car have the same DOM as the axle template.
PrefabTestDomUtils::ValidatePrefabDomInstances(axleInstanceAliasesUnderCar, carTemplateDom, axleTemplateDom);
}
}
@@ -0,0 +1,131 @@
/*
* 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 <Prefab/PrefabDomUtils.h>
#include <Prefab/PrefabTestComponent.h>
#include <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestFixture.h>
#include <AzCore/Component/ComponentApplicationBus.h>
namespace UnitTest
{
using PrefabUpdateWithPatchesTest = PrefabTestFixture;
/*
The below tests use an example of car->axle->wheel templates to test that change propagation works correctly within templates.
The car template will have axle templates nested under it and the axle template will have wheel templates nested under it.
Because of the complexity that arises from multiple levels of prefab nesting, it's easier to write tests using an example scenario
than use generic nesting terminology.
*/
TEST_F(PrefabUpdateWithPatchesTest, ApplyPatchesToInstance_ComponentUpdated_PatchAppliedCorrectly)
{
// Create a single entity wheel instance with a PrefabTestComponent and create a template out of it.
AZ::Entity* wheelEntity = CreateEntity("WheelEntity1", false);
PrefabTestComponent* prefabTestComponent = aznew PrefabTestComponent(true);
wheelEntity->AddComponent(prefabTestComponent);
wheelEntity->Init();
wheelEntity->Activate();
AZStd::unique_ptr<Instance> wheelIsolatedInstance = m_prefabSystemComponent->CreatePrefab({ wheelEntity },
{}, WheelPrefabMockFilePath);
const TemplateId wheelTemplateId = wheelIsolatedInstance->GetTemplateId();
PrefabDom& wheelTemplateDom = m_prefabSystemComponent->FindTemplateDom(wheelTemplateId);
AZStd::vector<EntityAlias> wheelTemplateEntityAliases = wheelIsolatedInstance->GetEntityAliases();
// Validate that the wheel template has the same entities(1) as the instance it was created from.
ASSERT_EQ(wheelTemplateEntityAliases.size(), 1);
// Validate that the wheel entity has 1 component under it.
AZStd::string entityAlias = wheelTemplateEntityAliases.front();
PrefabDomValue* wheelEntityComponents =
PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(wheelTemplateDom);
ASSERT_TRUE(wheelEntityComponents != nullptr && wheelEntityComponents->IsArray());
EXPECT_EQ(wheelEntityComponents->GetArray().Size(), 1);
// Extract the component id of the entity in wheel template and verify that it matches with the component id of the wheel instance.
PrefabDomValueReference wheelEntityComponentIdValue =
PrefabDomUtils::FindPrefabDomValue(*wheelEntityComponents->Begin(), PrefabTestDomUtils::ComponentIdName);
ASSERT_TRUE(wheelEntityComponentIdValue.has_value());
EXPECT_EQ(prefabTestComponent->GetId(), wheelEntityComponentIdValue->get().GetUint64());
// Create an axle with 0 entities and 1 wheel instance.
AZStd::unique_ptr<Instance> wheel1UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
const AZStd::vector<EntityAlias> wheelEntityAliasesUnderAxle = wheel1UnderAxle->GetEntityAliases();
AZStd::unique_ptr<Instance> axleInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList(AZStd::move(wheel1UnderAxle)), AxlePrefabMockFilePath);
const TemplateId axleTemplateId = axleInstance->GetTemplateId();
PrefabDom& axleTemplateDom = m_prefabSystemComponent->FindTemplateDom(axleTemplateId);
const AZStd::vector<InstanceAlias> wheelInstanceAliasesUnderAxle = axleInstance->GetNestedInstanceAliases(wheelTemplateId);
// Create a car with 0 entities and 1 axle instance.
AZStd::unique_ptr<Instance> axleUnderCar = m_prefabSystemComponent->InstantiatePrefab(axleTemplateId);
AZStd::unique_ptr<Instance> carInstance = m_prefabSystemComponent->CreatePrefab({},
MakeInstanceList( AZStd::move(axleUnderCar) ), CarPrefabMockFilePath);
const TemplateId carTemplateId = carInstance->GetTemplateId();
const AZStd::vector<InstanceAlias> axleInstanceAliasesUnderCar = carInstance->GetNestedInstanceAliases(axleTemplateId);
PrefabDom& carTemplateDom = m_prefabSystemComponent->FindTemplateDom(carTemplateId);
//activate the entity so we can access via transform bus
axleInstance->InitializeNestedEntities();
axleInstance->ActivateNestedEntities();
//get the entity id
AZStd::vector<AZ::EntityId> entityIdVector;
axleInstance->GetNestedEntityIds([&entityIdVector](const AZ::EntityId& entityId)
{
entityIdVector.push_back(entityId);
return true;
});
EXPECT_EQ(entityIdVector.size(), 1);
AZ::EntityId wheelEntityIdUnderAxle = entityIdVector.front();
// Retrieve the entity pointer from the component application bus.
AZ::Entity* wheelEntityUnderAxle = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(wheelEntityUnderAxle, &AZ::ComponentApplicationBus::Events::FindEntity, wheelEntityIdUnderAxle);
//create document with before change snapshot
PrefabDom entityDomBefore;
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *wheelEntityUnderAxle);
PrefabTestComponent* axlewheelComponent = wheelEntityUnderAxle->FindComponent<PrefabTestComponent>();
// Change the bool property of the component from Wheel instance and use it to update the wheel template.
axlewheelComponent->m_boolProperty = false;
//create document with after change snapshot
PrefabDom entityDomAfter;
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomAfter, *wheelEntityUnderAxle);
InstanceOptionalReference topMostInstanceInHierarchy = m_instanceToTemplateInterface->GetTopMostInstanceInHierarchy(wheelEntityIdUnderAxle);
ASSERT_TRUE(topMostInstanceInHierarchy);
PrefabDom patches;
InstanceOptionalReference wheelInstanceUnderAxle = axleInstance->FindNestedInstance(wheelInstanceAliasesUnderAxle.front());
m_instanceToTemplateInterface->GeneratePatchForLink(patches, entityDomBefore, entityDomAfter, wheelInstanceUnderAxle->get().GetLinkId());
m_instanceToTemplateInterface->ApplyPatchesToInstance(wheelEntityIdUnderAxle, patches, topMostInstanceInHierarchy->get());
// Validate that the prefabTestComponent in the wheel instance under axle doesn't have a BoolProperty.
// Even though we changed the property to false, it won't be serialized out because it's a default value.
PrefabDomValue* wheelInstanceDomUnderAxle =
PrefabTestDomUtils::GetPrefabDomInstancePath(wheelInstanceAliasesUnderAxle.front()).Get(axleTemplateDom);
wheelEntityComponents = PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(*wheelInstanceDomUnderAxle);
ASSERT_TRUE(wheelEntityComponents != nullptr);
PrefabDomValueReference wheelEntityComponentBoolPropertyValue =
PrefabDomUtils::FindPrefabDomValue(*wheelEntityComponents->Begin(), PrefabTestDomUtils::BoolPropertyName);
ASSERT_FALSE(wheelEntityComponentBoolPropertyValue.has_value());
// Validate that the axles under the car have the same DOM as the axle template.
PrefabTestDomUtils::ValidatePrefabDomInstances(axleInstanceAliasesUnderCar, carTemplateDom, axleTemplateDom);
}
}
@@ -0,0 +1,75 @@
/*
* 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 "IntegerPrimtitiveTestConfig.h"
namespace UnitTest
{
using namespace AzToolsFramework;
template<typename ValueType>
struct PropertyIntCtrlCommonFixture
: public ToolsApplicationFixture
{
};
TYPED_TEST_CASE(PropertyIntCtrlCommonFixture, IntegerPrimtitiveTestConfigs);
TYPED_TEST(PropertyIntCtrlCommonFixture, ValidMinValue_ExpectSafeValueEqualToOriginalValue)
{
// Given a valid value for the minimum attribute
AZ::s64 value = aznumeric_cast<AZ::s64>(QtWidgetLimits<TypeParam>::Min());
// Attempt to get a safe value in the Qt range
AZ::s64 result = GetSafeAttributeValue<TypeParam>(value, "Test Property", "Test Attribute");
// Expect the result to be equal the original value
EXPECT_EQ(result, value);
}
TYPED_TEST(PropertyIntCtrlCommonFixture, InvalidMinValue_ExpectSafeValueEqualToValueTypeMinLimit)
{
// Given an invalid value for the minimum attribute
AZ::s64 value = aznumeric_cast<AZ::s64>(QtWidgetLimits<TypeParam>::Min()) - 1;
// Attempt to get a safe value in the Qt range
AZ::s64 result = GetSafeAttributeValue<TypeParam>(value, "Test Property", "Test Attribute");
// Expect the result to be equal to the limit for this value type
EXPECT_EQ(result, QtWidgetLimits<TypeParam>::Min());
}
TYPED_TEST(PropertyIntCtrlCommonFixture, ValidMaxValue_ExpectSafeValueEqualToOriginalValue)
{
// Given a valid value for the maximum attribute
AZ::s64 value = aznumeric_cast<AZ::s64>(QtWidgetLimits<TypeParam>::Max());
// Attempt to get a safe value in the Qt range
AZ::s64 result = GetSafeAttributeValue<TypeParam>(value, "Test Property", "Test Attribute");
// Expect the result to be equal the original value
EXPECT_EQ(result, value);
}
TYPED_TEST(PropertyIntCtrlCommonFixture, InvalidMaxValue_ExpectSafeValueEqualToValueTypeMinLimit)
{
// Given an invalid value for the maximum attribute
AZ::s64 value = aznumeric_cast<AZ::s64>(QtWidgetLimits<TypeParam>::Min()) - 1;
// Attempt to get a safe value in the Qt range
AZ::s64 result = GetSafeAttributeValue<TypeParam>(value, "Test Property", "Test Attribute");
// Expect the result to be equal to the limit for this value type
EXPECT_EQ(result, QtWidgetLimits<TypeParam>::Min());
}
} // namespace UnitTest
@@ -0,0 +1,167 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/UI/PropertyEditor/PropertyIntCtrlCommon.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzCore/std/typetraits/is_signed.h>
#include "IntegerPrimtitiveTestConfig.h"
#include <QApplication>
#include <sstream>
namespace UnitTest
{
template<typename ValueType, template <typename> class HandlerType>
class IntrCtrlHandlerAPI
: public HandlerType<ValueType>
{
using Parent = HandlerType<ValueType>;
using Parent::CreateGUI;
using Parent::ConsumeAttribute;
using Parent::ReadValuesIntoGUI;
using Parent::WriteGUIValuesIntoProperty;
using Parent::ModifyTooltip;
public:
QWidget* CreateGUI(QWidget* pParent)
{
return Parent::CreateGUI(pParent);
}
void ConsumeAttribute(IntrCtrlHandlerAPI* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName)
{
Parent::ConsumeAttribute(GUI, attrib, attrValue, debugName);
}
bool ReadValuesIntoGUI(size_t index, IntrCtrlHandlerAPI* GUI, const ValueType& instance, AzToolsFramework::InstanceDataNode* node)
{
return Parent::ReadValuesIntoGUI(index, GUI, instance, node);
}
void WriteGUIValuesIntoProperty(size_t index, IntrCtrlHandlerAPI* GUI, ValueType& instance, AzToolsFramework::InstanceDataNode* node)
{
Parent::WriteGUIValuesIntoProperty(index, GUI, instance, node);
}
bool ModifyTooltip(QWidget* widget, QString& toolTipString)
{
return Parent::ModifyTooltip(widget, toolTipString);
}
};
template<typename ValueType, typename WidgetType, template <typename> class HandlerType>
struct PropertyCtrlFixture
: public ToolsApplicationFixture
{
using HandlerAPI = IntrCtrlHandlerAPI<ValueType, HandlerType>;
void SetUpEditorFixtureImpl() override
{
// note: must set a widget as the active window and add widgets
// as children to ensure focus in/out events fire correctly
m_dummyWidget = AZStd::make_unique<QWidget>();
QApplication::setActiveWindow(m_dummyWidget.get());
m_handler = AZStd::make_unique<HandlerAPI>();
m_widget = static_cast<WidgetType*>(m_handler->CreateGUI(m_dummyWidget.get()));
}
void TearDownEditorFixtureImpl() override
{
QApplication::setActiveWindow(nullptr);
m_dummyWidget.reset();
m_handler.reset();
}
static void SetWidgetRangeToNonExtremeties(WidgetType* widget)
{
widget->setMinimum(widget->minimum() + 1);
widget->setMaximum(widget->maximum() - 1);
}
static std::string GetToolTipStringAtLimits()
{
if constexpr (std::is_signed<ValueType>::value)
{
return "[-INF, INF]";
}
else
{
return "[0, INF]";
}
}
void PropertyCtrlHandlersCreated()
{
using ::testing::Ne;
EXPECT_THAT(m_handler, Ne(nullptr));
}
void PropertyCtrlWidgetsCreated()
{
using ::testing::Ne;
EXPECT_THAT(m_widget, Ne(nullptr));
}
void Widget_Minimum_ExpectQtWidgetLimits_Min()
{
EXPECT_EQ(m_widget->minimum(), AzToolsFramework::QtWidgetLimits<ValueType>::Min());
}
void Widget_Maximum_ExpectQtWidgetLimits_Max()
{
EXPECT_EQ(m_widget->maximum(), AzToolsFramework::QtWidgetLimits<ValueType>::Max());
}
void HandlerMinMaxLimit_ModifyHandler_ExpectSuccessAndValidRangeLimitToolTipString()
{
// Given a widget
auto& widget = m_widget;
auto& handler = m_handler;
QString tooltip;
std::string expected;
// Retrieve the tooltip string for this widget
auto success = handler->ModifyTooltip(widget, tooltip);
expected = GetToolTipStringAtLimits();
// Expect the operation to be successful and a valid limit tooltip string generated
EXPECT_TRUE(success);
EXPECT_STREQ(tooltip.toStdString().c_str(), expected.c_str());
}
void HandlerMinMaxLessLimit_ModifyHandler_ExpectSuccessAndValidLessLimitToolTipString()
{
// Given a widget
auto& widget = m_widget;
auto& handler = m_handler;
QString tooltip;
std::stringstream expected;
// That is not at the extremeties of the type range limit
SetWidgetRangeToNonExtremeties(widget);
// Retrieve the tooltip string for this widget
auto success = handler->ModifyTooltip(widget, tooltip);
expected << "[" << widget->minimum() << ", " << widget->maximum() << "]";
// Expect the operation to be successful and a valid less than limit tooltip string generated
EXPECT_TRUE(success);
EXPECT_STREQ(tooltip.toStdString().c_str(), expected.str().c_str());
}
AZStd::unique_ptr<QWidget> m_dummyWidget;
AZStd::unique_ptr<HandlerAPI> m_handler;
WidgetType* m_widget;
};
} // namespace UnitTest
@@ -0,0 +1,54 @@
/*
* 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 "PropertyIntCtrlCommonTests.h"
#include <AzToolsFramework/UI/PropertyEditor/PropertyIntSliderCtrl.hxx>
namespace UnitTest
{
using namespace AzToolsFramework;
template <typename ValueType>
using PropertySliderCtrlFixture = PropertyCtrlFixture<ValueType, PropertyIntSliderCtrl, IntSliderHandler>;
TYPED_TEST_CASE(PropertySliderCtrlFixture, IntegerPrimtitiveTestConfigs);
TYPED_TEST(PropertySliderCtrlFixture, PropertySliderCtrlHandlersCreated)
{
this->PropertyCtrlHandlersCreated();
}
TYPED_TEST(PropertySliderCtrlFixture, PropertySliderCtrlWidgetsCreated)
{
this->PropertyCtrlWidgetsCreated();
}
TYPED_TEST(PropertySliderCtrlFixture, SliderWidget_Minimum_ExpectQtWidgetLimits_Min)
{
this->Widget_Minimum_ExpectQtWidgetLimits_Min();
}
TYPED_TEST(PropertySliderCtrlFixture, SliderWidget_Maximum_ExpectQtWidgetLimits_Max)
{
EXPECT_EQ(this->m_widget->maximum(), AzToolsFramework::QtWidgetLimits<TypeParam>::Max());
}
TYPED_TEST(PropertySliderCtrlFixture, SliderHandlerMinMaxLimit_ModifyHandler_ExpectSuccessAndValidRangeLimitToolTipString)
{
this->HandlerMinMaxLimit_ModifyHandler_ExpectSuccessAndValidRangeLimitToolTipString();
}
TYPED_TEST(PropertySliderCtrlFixture, SliderHandlerMinMaxLessLimit_ModifyHandler_ExpectSuccessAndValidLessLimitToolTipString)
{
this->HandlerMinMaxLessLimit_ModifyHandler_ExpectSuccessAndValidLessLimitToolTipString();
}
} // namespace UnitTest
@@ -0,0 +1,54 @@
/*
* 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 "PropertyIntCtrlCommonTests.h"
#include <AzToolsFramework/UI/PropertyEditor/PropertyIntSpinCtrl.hxx>
namespace UnitTest
{
using namespace AzToolsFramework;
template <typename ValueType>
using PropertySpinCtrlFixture = PropertyCtrlFixture<ValueType, PropertyIntSpinCtrl, IntSpinBoxHandler>;
TYPED_TEST_CASE(PropertySpinCtrlFixture, IntegerPrimtitiveTestConfigs);
TYPED_TEST(PropertySpinCtrlFixture, PropertySpinCtrlHandlersCreated)
{
this->PropertyCtrlHandlersCreated();
}
TYPED_TEST(PropertySpinCtrlFixture, PropertySpinCtrlWidgetsCreated)
{
this->PropertyCtrlWidgetsCreated();
}
TYPED_TEST(PropertySpinCtrlFixture, SpinBoxWidget_Minimum_ExpectQtWidgetLimits_Min)
{
this->Widget_Minimum_ExpectQtWidgetLimits_Min();
}
TYPED_TEST(PropertySpinCtrlFixture, SpinBoxWidget_Maximum_ExpectQtWidgetLimits_Max)
{
EXPECT_EQ(this->m_widget->maximum(), AzToolsFramework::QtWidgetLimits<TypeParam>::Max());
}
TYPED_TEST(PropertySpinCtrlFixture, SpinBoxHandlerMinMaxLimit_ModifyHandler_ExpectSuccessAndValidRangeLimitToolTipString)
{
this->HandlerMinMaxLimit_ModifyHandler_ExpectSuccessAndValidRangeLimitToolTipString();
}
TYPED_TEST(PropertySpinCtrlFixture, SpinBoxHandlerMinMaxLessLimit_ModifyHandler_ExpectSuccessAndValidLessLimitToolTipString)
{
this->HandlerMinMaxLessLimit_ModifyHandler_ExpectSuccessAndValidLessLimitToolTipString();
}
} // namespace UnitTest
@@ -0,0 +1,776 @@
/*
* 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 <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzFramework/Asset/SimpleAsset.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzToolsFramework/PropertyTreeEditor/PropertyTreeEditor.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <QtTest/QtTest>
#include <QApplication>
namespace UnitTest
{
using namespace AzToolsFramework;
struct PropertyTreeEditorSubBlockTester
{
AZ_TYPE_INFO(PropertyTreeEditorTester, "{E9497A1E-9B41-4A33-8F05-92CE41A0ABD9}");
AZ::s16 m_myNegativeShort = -42;
};
struct MockAssetData
: public AZ::Data::AssetData
{
AZ_RTTI(MyTestAssetData, "{8B0A8DCA-7F29-4B8E-B5D7-08E0EAB2C900}", AZ::Data::AssetData);
MockAssetData(const AZ::Data::AssetId& assetId)
: AssetData(assetId)
{
// to skip the automatic removal from the asset system
m_useCount = 2;
}
};
class TestSimpleAsset
{
public:
AZ_TYPE_INFO(TestSimpleAsset, "{10A39072-9287-49FE-93C8-55F7715FC758}");
bool m_data = false;
static const char* GetFileFilter()
{
return "*.NaN";
}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestSimpleAsset>()
->Version(0)
->Field("data", &TestSimpleAsset::m_data)
;
AzFramework::SimpleAssetReference<TestSimpleAsset>::Register(*serializeContext);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<TestSimpleAsset>("TestSimpleAsset", "Test data block for a simple asset mock data block")
->DataElement(0, &TestSimpleAsset::m_data, "My Data", "A test bool value.")
;
}
}
}
};
//! Test class
struct PropertyTreeEditorTester
{
AZ_TYPE_INFO(PropertyTreeEditorTester, "{D3E17BE6-0FEB-4A04-B8BE-105A4666E79F}");
int m_myInt = 42;
int m_myNewInt = 43;
bool m_myBool = true;
float m_myFloat = 42.0f;
AZStd::string m_myString = "StringValue";
AZStd::string m_myGroupedString = "GroupedStringValue";
PropertyTreeEditorSubBlockTester m_mySubBlock;
double m_myHiddenDouble = 42.0;
AZ::u16 m_myReadOnlyShort = 42;
AZ::Data::Asset<MockAssetData> m_myAssetData;
AzFramework::SimpleAssetReference<TestSimpleAsset> m_myTestSimpleAsset;
struct PropertyTreeEditorNestedTester
{
AZ_TYPE_INFO(PropertyTreeEditorTester, "{F5814544-424D-41C5-A5AB-632371615B6A}");
AZStd::string m_myNestedString = "NestedString";
};
AZStd::vector<PropertyTreeEditorNestedTester> m_myList;
AZStd::unordered_map<AZStd::string, PropertyTreeEditorNestedTester> m_myMap;
PropertyTreeEditorNestedTester m_nestedTester;
PropertyTreeEditorNestedTester m_nestedTesterHiddenChildren;
void Reflect(AZ::ReflectContext* context)
{
TestSimpleAsset::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<PropertyTreeEditorSubBlockTester>()
->Version(0)
->Field("myNegativeShort", &PropertyTreeEditorSubBlockTester::m_myNegativeShort);
serializeContext->Class<PropertyTreeEditorTester>()
->Version(1)
->Field("myInt", &PropertyTreeEditorTester::m_myInt)
->Field("myBool", &PropertyTreeEditorTester::m_myBool)
->Field("myFloat", &PropertyTreeEditorTester::m_myFloat)
->Field("myString", &PropertyTreeEditorTester::m_myString)
->Field("NestedTester", &PropertyTreeEditorTester::m_nestedTester)
->Field("myNewInt", &PropertyTreeEditorTester::m_myNewInt)
->Field("myGroupedString", &PropertyTreeEditorTester::m_myGroupedString)
->Field("myList", &PropertyTreeEditorTester::m_myList)
->Field("myMap", &PropertyTreeEditorTester::m_myMap)
->Field("mySubBlock", &PropertyTreeEditorTester::m_mySubBlock)
->Field("myHiddenDouble", &PropertyTreeEditorTester::m_myHiddenDouble)
->Field("myReadOnlyShort", &PropertyTreeEditorTester::m_myReadOnlyShort)
->Field("nestedTesterHiddenChildren", &PropertyTreeEditorTester::m_nestedTesterHiddenChildren)
->Field("myAssetData", &PropertyTreeEditorTester::m_myAssetData)
->Field("myTestSimpleAsset", &PropertyTreeEditorTester::m_myTestSimpleAsset)
;
serializeContext->Class<PropertyTreeEditorNestedTester>()
->Version(1)
->Field("myNestedString", &PropertyTreeEditorNestedTester::m_myNestedString)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<PropertyTreeEditorSubBlockTester>(
"PropertyTreeEditorSubBlock Tester", "Tester sub block for the PropertyTreeEditor test")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorSubBlockTester::m_myNegativeShort, "My Negative Short", "A test short int.")
;
editContext->Class<PropertyTreeEditorTester>(
"PropertyTreeEditor Tester", "Tester for the PropertyTreeEditor")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myInt, "My Int", "A test int.")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myBool, "My Bool", "A test bool.")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myFloat, "My Float", "A test float.")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myString, "My String", "A test string.")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_nestedTester, "Nested", "A nested class.")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myNewInt, "My New Int", "A test int.", "My Old Int")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myList, "My New List", "A test vector<>.", "My Old List")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myMap, "My Map", "A test unordered_map<>.", "My Old Map")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myAssetData, "My Asset Data", "An test asset data.")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myTestSimpleAsset, "My Test Simple Asset", "A test simple asset ref.")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myHiddenDouble, "My Hidden Double", "A test hidden node.", "My Old Double")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide)
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_nestedTesterHiddenChildren, "Nested Hidden Children", "A test node with hidden children.")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::HideChildren)
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myReadOnlyShort, "My Read Only", "A test read only node.")
->Attribute(AZ::Edit::Attributes::ReadOnly, true)
->DataElement(0, &PropertyTreeEditorTester::m_mySubBlock, "My Sub Block", "sub block test")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->ClassElement(AZ::Edit::ClassElements::Group, "Grouped")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorTester::m_myGroupedString, "My Grouped String", "A test grouped string.")
;
editContext->Class<PropertyTreeEditorNestedTester>(
"PropertyTreeEditor Nested Tester", "SubClass Tester for the PropertyTreeEditor")
->DataElement(AZ::Edit::UIHandlers::Default, &PropertyTreeEditorNestedTester::m_myNestedString, "My Nested String", "A test string.")
;
}
}
}
};
class PropertyTreeEditorTests
: public ::testing::Test
{
public:
void SetUp() override
{
m_app.Start(AzFramework::Application::Descriptor());
// 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);
}
void TearDown() override
{
m_app.Stop();
}
ToolsApplication m_app;
AZ::SerializeContext* m_serializeContext = nullptr;
};
TEST_F(PropertyTreeEditorTests, ReadPropertyTreeValues)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
PropertyTreeEditorTester propertyTreeEditorTester;
propertyTreeEditorTester.Reflect(m_serializeContext);
PropertyTreeEditor propertyTree = PropertyTreeEditor(&propertyTreeEditorTester, AZ::AzTypeInfo<PropertyTreeEditorTester>::Uuid());
// Test existing properties of different types
{
PropertyTreeEditor::PropertyAccessOutcome boolOutcome = propertyTree.GetProperty("My Bool");
EXPECT_TRUE(boolOutcome.IsSuccess());
EXPECT_TRUE(AZStd::any_cast<bool>(boolOutcome.GetValue()));
}
{
PropertyTreeEditor::PropertyAccessOutcome intOutcome = propertyTree.GetProperty("My Int");
EXPECT_TRUE(intOutcome.IsSuccess());
EXPECT_EQ(AZStd::any_cast<int>(intOutcome.GetValue()), propertyTreeEditorTester.m_myInt);
}
{
PropertyTreeEditor::PropertyAccessOutcome floatOutcome = propertyTree.GetProperty("My Float");
EXPECT_TRUE(floatOutcome.IsSuccess());
EXPECT_FLOAT_EQ(AZStd::any_cast<float>(floatOutcome.GetValue()), propertyTreeEditorTester.m_myFloat);
}
{
PropertyTreeEditor::PropertyAccessOutcome stringOutcome = propertyTree.GetProperty("My String");
EXPECT_TRUE(stringOutcome.IsSuccess());
EXPECT_STREQ(AZStd::any_cast<AZStd::string>(stringOutcome.GetValue()).data(), propertyTreeEditorTester.m_myString.data());
}
{
PropertyTreeEditor::PropertyAccessOutcome nestedOutcome = propertyTree.GetProperty("Nested|My Nested String");
EXPECT_TRUE(nestedOutcome.IsSuccess());
EXPECT_STREQ(AZStd::any_cast<AZStd::string>(nestedOutcome.GetValue()).data(), propertyTreeEditorTester.m_nestedTester.m_myNestedString.data());
}
{
PropertyTreeEditor::PropertyAccessOutcome groupedOutcome = propertyTree.GetProperty("Grouped|My Grouped String");
EXPECT_TRUE(groupedOutcome.IsSuccess());
EXPECT_STREQ(AZStd::any_cast<AZStd::string>(groupedOutcome.GetValue()).data(), propertyTreeEditorTester.m_myGroupedString.data());
}
// Test non-existing properties
{
PropertyTreeEditor::PropertyAccessOutcome intOutcome = propertyTree.GetProperty("Wrong Property");
EXPECT_FALSE(intOutcome.IsSuccess());
}
{
PropertyTreeEditor::PropertyAccessOutcome nestedOutcome = propertyTree.GetProperty("Nested|Wrong Nested Property");
EXPECT_FALSE(nestedOutcome.IsSuccess());
}
{
// Addressing the grouped property by name directly without the group should fail
PropertyTreeEditor::PropertyAccessOutcome groupedOutcome = propertyTree.GetProperty("My Grouped String");
EXPECT_FALSE(groupedOutcome.IsSuccess());
}
}
TEST_F(PropertyTreeEditorTests, WritePropertyTreeValues)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
PropertyTreeEditorTester propertyTreeEditorTester;
propertyTreeEditorTester.Reflect(m_serializeContext);
PropertyTreeEditor propertyTree = PropertyTreeEditor(&propertyTreeEditorTester, AZ::AzTypeInfo<PropertyTreeEditorTester>::Uuid());
// Test existing properties of different types
{
PropertyTreeEditor::PropertyAccessOutcome boolOutcomeSet = propertyTree.SetProperty("My Bool", AZStd::any(false));
EXPECT_TRUE(boolOutcomeSet.IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome boolOutcomeGet = propertyTree.GetProperty("My Bool");
EXPECT_TRUE(boolOutcomeGet.IsSuccess());
EXPECT_FALSE(AZStd::any_cast<bool>(boolOutcomeGet.GetValue()));
}
{
PropertyTreeEditor::PropertyAccessOutcome intOutcomeSet = propertyTree.SetProperty("My Int", AZStd::any(48));
EXPECT_TRUE(intOutcomeSet.IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome intOutcomeGet = propertyTree.GetProperty("My Int");
EXPECT_TRUE(intOutcomeGet.IsSuccess());
EXPECT_EQ(AZStd::any_cast<int>(intOutcomeGet.GetValue()), AZStd::any_cast<int>(intOutcomeSet.GetValue()));
}
{
PropertyTreeEditor::PropertyAccessOutcome floatOutcomeSet = propertyTree.SetProperty("My Float", AZStd::any(48.0f));
EXPECT_TRUE(floatOutcomeSet.IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome floatOutcomeGet = propertyTree.GetProperty("My Float");
EXPECT_TRUE(floatOutcomeGet.IsSuccess());
EXPECT_FLOAT_EQ(AZStd::any_cast<float>(floatOutcomeGet.GetValue()), AZStd::any_cast<float>(floatOutcomeSet.GetValue()));
}
{
PropertyTreeEditor::PropertyAccessOutcome stringOutcomeSet = propertyTree.SetProperty("My String", AZStd::make_any<AZStd::string>("New Value"));
EXPECT_TRUE(stringOutcomeSet.IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome stringOutcomeGet = propertyTree.GetProperty("My String");
EXPECT_TRUE(stringOutcomeGet.IsSuccess());
EXPECT_STREQ(AZStd::any_cast<AZStd::string>(stringOutcomeSet.GetValue()).data(), AZStd::any_cast<AZStd::string>(stringOutcomeGet.GetValue()).data());
}
{
PropertyTreeEditor::PropertyAccessOutcome stringOutcomeSet = propertyTree.SetProperty("Nested|My Nested String", AZStd::make_any<AZStd::string>("New Nested Value"));
EXPECT_TRUE(stringOutcomeSet.IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome stringOutcomeGet = propertyTree.GetProperty("Nested|My Nested String");
EXPECT_TRUE(stringOutcomeGet.IsSuccess());
EXPECT_STREQ(AZStd::any_cast<AZStd::string>(stringOutcomeSet.GetValue()).data(), AZStd::any_cast<AZStd::string>(stringOutcomeGet.GetValue()).data());
}
{
PropertyTreeEditor::PropertyAccessOutcome stringOutcomeSet = propertyTree.SetProperty("Grouped|My Grouped String", AZStd::make_any<AZStd::string>("New Grouped Value"));
EXPECT_TRUE(stringOutcomeSet.IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome stringOutcomeGet = propertyTree.GetProperty("Grouped|My Grouped String");
EXPECT_TRUE(stringOutcomeGet.IsSuccess());
EXPECT_STREQ(AZStd::any_cast<AZStd::string>(stringOutcomeSet.GetValue()).data(), AZStd::any_cast<AZStd::string>(stringOutcomeGet.GetValue()).data());
}
// Test non-existing properties
{
PropertyTreeEditor::PropertyAccessOutcome intOutcome = propertyTree.SetProperty("Wrong Property", AZStd::any(12));
EXPECT_FALSE(intOutcome.IsSuccess());
}
{
PropertyTreeEditor::PropertyAccessOutcome nestedOutcome = propertyTree.SetProperty("Nested|Wrong Nested Property", AZStd::make_any<AZStd::string>("Some Value"));
EXPECT_FALSE(nestedOutcome.IsSuccess());
}
{
PropertyTreeEditor::PropertyAccessOutcome groupedOutcome = propertyTree.SetProperty("Grouped|Wrong Grouped Property", AZStd::make_any<AZStd::string>("Some Value"));
EXPECT_FALSE(groupedOutcome.IsSuccess());
}
{
// Addressing the grouped property by name directly without the group should fail
PropertyTreeEditor::PropertyAccessOutcome groupedOutcome = propertyTree.SetProperty("My Grouped String", AZStd::make_any<AZStd::string>("Some Value"));
EXPECT_FALSE(groupedOutcome.IsSuccess());
}
// Test existing properties with wrong type
{
PropertyTreeEditor::PropertyAccessOutcome intOutcome = propertyTree.SetProperty("My Int", AZStd::any(12.0f));
EXPECT_FALSE(intOutcome.IsSuccess());
}
{
PropertyTreeEditor::PropertyAccessOutcome nestedOutcome = propertyTree.SetProperty("Nested|My Nested String", AZStd::any(42.0f));
EXPECT_FALSE(nestedOutcome.IsSuccess());
}
{
PropertyTreeEditor::PropertyAccessOutcome groupedOutcome = propertyTree.SetProperty("Grouped|My Grouped String", AZStd::any(42.0f));
EXPECT_FALSE(groupedOutcome.IsSuccess());
}
}
TEST_F(PropertyTreeEditorTests, PropertyTreeVectorContainerSupport)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
PropertyTreeEditorTester propertyTreeEditorTester;
propertyTreeEditorTester.Reflect(m_serializeContext);
PropertyTreeEditor propertyTree = PropertyTreeEditor(&propertyTreeEditorTester, AZ::AzTypeInfo<PropertyTreeEditorTester>::Uuid());
// IsContainer
{
EXPECT_FALSE(propertyTree.IsContainer("My New Int"));
EXPECT_TRUE(propertyTree.IsContainer("My New List"));
}
// AddContainerItem
{
AZStd::any key = AZStd::make_any<AZ::s32>(0);
AZStd::any value = AZStd::make_any<PropertyTreeEditorTester::PropertyTreeEditorNestedTester>();
PropertyTreeEditor::PropertyAccessOutcome outcomeAdd0 = propertyTree.AddContainerItem("My New Int", key, value);
EXPECT_FALSE(outcomeAdd0.IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome outcomeAdd1 = propertyTree.AddContainerItem("My New List", key, value);
EXPECT_TRUE(outcomeAdd1.IsSuccess());
}
// GetContainerCount
{
EXPECT_FALSE(propertyTree.GetContainerCount("My New Int").IsSuccess());
EXPECT_EQ(1, AZStd::any_cast<AZ::u64>(propertyTree.GetContainerCount("My New List").GetValue()));
}
// GetContainerItem
{
AZStd::any key = AZStd::make_any<AZ::s32>(0);
AZStd::any keyString = AZStd::make_any<AZStd::string_view>("0");
EXPECT_FALSE(propertyTree.GetContainerItem("My New Int", key).IsSuccess());
EXPECT_FALSE(propertyTree.GetContainerItem("My New List", keyString).IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome outcome = propertyTree.GetContainerItem("My New List", key);
EXPECT_TRUE(outcome.IsSuccess());
if (outcome.IsSuccess())
{
auto&& testerValue = AZStd::any_cast<PropertyTreeEditorTester::PropertyTreeEditorNestedTester>(&outcome.GetValue());
EXPECT_STREQ("NestedString", testerValue->m_myNestedString.c_str());
}
}
// UpdateContainerItem
{
AZStd::any key = AZStd::make_any<AZ::s32>(0);
AZStd::any keyString = AZStd::make_any<AZStd::string_view>("0");
PropertyTreeEditorTester::PropertyTreeEditorNestedTester testUpdate;
testUpdate.m_myNestedString = "a new value";
AZStd::any value = AZStd::make_any<PropertyTreeEditorTester::PropertyTreeEditorNestedTester>(testUpdate);
EXPECT_FALSE(propertyTree.UpdateContainerItem("My New Int", key, value).IsSuccess());
EXPECT_FALSE(propertyTree.UpdateContainerItem("My New List", keyString, value).IsSuccess());
EXPECT_TRUE(propertyTree.UpdateContainerItem("My New List", key, value).IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome outcome = propertyTree.GetContainerItem("My New List", key);
EXPECT_TRUE(outcome.IsSuccess());
if (outcome.IsSuccess())
{
auto&& testerValue = AZStd::any_cast<PropertyTreeEditorTester::PropertyTreeEditorNestedTester>(&outcome.GetValue());
EXPECT_STREQ(testUpdate.m_myNestedString.c_str(), testerValue->m_myNestedString.c_str());
}
}
// RemoveContainerItem
{
AZStd::any key = AZStd::make_any<AZ::s32>(0);
AZStd::any keyString = AZStd::make_any<AZStd::string_view>("0");
EXPECT_FALSE(propertyTree.RemoveContainerItem("My New Int", key).IsSuccess());
EXPECT_FALSE(propertyTree.RemoveContainerItem("My New List", keyString).IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome outcomeAdd1 = propertyTree.RemoveContainerItem("My New List", key);
EXPECT_TRUE(outcomeAdd1.IsSuccess());
EXPECT_EQ(0, AZStd::any_cast<AZ::u64>(propertyTree.GetContainerCount("My New List").GetValue()));
}
// ResetContainer
{
AZStd::any value = AZStd::make_any<PropertyTreeEditorTester::PropertyTreeEditorNestedTester>();
propertyTree.AddContainerItem("My New List", AZStd::make_any<AZ::s32>(0), value);
propertyTree.AddContainerItem("My New List", AZStd::make_any<AZ::s32>(1), value);
propertyTree.AddContainerItem("My New List", AZStd::make_any<AZ::s32>(2), value);
EXPECT_EQ(3, AZStd::any_cast<AZ::u64>(propertyTree.GetContainerCount("My New List").GetValue()));
propertyTree.ResetContainer("My New List");
EXPECT_EQ(0, AZStd::any_cast<AZ::u64>(propertyTree.GetContainerCount("My New List").GetValue()));
}
// AppendContainerItem
{
AZStd::any value = AZStd::make_any<PropertyTreeEditorTester::PropertyTreeEditorNestedTester>();
EXPECT_TRUE(propertyTree.AppendContainerItem("My New List", value).IsSuccess());
EXPECT_TRUE(propertyTree.AppendContainerItem("My New List", value).IsSuccess());
EXPECT_TRUE(propertyTree.AppendContainerItem("My New List", value).IsSuccess());
EXPECT_EQ(3, AZStd::any_cast<AZ::u64>(propertyTree.GetContainerCount("My New List").GetValue()));
propertyTree.ResetContainer("My New List");
}
}
TEST_F(PropertyTreeEditorTests, PropertyTreeUnorderedMapContainerSupport)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
using TestData = PropertyTreeEditorTester::PropertyTreeEditorNestedTester;
PropertyTreeEditorTester propertyTreeEditorTester;
propertyTreeEditorTester.Reflect(m_serializeContext);
propertyTreeEditorTester.m_myMap.emplace(AZStd::make_pair("one", TestData()));
const char* testDataString = "a test string";
PropertyTreeEditor propertyTree = PropertyTreeEditor(&propertyTreeEditorTester, AZ::AzTypeInfo<PropertyTreeEditorTester>::Uuid());
// AddContainerItem
{
AZStd::any key = AZStd::make_any<AZStd::string>("two");
TestData testItem;
testItem.m_myNestedString = testDataString;
AZStd::any value = AZStd::make_any<TestData>(testItem);
EXPECT_TRUE(propertyTree.AddContainerItem("My Map", key, value).IsSuccess());
}
// GetContainerCount
{
EXPECT_EQ(2, AZStd::any_cast<AZ::u64>(propertyTree.GetContainerCount("My Map").GetValue()));
}
// GetContainerItem
{
AZStd::any key = AZStd::make_any<AZStd::string>("two");
PropertyTreeEditor::PropertyAccessOutcome outcome = propertyTree.GetContainerItem("My Map", key);
EXPECT_TRUE(outcome.IsSuccess());
if (outcome.IsSuccess())
{
auto&& testerValue = AZStd::any_cast<TestData>(&outcome.GetValue());
EXPECT_STREQ(testDataString, testerValue->m_myNestedString.c_str());
}
}
// UpdateContainerItem
{
AZStd::any key = AZStd::make_any<AZStd::string>("two");
TestData testUpdate;
testUpdate.m_myNestedString = "a new value";
AZStd::any value = AZStd::make_any<TestData>(testUpdate);
EXPECT_TRUE(propertyTree.UpdateContainerItem("My Map", key, value).IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome outcome = propertyTree.GetContainerItem("My Map", key);
if (outcome.IsSuccess())
{
auto&& testerValue = AZStd::any_cast<PropertyTreeEditorTester::PropertyTreeEditorNestedTester>(&outcome.GetValue());
EXPECT_STREQ(testUpdate.m_myNestedString.c_str(), testerValue->m_myNestedString.c_str());
}
}
// RemoveContainerItem
{
AZStd::any key = AZStd::make_any<AZStd::string>("two");
EXPECT_TRUE(propertyTree.RemoveContainerItem("My Map", key).IsSuccess());
EXPECT_EQ(1, AZStd::any_cast<AZ::u64>(propertyTree.GetContainerCount("My Map").GetValue()));
}
// ResetContainer
{
EXPECT_EQ(1, AZStd::any_cast<AZ::u64>(propertyTree.GetContainerCount("My Map").GetValue()));
propertyTree.ResetContainer("My Map");
EXPECT_EQ(0, AZStd::any_cast<AZ::u64>(propertyTree.GetContainerCount("My Map").GetValue()));
}
// AppendContainerItem
{
AZStd::any value = AZStd::make_any<PropertyTreeEditorTester::PropertyTreeEditorNestedTester>();
EXPECT_FALSE(propertyTree.AppendContainerItem("My Map", value).IsSuccess());
EXPECT_EQ(0, AZStd::any_cast<AZ::u64>(propertyTree.GetContainerCount("My Map").GetValue()));
}
}
TEST_F(PropertyTreeEditorTests, PropertyTreeInspection)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
PropertyTreeEditorTester propertyTreeEditorTester;
propertyTreeEditorTester.Reflect(m_serializeContext);
PropertyTreeEditor propertyTree = PropertyTreeEditor(&propertyTreeEditorTester, AZ::AzTypeInfo<PropertyTreeEditorTester>::Uuid());
// BuildPathsList
{
auto&& pathList = propertyTree.BuildPathsList();
EXPECT_TRUE(!pathList.empty());
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "My Map"; }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "My New List"; }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "Nested|My Nested String"; }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "Grouped|My Grouped String"; }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "My Hidden Double"; }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "My Sub Block|My Negative Short"; }));
}
// BuildPathsListWithTypes
{
static auto stringContains = [](const AZStd::string& data, const char* subString) -> bool
{
return data.find(subString) != AZStd::string::npos;
};
auto&& pathList = propertyTree.BuildPathsListWithTypes();
EXPECT_TRUE(!pathList.empty());
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return stringContains(path,"NotVisible"); }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return stringContains(path,"Visible"); }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return stringContains(path,"ShowChildrenOnly"); }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return stringContains(path,"HideChildren"); }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return stringContains(path,"ReadOnly"); }));
}
// GetPropertyType
{
EXPECT_STREQ("AZStd::unordered_map", propertyTree.GetPropertyType("My Map").c_str());
EXPECT_STREQ("AZStd::vector", propertyTree.GetPropertyType("My New List").c_str());
EXPECT_STREQ("AZStd::string", propertyTree.GetPropertyType("Nested|My Nested String").c_str());
EXPECT_STREQ("double", propertyTree.GetPropertyType("My Hidden Double").c_str());
EXPECT_STREQ("PropertyTreeEditorTester", propertyTree.GetPropertyType("Nested").c_str());
}
// BuildPathsList after enforcement removes the "show children only" nodes from the paths
{
propertyTree.SetVisibleEnforcement(true);
auto&& pathList = propertyTree.BuildPathsList();
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "My Map"; }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "My New List"; }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "Nested|My Nested String"; }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "Grouped|My Grouped String"; }));
EXPECT_FALSE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "My Hidden Double"; }));
EXPECT_FALSE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "My Sub Block|My Negative Short"; }));
EXPECT_TRUE(AZStd::any_of(pathList.begin(), pathList.end(), [](auto&& path) { return path == "My Negative Short"; }));
}
}
TEST_F(PropertyTreeEditorTests, PropertyTreeAttributeInspection)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
PropertyTreeEditorTester propertyTreeEditorTester;
propertyTreeEditorTester.Reflect(m_serializeContext);
PropertyTreeEditor propertyTree = PropertyTreeEditor(&propertyTreeEditorTester, AZ::AzTypeInfo<PropertyTreeEditorTester>::Uuid());
// HasAttribute
{
EXPECT_TRUE(propertyTree.HasAttribute("My Read Only", "ReadOnly"));
EXPECT_TRUE(propertyTree.HasAttribute("My Hidden Double", "Visibility"));
EXPECT_TRUE(propertyTree.HasAttribute("My Sub Block", "AutoExpand"));
}
}
TEST_F(PropertyTreeEditorTests, HandlesVisibleEnforcement)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
PropertyTreeEditorTester propertyTreeEditorTester;
propertyTreeEditorTester.Reflect(m_serializeContext);
PropertyTreeEditor propertyTree = PropertyTreeEditor(&propertyTreeEditorTester, AZ::AzTypeInfo<PropertyTreeEditorTester>::Uuid());
// can access a hidden value with 'visible enforcement' set to false
{
PropertyTreeEditor::PropertyAccessOutcome outcomeGet = propertyTree.GetProperty("My Hidden Double");
EXPECT_TRUE(outcomeGet.IsSuccess());
EXPECT_EQ(42.0, AZStd::any_cast<double>(outcomeGet.GetValue()));
}
// can mutate a hidden value with 'visible enforcement' set to false
{
PropertyTreeEditor::PropertyAccessOutcome outcomeSet = propertyTree.SetProperty("My Hidden Double", AZStd::any(12.0));
EXPECT_TRUE(outcomeSet.IsSuccess());
PropertyTreeEditor::PropertyAccessOutcome outcomeGet = propertyTree.GetProperty("My Hidden Double");
EXPECT_TRUE(outcomeGet.IsSuccess());
EXPECT_EQ(12.0, AZStd::any_cast<double>(outcomeGet.GetValue()));
}
propertyTree.SetVisibleEnforcement(true);
// can NOT access hidden value with 'visible enforcement' set to true
{
PropertyTreeEditor::PropertyAccessOutcome outcomeGet = propertyTree.GetProperty("My Hidden Double");
EXPECT_FALSE(outcomeGet.IsSuccess());
}
// can NOT mutate a hidden value with 'visible enforcement' set to false
{
PropertyTreeEditor::PropertyAccessOutcome outcomeSet = propertyTree.SetProperty("My Hidden Double", AZStd::any(42.0));
EXPECT_FALSE(outcomeSet.IsSuccess());
}
}
TEST_F(PropertyTreeEditorTests, PropertyTreeDeprecatedNamesSupport)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
PropertyTreeEditorTester propertyTreeEditorTester;
propertyTreeEditorTester.Reflect(m_serializeContext);
PropertyTreeEditor propertyTree = PropertyTreeEditor(&propertyTreeEditorTester, AZ::AzTypeInfo<PropertyTreeEditorTester>::Uuid());
// Test that new and deprecated name both refer to the same property
{
int newIntValue = 0;
// get current value of My New Int
PropertyTreeEditor::PropertyAccessOutcome intOutcomeGet = propertyTree.GetProperty("My New Int");
EXPECT_TRUE(intOutcomeGet.IsSuccess());
newIntValue = AZStd::any_cast<int>(intOutcomeGet.GetValue());
// Set new value to My Old Int
PropertyTreeEditor::PropertyAccessOutcome intOutcomeSet = propertyTree.SetProperty("My Old Int", AZStd::any(12));
EXPECT_TRUE(intOutcomeSet.IsSuccess());
// Read value of My New Int again
PropertyTreeEditor::PropertyAccessOutcome intOutcomeGetAgain = propertyTree.GetProperty("My New Int");
EXPECT_TRUE(intOutcomeGetAgain.IsSuccess());
// Verify that My Old Int and My New Int refer to the same property
EXPECT_TRUE(AZStd::any_cast<int>(intOutcomeGetAgain.GetValue()) != newIntValue);
}
}
TEST_F(PropertyTreeEditorTests, ClearWithEmptyAny)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ::Data::AssetId mockAssetId = AZ::Data::AssetId::CreateString("{66CC8A20-DC4D-4856-95FE-5C75A47B6A21}:0");
MockAssetData mockAssetData(mockAssetId);
AZ::Data::Asset<MockAssetData> mockAsset(&mockAssetData, AZ::Data::AssetLoadBehavior::Default);
AzFramework::SimpleAssetReference<TestSimpleAsset> mockSimpleAsset;
mockSimpleAsset.SetAssetPath("path/to/42");
PropertyTreeEditorTester propertyTreeEditorTester;
propertyTreeEditorTester.Reflect(m_serializeContext);
propertyTreeEditorTester.m_myInt = 42;
propertyTreeEditorTester.m_mySubBlock.m_myNegativeShort = -42;
propertyTreeEditorTester.m_myList.push_back({});
propertyTreeEditorTester.m_myAssetData = AZStd::move(mockAsset);
propertyTreeEditorTester.m_myTestSimpleAsset = mockSimpleAsset;
PropertyTreeEditor propertyTree(&propertyTreeEditorTester, AZ::AzTypeInfo<PropertyTreeEditorTester>::Uuid());
propertyTree.SetVisibleEnforcement(true);
// use an empty any<> to set properties back to a default value
{
AZStd::any anEmpty;
EXPECT_TRUE(propertyTree.SetProperty("My Int", anEmpty).IsSuccess());
EXPECT_TRUE(propertyTree.SetProperty("My Negative Short", anEmpty).IsSuccess());
EXPECT_TRUE(propertyTree.SetProperty("My New List", anEmpty).IsSuccess());
EXPECT_TRUE(propertyTree.SetProperty("My Asset Data", anEmpty).IsSuccess());
EXPECT_TRUE(propertyTree.SetProperty("My Test Simple Asset", anEmpty).IsSuccess());
}
// check that the properties went back to default values
{
EXPECT_EQ(0, propertyTreeEditorTester.m_myInt);
EXPECT_EQ(0, propertyTreeEditorTester.m_mySubBlock.m_myNegativeShort);
EXPECT_TRUE(propertyTreeEditorTester.m_myList.empty());
EXPECT_FALSE(propertyTreeEditorTester.m_myAssetData.GetId().IsValid());
EXPECT_TRUE(propertyTreeEditorTester.m_myTestSimpleAsset.GetAssetPath().empty());
}
}
} // namespace UnitTest
@@ -0,0 +1,64 @@
/*
* 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 <AzCore/Component/Entity.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
class AzToolsFrameworkPythonBindingsFixture
: public ToolsApplicationFixture
{
};
TEST_F(AzToolsFrameworkPythonBindingsFixture, AzToolsFrameworkToolsApplicationRequestBus_ApiExists)
{
AZ::BehaviorContext* behaviorContext = GetApplication()->GetBehaviorContext();
ASSERT_TRUE(behaviorContext);
auto toolsApplicationRequestBus = behaviorContext->m_ebuses.find("ToolsApplicationRequestBus");
EXPECT_TRUE(behaviorContext->m_ebuses.end() != toolsApplicationRequestBus);
}
TEST_F(AzToolsFrameworkPythonBindingsFixture, AzToolsFrameworkToolsApplicationNotificationBus_ApiExists)
{
AZ::BehaviorContext* behaviorContext = GetApplication()->GetBehaviorContext();
ASSERT_TRUE(behaviorContext);
auto toolsApplicationNotificationBus = behaviorContext->m_ebuses.find("ToolsApplicationNotificationBus");
EXPECT_TRUE(behaviorContext->m_ebuses.end() != toolsApplicationNotificationBus);
}
TEST_F(AzToolsFrameworkPythonBindingsFixture, AzToolsFrameworkEditorEntityContextNotificationBus_ApiExists)
{
AZ::BehaviorContext* behaviorContext = GetApplication()->GetBehaviorContext();
ASSERT_TRUE(behaviorContext);
auto editorEntityContextNotificationBus = behaviorContext->m_ebuses.find("EditorEntityContextNotificationBus");
EXPECT_TRUE(behaviorContext->m_ebuses.end() != editorEntityContextNotificationBus);
}
TEST_F(AzToolsFrameworkPythonBindingsFixture, AzToolsFrameworkSliceRequestBus_ApiExists)
{
AZ::BehaviorContext* behaviorContext = GetApplication()->GetBehaviorContext();
ASSERT_TRUE(behaviorContext);
auto sliceRequestBus = behaviorContext->m_ebuses.find("SliceRequestBus");
EXPECT_TRUE(behaviorContext->m_ebuses.end() != sliceRequestBus);
}
}
@@ -0,0 +1,106 @@
/*
* 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 <AzToolsFramework/UI/PropertyEditor/QtWidgetLimits.h>
#include "IntegerPrimtitiveTestConfig.h"
namespace UnitTest
{
using namespace AzToolsFramework;
template<typename ValueType>
struct QtWidgetLimitsFixture
: public ToolsApplicationFixture
{
};
TYPED_TEST_CASE(QtWidgetLimitsFixture, IntegerPrimtitiveTestConfigs);
TYPED_TEST(QtWidgetLimitsFixture, MinRange)
{
switch (AZ::IntegralTypeCompare<TypeParam, QtWidgetValueType>())
{
// Given an LY widget value type of equal signedness and size to QtWidgetValueType
case AZ::IntegralTypeDiff::LSignedRSignedEqSize:
{
// Expect the minimum range of widget type to equal QtWidgetValueType
EXPECT_EQ(QtWidgetLimits<TypeParam>::Min(), std::numeric_limits<TypeParam>::min());
EXPECT_EQ(QtWidgetLimits<TypeParam>::Min(), std::numeric_limits<QtWidgetValueType>::min());
break;
}
// Given an LY widget type of equal signedness but wider than QtWidgetValueType
case AZ::IntegralTypeDiff::LSignedRSignedLWider:
{
// Expect the minimum range of widget type to be clamped to the range of QtWidgetValueType
EXPECT_NE(QtWidgetLimits<TypeParam>::Min(), std::numeric_limits<TypeParam>::min());
EXPECT_EQ(QtWidgetLimits<TypeParam>::Min(), std::numeric_limits<QtWidgetValueType>::min());
break;
}
// Given an LY widget type with a minimum range greater than the range of QtWidgetValueType
case AZ::IntegralTypeDiff::LSignedRSignedRWider:
case AZ::IntegralTypeDiff::LUnsignedRSignedLWider:
case AZ::IntegralTypeDiff::LUnsignedRSignedEqSize:
case AZ::IntegralTypeDiff::LUnsignedRSignedRWider:
{
// Expect the minimum range of widget type to be greater than the minimum range of QtWidgetValueType
EXPECT_EQ(QtWidgetLimits<TypeParam>::Min(), std::numeric_limits<TypeParam>::min());
EXPECT_NE(QtWidgetLimits<TypeParam>::Min(), std::numeric_limits<QtWidgetValueType>::min());
break;
}
default:
FAIL();
}
}
TYPED_TEST(QtWidgetLimitsFixture, MaxRange)
{
switch (AZ::IntegralTypeCompare<TypeParam, QtWidgetValueType>())
{
// Given an LY widget value type of equal signedness and size to QtWidgetValueType
case AZ::IntegralTypeDiff::LSignedRSignedEqSize:
{
// Expect the maximum range of widget type to equal QtWidgetValueType
EXPECT_EQ(QtWidgetLimits<TypeParam>::Max(), std::numeric_limits<TypeParam>::max());
EXPECT_EQ(QtWidgetLimits<TypeParam>::Max(), std::numeric_limits<QtWidgetValueType>::max());
break;
}
// Given an LY widget type with a maximum range greater than the range of QtWidgetValueType
case AZ::IntegralTypeDiff::LSignedRSignedLWider:
case AZ::IntegralTypeDiff::LUnsignedRSignedLWider:
case AZ::IntegralTypeDiff::LUnsignedRSignedEqSize:
{
// Expect the maximum range of widget type to be clamped to the range of QtWidgetValueType
EXPECT_NE(QtWidgetLimits<TypeParam>::Max(), std::numeric_limits<TypeParam>::max());
EXPECT_EQ(QtWidgetLimits<TypeParam>::Max(), std::numeric_limits<QtWidgetValueType>::max());
break;
}
// Given an LY widget type with a maximum range less than the range of QtWidgetValueType
case AZ::IntegralTypeDiff::LUnsignedRSignedRWider:
case AZ::IntegralTypeDiff::LSignedRSignedRWider:
{
// Expect the maximum range of widget type to be less than the minimum range of QtWidgetValueType
EXPECT_EQ(QtWidgetLimits<TypeParam>::Max(), std::numeric_limits<TypeParam>::max());
EXPECT_NE(QtWidgetLimits<TypeParam>::Max(), std::numeric_limits<QtWidgetValueType>::max());
break;
}
default:
FAIL();
}
}
} // namespace UnitTest
@@ -0,0 +1,604 @@
/*
* 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 <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Slice/SliceAssetHandler.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzToolsFramework/Slice/SliceUtilities.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/UI/Slice/SlicePushWidget.hxx>
namespace UnitTest
{
class SlicePushCyclicDependencyTest
: public AllocatorsTestFixture
{
public:
SlicePushCyclicDependencyTest()
: AllocatorsTestFixture()
{ }
void SetUp() override
{
AZ::ComponentApplication::Descriptor componentApplicationDesc;
componentApplicationDesc.m_useExistingAllocator = true;
m_application = aznew AzToolsFramework::ToolsApplication();
m_application->Start(componentApplicationDesc);
// 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);
}
void TearDown() override
{
// Release all slice asset references, so AssetManager doens't complain.
m_sliceAssets.clear();
delete m_application;
}
// This function transfers the ownership of the argument `entity`. Do not delete or use it afterwards.
AZ::Data::AssetId SaveAsSlice(AZ::Entity* entity)
{
AZStd::vector<AZ::Entity*> entities;
entities.push_back(entity);
return SaveAsSlice(entities);
}
// This function transfers the ownership of all the entity pointers. Do not delete or use them afterwards.
AZ::Data::AssetId SaveAsSlice(AZStd::vector<AZ::Entity*> entities)
{
AZ::Entity* sliceEntity = aznew AZ::Entity();
AZ::SliceComponent* sliceComponent = nullptr;
sliceComponent = aznew AZ::SliceComponent();
sliceComponent->SetSerializeContext(m_application->GetSerializeContext());
for (auto& entity : entities)
{
sliceComponent->AddEntity(entity);
}
// Don't activate `sliceEntity`, whose purpose is to be attached by `sliceComponent`.
sliceEntity->AddComponent(sliceComponent);
AZ::Data::AssetId assetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0);
AZ::Data::Asset<AZ::SliceAsset> sliceAssetHolder = AZ::Data::AssetManager::Instance().CreateAsset<AZ::SliceAsset>(assetId, AZ::Data::AssetLoadBehavior::Default);
sliceAssetHolder.GetAs<AZ::SliceAsset>()->SetData(sliceEntity, sliceComponent);
// Hold on to sliceAssetHolder so it's not ref-counted away.
m_sliceAssets.emplace(assetId, sliceAssetHolder);
return assetId;
}
AZ::SliceComponent::EntityList InstantiateSlice(AZ::Data::AssetId sliceAssetId)
{
auto foundItr = m_sliceAssets.find(sliceAssetId);
AZ_TEST_ASSERT(foundItr != m_sliceAssets.end());
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
AZ::SliceComponent::SliceInstanceAddress sliceInstAddress = rootSlice->AddSlice(foundItr->second);
rootSlice->Instantiate();
const AZ::SliceComponent::InstantiatedContainer* instanceContainer = sliceInstAddress.GetInstance()->GetInstantiated();
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequestBus::Events::HandleEntitiesAdded, instanceContainer->m_entities);
return instanceContainer->m_entities;
}
void RemoveAllSlices()
{
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
for (auto sliceAssetPair : m_sliceAssets)
{
rootSlice->RemoveSlice(sliceAssetPair.second);
}
}
public:
AZ::IO::LocalFileIO m_localFileIO;
AzToolsFramework::ToolsApplication* m_application = nullptr;
AZStd::unordered_map<AZ::Data::AssetId, AZ::Data::Asset<AZ::SliceAsset>> m_sliceAssets;
};
// Test pushing slices to create news slices that could result in cyclic
// dependency, e.g. push slice1 => slice2 and slice2 => slice1 at the same
// time.
TEST_F(SlicePushCyclicDependencyTest, PushTwoSlicesToDependOnEachOther)
{
AZ::Entity* entity = aznew AZ::Entity("TestEntity0");
entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
AZ::Data::AssetId sliceAssetId0 = SaveAsSlice(entity);
entity = nullptr;
entity = aznew AZ::Entity("TestEntity1");
entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
AZ::Data::AssetId sliceAssetId1 = SaveAsSlice(entity);
entity = nullptr;
AZ::SliceComponent::EntityList slice0EntitiesA = InstantiateSlice(sliceAssetId0);
EXPECT_EQ(slice0EntitiesA.size(), 1);
AZ::SliceComponent::EntityList slice0EntitiesB = InstantiateSlice(sliceAssetId0);
EXPECT_EQ(slice0EntitiesB.size(), 1);
AZ::SliceComponent::EntityList slice1EntitiesA = InstantiateSlice(sliceAssetId1);
EXPECT_EQ(slice1EntitiesA.size(), 1);
AZ::SliceComponent::EntityList slice1EntitiesB = InstantiateSlice(sliceAssetId1);
EXPECT_EQ(slice1EntitiesA.size(), 1);
// Reparent entities to slice1EntityA <-- slice0EntityA, slice0EntityB <-- slice1EntityA (<-- points to parent).
AZ::TransformBus::Event(slice0EntitiesA[0]->GetId(), &AZ::TransformBus::Events::SetParent, slice1EntitiesA[0]->GetId());
AZ::TransformBus::Event(slice1EntitiesB[0]->GetId(), &AZ::TransformBus::Events::SetParent, slice0EntitiesB[0]->GetId());
AZStd::unordered_map<AZ::Data::AssetId, AZ::SliceComponent::EntityIdSet> unpushableEntityIdsPerAsset;
AZStd::unordered_map<AZ::EntityId, AZ::SliceComponent::EntityAncestorList> sliceAncestryMapping;
AZStd::vector<AZStd::pair<AZ::EntityId, AZ::SliceComponent::EntityAncestorList>> newChildEntityIdAncestorPairs;
AZStd::unordered_set<AZ::EntityId> entitiesToAdd;
AzToolsFramework::EntityIdList inputEntityIds = { slice0EntitiesA[0]->GetId(), slice0EntitiesB[0]->GetId(), slice1EntitiesA[0]->GetId(), slice1EntitiesB[0]->GetId() };
AZStd::unordered_set<AZ::EntityId> pushableNewChildEntityIds = AzToolsFramework::SliceUtilities::GetPushableNewChildEntityIds(
inputEntityIds, unpushableEntityIdsPerAsset, sliceAncestryMapping, newChildEntityIdAncestorPairs, entitiesToAdd);
// Because there would be cyclic dependency in the resulting slices, we only allow pushing of one entity.
AZ_TEST_ASSERT(unpushableEntityIdsPerAsset.size() == 1);
AZ_TEST_ASSERT(newChildEntityIdAncestorPairs.size() == 1);
RemoveAllSlices();
}
TEST_F(SlicePushCyclicDependencyTest, PushMultipleEntitiesOneOfChildrenCauseCyclicDependency)
{
AZ::Entity* tempAssetEntity = aznew AZ::Entity("TestEntity0");
tempAssetEntity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
AZ::Data::AssetId sliceAssetId0 = SaveAsSlice(tempAssetEntity);
tempAssetEntity = nullptr;
AZ::SliceComponent::EntityList slice0EntitiesA = InstantiateSlice(sliceAssetId0);
EXPECT_EQ(slice0EntitiesA.size(), 1);
AZ::SliceComponent::EntityList slice0EntitiesB = InstantiateSlice(sliceAssetId0);
EXPECT_EQ(slice0EntitiesB.size(), 1);
AZ::Entity* looseEntity0 = aznew AZ::Entity("LooseEntity");
looseEntity0->CreateComponent<AzToolsFramework::Components::TransformComponent>();
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequestBus::Events::AddEditorEntity, looseEntity0);
// Add one pushable entity as a parent of the one that will cause cyclic dependency.
AZ::TransformBus::Event(looseEntity0->GetId(), &AZ::TransformBus::Events::SetParent, slice0EntitiesA[0]->GetId());
AZ::TransformBus::Event(slice0EntitiesB[0]->GetId(), &AZ::TransformBus::Events::SetParent, looseEntity0->GetId());
AZ::SliceComponent::EntityIdSet unpushableEntityIds;
AZStd::unordered_set<AZ::EntityId> entitiesToAdd;
AZStd::unordered_map<AZ::Data::AssetId, AZ::SliceComponent::EntityIdSet> unpushableEntityIdsPerAsset;
AZStd::unordered_map<AZ::EntityId, AZ::SliceComponent::EntityAncestorList> sliceAncestryMapping;
AZStd::vector<AZStd::pair<AZ::EntityId, AZ::SliceComponent::EntityAncestorList>> newChildEntityIdAncestorPairs;
AzToolsFramework::EntityIdList inputEntityIds = { slice0EntitiesA[0]->GetId(), slice0EntitiesB[0]->GetId(), looseEntity0->GetId() };
AZStd::unordered_set<AZ::EntityId> pushableNewChildEntityIds = AzToolsFramework::SliceUtilities::GetPushableNewChildEntityIds(
inputEntityIds, unpushableEntityIdsPerAsset, sliceAncestryMapping, newChildEntityIdAncestorPairs, entitiesToAdd);
// slice0EntityB can't be pushed to slice0EntityA, but its parent (looseEntity) can.
AZ_TEST_ASSERT(unpushableEntityIdsPerAsset.size() == 1);
AZ_TEST_ASSERT(newChildEntityIdAncestorPairs.size() == 1);
AZ::Entity* looseEntity1 = aznew AZ::Entity("LooseEntity");
looseEntity1->CreateComponent<AzToolsFramework::Components::TransformComponent>();
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequestBus::Events::AddEditorEntity, looseEntity1);
// Add one more pushable entity as a parent.
AZ::TransformBus::Event(slice0EntitiesB[0]->GetId(), &AZ::TransformBus::Events::SetParent, looseEntity1->GetId());
AZ::TransformBus::Event(looseEntity1->GetId(), &AZ::TransformBus::Events::SetParent, looseEntity0->GetId());
inputEntityIds.push_back(looseEntity1->GetId());
unpushableEntityIds.clear();
sliceAncestryMapping.clear();
newChildEntityIdAncestorPairs.clear();
pushableNewChildEntityIds = AzToolsFramework::SliceUtilities::GetPushableNewChildEntityIds(inputEntityIds,
unpushableEntityIdsPerAsset, sliceAncestryMapping, newChildEntityIdAncestorPairs, entitiesToAdd);
// slice0EntityB can't be pushed to slice0EntityA, but the two LooseEntity instances can
AZ_TEST_ASSERT(unpushableEntityIdsPerAsset.size() == 1);
AZ_TEST_ASSERT(newChildEntityIdAncestorPairs.size() == 2);
tempAssetEntity = aznew AZ::Entity("TestEntity1");
tempAssetEntity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
AZ::Data::AssetId sliceAssetId1 = SaveAsSlice(tempAssetEntity);
tempAssetEntity = nullptr;
AZ::SliceComponent::EntityList slice1EntitiesA = InstantiateSlice(sliceAssetId0);
EXPECT_EQ(slice1EntitiesA.size(), 1);
// Add another slice-owned entity `slice1EntitiesA` as the parent of the one causing cyclic dependency,
// and push addition of `slice1EntitiesA`.
AZ::TransformBus::Event(slice0EntitiesB[0]->GetId(), &AZ::TransformBus::Events::SetParent, slice1EntitiesA[0]->GetId());
AZ::TransformBus::Event(slice1EntitiesA[0]->GetId(), &AZ::TransformBus::Events::SetParent, slice0EntitiesA[0]->GetId());
inputEntityIds.clear();
inputEntityIds.push_back(slice0EntitiesA[0]->GetId());
inputEntityIds.push_back(slice0EntitiesB[0]->GetId());
inputEntityIds.push_back(slice1EntitiesA[0]->GetId());
unpushableEntityIds.clear();
sliceAncestryMapping.clear();
newChildEntityIdAncestorPairs.clear();
pushableNewChildEntityIds = AzToolsFramework::SliceUtilities::GetPushableNewChildEntityIds(inputEntityIds,
unpushableEntityIdsPerAsset, sliceAncestryMapping, newChildEntityIdAncestorPairs, entitiesToAdd);
AZ_TEST_ASSERT(unpushableEntityIdsPerAsset.size() == 1);
if (unpushableEntityIdsPerAsset.size() == 1)
{
AzToolsFramework::EntityIdSet ids = unpushableEntityIdsPerAsset.begin()->second;
AZ_TEST_ASSERT(ids.size() == 2);
}
AZ_TEST_ASSERT(newChildEntityIdAncestorPairs.size() == 0);
// But if an entity is not a parent of an unpushable one, it should be added.
AZ::TransformBus::Event(looseEntity0->GetId(), &AZ::TransformBus::Events::SetParent, slice0EntitiesA[0]->GetId());
inputEntityIds.push_back(looseEntity0->GetId());
unpushableEntityIds.clear();
sliceAncestryMapping.clear();
newChildEntityIdAncestorPairs.clear();
pushableNewChildEntityIds = AzToolsFramework::SliceUtilities::GetPushableNewChildEntityIds(inputEntityIds,
unpushableEntityIdsPerAsset, sliceAncestryMapping, newChildEntityIdAncestorPairs, entitiesToAdd);
AZ_TEST_ASSERT(unpushableEntityIdsPerAsset.size() == 1);
if (unpushableEntityIdsPerAsset.size() == 1)
{
AzToolsFramework::EntityIdSet ids = unpushableEntityIdsPerAsset.begin()->second;
AZ_TEST_ASSERT(ids.size() == 2);
}
AZ_TEST_ASSERT(newChildEntityIdAncestorPairs.size() == 1);
RemoveAllSlices();
}
TEST_F(SlicePushCyclicDependencyTest, PushSliceWithNewDuplicatedChild)
{
AZ::Entity* entity = aznew AZ::Entity("TestEntity0");
entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
AZ::Data::AssetId sliceAssetId0 = SaveAsSlice(entity);
entity = nullptr;
entity = aznew AZ::Entity("TestEntity1");
entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
AZ::Data::AssetId sliceAssetId1 = SaveAsSlice(entity);
entity = nullptr;
AZ::SliceComponent::EntityList slice0Entities = InstantiateSlice(sliceAssetId0);
EXPECT_EQ(slice0Entities.size(), 1);
AZ::SliceComponent::EntityList slice1EntitiesA = InstantiateSlice(sliceAssetId1);
EXPECT_EQ(slice1EntitiesA.size(), 1);
AZ::SliceComponent::EntityList slice1EntitiesB = InstantiateSlice(sliceAssetId1);
EXPECT_EQ(slice1EntitiesB.size(), 1);
// Reparent the entity1s to be children of entity0
AZ::TransformBus::Event(slice1EntitiesA[0]->GetId(), &AZ::TransformBus::Events::SetParent, slice0Entities[0]->GetId());
AZ::TransformBus::Event(slice1EntitiesB[0]->GetId(), &AZ::TransformBus::Events::SetParent, slice0Entities[0]->GetId());
AZStd::unordered_set<AZ::EntityId> entitiesToAdd;
AZStd::unordered_map<AZ::Data::AssetId, AZ::SliceComponent::EntityIdSet> unpushableEntityIdsPerAsset;
AZStd::unordered_map<AZ::EntityId, AZ::SliceComponent::EntityAncestorList> sliceAncestryMapping;
AZStd::vector<AZStd::pair<AZ::EntityId, AZ::SliceComponent::EntityAncestorList>> newChildEntityIdAncestorPairs;
AzToolsFramework::EntityIdList inputEntityIds = { slice0Entities[0]->GetId(), slice1EntitiesA[0]->GetId(), slice1EntitiesB[0]->GetId() };
AZStd::unordered_set<AZ::EntityId> pushableNewChildEntityIds = AzToolsFramework::SliceUtilities::GetPushableNewChildEntityIds(
inputEntityIds, unpushableEntityIdsPerAsset, sliceAncestryMapping, newChildEntityIdAncestorPairs, entitiesToAdd);
// Because there would be cyclic dependency in the resulting slices, we only allow pushing of one entity.
AZ_TEST_ASSERT(newChildEntityIdAncestorPairs.size() == 2);
AZ_TEST_ASSERT(unpushableEntityIdsPerAsset.size() == 0);
AZ_TEST_ASSERT(newChildEntityIdAncestorPairs.size() == 2);
RemoveAllSlices();
}
// Test pushing slice with children that aren't going to be in the pushed version
// either because the user has chosen to leave them out, or they are unpushable for some reason
// (e.g. they would create a circular dependency).
TEST_F(SlicePushCyclicDependencyTest, SlicePush_DontPushSomeChildren_ChildrenRemovedFromChildOrderArray)
{
AZ::Data::AssetManager& assetManager = AZ::Data::AssetManager::Instance();
// Create a slice
AZ::Entity* entity = aznew AZ::Entity("TestEntity0");
entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
AZ::Data::AssetId sliceAssetId0 = SaveAsSlice(entity);
entity = nullptr;
// Instantiate two copies of the slice.
AZ::SliceComponent::EntityList parentSlice = InstantiateSlice(sliceAssetId0);
AZ::SliceComponent::EntityList childSlice = InstantiateSlice(sliceAssetId0);
// Make one a child of the other.
AZ::TransformBus::Event(childSlice[0]->GetId(), &AZ::TransformBus::Events::SetParent, parentSlice[0]->GetId());
// Grab the parent entity and add an EditorEntitySortComponent to it.
AzToolsFramework::Components::EditorEntitySortComponent* parentSortComponent;
AZ::Entity* parentEntity = nullptr;
{
AZ::ComponentApplicationBus::BroadcastResult(parentEntity, &AZ::ComponentApplicationBus::Handler::FindEntity, parentSlice[0]->GetId());
AZ_Assert(parentEntity, "Failed to find parentEntity\n");
parentEntity->Deactivate();
parentSortComponent = parentEntity->CreateComponent<AzToolsFramework::Components::EditorEntitySortComponent>();
AZ_Assert(parentSortComponent, "Failed to create parentSortComponent\n");
parentEntity->Activate();
}
// Create two entities and make them children of the parent
AZ::Entity* childEntity0;
{
childEntity0 = aznew AZ::Entity("TestChildEntity");
childEntity0->CreateComponent<AzToolsFramework::Components::TransformComponent>();
childEntity0->Init();
childEntity0->Activate();
AZ::TransformBus::Event(childEntity0->GetId(), &AZ::TransformBus::Events::SetParent, parentEntity->GetId());
AZ_Assert(childEntity0, "Failed to create childEntity0\n");
}
AZ::Entity* childEntity1;
{
childEntity1 = aznew AZ::Entity("TestChildEntity");
childEntity1->CreateComponent<AzToolsFramework::Components::TransformComponent>();
childEntity1->Init();
childEntity1->Activate();
AZ::TransformBus::Event(childEntity1->GetId(), &AZ::TransformBus::Events::SetParent, parentEntity->GetId());
AZ_Assert(childEntity1, "Failed to create childEntity0\n");
}
// Analyse hierarchy for unpushable entities.
AZStd::unordered_map<AZ::Data::AssetId, AZ::SliceComponent::EntityIdSet> unpushableEntityIdsPerAsset;
{
AZStd::unordered_map<AZ::EntityId, AZ::SliceComponent::EntityAncestorList> sliceAncestryMapping;
AZStd::vector<AZStd::pair<AZ::EntityId, AZ::SliceComponent::EntityAncestorList>> newChildEntityIdAncestorPairs;
AZStd::unordered_set<AZ::EntityId> entitiesToAdd;
// Make list of entities to be pushed. Leave out childEntity1 to emulate a user having unchecked it in the advanced push widget.
AzToolsFramework::EntityIdList inputEntityIds = { parentEntity->GetId(), childSlice[0]->GetId(), childEntity0->GetId() };
AZStd::unordered_set<AZ::EntityId> pushableNewChildEntityIds = AzToolsFramework::SliceUtilities::GetPushableNewChildEntityIds(
inputEntityIds, unpushableEntityIdsPerAsset, sliceAncestryMapping, newChildEntityIdAncestorPairs, entitiesToAdd);
// UnpushableEntityIdsPerAsset should now contain a reference to childSlice which can't be
// pushed as it would create a circular reference. This would get picked up by advanced or quick push
// during GetPushableNewChildEntityIds.
AZ_TEST_ASSERT(unpushableEntityIdsPerAsset.size() == 1);
}
// Add all child entities to the parent slice's child order array.
parentSortComponent->AddChildEntity(childSlice[0]->GetId(), false);
parentSortComponent->AddChildEntity(childEntity0->GetId(), false);
parentSortComponent->AddChildEntity(childEntity1->GetId(), false);
AzToolsFramework::EntityOrderArray orderArray = parentSortComponent->GetChildEntityOrderArray();
// Make a list of entities that we don't want to push (childEntity1). This will emulate a user deciding not to push
// certain entities in the advanced push widget.
AZStd::vector <AZ::EntityId> idsNotToPush;
idsNotToPush.push_back(childEntity1->GetId());
// Do the pruning to produce the list of entities that will be pushed.
AzToolsFramework::EntityOrderArray prunedOrderArray;
{
prunedOrderArray.reserve(orderArray.size());
AzToolsFramework::SliceUtilities::WillPushEntityCallback willPushEntityCallback =
[&unpushableEntityIdsPerAsset, &idsNotToPush]
(const AZ::EntityId entityId, const AZ::Data::Asset <AZ::SliceAsset>& assetToPushTo) -> bool
{
if (unpushableEntityIdsPerAsset[assetToPushTo.GetId()].find(entityId) != unpushableEntityIdsPerAsset[assetToPushTo.GetId()].end())
{
return false;
}
for (AZ::EntityId id : idsNotToPush)
{
if (id == entityId)
{
return false;
}
}
return true;
};
AZ::Data::Asset<AZ::SliceAsset> sliceAsset = assetManager.FindOrCreateAsset<AZ::SliceAsset>(sliceAssetId0, AZ::Data::AssetLoadBehavior::Default);
AzToolsFramework::SliceUtilities::RemoveInvalidChildOrderArrayEntries(orderArray, prunedOrderArray, sliceAsset, willPushEntityCallback);
}
// At this point there should only be childEntity0 in the pruned order array.
bool pruningCorrect = false;
if (prunedOrderArray.size() == 1 && prunedOrderArray[0] == childEntity0->GetId())
{
pruningCorrect = true;
}
EXPECT_EQ(pruningCorrect, true);
RemoveAllSlices();
}
// Rename our fixture class for the next test so that it has a more accurate test name.
class SliceActivationOrderTest : public SlicePushCyclicDependencyTest {};
// Class that listens for AZ_Warning messages and asserts if any are found.
class SliceTestWarningInterceptor :
public AZ::Debug::TraceMessageBus::Handler
{
public:
SliceTestWarningInterceptor()
{
AZ::Debug::TraceMessageBus::Handler::BusConnect();
}
~SliceTestWarningInterceptor()
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
}
bool OnWarning(const char *window, const char* message) override
{
(void)window;
ADD_FAILURE() << "Test failed due to an undesirable warning being generated:\n" << message;
return true;
}
};
// LY-95800: If a child entity with a transform is present in a slice asset earlier
// than its parent, the activation of the parent entity can cause the child to have a
// state that doesn't match the undo cache, which generates a warning about inconsistent data.
// (See PreemptiveUndoCache::Validate)
// If the bug is present, a warning will be thrown which fails this unit test.
TEST_F(SliceActivationOrderTest, ActivationOrderShouldNotAffectUndoCache)
{
// Create a parent entity with a transform component
AZ::Entity* parentEntity = aznew AZ::Entity("TestParentEntity");
parentEntity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
parentEntity->Init();
parentEntity->Activate();
// Create a child entity with a transform component
AZ::Entity* childEntity = aznew AZ::Entity("TestChildEntity");
childEntity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
childEntity->Init();
childEntity->Activate();
// Make the child an actual child of the parent entity
AZ::TransformBus::Event(childEntity->GetId(), &AZ::TransformBus::Events::SetParent, parentEntity->GetId());
AZStd::vector<AZ::Entity*> entities;
// Add our entities to the list of entities to make a slice from.
// IMPORTANT: The child should be added before the parent. For this bug to manifest, the
// child entity needs to get instantiated and activated before the parent when instantiating
// the slice.
childEntity->Deactivate();
parentEntity->Deactivate();
entities.push_back(childEntity);
entities.push_back(parentEntity);
// When saving a slice, SliceUtilities::VerifyAndApplySliceWorldTransformRules() clears out the
// cached world transforms prior to writing out the slice asset.
for (AZ::Entity* entity : entities)
{
AzToolsFramework::Components::TransformComponent* transformComponent = entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
if (transformComponent)
{
transformComponent->ClearCachedWorldTransform();
}
}
// Create our slice asset
AZ::Data::AssetId sliceAssetId = SaveAsSlice(entities);
childEntity = nullptr;
parentEntity = nullptr;
entities.clear();
// Create an undo batch to wrap the slice instantiation.
// This is necessary, because ending the undo batch is what causes the batch to get validated.
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::Bus::Events::BeginUndoBatch, "Slice Instantiation");
// Instantiate the slice.
// This will instantiate the child, save it in the undo batch, instantiate the parent,
// save the parent in the undo batch, and modify the child.
// If the bug exists, this will cause the child's undo batch record to become inconsistent,
// which will cause a warning when we call EndUndoBatch.
// If the bug is fixed, the child's undo batch record will be updated.
AZ::SliceComponent::EntityList sliceEntities = InstantiateSlice(sliceAssetId);
// When instantiating a slice, SliceEditorEntityOwnershipService::OnSliceInstantiated() removes any entities
// in the slice from the dirty entity list. This step is important because in the buggy case, the child
// will be marked dirty above, but won't be updated in the undo cache yet. Removing it ensures it never
// will be. If it isn't removed, it will get updated as a dirty entity when the undo batch ends.
for (AZ::Entity* entity : sliceEntities)
{
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::RemoveDirtyEntity, entity->GetId());
}
// End the slice instantiation undo batch.
// At this point, if the child entity's undo record doesn't match the current child entity, a warning will be emitted.
{
// The point of this test is to determine whether or not we got a warning from PreemptiveUndoCache
// about inconsistent undo data. So intercept warnings during this step and fail the test if we get one.
SliceTestWarningInterceptor warningInterceptor;
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::Bus::Events::EndUndoBatch);
}
RemoveAllSlices();
}
class SlicePushWidgetTest : public SlicePushCyclicDependencyTest {};
TEST_F(SlicePushWidgetTest, SlicePushWidget_CalculateLevelReferences_ReferenceCountCorrect)
{
// Create an entities and make it a slice.
AZ::Entity* entity0 = aznew AZ::Entity("TestEntity0");
entity0->CreateComponent<AzToolsFramework::Components::TransformComponent>();
AZ::Data::AssetId sliceAssetIdChild = SaveAsSlice(entity0);
// Instantiate 5 copies.
AZ::SliceComponent::EntityList slice0EntitiesA = InstantiateSlice(sliceAssetIdChild);
AZ::SliceComponent::EntityList slice0EntitiesB = InstantiateSlice(sliceAssetIdChild);
AZ::SliceComponent::EntityList slice0EntitiesC = InstantiateSlice(sliceAssetIdChild);
AZ::SliceComponent::EntityList slice0EntitiesD = InstantiateSlice(sliceAssetIdChild);
AZ::SliceComponent::EntityList slice0EntitiesE = InstantiateSlice(sliceAssetIdChild);
// Make an entity to parent the slice instances
AZ::Entity* parent0 = aznew AZ::Entity("TestParent0");
parent0->CreateComponent<AzToolsFramework::Components::TransformComponent>();
parent0->Init();
parent0->Activate();
AZ::TransformBus::Event(slice0EntitiesA[0]->GetId(), &AZ::TransformBus::Events::SetParent, parent0->GetId());
AZ::TransformBus::Event(slice0EntitiesB[0]->GetId(), &AZ::TransformBus::Events::SetParent, parent0->GetId());
AZ::TransformBus::Event(slice0EntitiesC[0]->GetId(), &AZ::TransformBus::Events::SetParent, parent0->GetId());
AZ::TransformBus::Event(slice0EntitiesD[0]->GetId(), &AZ::TransformBus::Events::SetParent, parent0->GetId());
AZ::TransformBus::Event(slice0EntitiesE[0]->GetId(), &AZ::TransformBus::Events::SetParent, parent0->GetId());
// Save parent as a slice.
AZ::Data::AssetId sliceAssetIdParent = SaveAsSlice(parent0);
AZ::SliceComponent::EntityList slice2EntitiesA = InstantiateSlice(sliceAssetIdParent);
// Make another parent entity and add a sixth instance of the child slice.
AZ::Entity* parent1 = aznew AZ::Entity("TestParent1");
parent1->CreateComponent<AzToolsFramework::Components::TransformComponent>();
parent1->Init();
parent1->Activate();
AZ::SliceComponent::EntityList slice0EntitiesF = InstantiateSlice(sliceAssetIdChild);
AZ::TransformBus::Event(slice0EntitiesF[0]->GetId(), &AZ::TransformBus::Events::SetParent, parent1->GetId());
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
size_t parentSliceCount = AzToolsFramework::SlicePushWidget::CalculateReferenceCount(sliceAssetIdParent, rootSlice);
size_t childSliceCount = AzToolsFramework::SlicePushWidget::CalculateReferenceCount(sliceAssetIdChild, rootSlice);
EXPECT_EQ(parentSliceCount, 1);
EXPECT_EQ(childSliceCount, 6);
RemoveAllSlices();
}
}
@@ -0,0 +1,485 @@
/*
* 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 <Tests/SliceStabilityTests/SliceStabilityTestFramework.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
namespace UnitTest
{
TEST_F(SliceStabilityTest, CreateSlice_ValidSingleParentEntityWithValidChildEntity_EntityStateRemainsTheSame_FT)
{
// Generate Parent entity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId parent = CreateEditorEntity("Parent", liveEntityIds);
ASSERT_TRUE(parent.IsValid());
// Generate Child entity and set its parent to Parent entity
ASSERT_TRUE(CreateEditorEntity("Child", liveEntityIds, parent).IsValid());
// Capture initial hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create slice from hierarchy
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_ValidGrandparentParentChildHierarchy_EntityStateRemainsTheSame_FT)
{
// Build Grandparent->Parent->Child and link parent entities between them
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId grandparent = CreateEditorEntity("Grandparent", liveEntityIds);
ASSERT_TRUE(grandparent.IsValid());
AZ::EntityId parent = CreateEditorEntity("Parent", liveEntityIds, grandparent);
ASSERT_TRUE(parent.IsValid());
ASSERT_TRUE(CreateEditorEntity("Child", liveEntityIds, parent).IsValid());
// Capture initial hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create slice from hierarchy
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_10DeepParentChildHierarchy_EntityStateRemainsTheSame_FT)
{
AzToolsFramework::EntityIdList liveEntityIds;
// Build a 10 entity deep hierarchy
AZ::EntityId parent;
for (size_t entityCounter = 0; entityCounter < 10; ++entityCounter)
{
// For each iteration capture the entity made to be used as the parent for the next
parent = CreateEditorEntity(AZStd::string::format("Entity Level %zu", entityCounter).c_str(), liveEntityIds, parent);
ASSERT_TRUE(parent.IsValid());
}
// Capture the hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create slice from hierarchy
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_ValidParentWith10ValidChildren_EntityStateRemainsTheSame_FT)
{
AzToolsFramework::EntityIdList liveEntityIds;
// Create the parent entity and hold on to its id
AZ::EntityId parent = CreateEditorEntity("Parent", liveEntityIds);
ASSERT_TRUE(parent.IsValid());
// Build 10 children and set all of their parent ids to the same parent entity
for (size_t childEntityCounter = 0; childEntityCounter < 10; ++childEntityCounter)
{
ASSERT_TRUE(CreateEditorEntity(AZStd::string::format("Child #%zu", childEntityCounter + 1).c_str(), liveEntityIds, parent).IsValid());
}
// Capture the hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create slice from hierarchy
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_ValidParentEntityWithValidChildEntity_OnlyChildEntityAddedToSlice_EntityStateRemainsTheSame_FT)
{
AzToolsFramework::EntityIdList liveEntityIds;
// Build parent and child entities and connect child to parent
AZ::EntityId parent = CreateEditorEntity("Parent", liveEntityIds);
ASSERT_TRUE(parent.IsValid());
AZ::EntityId child = CreateEditorEntity("Child", liveEntityIds, parent);
ASSERT_TRUE(child.IsValid());
// Capture just the child to compare to
EXPECT_TRUE(m_validator.Capture({ child }));
// Build a slice from only the child entity
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", { child }, sliceInstanceAddress).IsValid());
// Validate that the slice instance only contains the child entity
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_EntityWithExternalReference_ExternalReferenceEntityAutoAddedToSlice_EntityStateRemainsTheSame_FT)
{
// Generate a root entity that will be referenced externally by the entities used to create the slice
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId externalRootId = CreateEditorEntity("ExternalRoot", liveEntityIds);
ASSERT_TRUE(externalRootId.IsValid());
// Generate the entity that will contain the external entity reference to ExternalRoot and set its parent to ExternalRoot
AZ::EntityId entityWithExternalReferenceId = CreateEditorEntity("EntityWithExternalReference", liveEntityIds, externalRootId);
ASSERT_TRUE(entityWithExternalReferenceId.IsValid());
// Acquire the Entity* of EntityWithExternalReference and validate that we successfully acquired it
AZ::Entity* entityWithExternalReference = FindEntityInEditor(entityWithExternalReferenceId);
ASSERT_TRUE(entityWithExternalReference);
// Deactivate the entity so that we can give it a new component
entityWithExternalReference->Deactivate();
// Add an EntityReferenceComponent to EntityWithExternalReference and validate that the component was successfully created
EntityReferenceComponent* externalEntityReferenceComponent = entityWithExternalReference->CreateComponent<EntityReferenceComponent>();
ASSERT_TRUE(externalEntityReferenceComponent);
// Activate the entity
entityWithExternalReference->Activate();
// Set its external entity reference field to the ExternalRoot
externalEntityReferenceComponent->m_entityReference = externalRootId;
// Capture the hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice just from the entity containing the external reference
// Create slice should detect the external reference and auto add ExternalRoot to the slice
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("Slice1", { entityWithExternalReferenceId }, sliceInstanceAddress).IsValid());
// Validate that the slice instance contains both entities
// Confirming that the externally referenced entity was auto added
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, CreateSlice_2ValidWithSharedParent_ParentNotIncludedInSliceCreate_ParentIsGenerated_EntityStateRemainsTheSame_FT)
{
// Create a shared parent that won't be included in the CreateSlice call
// Including a shared parent will validate that the generated parent becomes a child of the original parent
// A generated parent is made because CreateSlice will not have a parent entity to work with and one is required
AzToolsFramework::EntityIdList rootParentEntityId;
AZ::EntityId rootParentEntity = CreateEditorEntity("RootParentEntity", rootParentEntityId);
ASSERT_TRUE(rootParentEntity.IsValid());
// Create two entities and set their parent to rootParentEntity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId entity1Id = CreateEditorEntity("Entity1", liveEntityIds, rootParentEntity);
ASSERT_TRUE(entity1Id.IsValid());
AZ::EntityId entity2Id = CreateEditorEntity("Entity2", liveEntityIds, rootParentEntity);
ASSERT_TRUE(entity2Id.IsValid());
// Gather the transform data of entity 1 and entity 2
// Also set the transform data of entity 1 to be different from entity 2
// Since we're calling multiple TransformBus events on entity 1
// We can batch them in an Event lambda
AZ::Transform entity1WorldTransform;
AZ::TransformBus::Event(entity1Id, [&entity1WorldTransform]
(AZ::TransformInterface* transformInterface)
{
AZ::Vector3 entity1LocalTranslate = transformInterface->GetLocalTranslation();
AZ::Vector3 entity1LocalRotation = transformInterface->GetLocalRotation();
transformInterface->SetLocalTranslation(entity1LocalTranslate * 2);
transformInterface->SetLocalRotation(entity1LocalRotation * 2);
entity1WorldTransform = transformInterface->GetWorldTM();
});
AZ::Transform entity2WorldTransform;
AZ::TransformBus::EventResult(entity2WorldTransform, entity2Id, &AZ::TransformBus::Events::GetWorldTM);
// Validate that both transforms are different from the identity
// Validate that both transforms are different from each other
EXPECT_FALSE(entity1WorldTransform.IsClose(AZ::Transform::Identity()));
EXPECT_FALSE(entity2WorldTransform.IsClose(AZ::Transform::Identity()));
EXPECT_FALSE(entity1WorldTransform.IsClose(entity2WorldTransform));
// Create a slice from these two entities
// Create slice should detect that the provided entity list does not contain a shared parent and will generate one
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Grab the instantiated entities within the generated slice instance
const AZ::SliceComponent::InstantiatedContainer* sliceInstanceEntities = sliceInstanceAddress.GetInstance()->GetInstantiated();
ASSERT_TRUE(sliceInstanceEntities);
// Confirm that it contains 3 entities (Entity1, Entity2, GeneratedRoot)
EXPECT_EQ(sliceInstanceEntities->m_entities.size(), 3);
// Validate that the entities are not null
ASSERT_TRUE(sliceInstanceEntities->m_entities[0]);
ASSERT_TRUE(sliceInstanceEntities->m_entities[1]);
ASSERT_TRUE(sliceInstanceEntities->m_entities[2]);
// Validate that the first two entities have the same ids as Entity1 and Entity2
EXPECT_EQ(sliceInstanceEntities->m_entities[0]->GetId(), entity1Id);
EXPECT_EQ(sliceInstanceEntities->m_entities[1]->GetId(), entity2Id);
// Get Entity1's parent id
AZ::EntityId entity1Parent;
AZ::TransformBus::EventResult(entity1Parent, entity1Id, &AZ::TransformBus::Events::GetParentId);
// Get Entity2's parent id
AZ::EntityId entity2Parent;
AZ::TransformBus::EventResult(entity2Parent, entity2Id, &AZ::TransformBus::Events::GetParentId);
// Confirm the parent id is valid and the same between Entity1 and Entity2
EXPECT_TRUE(entity1Parent.IsValid());
EXPECT_EQ(entity1Parent, entity2Parent);
// Confirm that the parentId is not the original parent but instead a new parent
EXPECT_NE(entity1Parent, rootParentEntity);
// Get the parent of entity 1 and 2's parent
// This should be the original rootParentEntity
AZ::EntityId grandparent;
AZ::TransformBus::EventResult(grandparent, entity1Parent, &AZ::TransformBus::Events::GetParentId);
// Confirm that the new parent is a child of the original parent
EXPECT_EQ(grandparent, rootParentEntity);
// Gather the transform information of entity 1 and entity 2 after the create slice operation
AZ::Transform entity1SliceWorldTransform;
AZ::TransformBus::EventResult(entity1SliceWorldTransform, entity1Id, &AZ::TransformBus::Events::GetWorldTM);
AZ::Transform entity2SliceWorldTransform;
AZ::TransformBus::EventResult(entity2SliceWorldTransform, entity2Id, &AZ::TransformBus::Events::GetWorldTM);
// Validate that the create slice operation did not impact the transform data
EXPECT_TRUE(entity1WorldTransform.IsClose(entity1SliceWorldTransform));
EXPECT_TRUE(entity2WorldTransform.IsClose(entity2SliceWorldTransform));
}
TEST_F(SliceStabilityTest, CreateSlice_TestSubsliceOfSameType_EntityStateRemainsTheSame_FT)
{
// Create a Root entity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId rootEntity = CreateEditorEntity("Root", liveEntityIds);
ASSERT_TRUE(rootEntity.IsValid());
// Capture entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create slice from root entity
AZ::SliceComponent::SliceInstanceAddress parentSliceInstance;
AZ::Data::AssetId parentSliceId = CreateSlice("InheritedSlice", liveEntityIds, parentSliceInstance);
ASSERT_TRUE(parentSliceId.IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(parentSliceInstance));
m_validator.Reset();
// Create a second instance of the slice and make it a child of the Root entity
AZ::SliceComponent::SliceInstanceAddress childSliceInstance = InstantiateEditorSlice(parentSliceId, liveEntityIds, rootEntity);
ASSERT_TRUE(childSliceInstance.IsValid());
// Capture this new hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from this new hierarchy
AZ::SliceComponent::SliceInstanceAddress finalSliceInstance;
AZ::Data::AssetId finalSliceId = CreateSlice("FinalSlice", liveEntityIds, finalSliceInstance);
ASSERT_TRUE(finalSliceId.IsValid());
// Compare genarated slice instance to capture state
EXPECT_TRUE(m_validator.Compare(finalSliceInstance));
}
TEST_F(SliceStabilityTest, CreateSlice_TestSubsliceOfDifferentType_EntityStateRemainsTheSame_FT)
{
// Create a root entity to be used in Slice1
AzToolsFramework::EntityIdList slice1Entities;
AZ::EntityId slice1Root = CreateEditorEntity("Slice1Root", slice1Entities);
ASSERT_TRUE(slice1Root.IsValid());
// Capture the entity state of Slice1Root
EXPECT_TRUE(m_validator.Capture(slice1Entities));
// Create a slice from Slice1Root
AZ::SliceComponent::SliceInstanceAddress slice1Instance;
ASSERT_TRUE(CreateSlice("Slice1", slice1Entities, slice1Instance).IsValid());
// Compare generated slice1Instance to Slice1Root
EXPECT_TRUE(m_validator.Compare(slice1Instance));
m_validator.Reset();
// Create a root entity to be used in Slice2
AzToolsFramework::EntityIdList slice2Entities;
AZ::EntityId slice2Root = CreateEditorEntity("Slice2Root", slice2Entities);
ASSERT_TRUE(slice2Root.IsValid());
// Capture the entity state of Slice2Root
EXPECT_TRUE(m_validator.Capture(slice2Entities));
// Create a slice from Slice2Root
AZ::SliceComponent::SliceInstanceAddress slice2Instance;
ASSERT_TRUE(CreateSlice("Slice2", slice2Entities, slice2Instance).IsValid());
// Compare generated slice2Instance to Slice2Root
EXPECT_TRUE(m_validator.Compare(slice2Instance));
m_validator.Reset();
// Make Slice1Root the parent of Slice2Root
AZ::TransformBus::Event(slice2Root, &AZ::TransformBus::Events::SetParent, slice1Root);
// Validate that the parent of Slice2Root was correctly set
AZ::EntityId slice2RootParent;
AZ::TransformBus::EventResult(slice2RootParent, slice2Root, &AZ::TransformBus::Events::GetParentId);
EXPECT_EQ(slice2RootParent, slice1Root);
// Combine entity lists
AzToolsFramework::EntityIdList slice3Entities = slice1Entities;
slice3Entities.insert(slice3Entities.end(), slice2Entities.begin(), slice2Entities.end());
// Capture final hierarchy state
EXPECT_TRUE(m_validator.Capture(slice3Entities));
// Create a slice from final hiararchy
AZ::SliceComponent::SliceInstanceAddress slice3Instance;
ASSERT_TRUE(CreateSlice("Slice3", slice3Entities, slice3Instance).IsValid());
// Compare generated slice instance to capture state
EXPECT_TRUE(m_validator.Compare(slice3Instance));
}
TEST_F(SliceStabilityTest, CreateSlice_Test10DeepSliceAncestry_EntityStateRemainsTheSame_InstanceAncestryIntact_FT)
{
AZ::u32 totalAncestors = 10;
// Generate a Root entity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId rootEntity = CreateEditorEntity("Root", liveEntityIds);
ASSERT_TRUE(rootEntity.IsValid());
// Capture the entity state of Root
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
for (AZ::u32 ancestorCount = 0; ancestorCount < totalAncestors; ++ancestorCount)
{
// Continue to make a slice off of Root entity where each iteration Root entity is owned by an instance of the previously made slice
// For each iteration validate the state of each instance matches the state of the initally captured Root entity state
ASSERT_TRUE(CreateSlice(AZStd::string::format("Slice Level: %i", ancestorCount + 1).c_str(), liveEntityIds, sliceInstanceAddress).IsValid());
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
// Acquire the ancestor hierarchy of Root entity
// We pass in totalAncestors + 1 for maxLevels to ensure we rule out the ancestry is greater than expected fail state
AZ::SliceComponent::EntityAncestorList ancestors;
sliceInstanceAddress.GetReference()->GetInstanceEntityAncestry(rootEntity, ancestors, totalAncestors + 1);
// Confirm that the ancestor hierarchy size is the same as the number of slices we iteratively built off of Root entity
EXPECT_EQ(ancestors.size(), totalAncestors);
}
TEST_F(SliceStabilityTest, CreateSlice_Test5DeepSliceAncestryWithSubslices_EntityStateRemainsTheSame_FT)
{
// Generate a Root entity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId rootEntity = CreateEditorEntity("Root", liveEntityIds);
ASSERT_TRUE(rootEntity.IsValid());
// This loop moves each iteration's hierarchy into a slice instance
// It then instantiates a second instance and places the second instance under the original hierachy
// This results in the number of entities growing at a power of 2
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
for (size_t ancestorCount = 0; ancestorCount < 5; ++ancestorCount)
{
// Each iteration capture the entity hierarchy state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from the current hierarchy
AZ::Data::AssetId newSlice = CreateSlice(AZStd::string::format("Slice Level: %zu", ancestorCount + 1).c_str(), liveEntityIds, sliceInstanceAddress);
ASSERT_TRUE(newSlice.IsValid());
// Compare the generated slice instance against the capture state and reset the capture for the next iteration
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Instantiate a second copy of this iteration's slice and set Root entity as its parent
// liveEntityIds is updated by this call to include the new instances entities
ASSERT_TRUE(InstantiateEditorSlice(newSlice, liveEntityIds, rootEntity).IsValid());
}
}
TEST_F(SliceStabilityTest, CreateSlice_TestOverride_OverrideAppliesSuccesfully_FT)
{
// Generate a Root entity
AzToolsFramework::EntityIdList liveEntityIds;
ASSERT_TRUE(CreateEditorEntity("Root", liveEntityIds).IsValid());
// Capture the Root entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from Root entity
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("Slice1", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance to the capture state and then reset capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Validator passing guarantees instance and its instantiated container are not nullptr and instantiated is size 1
const AZ::SliceComponent::InstantiatedContainer* instantiatedEntities = sliceInstanceAddress.GetInstance()->GetInstantiated();
// Confirm that the first entity entry is not nullptr
EXPECT_TRUE(instantiatedEntities->m_entities[0]);
// Rename the Root entity
constexpr const char* newRootName = "Renamed Root";
instantiatedEntities->m_entities[0]->SetName(newRootName);
// Capture the new entity state which includes the rename
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from the renamed Root
// This should create a slice with an override on Slice1 that performs the entity rename
AZ::SliceComponent::SliceInstanceAddress slice2InstanceAddress;
AZ::Data::AssetId slice2Asset = CreateSlice("Slice2", liveEntityIds, slice2InstanceAddress);
ASSERT_TRUE(slice2Asset.IsValid());
// Compare the generated slice instance to the captured entity state
EXPECT_TRUE(m_validator.Compare(slice2InstanceAddress));
// Instantiate a second instance of this slice
// We want to validate that further instantiations after the slice create persist the override
AzToolsFramework::EntityIdList slice2NewInstanceEntities;
AZ::SliceComponent::SliceInstanceAddress slice2NewInstanceAddress = InstantiateEditorSlice(slice2Asset, slice2NewInstanceEntities);
// Confirm the instance is valid
ASSERT_TRUE(slice2NewInstanceAddress.IsValid());
// Acquire the instantiated container from the instance and confirm the container is valid
const AZ::SliceComponent::SliceInstance* slice2NewInstance = slice2NewInstanceAddress.GetInstance();
const AZ::SliceComponent::InstantiatedContainer* newSlice2InstantiatedEntities = slice2NewInstance->GetInstantiated();
ASSERT_TRUE(newSlice2InstantiatedEntities);
// Confirm that the slice instance contains only 1 entity and that its name matches the renamed entity
ASSERT_EQ(newSlice2InstantiatedEntities->m_entities.size(), 1);
ASSERT_TRUE(newSlice2InstantiatedEntities->m_entities[0]);
EXPECT_EQ(newSlice2InstantiatedEntities->m_entities[0]->GetName(), newRootName);
}
}
@@ -0,0 +1,303 @@
/*
* 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 <Tests/SliceStabilityTests/SliceStabilityTestFramework.h>
namespace UnitTest
{
TEST_F(SliceStabilityTest, PushToSlice_PushSingleEntityToSlice_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used in a slice
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId sliceEntity = CreateEditorEntity("SliceEntity", liveEntityIds);
ASSERT_TRUE(sliceEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from the entity
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Create an entity to be pushed to slice and set its parent to be the first SliceEntity
AZ::EntityId addedEntity = CreateEditorEntity("AddedEntity", liveEntityIds, sliceEntity);
ASSERT_TRUE(addedEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Push AddedEntity to the existing slice instance
ASSERT_TRUE(PushEntitiesToSlice(sliceInstanceAddress, liveEntityIds));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, PushToSlice_PushSingleParentEntityWithChildEntity_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used as a slice root
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId sliceEntity = CreateEditorEntity("SliceEntity", liveEntityIds);
ASSERT_TRUE(sliceEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from the current entity state
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Create a parent and child entity to be pushed to the slice
// Set AddedParent's parent to be SliceEntity
// Set AddedChild's parent to be AddedParent
AZ::EntityId addedParent = CreateEditorEntity("AddedParent", liveEntityIds, sliceEntity);
ASSERT_TRUE(addedParent.IsValid());
AZ::EntityId addedChild = CreateEditorEntity("AddedChild", liveEntityIds, addedParent);
ASSERT_TRUE(addedChild.IsValid());
// Capture the current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Push AddedParent and AddedChild to the existing slice instance
ASSERT_TRUE(PushEntitiesToSlice(sliceInstanceAddress, liveEntityIds));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
// Disabled in SPEC-3077
TEST_F(SliceStabilityTest, DISABLED_PushToSlice_PushGrandparentParentChildHierarchy_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used as a slice root
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId sliceEntity = CreateEditorEntity("SliceEntity", liveEntityIds);
ASSERT_TRUE(sliceEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from current entity state
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Create a grandparent->parent->child to be pushed to the slice and connect their parent hierarchy accordingly
AZ::EntityId addedGrandparent = CreateEditorEntity("AddedGrandParent", liveEntityIds, sliceEntity);
ASSERT_TRUE(addedGrandparent.IsValid());
AZ::EntityId addedParent = CreateEditorEntity("AddedParent", liveEntityIds, addedGrandparent);
ASSERT_TRUE(addedParent.IsValid());
AZ::EntityId addedChild = CreateEditorEntity("AddedChild", liveEntityIds, addedParent);
ASSERT_TRUE(addedChild.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Push grandparent, parent, and child to slice
ASSERT_TRUE(PushEntitiesToSlice(sliceInstanceAddress, liveEntityIds));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, PushToSlice_Push10DeepParentChildHierarchy_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used as a slice root
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId sliceEntity = CreateEditorEntity("SliceEntity", liveEntityIds);
ASSERT_TRUE(sliceEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from current entity state
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
// Generate 10 new entities and set each entity's parent to be the entity generated before them
// This creates a 10 child deep hierarchy that we will push to slice
AZ::EntityId parent = sliceEntity;
for (size_t entityCounter = 0; entityCounter < 10; ++entityCounter)
{
parent = CreateEditorEntity(AZStd::string::format("Added Entity Level %zu", entityCounter).c_str(), liveEntityIds, parent);
ASSERT_TRUE(parent.IsValid());
}
// Capture the current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Push the newly created entities into the existing slice
ASSERT_TRUE(PushEntitiesToSlice(sliceInstanceAddress, liveEntityIds));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, PushToSlice_Push10Children_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used as a slice root
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId sliceEntity = CreateEditorEntity("SliceEntity", liveEntityIds);
ASSERT_TRUE(sliceEntity.IsValid());
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from current entity state
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
ASSERT_TRUE(CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress).IsValid());
// Compare the generated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
m_validator.Reset();
AZ::EntityId addedEntity;
AzToolsFramework::EntityIdList entitiesToPush;
for (size_t childEntityCounter = 0; childEntityCounter < 10; ++childEntityCounter)
{
// Generate a set of children who share the same parent (SliceEntity) and add them to the list of entities to push
addedEntity = CreateEditorEntity(AZStd::string::format("Child #%zu", childEntityCounter).c_str(), liveEntityIds, sliceEntity);
ASSERT_TRUE(addedEntity.IsValid());
entitiesToPush.emplace_back(addedEntity);
}
// Capture current entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Push the created child entities to the existing slice
ASSERT_TRUE(PushEntitiesToSlice(sliceInstanceAddress, liveEntityIds));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
}
TEST_F(SliceStabilityTest, PushToSlice_PushNestedSliceOfDifferentType_EntityStateRemainsTheSame_FT)
{
// Create an entity to be used for Slice1's root
AzToolsFramework::EntityIdList slice1Entities;
AZ::EntityId slice1Root = CreateEditorEntity("slice1Root", slice1Entities);
ASSERT_TRUE(slice1Root.IsValid());
// Capture entity state for slice1Root
EXPECT_TRUE(m_validator.Capture(slice1Entities));
// Create a slice from slice1Root
AZ::SliceComponent::SliceInstanceAddress slice1Instance;
ASSERT_TRUE(CreateSlice("Slice1", slice1Entities, slice1Instance).IsValid());
// Compare the state of slice1Instance to the captured state of slice1Root
EXPECT_TRUE(m_validator.Compare(slice1Instance));
m_validator.Reset();
// Create an entity to be used for Slice2's root and make its parent slice1Root
AzToolsFramework::EntityIdList slice2Entities;
AZ::EntityId slice2Root = CreateEditorEntity("Slice2Root", slice2Entities);
ASSERT_TRUE(slice2Root.IsValid());
// Provide Slice2Root a child entity to confirm all entities in Slice2 are included in the push
ASSERT_TRUE(CreateEditorEntity("Slice2Child", slice2Entities, slice2Root).IsValid());
// Capture entity state for Slice2Root
EXPECT_TRUE(m_validator.Capture(slice2Entities));
// Create a slice from slice2Root
AZ::SliceComponent::SliceInstanceAddress slice2Instance;
ASSERT_TRUE(CreateSlice("Slice2", slice2Entities, slice2Instance).IsValid());
// Compare the state of slice2Instance to the captured state of slice2Root
EXPECT_TRUE(m_validator.Compare(slice2Instance));
m_validator.Reset();
// Parent slice2Root under slice1Root to prepare for the push
ReparentEntity(slice2Root, slice1Root);
// Combine the current entity lists
AzToolsFramework::EntityIdList totalEntities = slice1Entities;
totalEntities.insert(totalEntities.end(), slice2Entities.begin(), slice2Entities.end());
// Capture the total entity hierarchy state
EXPECT_TRUE(m_validator.Capture(totalEntities));
// Push the slice2Root entity into slice1Instance
ASSERT_TRUE(PushEntitiesToSlice(slice1Instance, totalEntities));
// Compare the updated slice instance against the captured entity state
EXPECT_TRUE(m_validator.Compare(slice1Instance));
}
TEST_F(SliceStabilityTest, PushToSliceAndCreateSlice_ValidateCombinationOfPushCreateOperations_EntityStateRemainsTheSame_FT)
{
// Create Slice1 root
AzToolsFramework::EntityIdList slice1Entities;
AZ::EntityId slice1Root = CreateEditorEntity("Slice1Root", slice1Entities);
ASSERT_TRUE(slice1Root.IsValid());
EXPECT_TRUE(m_validator.Capture(slice1Entities));
// Create Slice1 from Slice1 root
AZ::SliceComponent::SliceInstanceAddress slice1Instance;
AZ::Data::AssetId slice1Asset = CreateSlice("Slice1", slice1Entities, slice1Instance);
ASSERT_TRUE(slice1Asset.IsValid());
// Validate that Slice1 instance did not change the structure of Slice1 root
EXPECT_TRUE(m_validator.Compare(slice1Instance));
m_validator.Reset();
// Create Slice1 child and make Slice1 root its parent
AZ::EntityId slice1Child = CreateEditorEntity("Slice1Child", slice1Entities, slice1Root);
ASSERT_TRUE(slice1Child.IsValid());
EXPECT_TRUE(m_validator.Capture(slice1Entities));
// Push Slice1 child to Slice1
ASSERT_TRUE(PushEntitiesToSlice(slice1Instance, slice1Entities));
// Validate that Slice1 root and child did not change during push
EXPECT_TRUE(m_validator.Compare(slice1Instance));
m_validator.Reset();
// Instantiate a second instance of Slice1 and make the original Slice 1 child its parent
AzToolsFramework::EntityIdList secondSlice1InstanceEntities;
ASSERT_TRUE(InstantiateEditorSlice(slice1Asset, secondSlice1InstanceEntities, slice1Child).IsValid());
// Slice 2 entities will be the combination of both Slice1 instances
AzToolsFramework::EntityIdList slice2Entities = slice1Entities;
slice2Entities.insert(slice2Entities.end(), secondSlice1InstanceEntities.begin(), secondSlice1InstanceEntities.end());
EXPECT_TRUE(m_validator.Capture(slice2Entities));
// Create slice 2
AZ::SliceComponent::SliceInstanceAddress slice2Instance;
ASSERT_TRUE(CreateSlice("Slice2", slice2Entities, slice2Instance).IsValid());
// Validate that entities in the Slice 2 instance are structurally the same as the input entities in its creation
EXPECT_TRUE(m_validator.Compare(slice2Instance));
}
}
@@ -0,0 +1,92 @@
/*
* 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 <Tests/SliceStabilityTests/SliceStabilityTestFramework.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
namespace UnitTest
{
TEST_F(SliceStabilityTest, ReParent_SliceEntityMovedFromOneInstanceToAnother_EntityIDRemainsTheSame_FT)
{
AzToolsFramework::EntityIdList instance1Entities;
AZ::EntityId instance1Root = CreateEditorEntity("Slice1Root", instance1Entities);
ASSERT_TRUE(instance1Root.IsValid());
AZ::EntityId instance1Child = CreateEditorEntity("Slice1Child", instance1Entities, instance1Root);
ASSERT_TRUE(instance1Child.IsValid());
EXPECT_TRUE(m_validator.Capture(instance1Entities));
AZ::SliceComponent::SliceInstanceAddress slice1InstanceAddress;
AZ::Data::AssetId slice1Asset = CreateSlice("Slice1", instance1Entities, slice1InstanceAddress);
ASSERT_TRUE(slice1Asset.IsValid());
EXPECT_TRUE(m_validator.Compare(slice1InstanceAddress));
AzToolsFramework::EntityIdList instance2Entities;
AZ::EntityId instance2Root = CreateEditorEntity("Slice2Root", instance2Entities);
ASSERT_TRUE(instance2Root.IsValid());
AZ::SliceComponent::SliceInstanceAddress slice2InstanceAddress;
ASSERT_TRUE(CreateSlice("Slice2", instance2Entities, slice2InstanceAddress).IsValid());
ReparentEntity(instance1Child, instance2Root);
AzToolsFramework::EntityIdList instance2RootChildren;
AZ::TransformBus::EventResult(instance2RootChildren, instance2Root, &AZ::TransformBus::Events::GetChildren);
ASSERT_EQ(instance2RootChildren.size(), 1);
ASSERT_EQ(instance2RootChildren[0], instance1Child);
}
/*
Even though we are not explicitly reparenting here,creating a nested slice from a slice instance reparents the slice internally.
Therefore, this test belongs in this class.
*/
TEST_F(SliceStabilityTest, ReParent_NestedSliceCreatedFromSliceInstanceChild_SliceHierarchyRemainsSame_FT)
{
AzToolsFramework::EntityIdList instance1Entities;
AZ::EntityId instance1Root = CreateEditorEntity("Slice1Root", instance1Entities);
ASSERT_TRUE(instance1Root.IsValid());
AZ::EntityId instance1Child = CreateEditorEntity("Slice1Child", instance1Entities, instance1Root);
ASSERT_TRUE(instance1Child.IsValid());
EXPECT_TRUE(m_validator.Capture(instance1Entities));
AZ::SliceComponent::SliceInstanceAddress slice1InstanceAddress;
AZ::Data::AssetId slice1Asset = CreateSlice("Slice1", instance1Entities, slice1InstanceAddress);
ASSERT_TRUE(slice1Asset.IsValid());
EXPECT_TRUE(m_validator.Compare(slice1InstanceAddress));
m_validator.Reset();
AzToolsFramework::EntityIdList nestedSliceEntities = AzToolsFramework::EntityIdList{ instance1Child };
EXPECT_TRUE(m_validator.Capture(nestedSliceEntities));
AZ::SliceComponent::SliceInstanceAddress nestedSliceInstanceAddress;
AZ::Data::AssetId nestedSliceAsset =
CreateSlice("NestedSlice", nestedSliceEntities, nestedSliceInstanceAddress);
ASSERT_TRUE(nestedSliceAsset.IsValid());
EXPECT_TRUE(m_validator.Compare(nestedSliceInstanceAddress));
const AZ::SliceComponent::EntityList nestedSliceInstanceEntities =
nestedSliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities;
EXPECT_EQ(nestedSliceInstanceEntities.size(), 1);
AZ::EntityId nestedSliceRootParentId;
AZ::TransformBus::EventResult(nestedSliceRootParentId, nestedSliceInstanceEntities[0]->GetId(), &AZ::TransformBus::Events::GetParentId);
// Validate that the parent of nested slice root is the same as the parent of the instance it was created from.
EXPECT_EQ(instance1Root, nestedSliceRootParentId);
}
}
@@ -0,0 +1,717 @@
/*
* 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 <Tests/SliceStabilityTests/SliceStabilityTestFramework.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Slice/SliceAsset.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/Slice/SliceUtilities.h>
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
namespace UnitTest
{
void SliceStabilityTest::SetUpEditorFixtureImpl()
{
auto* app = GetApplication();
ASSERT_TRUE(app);
// Get the serialize context to reflect our types and set our validator's serialize context
AZ::SerializeContext* serializeContext = app->GetSerializeContext();
m_validator.SetSerializeContext(serializeContext);
app->RegisterComponentDescriptor(EntityReferenceComponent::CreateDescriptor());
// Grab the system entity from the component application
AZ::Entity* systemEntity = app->FindEntity(AZ::SystemEntityId);
// Deactivate the AssetSystemComponent
// We will be implementing the AssetSystemRequestBus and want to avoid Ebus connection conflicts
AzToolsFramework::AssetSystem::AssetSystemComponent* assetSystemComponent = systemEntity->FindComponent<AzToolsFramework::AssetSystem::AssetSystemComponent>();
assetSystemComponent->Deactivate();
AzToolsFramework::AssetSystemRequestBus::Handler::BusConnect();
AzToolsFramework::EditorRequestBus::Handler::BusConnect();
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusConnect();
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
// Cache the existing file io instance and build our mock file io
m_priorFileIO = AZ::IO::FileIOBase::GetInstance();
m_fileIOMock = AZStd::make_unique<testing::NiceMock<AZ::IO::MockFileIOBase>>();
// Swap out current file io instance for our mock
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_fileIOMock.get());
// Setup the default returns for our mock file io calls
AZ::IO::MockFileIOBase::InstallDefaultReturns(*m_fileIOMock.get());
// For write we set the default of the 4th param (bytesWritten) to 1
// otherwise slice transaction errors out during the mock write for writing the default 0 bytes
ON_CALL(*m_fileIOMock.get(), Write(testing::_, testing::_, testing::_, testing::_))
.WillByDefault(
testing::DoAll(
testing::SetArgPointee<3>(1),
testing::Return(AZ::IO::Result(AZ::IO::ResultCode::Success))));
ON_CALL(*m_fileIOMock.get(), GetAlias(testing::_))
.WillByDefault(
testing::Return(""));
ON_CALL(*m_fileIOMock.get(), Rename(testing::_, testing::_))
.WillByDefault(
testing::Return(AZ::IO::Result(AZ::IO::ResultCode::Success)));
}
void SliceStabilityTest::TearDownEditorFixtureImpl()
{
// Get the system entity from the component application
AZ::Entity* systemEntity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(systemEntity, &AZ::ComponentApplicationBus::Events::FindEntity, AZ::SystemEntityId);
// Deactivate the EditorEntityContextComponent
// This triggers the entity context to destroy its root slice asset which destroys all entities, slice instances, and meta data entities
AzToolsFramework::EditorEntityContextComponent* editorEntityContext = systemEntity->FindComponent<AzToolsFramework::EditorEntityContextComponent>();
editorEntityContext->Deactivate();
// Restore our original file io instance
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_priorFileIO);
AzToolsFramework::EditorRequestBus::Handler::BusDisconnect();
AzToolsFramework::AssetSystemRequestBus::Handler::BusDisconnect();
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusDisconnect();
}
AZ::EntityId SliceStabilityTest::CreateEditorEntity(const char* entityName, AzToolsFramework::EntityIdList& entityList, const AZ::EntityId& parentId /*= AZ::EntityId()*/)
{
// Start by creating and registering a new loose entity with the editor entity context
// This call also adds required components onto the entity
AZ::EntityId newEntityId;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
newEntityId, &AzToolsFramework::EditorEntityContextRequestBus::Events::CreateNewEditorEntity, entityName);
AZ::Entity* newEntity = AzToolsFramework::GetEntityById(newEntityId);
// If newEntity is nullptr still then there was a failure in the above EBus call and we cannot proceed
if (!newEntity)
{
return AZ::EntityId();
}
// Add to our entities container
entityList.emplace_back(newEntity->GetId());
// Get the new entity's transform component
AzToolsFramework::Components::TransformComponent* entityTransform =
newEntity->FindComponent<AzToolsFramework::Components::TransformComponent>();
// If new entity has no Transform component then there was a failure in the create entity call
// and the application of required components
if (!entityTransform)
{
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity, newEntity->GetId());
return AZ::EntityId();
}
// If supplied set the parent of the new entity
if (parentId.IsValid())
{
entityTransform->SetParent(parentId);
}
// Set the new entity's transform to non zero values
// This helps validate in comparison tests that the transform values of created entities persist during slice operations
entityTransform->SetLocalScale(AZ::Vector3(5, 5, 5));
entityTransform->SetLocalRotation(AZ::Vector3RadToDeg(AZ::Vector3(90, 90, 90)));
entityTransform->SetLocalTranslation(AZ::Vector3(100, 100, 100));
return entityList.back();
}
AZ::Data::AssetId SliceStabilityTest::CreateSlice(AZStd::string sliceAssetName, AzToolsFramework::EntityIdList entityList, AZ::SliceComponent::SliceInstanceAddress& sliceAddress)
{
// Fabricate a new asset id for this slice and set its sub id to the SliceAsset sub id
m_newSliceId = AZ::Uuid::CreateRandom();
m_newSliceId.m_subId = AZ::SliceAsset::GetAssetSubId();
// Init the sliceAddress to invalid
sliceAddress = AZ::SliceComponent::SliceInstanceAddress();
// The relative slice asset path will be used in registering the slice with the asset catalog
// It will show up in debugging and is useful for tracking multiple slice assets in a test
// Since we are mocking file io m_relativeSourceAssetRoot is purely cosmetic
AZStd::string relativeSliceAssetPath = m_relativeSourceAssetRoot + sliceAssetName;
// Call MakeNewSlice and deactivate all prompts for user input
// Since MakeNewSlice is tightly joined to QT dialogs and popups we default all decisions and silence all popups so we can run tests without user input
// inheritSlices: whether to inherit slice ancestry of added instance entities or make a new slice with no ancestry
// setAsDynamic: whether to mark the slice asset as dynamic
// acceptDefaultPath: whether to prompt the user for a path save location or to proceed with the generated one
// defaultMoveExternalRefs: whether to prompt the user on if external entity references found in added entities get added to the created slice or do this automatically
// defaultGenerateSharedRoot: whether to generate a shared root if one or more added entities do not share the same root
// silenceWarningPopups: disables QT warning popups from being generated, we can still rely on the return of MakeNewSlice for error handling
bool sliceCreateSuccess = AzToolsFramework::SliceUtilities::MakeNewSlice(AzToolsFramework::EntityIdSet(entityList.begin(), entityList.end()),
relativeSliceAssetPath.c_str(),
true /*inheritSlices*/,
false /*setAsDynamic*/,
true /*acceptDefaultPath*/,
true /*defaultMoveExternalRefs*/,
true /*defaultGenerateSharedRoot*/,
true /*silenceWarningPopups*/);
if (sliceCreateSuccess)
{
// Setup the mock asset info for our new slice
AZ::Data::AssetInfo newSliceInfo;
newSliceInfo.m_assetId = m_newSliceId;
newSliceInfo.m_relativePath = relativeSliceAssetPath;
newSliceInfo.m_assetType = azrtti_typeid<AZ::SliceAsset>();
newSliceInfo.m_sizeBytes = 1;
// Register the asset with the asset catalog
// This mocks the asset load pipeline that triggers the OnCatalogAssetAdded event
// OnCatalogAssetAdded triggers the final steps of the create slice flow by building the first slice instance out of the added entities
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, m_newSliceId, newSliceInfo);
}
else
{
return AZ::Uuid::CreateNull();
}
// Acquire the slice instance address the added entities were promoted into
AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, *entityList.begin(),
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
// Validate the slice instance
if (!sliceAddress.IsValid())
{
return AZ::Uuid::CreateNull();
}
// Validate the new slice asset id matches our generated asset id
AZ::Data::AssetId createdSliceId = sliceAddress.GetReference()->GetSliceAsset().GetId();
if (m_newSliceId != createdSliceId)
{
// Return invalid id as error
createdSliceId = AZ::Uuid::CreateNull();
}
// Reset our newSliceId so it's invalid for any OnSliceInstantiated calls
m_newSliceId = AZ::Uuid::CreateNull();
return createdSliceId;
}
bool SliceStabilityTest::PushEntitiesToSlice(AZ::SliceComponent::SliceInstanceAddress& sliceInstanceAddress, const AzToolsFramework::EntityIdList& entitiesToPush)
{
// Nothing to push
if (entitiesToPush.empty())
{
return true;
}
// Cannot push to an invalid slice
if (!sliceInstanceAddress.IsValid())
{
return false;
}
// Copy the slice instance id
// The internal instance of the slicecomponent we push to will be destroyed
// We will use this id to validate that the new instance maps to the same id after the push
AZ::SliceComponent::SliceInstance* sliceInstance = sliceInstanceAddress.GetInstance();
AZ::SliceComponent::SliceInstanceId sliceInstanceId = sliceInstance->GetId();
// Get the currently instantiated entities in this slice instance
const AZ::SliceComponent::EntityList& sliceInstanceInstantiatedEntities = sliceInstance->GetInstantiated() ? sliceInstance->GetInstantiated()->m_entities : AZ::SliceComponent::EntityList();
// Acquire the slice instance's asset and start the push slice transaction
const AZ::Data::Asset<AZ::SliceAsset> sliceAsset = sliceInstanceAddress.GetReference()->GetSliceAsset();
AzToolsFramework::SliceUtilities::SliceTransaction::TransactionPtr transaction = AzToolsFramework::SliceUtilities::SliceTransaction::BeginSlicePush(sliceAsset);
// Since a slice push causes the current instance to re-instantiate all added entities will be remade in the new instance
// We will be deleting the existing entities being added as they will be replaced in this manner
AzToolsFramework::EntityIdList entitiesToRemove;
for (const AZ::EntityId& entityToPush : entitiesToPush)
{
AzToolsFramework::SliceUtilities::SliceTransaction::Result result;
// If the entity already exists in the slice then we will update it
if (FindEntityInList(entityToPush, sliceInstanceInstantiatedEntities))
{
result = transaction->UpdateEntity(entityToPush);
}
else
{
// Otherwise we add it to the slice transaction
// and mark the entity for delete since it will be replaced
result = transaction->AddEntity(entityToPush);
entitiesToRemove.emplace_back(entityToPush);
}
if (!result.IsSuccess())
{
return false;
}
}
// This asset mocks the reloaded temp asset that would trigger the ReloadAssetFromData call after a slice push
AZ::Data::Asset<AZ::SliceAsset> slicePushResultClone;
AzToolsFramework::SliceUtilities::SliceTransaction::PostSaveCallback postSaveCallback =
[&sliceAsset, &slicePushResultClone](AzToolsFramework::SliceUtilities::SliceTransaction::TransactionPtr transaction, const char* fullSourcePath, const AzToolsFramework::SliceUtilities::SliceTransaction::SliceAssetPtr& asset) -> void
{
// SlicePostPushCallback updates the slice component that owns our instance's reference (usually the root slice component of the entity context)
// the update is to make a mapping of the existing entity id (about to be deleted) with the asset entity id (about to be instantiated and replace the existing)
// this sets the replacement entity back to its original id so that external references to that entity do not break by it not having the same id
AzToolsFramework::SliceUtilities::SlicePostPushCallback(transaction, fullSourcePath, asset);
// Clone our slice asset so that our temp has the same asset id
slicePushResultClone = { sliceAsset.Get()->Clone(), AZ::Data::AssetLoadBehavior::Default };
// Move the transaction's asset data into our temp
// the transaction's asset data is what would be saved to disk and reloaded into our temp
slicePushResultClone.Get()->SetData(asset.Get()->GetEntity(), asset.Get()->GetComponent());
asset.Get()->SetData(nullptr, nullptr, false);
};
// Commit our queued entity adds and updates to be pushed to our slice asset and set our pre and post commit callbacks
const AzToolsFramework::SliceUtilities::SliceTransaction::Result result = transaction->Commit(
"NotAValidAssetPath",
AzToolsFramework::SliceUtilities::SlicePreSaveCallbackForWorldEntities,
postSaveCallback);
if (!result.IsSuccess())
{
return false;
}
// Send the reload event that will trigger the owning slice component to re-instantiate its data with what was "written" to disk
// This replaces our deleted entities with their versions pushed to the slice and rebuilds our slice instance to contain those entities
// Because of the mapping we did in the post commit callback they will be re-mapped back to their original ids during the instantiation process
AZ::Data::AssetManager::Instance().ReloadAssetFromData(slicePushResultClone);
// Acquire the root slice
AZ::SliceComponent* rootSlice = nullptr;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
if (!rootSlice)
{
return false;
}
// Find the owning slice instance of one of the entities we added
// This instance should contain all entities prior to the push plus the pushed entities
// We need to update the slice instance here since the instantiated entities in the original instance have been destroyed and re-allocated
// The data and ids should be the same but the SliceInstance* and SliceReference* of the input instance address are invalid and need to be updated
sliceInstanceAddress = rootSlice->FindSlice(*entitiesToPush.begin());
// The instance should be valid and its instance id should match our original instance before the asset reload
if (!sliceInstanceAddress.IsValid() ||
(sliceInstanceAddress.GetInstance()->GetId() != sliceInstanceId))
{
return false;
}
return true;
}
AZ::SliceComponent::SliceInstanceAddress SliceStabilityTest::InstantiateEditorSlice(AZ::Data::AssetId sliceAssetId, AzToolsFramework::EntityIdList& entityList, const AZ::EntityId& parent /*= AZ::EntityId()*/)
{
// Make sure we've created this asset before trying to instantiate it
auto findIt = m_createdSlices.find(sliceAssetId);
if (findIt == m_createdSlices.end())
{
return AZ::SliceComponent::SliceInstanceAddress();
}
// Cache how many instances of this asset exist currently
size_t currentInstanceCount = findIt->second.size();
// Acquire the SliceAsset
AZ::Data::Asset<AZ::SliceAsset> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::SliceAsset>(sliceAssetId, AZ::Data::AssetLoadBehavior::Default);
if (asset.GetStatus() != AZ::Data::AssetData::AssetStatus::NotLoaded)
{
asset.BlockUntilLoadComplete();
}
if (!asset)
{
return AZ::SliceComponent::SliceInstanceAddress();
}
// Instantiate a new slice instance into the editor from the slice asset
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(m_ticket,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::InstantiateEditorSlice,
asset, AZ::Transform::CreateIdentity());
// InstantiateEditorSlice queued the actual instantiation logic onto the tick bus queued events
// Execute the tickbus queue to complete the instantiation
// This should trigger our OnSliceInstantiated callback
AZ::TickBus::ExecuteQueuedEvents();
// Validate that our instances under this asset have grown by 1
// This confirms that OnSliceInstantiated was called during ExecuteQueuedEvents
if (findIt->second.size() != (currentInstanceCount + 1))
{
return AZ::SliceComponent::SliceInstanceAddress();
}
// OnSliceInstantiated has updated the instance list for this asset
// Acquire it now and check if it's valid
AZ::SliceComponent::SliceInstanceAddress& newInstanceAddress = findIt->second.back();
if (!newInstanceAddress.IsValid())
{
return AZ::SliceComponent::SliceInstanceAddress();
}
// Get the root entity of our new instance and check if it's valid
AZ::EntityId sliceInstanceRoot;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(sliceInstanceRoot, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetRootEntityIdOfSliceInstance, newInstanceAddress);
if (!sliceInstanceRoot.IsValid())
{
return AZ::SliceComponent::SliceInstanceAddress();
}
// If a parent was provided then make it the parent of our new slice instance
if (parent.IsValid())
{
AZ::TransformBus::Event(sliceInstanceRoot, &AZ::TransformBus::Events::SetParent, parent);
}
// Reset our ticket
m_ticket = AzFramework::SliceInstantiationTicket();
// For each of the new instances instantiated entities
// Add them to our live entity id list
const AZ::SliceComponent::EntityList& instanceEntities = newInstanceAddress.GetInstance()->GetInstantiated()->m_entities;
for (const AZ::Entity* instanceEntity : instanceEntities)
{
if (instanceEntity)
{
entityList.emplace_back(instanceEntity->GetId());
}
}
// Return the new instance
return newInstanceAddress;
}
void SliceStabilityTest::ReparentEntity(AZ::EntityId& entity, const AZ::EntityId& newParent)
{
if (AzToolsFramework::SliceUtilities::IsReparentNonTrivial(entity, newParent))
{
AzToolsFramework::SliceUtilities::ReparentNonTrivialSliceInstanceHierarchy(entity, newParent);
}
else
{
AZ::TransformBus::Event(entity, &AZ::TransformBus::Events::SetParent, newParent);
}
}
// A helper to find an entity within an entity list
// Used to determine whether to update or push an entity to slice
// As well as to sort our comparison captures in tests
AZ::Entity* SliceStabilityTest::FindEntityInList(const AZ::EntityId& entityId, const AZ::SliceComponent::EntityList& entityList)
{
auto findIt = AZStd::find_if(entityList.begin(), entityList.end(),
[&entityId](AZ::Entity* entity) -> bool
{
if (entity && entity->GetId() == entityId)
{
return true;
}
return false;
});
if (findIt != entityList.end())
{
return *findIt;
}
return nullptr;
}
// Wrapper around finding an entity in the Editor Root Slice
AZ::Entity* SliceStabilityTest::FindEntityInEditor(const AZ::EntityId& entityId)
{
AZ::SliceComponent* editorRootSlice = nullptr;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(editorRootSlice,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
if (!editorRootSlice)
{
return nullptr;
}
return editorRootSlice->FindEntity(entityId);
}
/*
* EditorEntityContextNotificationBus
*/
void SliceStabilityTest::OnSliceInstantiated(const AZ::Data::AssetId& sliceAssetId, AZ::SliceComponent::SliceInstanceAddress& sliceAddress, const AzFramework::SliceInstantiationTicket& ticket)
{
if (!sliceAssetId.IsValid())
{
EXPECT_TRUE(sliceAssetId.IsValid());
return;
}
// We instantiate slices in 2 manners
// The first is creating a new slice asset and in this case we have no ticket to check against so check the asset id
// The other is we instantiated an instance from an existing asset and we have a ticket to compare against
if (ticket == m_ticket || sliceAssetId == m_newSliceId)
{
m_createdSlices[sliceAssetId].emplace_back(sliceAddress);
m_ticket = AzFramework::SliceInstantiationTicket();
}
}
void SliceStabilityTest::OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId, const AzFramework::SliceInstantiationTicket& ticket)
{
// This should never occur for an instantiation we're responsible for
EXPECT_FALSE(ticket == m_ticket || sliceAssetId == m_newSliceId);
}
/*
* EditorRequestBus
*/
void SliceStabilityTest::CreateEditorRepresentation(AZ::Entity* entity)
{
if (!entity)
{
EXPECT_TRUE(entity);
return;
}
// CreateEditorEntity triggers this event so we add required components here
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, *entity);
}
/*
* AssetSystemRequestBus
*/
bool SliceStabilityTest::GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, [[maybe_unused]] AZStd::string& watchFolder)
{
// Mock stub for GetSourceInfoBySourcePath
// This call is invoked during Create Slice to predict the asset id of the new slice before it gets processed
assetInfo.m_relativePath = sourcePath;
assetInfo.m_assetId = m_newSliceId;
return true;
}
SliceStabilityTest::SliceOperationValidator::SliceOperationValidator() :
m_serializeContext(nullptr)
{
}
SliceStabilityTest::SliceOperationValidator::~SliceOperationValidator()
{
// Destroy any entities within our capture and clear our capture list
Reset();
}
void SliceStabilityTest::SliceOperationValidator::SetSerializeContext(AZ::SerializeContext* serializeContext)
{
m_serializeContext = serializeContext;
}
bool SliceStabilityTest::SliceOperationValidator::Capture(const AzToolsFramework::EntityIdList& entitiesToCapture)
{
// We either haven't released our current capture or were given nothing to capture or we weren't activated
if (!m_entityStateCapture.empty() || entitiesToCapture.empty() || !m_serializeContext)
{
return false;
}
// Validate that all entities to capture are real entities in the Editor Entity Context
// Place their Entity* in a temp list to clone
AZ::SliceComponent::EntityList captureList;
for (const AZ::EntityId& entityId : entitiesToCapture)
{
AZ::Entity* entity = FindEntityInEditor(entityId);
if (!entity)
{
return false;
}
captureList.emplace_back(entity);
}
// Clone the entities
// The clones should not be active within the entity context and are safe from our slice operations
m_serializeContext->CloneObjectInplace(m_entityStateCapture, &captureList);
// Success if the clone completed and matches the size of the input
return m_entityStateCapture.size() == entitiesToCapture.size();
}
bool SliceStabilityTest::SliceOperationValidator::Compare(const AZ::SliceComponent::SliceInstanceAddress& instanceToCompare)
{
// We've either captured nothing or our instance to compare has no instantiated entities
if (m_entityStateCapture.empty() || !instanceToCompare.IsValid() || !instanceToCompare.GetInstance()->GetInstantiated())
{
return false;
}
// Get the instantiated list of entities and early out if the entity count doesn't match out capture
AZ::SliceComponent::EntityList instanceEntityList = instanceToCompare.GetInstance()->GetInstantiated()->m_entities;
if (instanceEntityList.size() != m_entityStateCapture.size())
{
return false;
}
// Since slice instantiation can alter the order of entities against the original input we need to sort our capture to match
// We do not care if the order of entities is different, only that both sets of entities are identical
// SortCapture will early out if a comparison entity cannot be found in our capture
if (!SortCapture(instanceEntityList))
{
return false;
}
// Build a data patch between our sorted capture and the instantiated comparison entities
// This will diff every reflected element within both entity lists including: Entity Ids, Parent/Child Hierarchies, Component IDs, Component properties, etc.
AZ::DataPatch patch;
bool result = patch.Create(&m_entityStateCapture, &instanceEntityList, AZ::DataPatch::FlagsMap(), AZ::DataPatch::FlagsMap(), m_serializeContext);
// If the patch has any delta between the two then they do not match
return result & !patch.IsData();
}
bool SliceStabilityTest::SliceOperationValidator::SortCapture(const AzToolsFramework::EntityList& orderToMatch)
{
// Since slice instantiation can alter the order of entities against the original input we need to sort our capture to match
// We do not care if the order of entities is different, only that both sets of entities are identical
// SortCapture will early out if a comparison entity cannot be found in our capture
AzToolsFramework::EntityList sortedCapture;
for (const AZ::Entity* entity : orderToMatch)
{
// If an entity is ever nullptr early out
if (!entity)
{
return false;
}
// Try and find the entity within our capture state, early out if we can't find it
AZ::Entity* foundCaptureEntity = FindEntityInList(entity->GetId(), m_entityStateCapture);
if (!foundCaptureEntity)
{
return false;
}
// Place the found entity into our temp
// This builds a sequence of entities that match our orderToMatch list
sortedCapture.emplace_back(foundCaptureEntity);
}
// Update our capture
m_entityStateCapture = sortedCapture;
return true;
}
void SliceStabilityTest::SliceOperationValidator::Reset()
{
// Since our entity capture is made of clones we need to delete them
for (AZ::Entity* capturedEntity : m_entityStateCapture)
{
EXPECT_NE(capturedEntity, nullptr);
delete capturedEntity;
}
m_entityStateCapture.clear();
}
void SliceStabilityTest::EntityReferenceComponent::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<EntityReferenceComponent, AzToolsFramework::Components::EditorComponentBase>()->
Field("EntityReference", &EntityReferenceComponent::m_entityReference);
}
}
// Sanity check test to confirm validator will catch differences
TEST_F(SliceStabilityTest, ValidatorCompare_DifferenceInObjects_DifferenceDetected_FT)
{
// Generate a root entity
AzToolsFramework::EntityIdList liveEntityIds;
AZ::EntityId rootEntityId = CreateEditorEntity("Root", liveEntityIds);
ASSERT_TRUE(rootEntityId.IsValid());
// Capture entity state
EXPECT_TRUE(m_validator.Capture(liveEntityIds));
// Create a slice from the root entity
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
AZ::Data::AssetId newSliceAssetId = CreateSlice("NewSlice", liveEntityIds, sliceInstanceAddress);
ASSERT_TRUE(newSliceAssetId.IsValid());
// Compare generated slice instance to initial capture state
EXPECT_TRUE(m_validator.Compare(sliceInstanceAddress));
// Make a second instance of our new slice
// This instance should have a unique entity id for its root entity
AzToolsFramework::EntityIdList newInstanceEntities;
AZ::SliceComponent::SliceInstanceAddress newInstanceAddress = InstantiateEditorSlice(newSliceAssetId, newInstanceEntities);
ASSERT_TRUE(newInstanceAddress.IsValid());
// Validate that our first instance has a single valid entity
ASSERT_TRUE(sliceInstanceAddress.IsValid());
ASSERT_TRUE(sliceInstanceAddress.GetInstance()->GetInstantiated());
ASSERT_EQ(sliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities.size(), 1);
ASSERT_TRUE(sliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities[0]);
// Validate that our first instance's entity has rootEntityId as its EntityID
EXPECT_EQ(sliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities[0]->GetId(), rootEntityId);
// Validate that our second instance has a single valid entity
ASSERT_TRUE(newInstanceAddress.IsValid());
ASSERT_TRUE(newInstanceAddress.GetInstance()->GetInstantiated());
ASSERT_EQ(newInstanceAddress.GetInstance()->GetInstantiated()->m_entities.size(), 1);
ASSERT_TRUE(newInstanceAddress.GetInstance()->GetInstantiated()->m_entities[0]);
// Validate that our two instances have different EntityIDs for their root entities
EXPECT_NE(sliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities[0]->GetId(), newInstanceAddress.GetInstance()->GetInstantiated()->m_entities[0]->GetId());
// Compare the new instance against the inital capture
// We expect the compare to fail since there is a difference in entity ids
EXPECT_FALSE(m_validator.Compare(newInstanceAddress));
}
}
@@ -0,0 +1,171 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/functional.h>
#include <AzCore/UnitTest/Mocks/MockFileIOBase.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace UnitTest
{
class SliceStabilityTest
: public ToolsApplicationFixture,
public AzToolsFramework::AssetSystemRequestBus::Handler,
public AzToolsFramework::EditorRequestBus::Handler,
public AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler
{
public:
//! Creates an entity within the EditorEntityContext and supplies it required components
//! @param entityName The name the created entity will use
//! @param entityList The created entity will be placed at the back of the provided entityList
//! @param parentId The EntityId to be assigned as the parent of the created entity.
//! Defaults to an invalid id
//! @return The EntityId of the created Entity, or an invalid id if operation failed
AZ::EntityId CreateEditorEntity(const char* entityName, AzToolsFramework::EntityIdList& entityList, const AZ::EntityId& parentId = AZ::EntityId());
//! Creates a new slice asset out of the provided entityList and generates the first slice instance using the provided entityList
//! @param sliceAssetName Name of the newly created slice Asset
//! @param entityList Created slice will be comprised of the entity hierarchy found in entityList
//! entities need to exist within the EditorEntityContext and those same entities will be promoted into the first slice instance of the created slice
//! @param sliceAddress The SliceInstanceAddress of the first slice instance generated out of the entities found in entityList
//! @return The AssetId of the newly created slice, or an invalid id if operation failed
AZ::Data::AssetId CreateSlice(AZStd::string sliceAssetName, AzToolsFramework::EntityIdList entityList, AZ::SliceComponent::SliceInstanceAddress& sliceAddress);
//! Pushes a set of entities to an existing slice asset generated via CreateSlice
//! @param sliceInstanceAddress An instance of the slice being pushed to
//! Note: The act of pushing to a slice destroys and remakes all existing instances sliceInstanceAddress will be updated to represent the remade slice instance.
//! Any copies of sliceInstanceAddress from before this operation are invalid.
//! @param entitiesToPush A list of entities to push to the slice.
//! If an entity in the list is already in the slice instance then it will be pushed as an updated entity
//! If an entity in the list is not in the slice instance then it will be pushed as an added entity
//! @return true if the push succeeded, or false if the operation failed
bool PushEntitiesToSlice(AZ::SliceComponent::SliceInstanceAddress& sliceInstanceAddress, const AzToolsFramework::EntityIdList& entitiesToPush);
//! Instantiates a slice into the EditorEntityContext using an existing slice Asset created via CreateSlice
//! @param sliceAssetId The asset id of the slice being instantiated
//! @param entityList All newly instantiated entities will be added to the back of entityList
//! @param parent The EntityId to be assigned as the parent of the slice instance entities.
//! Defaults to an invalid id
//! @return The SliceInstanceAddress of the new slice instance, or an invalid address if operation failed
AZ::SliceComponent::SliceInstanceAddress InstantiateEditorSlice(AZ::Data::AssetId sliceAssetId, AzToolsFramework::EntityIdList& entityList, const AZ::EntityId& parent = AZ::EntityId());
//! Performs a reparent of entity to newParent. Handles any slice hierarchy manipulation needed
//! @param entity The entity being reparented
//! @param newParent The new parent in the reparent operation
void ReparentEntity(AZ::EntityId& entity, const AZ::EntityId& newParent);
//! Helper that searches for an entityId within a list of entities
//! @param entityId The EntityId being searched for
//! @param entityList the list to search in
//! @return The Entity* whose EntityId matches entityId and is found in entityList.
//! Returns nullptr if not found
static AZ::Entity* FindEntityInList(const AZ::EntityId& entityId, const AZ::SliceComponent::EntityList& entityList);
//! Helper that searches for an entity within the EditorEntityContext
//! @param entityId The entityId being searched for
//! @return The Entity* whose id matches entityId and is found in the EditorEntityContext
//! Returns nullptr if not found
static AZ::Entity* FindEntityInEditor(const AZ::EntityId& entityId);
class SliceOperationValidator
{
public:
SliceOperationValidator();
~SliceOperationValidator();
void SetSerializeContext(AZ::SerializeContext* serializeContext);
//! Clones the provided entities out of the EditorEntityContext and caches them for Compare operations
//! @param entitiesToCapture List of EntityIDs that is used to search the EditorEntityContext and clone the respective Entity* into a cache for Compare operations
//! @return Returns whether the capture was successful
//! Can fail if there is already a cached capture or the entities could not be found in the EditorEntityContext
bool Capture(const AzToolsFramework::EntityIdList& entitiesToCapture);
//! Does a DataPatch compare of the reflected fields of a captured EntityList and the Instantiated entities found in instanceToCompare
//! @param instanceToCompare A SliceInstanceAddress whose InstantiatedContainer will be diffed against a previously captured EntityList using DataPatch
bool Compare(const AZ::SliceComponent::SliceInstanceAddress& instanceToCompare);
//! Resets the current capture so a new one can be made
void Reset();
private:
bool SortCapture(const AzToolsFramework::EntityList& orderToMatch);
AZ::SerializeContext* m_serializeContext;
AZ::SliceComponent::EntityList m_entityStateCapture;
};
class EntityReferenceComponent
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_EDITOR_COMPONENT(EntityReferenceComponent, "{3628F6A3-DFAD-4C1E-B9DE-EFBB1B6915C3}");
void Init() override {}
void Activate() override {}
void Deactivate() override {}
static void Reflect(AZ::ReflectContext* reflection);
AZ::EntityId m_entityReference;
};
SliceOperationValidator m_validator;
private:
void SetUpEditorFixtureImpl() override;
void TearDownEditorFixtureImpl() override;
/*
* SliceEditorEntityOwnershipServiceNotificationBus
*/
void OnSliceInstantiated(const AZ::Data::AssetId& sliceAssetId, AZ::SliceComponent::SliceInstanceAddress& sliceAddress, const AzFramework::SliceInstantiationTicket& ticket) override;
void OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId, const AzFramework::SliceInstantiationTicket& ticket) override;
/*
* EditorRequestBus
*/
void CreateEditorRepresentation(AZ::Entity* entity) override;
void BrowseForAssets(AzToolsFramework::AssetBrowser::AssetSelectionModel& selection) override { AZ_UNUSED(selection); }
int GetIconTextureIdFromEntityIconPath(const AZStd::string& entityIconPath) override { AZ_UNUSED(entityIconPath); return 0; }
bool DisplayHelpersVisible() { return false; }
/*
* AssetSystemRequestBus
*/
const char* GetAbsoluteDevGameFolderPath() override { return ""; }
const char* GetAbsoluteDevRootFolderPath() override { return ""; }
bool GetRelativeProductPathFromFullSourceOrProductPath([[maybe_unused]] const AZStd::string& fullPath, [[maybe_unused]] AZStd::string& relativeProductPath) override { return false; }
bool GetFullSourcePathFromRelativeProductPath([[maybe_unused]] const AZStd::string& relPath, [[maybe_unused]] AZStd::string& fullSourcePath) override { return false; }
bool GetAssetInfoById([[maybe_unused]] const AZ::Data::AssetId& assetId, [[maybe_unused]] const AZ::Data::AssetType& assetType, [[maybe_unused]] const AZStd::string& platformName, [[maybe_unused]] AZ::Data::AssetInfo& assetInfo, [[maybe_unused]] AZStd::string& rootFilePath) override { return false; }
bool GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder) override;
bool GetSourceInfoBySourceUUID([[maybe_unused]] const AZ::Uuid& sourceUuid, [[maybe_unused]] AZ::Data::AssetInfo& assetInfo, [[maybe_unused]] AZStd::string& watchFolder) override { return false; }
bool GetScanFolders([[maybe_unused]] AZStd::vector<AZStd::string>& scanFolders) override { return false; }
bool GetAssetSafeFolders([[maybe_unused]] AZStd::vector<AZStd::string>& assetSafeFolders) override { return false; }
bool IsAssetPlatformEnabled([[maybe_unused]] const char* platform) override { return false; }
int GetPendingAssetsForPlatform([[maybe_unused]] const char* platform) override { return -1; }
bool GetAssetsProducedBySourceUUID([[maybe_unused]] const AZ::Uuid& sourceUuid, [[maybe_unused]] AZStd::vector<AZ::Data::AssetInfo>& productsAssetInfo) override { return false; }
AZStd::unique_ptr<testing::NiceMock<AZ::IO::MockFileIOBase>> m_fileIOMock;
AZ::IO::FileIOBase* m_priorFileIO = nullptr;
AZStd::unordered_map<AZ::Data::AssetId, AZStd::vector<AZ::SliceComponent::SliceInstanceAddress>> m_createdSlices;
AZ::Data::AssetId m_newSliceId;
AzFramework::SliceInstantiationTicket m_ticket;
static constexpr const char* m_relativeSourceAssetRoot = "Test/";
};
}
@@ -0,0 +1,620 @@
/*
* 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 <AzCore/PlatformDef.h>
AZ_PUSH_DISABLE_WARNING(,"-Wdelete-non-virtual-dtor")
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Streamer/Streamer.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/Slice/SliceMetadataInfoComponent.h>
#include <AzCore/Slice/SliceAssetHandler.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include "SliceUpgradeTestsData.h"
namespace UnitTest
{
class SliceUpgradeTest_MockCatalog final
: public AZ::Data::AssetCatalog
, public AZ::Data::AssetCatalogRequestBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(SliceUpgradeTest_MockCatalog, AZ::SystemAllocator, 0);
SliceUpgradeTest_MockCatalog()
{
AZ::Data::AssetCatalogRequestBus::Handler::BusConnect();
}
~SliceUpgradeTest_MockCatalog() override
{
AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect();
DisableCatalog();
}
//////////////////////////////////////////////////////////////////////////
// AssetCatalogRequestBus
AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& id) override
{
AZ::Data::AssetInfo result;
auto itr = m_assetInfoMap.find(id);
if (itr != m_assetInfoMap.end())
{
result = itr->second;
}
return result;
}
//////////////////////////////////////////////////////////////////////////
const AZ::Data::AssetInfo& GenerateSliceAssetInfo(AZ::Data::AssetId assetId, const char* assetHintName = "datapatch_test.slice")
{
EXPECT_TRUE(assetId.IsValid());
AZ::Data::AssetInfo& assetInfo = m_assetInfoMap[assetId];
assetInfo.m_assetId = assetId;
assetInfo.m_assetType = AZ::AzTypeInfo<AZ::SliceAsset>::Uuid();
assetInfo.m_relativePath = AZStd::string::format("%s-%s", assetId.ToString<AZStd::string>().c_str(), assetHintName);
return assetInfo;
}
AZ::Data::AssetStreamInfo GetStreamInfoForLoad(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType) override
{
EXPECT_EQ(assetType, AZ::AzTypeInfo<AZ::SliceAsset>::Uuid());
AZ::Data::AssetStreamInfo info;
info.m_streamFlags = AZ::IO::OpenMode::ModeRead;
auto assetInfoItr = m_assetInfoMap.find(assetId);
if (assetInfoItr != m_assetInfoMap.end())
{
info.m_streamName = assetInfoItr->second.m_relativePath;
}
if (!info.m_streamName.empty())
{
info.m_dataLen = static_cast<size_t>(AZ::IO::SystemFile::Length(info.m_streamName.c_str()));
}
return info;
}
AZ::Data::AssetStreamInfo GetStreamInfoForSave(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType) override
{
AZ::Data::AssetStreamInfo info;
info = GetStreamInfoForLoad(assetId, assetType);
info.m_streamFlags = AZ::IO::OpenMode::ModeWrite;
return info;
}
private:
AZStd::unordered_map<AZ::Data::AssetId, AZ::Data::AssetInfo> m_assetInfoMap;
};
class SliceUpgradeTest
: public AllocatorsTestFixture
{
protected:
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_sliceDescriptor;
AZStd::unique_ptr<SliceUpgradeTest_MockCatalog> m_mockCatalog;
AZStd::unique_ptr<AZ::IO::Streamer> m_streamer;
AZStd::unique_ptr<AZ::SliceComponent> m_rootSliceComponent;
AZStd::unordered_map<AZ::Data::AssetId, AZ::Data::Asset<AZ::SliceAsset>> m_sliceAssets;
AZStd::unordered_map<AZ::Data::AssetId, AZStd::vector<char>> m_sliceStreams;
public:
void SetUp() override
{
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
m_streamer = AZStd::make_unique<AZ::IO::Streamer>(AZStd::thread_desc{}, AZ::StreamerComponent::CreateStreamerStack());
AZ::Interface<AZ::IO::IStreamer>::Register(m_streamer.get());
m_serializeContext.reset(aznew AZ::SerializeContext(true, false));
ASSERT_NE(m_serializeContext, nullptr);
m_sliceDescriptor.reset(AZ::SliceComponent::CreateDescriptor());
m_sliceDescriptor->Reflect(m_serializeContext.get());
AZ::SliceMetadataInfoComponent::Reflect(m_serializeContext.get());
AzFramework::SimpleAssetReferenceBase::Reflect(m_serializeContext.get());
AZ::Entity::Reflect(m_serializeContext.get());
AZ::DataPatch::Reflect(m_serializeContext.get());
AZ::Data::AssetManager::Descriptor desc;
AZ::Data::AssetManager::Create(desc);
AZ::Data::AssetManager::Instance().RegisterHandler(aznew AZ::SliceAssetHandler(m_serializeContext.get()), AZ::AzTypeInfo<AZ::SliceAsset>::Uuid());
m_mockCatalog.reset(aznew SliceUpgradeTest_MockCatalog());
AZ::Data::AssetManager::Instance().RegisterCatalog(m_mockCatalog.get(), AZ::AzTypeInfo<AZ::SliceAsset>::Uuid());
m_rootSliceComponent.reset(aznew AZ::SliceComponent);
m_rootSliceComponent->Instantiate();
}
void TearDown() override
{
m_rootSliceComponent.reset();
{
AZStd::unordered_map<AZ::Data::AssetId, AZ::Data::Asset<AZ::SliceAsset>> clean_sliceAssets;
m_sliceAssets.swap(clean_sliceAssets);
AZStd::unordered_map<AZ::Data::AssetId, AZStd::vector<char>> clean_sliceStreams;
m_sliceStreams.swap(clean_sliceStreams);
}
m_mockCatalog.reset();
AZ::Data::AssetManager::Destroy();
m_sliceDescriptor.reset();
m_serializeContext.reset();
AZ::Interface<AZ::IO::IStreamer>::Unregister(m_streamer.get());
m_streamer.reset();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
}
void SaveSliceAssetToStream(AZ::Data::AssetId sliceAssetId)
{
auto sliceAssetItr = m_sliceAssets.find(sliceAssetId);
ASSERT_NE(sliceAssetItr, m_sliceAssets.end());
AZ::Entity* sliceAssetEntity = sliceAssetItr->second.GetAs<AZ::SliceAsset>()->GetEntity();
AZStd::vector<char>& buf = m_sliceStreams[sliceAssetId];
buf.clear();
AZ::IO::ByteContainerStream<AZStd::vector<char>> stream(&buf);
AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&stream, *m_serializeContext, AZ::ObjectStream::ST_XML);
objStream->WriteClass(sliceAssetEntity);
EXPECT_TRUE(objStream->Finalize());
}
void SaveRawSliceAssetXML(AZ::Data::AssetId sliceAssetId, const char* sliceStr, size_t sliceStrSize)
{
// Create empty slice asset placeholder which will be filled.
const AZ::Data::AssetInfo& assetInfo = m_mockCatalog->GenerateSliceAssetInfo(sliceAssetId);
AZ::Data::Asset<AZ::SliceAsset> sliceAssetHolder =
AZ::Data::AssetManager::Instance().CreateAsset<AZ::SliceAsset>(assetInfo.m_assetId, AZ::Data::AssetLoadBehavior::Default);
m_sliceAssets.emplace(assetInfo.m_assetId, sliceAssetHolder);
AZStd::vector<char>& buf = m_sliceStreams[sliceAssetId];
buf.resize_no_construct(sliceStrSize);
memcpy(buf.data(), sliceStr, sliceStrSize);
}
/**
* We "simulate" slice operations by doing everything without involving disk operations.
*
* If the argument `entity` is NOT from an existing slice instance, SaveAsSlice transfers
* the ownership of `entity` to the newly created slice asset, so don't use or delete it
* after calling SaveAsSlice.
*/
AZ::Data::AssetId SaveAsSlice(AZ::Entity* entity, const AZ::Uuid& newAssetUuid = AZ::Uuid::CreateRandom(), const char* assetHintName = "datapatch_test.slice")
{
AZ::Entity* sliceEntity = aznew AZ::Entity();
AZ::SliceComponent* sliceComponent = nullptr;
AZ::SliceComponent::SliceInstanceAddress sliceInstAddress = m_rootSliceComponent->FindSlice(entity);
if (sliceInstAddress.IsValid())
{
AZ::SliceComponent* tempSliceComponent = aznew AZ::SliceComponent();
// borrow the slice instance for making nested slice
sliceInstAddress = tempSliceComponent->AddSliceInstance(sliceInstAddress.GetReference(), sliceInstAddress.GetInstance());
AZ::SliceComponent::SliceInstanceToSliceInstanceMap sourceToCloneSliceInstanceMap;
sliceComponent = tempSliceComponent->Clone(*m_serializeContext, &sourceToCloneSliceInstanceMap);
// return the slice instance back
m_rootSliceComponent->AddSliceInstance(sliceInstAddress.GetReference(), sliceInstAddress.GetInstance());
delete tempSliceComponent;
}
else
{
sliceComponent = aznew AZ::SliceComponent();
sliceComponent->SetSerializeContext(m_serializeContext.get());
sliceComponent->AddEntity(entity);
}
sliceComponent->SetSerializeContext(m_serializeContext.get());
sliceEntity->AddComponent(sliceComponent);
sliceEntity->Init();
sliceEntity->Activate();
const AZ::Data::AssetInfo& assetInfo = m_mockCatalog->GenerateSliceAssetInfo(AZ::Data::AssetId(newAssetUuid, 1), assetHintName);
AZ::Data::Asset<AZ::SliceAsset> sliceAssetHolder =
AZ::Data::AssetManager::Instance().CreateAsset<AZ::SliceAsset>(assetInfo.m_assetId, AZ::Data::AssetLoadBehavior::Default);
sliceAssetHolder.GetAs<AZ::SliceAsset>()->SetData(sliceEntity, sliceComponent);
// Hold on to sliceAssetHolder so it's not ref-counted away.
m_sliceAssets.emplace(assetInfo.m_assetId, sliceAssetHolder);
// Serialize the slice to a stream, so later we can de-serialize it back with different data versions.
SaveSliceAssetToStream(assetInfo.m_assetId);
return assetInfo.m_assetId;
}
AZ::Entity* InstantiateSlice(AZ::Data::AssetId sliceAssetId)
{
auto sliceAssetItr = m_sliceAssets.find(sliceAssetId);
EXPECT_NE(sliceAssetItr, m_sliceAssets.end());
AZ::SliceComponent::SliceInstanceAddress sliceInstAddress = m_rootSliceComponent->AddSlice(sliceAssetItr->second);
m_rootSliceComponent->Instantiate();
const AZ::SliceComponent::InstantiatedContainer* entityContainer = sliceInstAddress.GetInstance()->GetInstantiated();
// For convenience reason, only single entity slices are allowed for now.
EXPECT_EQ(entityContainer->m_entities.size(), 1);
return entityContainer->m_entities[0];
}
void ReloadSliceAssetFromStream(AZ::Data::AssetId sliceAssetId)
{
auto sliceAssetItr = m_sliceAssets.find(sliceAssetId);
ASSERT_NE(sliceAssetItr, m_sliceAssets.end());
auto sliceStreamItr = m_sliceStreams.find(sliceAssetId);
ASSERT_NE(sliceStreamItr, m_sliceStreams.end());
AZ::IO::ByteContainerStream<AZStd::vector<char>> stream(&sliceStreamItr->second);
stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
AZ::Entity* newSliceAssetEntity = AZ::Utils::LoadObjectFromStream<AZ::Entity>(stream, m_serializeContext.get());
ASSERT_NE(newSliceAssetEntity, nullptr);
AZ::SliceComponent* newSliceAssetComponent = newSliceAssetEntity->FindComponent<AZ::SliceComponent>();
ASSERT_NE(newSliceAssetComponent, nullptr);
newSliceAssetComponent->SetSerializeContext(m_serializeContext.get());
sliceAssetItr->second.GetAs<AZ::SliceAsset>()->SetData(newSliceAssetEntity, newSliceAssetComponent);
}
};
TEST_F(SliceUpgradeTest, IntermmediateDataTypeChange)
{
TestDataA::Reflect(m_serializeContext.get());
TestComponentA_V0::Reflect(m_serializeContext.get());
AZ::Entity* entityA = aznew AZ::Entity();
TestComponentA_V0* component = entityA->CreateComponent<TestComponentA_V0>();
component->m_data.m_val = TestDataA_ExpectedVal;
AZ::Data::AssetId sliceAssetId = SaveAsSlice(entityA);
entityA = nullptr;
AZ::Entity* instantiatedSliceEntity0 = InstantiateSlice(sliceAssetId);
TestComponentA_V0* testComponentA = instantiatedSliceEntity0->FindComponent<TestComponentA_V0>();
AZ_TEST_ASSERT(testComponentA != nullptr);
AZ_TEST_ASSERT(testComponentA->m_data.m_val == TestDataA_ExpectedVal);
const float TestDataA_OverrideVal = 2.5f;
// Create a nested slice with overridding value.
testComponentA->m_data.m_val = TestDataA_OverrideVal;
AZ::Data::AssetId nestedSliceAssetId = SaveAsSlice(instantiatedSliceEntity0);
m_rootSliceComponent->RemoveEntity(instantiatedSliceEntity0, true, true);
instantiatedSliceEntity0 = nullptr;
AZ::Entity* instantiatedNestedSliceEntity0 = InstantiateSlice(nestedSliceAssetId);
testComponentA = instantiatedNestedSliceEntity0->FindComponent<TestComponentA_V0>();
AZ_TEST_ASSERT(testComponentA->m_data.m_val == TestDataA_OverrideVal);
m_rootSliceComponent->RemoveEntity(instantiatedNestedSliceEntity0, true, true);
instantiatedNestedSliceEntity0 = nullptr;
// Replace TestComponentA_V0 in Serialization context with TestComponentA_V1.
m_serializeContext->EnableRemoveReflection();
TestComponentA_V0::Reflect(m_serializeContext.get());
m_serializeContext->DisableRemoveReflection();
NewTestDataA::Reflect(m_serializeContext.get());
TestComponentA_V1::Reflect(m_serializeContext.get());
ReloadSliceAssetFromStream(nestedSliceAssetId);
instantiatedNestedSliceEntity0 = InstantiateSlice(nestedSliceAssetId);
TestComponentA_V1* testComponentA_V1 = instantiatedNestedSliceEntity0->FindComponent<TestComponentA_V1>();
AZ_TEST_ASSERT(testComponentA_V1 != nullptr);
EXPECT_EQ(testComponentA_V1->m_data.m_val, TestDataA_OverrideVal);
}
TEST_F(SliceUpgradeTest, TypeChangeInUnorderedMap)
{
TestDataB_V0::Reflect(m_serializeContext.get());
TestComponentB_V0::Reflect(m_serializeContext.get());
AZ::Entity* entityA = aznew AZ::Entity();
TestComponentB_V0* componentB = entityA->CreateComponent<TestComponentB_V0>();
componentB->m_unorderedMap.emplace(17, TestDataB_V0(17));
componentB->m_unorderedMap.emplace(29, TestDataB_V0(29));
componentB->m_unorderedMap.emplace(37, TestDataB_V0(37));
AZ::Data::AssetId sliceAssetId = SaveAsSlice(entityA);
entityA = nullptr;
componentB = nullptr;
AZ::Entity* instantiatedSliceEntity0 = InstantiateSlice(sliceAssetId);
componentB = instantiatedSliceEntity0->FindComponent<TestComponentB_V0>();
ASSERT_NE(componentB, nullptr);
EXPECT_EQ(componentB->m_unorderedMap.size(), 3);
auto foundItr = componentB->m_unorderedMap.find(17);
EXPECT_NE(foundItr, componentB->m_unorderedMap.end());
EXPECT_EQ(foundItr->second.m_data, 17);
foundItr = componentB->m_unorderedMap.find(29);
EXPECT_NE(foundItr, componentB->m_unorderedMap.end());
EXPECT_EQ(foundItr->second.m_data, 29);
foundItr = componentB->m_unorderedMap.find(37);
EXPECT_NE(foundItr, componentB->m_unorderedMap.end());
EXPECT_EQ(foundItr->second.m_data, 37);
// Creat a nested slice with overridding value.
componentB->m_unorderedMap[29].m_data = 92;
AZ::Data::AssetId nestedSliceAssetId = SaveAsSlice(instantiatedSliceEntity0);
m_rootSliceComponent->RemoveEntity(instantiatedSliceEntity0, true, true);
instantiatedSliceEntity0 = nullptr;
AZ::Entity* instantiatedNestedSliceEntity0 = InstantiateSlice(nestedSliceAssetId);
componentB = instantiatedNestedSliceEntity0->FindComponent<TestComponentB_V0>();
ASSERT_NE(componentB, nullptr);
EXPECT_EQ(componentB->m_unorderedMap.size(), 3);
foundItr = componentB->m_unorderedMap.find(29);
EXPECT_NE(foundItr, componentB->m_unorderedMap.end());
EXPECT_EQ(foundItr->second.m_data, 92);
m_serializeContext->EnableRemoveReflection();
TestComponentB_V0::Reflect(m_serializeContext.get());
TestDataB_V0::Reflect(m_serializeContext.get());
m_serializeContext->DisableRemoveReflection();
TestDataB_V1::Reflect(m_serializeContext.get());
TestComponentB_V0_1::Reflect(m_serializeContext.get());
ReloadSliceAssetFromStream(sliceAssetId);
ReloadSliceAssetFromStream(nestedSliceAssetId);
instantiatedNestedSliceEntity0 = InstantiateSlice(nestedSliceAssetId);
TestComponentB_V0_1* componentB1 = instantiatedNestedSliceEntity0->FindComponent<TestComponentB_V0_1>();
ASSERT_NE(componentB1, nullptr);
EXPECT_EQ(componentB1->m_unorderedMap.size(), 3);
auto foundItr_B1 = componentB1->m_unorderedMap.find(17);
EXPECT_NE(foundItr_B1, componentB1->m_unorderedMap.end());
EXPECT_EQ(foundItr_B1->second.m_info, 30.5f);
foundItr_B1 = componentB1->m_unorderedMap.find(29);
EXPECT_NE(foundItr_B1, componentB1->m_unorderedMap.end());
EXPECT_EQ(foundItr_B1->second.m_info, 105.5f);
foundItr_B1 = componentB1->m_unorderedMap.find(37);
EXPECT_NE(foundItr_B1, componentB1->m_unorderedMap.end());
EXPECT_EQ(foundItr_B1->second.m_info, 50.5f);
}
TEST_F(SliceUpgradeTest, TypeChangeInVector)
{
TestDataB_V0::Reflect(m_serializeContext.get());
TestComponentC_V0::Reflect(m_serializeContext.get());
AZ::Entity* entityA = aznew AZ::Entity();
TestComponentC_V0* componentC = entityA->CreateComponent<TestComponentC_V0>();
componentC->m_vec.push_back(TestDataB_V0(17));
componentC->m_vec.push_back(TestDataB_V0(29));
componentC->m_vec.push_back(TestDataB_V0(37));
AZ::Data::AssetId sliceAssetId = SaveAsSlice(entityA);
entityA = nullptr;
componentC = nullptr;
AZ::Entity* instantiatedSliceEntity0 = InstantiateSlice(sliceAssetId);
componentC = instantiatedSliceEntity0->FindComponent<TestComponentC_V0>();
ASSERT_NE(componentC, nullptr);
EXPECT_EQ(componentC->m_vec.size(), 3);
EXPECT_EQ(componentC->m_vec[0].m_data, 17);
EXPECT_EQ(componentC->m_vec[1].m_data, 29);
EXPECT_EQ(componentC->m_vec[2].m_data, 37);
// Creat a nested slice with overridding value.
componentC->m_vec[1].m_data = 92;
AZ::Data::AssetId nestedSliceAssetId = SaveAsSlice(instantiatedSliceEntity0);
m_rootSliceComponent->RemoveEntity(instantiatedSliceEntity0, true, true);
instantiatedSliceEntity0 = nullptr;
AZ::Entity* instantiatedNestedSliceEntity0 = InstantiateSlice(nestedSliceAssetId);
componentC = instantiatedNestedSliceEntity0->FindComponent<TestComponentC_V0>();
ASSERT_NE(componentC, nullptr);
EXPECT_EQ(componentC->m_vec.size(), 3);
EXPECT_EQ(componentC->m_vec[1].m_data, 92);
m_serializeContext->EnableRemoveReflection();
TestComponentC_V0::Reflect(m_serializeContext.get());
TestDataB_V0::Reflect(m_serializeContext.get());
m_serializeContext->DisableRemoveReflection();
TestDataB_V1::Reflect(m_serializeContext.get());
TestComponentC_V0_1::Reflect(m_serializeContext.get());
ReloadSliceAssetFromStream(sliceAssetId);
ReloadSliceAssetFromStream(nestedSliceAssetId);
instantiatedNestedSliceEntity0 = InstantiateSlice(nestedSliceAssetId);
TestComponentC_V0_1* componentC1 = instantiatedNestedSliceEntity0->FindComponent<TestComponentC_V0_1>();
ASSERT_NE(componentC1, nullptr);
EXPECT_EQ(componentC1->m_vec.size(), 3);
EXPECT_EQ(componentC1->m_vec[0].m_info, 30.5f);
EXPECT_EQ(componentC1->m_vec[1].m_info, 105.5f);
EXPECT_EQ(componentC1->m_vec[2].m_info, 50.5f);
}
TEST_F(SliceUpgradeTest, UpgradeSkipVersion_TypeChange_FloatToDouble)
{
// 1. Create an entity with a TestComponentE_V4 with the default value for m_data
TestComponentE_V4::Reflect(m_serializeContext.get());
AZ::Entity* testEntity = aznew AZ::Entity();
TestComponentE_V4* componentEV4 = testEntity->CreateComponent<TestComponentE_V4>();
componentEV4->m_data = V4_DefaultData;
// 2. Create a slice out of our default entity configuration
AZ::Data::AssetId sliceAssetId = SaveAsSlice(testEntity);
// 3. Clean everything up
componentEV4 = nullptr;
testEntity = nullptr;
// 4. Instantiate the slice we just created and verify that it contains default data
AZ::Entity* instantiatedSliceEntity = InstantiateSlice(sliceAssetId);
componentEV4 = instantiatedSliceEntity->FindComponent<TestComponentE_V4>();
ASSERT_NE(componentEV4, nullptr);
EXPECT_FLOAT_EQ(componentEV4->m_data, V4_DefaultData);
// 5. Override the data in our new slice and save it as a nested slice.
componentEV4->m_data = V4_OverrideData;
AZ::Data::AssetId nestedSliceAssetId = SaveAsSlice(instantiatedSliceEntity);
// 6. Clean everything up
m_rootSliceComponent->RemoveEntity(instantiatedSliceEntity, true, true);
componentEV4 = nullptr;
instantiatedSliceEntity = nullptr;
// 7. Instantiate the nested slice we just created and verify that it contains overridden data
instantiatedSliceEntity = InstantiateSlice(nestedSliceAssetId);
componentEV4 = instantiatedSliceEntity->FindComponent<TestComponentE_V4>();
ASSERT_NE(componentEV4, nullptr);
EXPECT_FLOAT_EQ(componentEV4->m_data, V4_OverrideData);
// 8. Clean everything up
m_rootSliceComponent->RemoveEntity(instantiatedSliceEntity, true, true);
componentEV4 = nullptr;
instantiatedSliceEntity = nullptr;
// 9. Remove TestComponentE_V4 from the serialize context and add TestComponentE_V5
m_serializeContext->EnableRemoveReflection();
TestComponentE_V4::Reflect(m_serializeContext.get());
m_serializeContext->DisableRemoveReflection();
TestComponentE_V5::Reflect(m_serializeContext.get());
// 10. Reload our slice assets
ReloadSliceAssetFromStream(sliceAssetId);
ReloadSliceAssetFromStream(nestedSliceAssetId);
// 11. Instantiate our nested slice and verify that the V4->V5 upgrade has
// been applied to the data patch and then patch has been properly applied
instantiatedSliceEntity = InstantiateSlice(nestedSliceAssetId);
TestComponentE_V5* componentEV5 = instantiatedSliceEntity->FindComponent<TestComponentE_V5>();
ASSERT_NE(componentEV5, nullptr);
EXPECT_EQ(componentEV5->m_data, V5_ExpectedData);
// 12. Clean everything up
m_rootSliceComponent->RemoveEntity(instantiatedSliceEntity, true, true);
componentEV5 = nullptr;
instantiatedSliceEntity = nullptr;
// 13. Remove TestComponentE_V5 from the serialize context and add TestComponentE_V6_1
m_serializeContext->EnableRemoveReflection();
TestComponentE_V5::Reflect(m_serializeContext.get());
m_serializeContext->DisableRemoveReflection();
TestComponentE_V6_1::Reflect(m_serializeContext.get());
// 14. Reload our slice assets
ReloadSliceAssetFromStream(sliceAssetId);
ReloadSliceAssetFromStream(nestedSliceAssetId);
// 15. Instantiate our nested slice and verify that the V4->V5 and V5->V6 upgrades have
// been applied to the data patch and then patch has been properly applied
instantiatedSliceEntity = InstantiateSlice(nestedSliceAssetId);
TestComponentE_V6_1* componentEV6_1 = instantiatedSliceEntity->FindComponent<TestComponentE_V6_1>();
ASSERT_NE(componentEV6_1, nullptr);
EXPECT_DOUBLE_EQ(componentEV6_1->m_data, V6_ExpectedData_NoSkip);
// 16. Clean everything up
m_rootSliceComponent->RemoveEntity(instantiatedSliceEntity, true, true);
componentEV6_1 = nullptr;
instantiatedSliceEntity = nullptr;
// 17. Remove TestComponentE_V6_1 from the serialize context and add TestComponentE_V6_2
m_serializeContext->EnableRemoveReflection();
TestComponentE_V6_1::Reflect(m_serializeContext.get());
m_serializeContext->DisableRemoveReflection();
TestComponentE_V6_2::Reflect(m_serializeContext.get());
// 18. Reload our slice assets
ReloadSliceAssetFromStream(sliceAssetId);
ReloadSliceAssetFromStream(nestedSliceAssetId);
// 19. Instantiate our nested slice and verify that the V4->V6 upgrade has
// been applied to the data patch and then patch has been properly applied
instantiatedSliceEntity = InstantiateSlice(nestedSliceAssetId);
TestComponentE_V6_2* componentEV6_2 = instantiatedSliceEntity->FindComponent<TestComponentE_V6_2>();
ASSERT_NE(componentEV6_2, nullptr);
EXPECT_TRUE(AZ::IsClose(componentEV6_2->m_data, V6_ExpectedData_Skip, 0.000001));
// 20. Clean everything up
m_rootSliceComponent->RemoveEntity(instantiatedSliceEntity, true, true);
componentEV6_2 = nullptr;
instantiatedSliceEntity = nullptr;
}
TEST_F(SliceUpgradeTest, TypeChangeTests)
{
// TEST TYPES
SliceUpgradeTestAsset::Reflect(m_serializeContext.get());
AzFramework::SimpleAssetReference<SliceUpgradeTestAsset>::Register(*m_serializeContext.get());
TestComponentD_V1::Reflect(m_serializeContext.get());
AZ::Entity* entity = aznew AZ::Entity();
TestComponentD_V1* component = entity->CreateComponent<TestComponentD_V1>();
// Supply a specific Asset Guid to help with debugging
AZ::Data::AssetId sliceAssetId = SaveAsSlice(entity, "{10000000-0000-0000-0000-000000000000}", "datapatch_base.slice");
entity = nullptr;
AZ::Entity* instantiatedSliceEntity = InstantiateSlice(sliceAssetId);
TestComponentD_V1* testComponent = instantiatedSliceEntity->FindComponent<TestComponentD_V1>();
ASSERT_NE(testComponent, nullptr);
EXPECT_EQ(testComponent->m_firstData, Value1_Initial);
EXPECT_EQ(testComponent->m_secondData, Value2_Initial);
EXPECT_EQ(testComponent->m_asset, AssetPath_Initial);
// Create a nested slice with overridden data.
testComponent->m_firstData = Value1_Override;
testComponent->m_secondData = Value2_Override;
testComponent->m_asset = AssetPath_Override;
AZ::Data::AssetId nestedSliceAssetId = SaveAsSlice(instantiatedSliceEntity,"{20000000-0000-0000-0000-000000000000}", "datapatch_nested.slice");
m_rootSliceComponent->RemoveEntity(instantiatedSliceEntity, true, true);
instantiatedSliceEntity = nullptr;
AZ::Entity* instantiatedNestedSliceEntity = InstantiateSlice(nestedSliceAssetId);
testComponent = instantiatedNestedSliceEntity->FindComponent<TestComponentD_V1>();
EXPECT_EQ(testComponent->m_firstData, Value1_Override);
EXPECT_EQ(testComponent->m_secondData, Value2_Override);
EXPECT_EQ(testComponent->m_asset, AssetPath_Override);
m_rootSliceComponent->RemoveEntity(instantiatedNestedSliceEntity, true, true);
instantiatedNestedSliceEntity = nullptr;
// Replace TestComponentD_V1 in Serialization context with TestComponentD_V2.
m_serializeContext->EnableRemoveReflection();
TestComponentD_V1::Reflect(m_serializeContext.get());
m_serializeContext->DisableRemoveReflection();
TestComponentD_V2::Reflect(m_serializeContext.get());
ReloadSliceAssetFromStream(sliceAssetId);
ReloadSliceAssetFromStream(nestedSliceAssetId);
instantiatedNestedSliceEntity = InstantiateSlice(nestedSliceAssetId);
TestComponentD_V2* newTestComponent = instantiatedNestedSliceEntity->FindComponent<TestComponentD_V2>();
ASSERT_NE(newTestComponent, nullptr);
EXPECT_EQ(newTestComponent->m_firstData, Value1_Final);
EXPECT_EQ(newTestComponent->m_secondData, Value2_Final);
EXPECT_EQ(newTestComponent->m_asset.GetAssetPath(), AZStd::string(AssetPath_Override));
}
} // namespace UnitTest
AZ_POP_DISABLE_WARNING
@@ -0,0 +1,686 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h>
#include <AzFramework/Asset/SimpleAsset.h>
namespace UnitTest
{
static const float TestDataA_ExpectedVal = 1.5f;
class TestDataA
{
public:
float m_val = TestDataA_ExpectedVal;
public:
AZ_RTTI(TestDataA, "{3B7949D0-07BF-408E-8101-264466AEC403}");
virtual ~TestDataA() = default;
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestDataA>()
// Version defaults to 0
->Field("Val", &TestDataA::m_val)
;
}
}
};
static const char* TestComponentATypeId = "{C802148B-7EDC-4518-9780-FB9F99880446}";
class TestComponentA_V0
: public AzToolsFramework::Components::EditorComponentBase
{
public:
TestDataA m_data;
public:
AZ_EDITOR_COMPONENT(TestComponentA_V0, TestComponentATypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentA_V0, AzToolsFramework::Components::EditorComponentBase>()
// Version defaults to 0
->Field("Data", &TestComponentA_V0::m_data)
;
}
}
};
class NewTestDataA
{
public:
float m_val = 2.5f;
public:
AZ_RTTI(NewTestDataA, "{2CEC8357-5156-4C8C-B664-501EA19213CB}");
virtual ~NewTestDataA() = default;
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<NewTestDataA>()
// Version defaults to 0
->Field("Val", &NewTestDataA::m_val)
;
}
}
};
class TestComponentA_V1
: public AzToolsFramework::Components::EditorComponentBase
{
public:
NewTestDataA m_data;
public:
AZ_EDITOR_COMPONENT(TestComponentA_V1, TestComponentATypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static int ConvertV0Float_to_V1Int(float in)
{
return (int)in;
}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentA_V1, AzToolsFramework::Components::EditorComponentBase>()
->Version(1)
->Field("NewData", &TestComponentA_V1::m_data)
->TypeChange<TestDataA, NewTestDataA>("Data", 0, 1, [](TestDataA in) -> NewTestDataA {
return NewTestDataA();
})
->NameChange(0, 1, "Data", "NewData")
;
}
}
};
static const char* TestDataB_TypeId = "{20E6777B-6857-409B-B27F-9E505D4378EF}";
struct TestDataB_V0
{
static AZ::u64 PersistentIdCounter;
AZ_RTTI(TestDataB_V0, TestDataB_TypeId);
TestDataB_V0()
: m_persistentId(++PersistentIdCounter)
, m_data(0)
{}
TestDataB_V0(int data)
: m_persistentId(++PersistentIdCounter)
, m_data(data)
{}
virtual ~TestDataB_V0() = default;
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestDataB_V0>()
->Version(0)
->PersistentId([](const void* instance) -> AZ::u64 { return reinterpret_cast<const TestDataB_V0*>(instance)->m_persistentId; })
->Field("PersistentId", &TestDataB_V0::m_persistentId)
->Field("Data", &TestDataB_V0::m_data)
;
}
}
AZ::u64 m_persistentId;
int m_data;
};
AZ::u64 TestDataB_V0::PersistentIdCounter = 1024;
struct TestDataB_V1
{
AZ_RTTI(TestDataB_V1, TestDataB_TypeId);
TestDataB_V1()
: m_persistentId(++TestDataB_V0::PersistentIdCounter)
, m_info(0)
{}
virtual ~TestDataB_V1() = default;
static float TestDataB_V0_V1(int in)
{
return in + 13.5f;
}
static bool VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
// conversion from Version 0 to Version 1
// - Data (int) becomes Info (float) with the conversion Info = Data + 13.5f
if (classElement.GetVersion() == 0)
{
int dataIndex = classElement.FindElement(AZ_CRC("Data"));
if (dataIndex < 0)
{
return false;
}
AZ::SerializeContext::DataElementNode& dataElement = classElement.GetSubElement(dataIndex);
int data;
dataElement.GetData<int>(data);
//Create a new info value
int infoIndex = classElement.AddElement<float>(context, "Info");
AZ::SerializeContext::DataElementNode& infoElement = classElement.GetSubElement(infoIndex);
//Set width and height data to x and y from the old size
infoElement.SetData<float>(context, data + 13.5f);
classElement.RemoveElement(dataIndex);
}
return true;
}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestDataB_V1>()
->Version(1, &VersionConverter)
->PersistentId([](const void* instance) -> AZ::u64 { return reinterpret_cast<const TestDataB_V1*>(instance)->m_persistentId; })
->Field("PersistentId", &TestDataB_V1::m_persistentId)
->Field("Info", &TestDataB_V1::m_info)
->TypeChange<int, float>("Data", 0, 1, [](int in)-> float {
float result = TestDataB_V0_V1(in);
return result;
})
->NameChange(0, 1, "Data", "Info")
;
}
}
AZ::u64 m_persistentId;
float m_info = 27.5f;
};
static const char* TestComponentBTypeId = "{10778D96-4860-4690-9A0E-B1066C00136B}";
class TestComponentB_V0
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZStd::unordered_map<int, TestDataB_V0> m_unorderedMap;
public:
AZ_EDITOR_COMPONENT(TestComponentB_V0, TestComponentBTypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentB_V0, AzToolsFramework::Components::EditorComponentBase>()
->Field("UnorderedMap", &TestComponentB_V0::m_unorderedMap)
;
}
}
};
// TestComponentB_V0_1 is NOT a version upgrade of TestComponentB_V0. It is TestComponentB_V0.
// We have to create a different class to represent TestComponentB_V0 so we can simulate
// version upgrade of TestDataB.
class TestComponentB_V0_1
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZStd::unordered_map<int, TestDataB_V1> m_unorderedMap;
public:
AZ_EDITOR_COMPONENT(TestComponentB_V0_1, TestComponentBTypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentB_V0_1, AzToolsFramework::Components::EditorComponentBase>()
->Field("UnorderedMap", &TestComponentB_V0_1::m_unorderedMap)
;
}
}
};
class TestComponentC_V0
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZStd::vector<TestDataB_V0> m_vec;
public:
AZ_EDITOR_COMPONENT(TestComponentC_V0, TestComponentBTypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentC_V0, AzToolsFramework::Components::EditorComponentBase>()
->Field("Vector", &TestComponentC_V0::m_vec)
;
}
}
};
// TestComponentC_V0_1 is NOT a version upgrade of TestComponentC_V0. It is TestComponentC_V0.
// We have to create a different class to represent TestComponentC_V0 so we can simulate
// version upgrade of TestDataB.
class TestComponentC_V0_1
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZStd::vector<TestDataB_V1> m_vec;
public:
AZ_EDITOR_COMPONENT(TestComponentC_V0_1, TestComponentBTypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentC_V0_1, AzToolsFramework::Components::EditorComponentBase>()
->Field("Vector", &TestComponentC_V0_1::m_vec)
;
}
}
};
static const char* TestComponentDTypeId = "{77655B67-3E03-418C-B010-D272DBCEAE25}";
// Initial Test Values
static const int Value1_Initial = 3;
static const float Value2_Initial = 7;
static const char* AssetPath_Initial = "C:/ly/dev/assets/myslicetestasset.NaN";
// Data Patch Override Values
static const int Value1_Override = 5;
static const float Value2_Override = 9;
static const char* AssetPath_Override = "C:/ly/dev/assets/SliceTestAssets/myslicetestasset.NaN";
// Final Test Values
static const AZStd::string_view Value1_Final = "Five";
static const AZStd::string_view Value2_Final = "Nine";
class TestComponentD_V1
: public AzToolsFramework::Components::EditorComponentBase
{
public:
int m_firstData = Value1_Initial;
float m_secondData = Value2_Initial;
AZStd::string m_asset = AssetPath_Initial;
public:
AZ_EDITOR_COMPONENT(TestComponentD_V1, TestComponentDTypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentD_V1, AzToolsFramework::Components::EditorComponentBase>()
->Version(1)
->Field("IntData", &TestComponentD_V1::m_firstData)
->Field("FloatData", &TestComponentD_V1::m_secondData)
->Field("AssetData", &TestComponentD_V1::m_asset)
;
}
}
};
class SliceUpgradeTestAsset
{
public:
AZ_TYPE_INFO(SliceUpgradeTestAsset, "{10A39071-9287-49FE-93C8-55F7715FC758}")
static const char* GetFileFilter()
{
return "*.NaN";
}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<SliceUpgradeTestAsset>()
->Version(1);
}
}
};
class TestComponentD_V2
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZStd::string m_firstData;
AZStd::string m_secondData;
AzFramework::SimpleAssetReference<SliceUpgradeTestAsset> m_asset;
public:
AZ_EDITOR_COMPONENT(TestComponentD_V2, TestComponentDTypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static AZStd::string IntToString(int val)
{
switch (val)
{
case 1:
return "One";
case 2:
return "Two";
case 3:
return "Three";
case 4:
return "Four";
case 5:
return "Five";
case 6:
return "Six";
case 7:
return "Seven";
case 8:
return "Eight";
case 9:
return "Nine";
case 0:
return "Zero";
default:
return "NaN";
}
}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentD_V2, AzToolsFramework::Components::EditorComponentBase>()
->Version(2)
->Field("StringData", &TestComponentD_V2::m_firstData)
->TypeChange<int, AZStd::string>("IntData", 1, 2, &IntToString)
->NameChange(1, 2, "IntData", "StringData")
->Field("SecondStringData", &TestComponentD_V2::m_secondData)
->TypeChange<float, AZStd::string>("FloatData", 1, 2, [](float in)->AZStd::string {return IntToString(int(in)); })
->NameChange(1, 2, "FloatData", "SecondStringData")
->Field("AssetData", &TestComponentD_V2::m_asset)
->TypeChange<AZStd::string, AzFramework::SimpleAssetReference<SliceUpgradeTestAsset>>("AssetData", 1, 2, [](const AZStd::string& in)->AzFramework::SimpleAssetReference<SliceUpgradeTestAsset>
{
AzFramework::SimpleAssetReference<SliceUpgradeTestAsset> sliceUpgradeAsset;
sliceUpgradeAsset.SetAssetPath(in.c_str());
return sliceUpgradeAsset;
});
}
}
};
// Test Data for: UpgradeSkipVersion_TypeChange_FloatToDouble
// This test makes sure the data patch upgrade system can
// properly select upgrades. It will attempt to perform
// each of the following upgrades:
// 1. float (V4) -> int (V5) // Applies a single upgrade to convert a data patch originally created using TestComponentE_V4 to one that can be applied to TestComponentE_V5
// 2. float (V4) -> int (V5), int (V5) -> double (V6) // Applies 2 incremental upgrades to upgrade a data patch created using TestComponentE_V4 so that it can be applied to TestComponentE_V6_1 (Expected data loss)
// 3. float (V4) -> double (V6) // Applies a skip-version patch to go directly from TestComponentE_V4 to TestComponentE_V6_2 to avoid the data loss in the previous scenario.
static const char* TestComponentETypeId = "{835E5A78-2283-4113-91BC-BFC022619388}";
// Original data for our test TestComponentE_V4
static const float V4_DefaultData = 3.75f;
// Overridden value in our data patch created using TestComponentE_V4
static const float V4_OverrideData = 6.33f;
// Expected value of the override when converting the patch to TestComponentE_V5
static const int V5_ExpectedData = 3;
// Expected value of the override when converting the patch to TestComponentE_V6_1 using upgrade method (2)
static const double V6_ExpectedData_NoSkip = 30.0;
// Expected value of the override when converting the patch to TestComponentE_V6_2 using upgrade method (3)
static const double V6_ExpectedData_Skip = 12.66;
class TestComponentE_V4
: public AzToolsFramework::Components::EditorComponentBase
{
public:
float m_data;
static const float ExpectedData;
public:
AZ_EDITOR_COMPONENT(TestComponentE_V4, TestComponentETypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentE_V4, AzToolsFramework::Components::EditorComponentBase>()
->Version(4)
->Field("FloatData", &TestComponentE_V4::m_data);
}
}
};
class TestComponentE_V5
: public AzToolsFramework::Components::EditorComponentBase
{
public:
int m_data;
public:
AZ_EDITOR_COMPONENT(TestComponentE_V5, TestComponentETypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static int ConvertV4Float_to_V5Int(float in)
{
return ((int)in) / 2;
}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentE_V5, AzToolsFramework::Components::EditorComponentBase>()
->Version(5)
->Field("IntData", &TestComponentE_V5::m_data)
->TypeChange<float, int>("FloatData", 4, 5, &ConvertV4Float_to_V5Int)
->NameChange(4, 5, "FloatData", "IntData");
}
}
};
class TestComponentE_V6_1
: public AzToolsFramework::Components::EditorComponentBase
{
public:
double m_data;
public:
AZ_EDITOR_COMPONENT(TestComponentE_V6_1, TestComponentETypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static int ConvertV4Float_to_V5Int(float in)
{
return ((int)in) / 2;
}
static double ConvertV5Int_to_V6Double(int in)
{
return (double)(in * 10);
}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentE_V6_1, AzToolsFramework::Components::EditorComponentBase>()
->Version(6)
->Field("DoubleData", &TestComponentE_V6_1::m_data)
->TypeChange<float, int>("FloatData", 4, 5, &ConvertV4Float_to_V5Int)
->NameChange(4, 5, "FloatData", "IntData")
->TypeChange<int, double>("IntData", 5, 6, &ConvertV5Int_to_V6Double)
->NameChange(5, 6, "IntData", "DoubleData");
}
}
};
class TestComponentE_V6_2
: public AzToolsFramework::Components::EditorComponentBase
{
public:
double m_data;
public:
AZ_EDITOR_COMPONENT(TestComponentE_V6_2, TestComponentETypeId);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override {}
void Deactivate() override {}
//////////////////////////////////////////////////////////////////////////
static int ConvertV4Float_to_V5Int(float in)
{
return (int)in;
}
static double ConvertV5Int_to_V6Double(int in)
{
return (double)(in * 10);
}
static double ConvertV4Float_to_V6Double(float in)
{
return ((double)in) * 2.0;
}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = AZ::RttiCast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TestComponentE_V6_2, AzToolsFramework::Components::EditorComponentBase>()
->Version(6)
->Field("DoubleData", &TestComponentE_V6_2::m_data)
->TypeChange<float, int>("FloatData", 4, 5, &ConvertV4Float_to_V5Int)
->NameChange(4, 5, "FloatData", "IntData")
->TypeChange<int, double>("IntData", 5, 6, &ConvertV5Int_to_V6Double)
->TypeChange<float, double>("FloatData", 4, 6, &ConvertV4Float_to_V6Double) // The skip-version converter to preserve the floating point data from V4.
->NameChange(5, 6, "IntData", "DoubleData");
}
}
};
} // namespace UnitTest
@@ -0,0 +1,284 @@
/*
* 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 <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzQtComponents/Components/Widgets/SpinBox.h>
#include <QApplication>
#include <QLineEdit>
#include <QWheelEvent>
namespace UnitTest
{
using namespace AzToolsFramework;
// Expose the LineEdit functionality so selection behavior can be more easily tested.
class DoubleSpinBoxWithLineEdit
: public AzQtComponents::DoubleSpinBox
{
public:
// const required as lineEdit() is const
QLineEdit* GetLineEdit() const { return lineEdit(); }
};
// A fixture to help test the int and double spin boxes.
class SpinBoxFixture
: public ToolsApplicationFixture
{
public:
void SetUpEditorFixtureImpl() override
{
// note: must set a widget as the active window and add widgets
// as children to ensure focus in/out events fire correctly
m_dummyWidget = AZStd::make_unique<QWidget>();
// Give the test window a valid windowHandle. SpinBox code uses this to access the QScreen
m_dummyWidget->winId();
QApplication::setActiveWindow(m_dummyWidget.get());
m_intSpinBox = AZStd::make_unique<AzQtComponents::SpinBox>();
m_doubleSpinBox = AZStd::make_unique<AzQtComponents::DoubleSpinBox>();
m_doubleSpinBoxWithLineEdit = AZStd::make_unique<DoubleSpinBoxWithLineEdit>();
m_spinBoxes = { m_intSpinBox.get(), m_doubleSpinBox.get(), m_doubleSpinBoxWithLineEdit.get() };
for (auto spinBox : m_spinBoxes)
{
// Polish is required to set up the SpinBoxWatcher event filter
spinBox->ensurePolished();
spinBox->setParent(m_dummyWidget.get());
spinBox->setKeyboardTracking(false);
spinBox->setFocusPolicy(Qt::StrongFocus);
spinBox->clearFocus();
}
}
void TearDownEditorFixtureImpl() override
{
QApplication::setActiveWindow(nullptr);
// Regenerate this list in case any of them were deleted during the test
m_spinBoxes = { m_intSpinBox.get(), m_doubleSpinBox.get(), m_doubleSpinBoxWithLineEdit.get() };
for (auto spinBox : m_spinBoxes)
{
if (spinBox)
{
spinBox->setParent(nullptr);
}
}
m_dummyWidget.reset();
m_doubleSpinBoxWithLineEdit.reset();
m_doubleSpinBox.reset();
m_intSpinBox.reset();
}
AZStd::unique_ptr<QWidget> m_dummyWidget;
AZStd::unique_ptr<AzQtComponents::SpinBox> m_intSpinBox;
AZStd::unique_ptr<AzQtComponents::DoubleSpinBox> m_doubleSpinBox;
AZStd::unique_ptr<DoubleSpinBoxWithLineEdit> m_doubleSpinBoxWithLineEdit;
AZStd::vector<QAbstractSpinBox*> m_spinBoxes;
};
TEST_F(SpinBoxFixture, SpinBoxesCreated)
{
using ::testing::Ne;
EXPECT_THAT(m_intSpinBox, Ne(nullptr));
EXPECT_THAT(m_doubleSpinBox, Ne(nullptr));
EXPECT_THAT(m_doubleSpinBoxWithLineEdit, Ne(nullptr));
}
// Note: There are a series of bugs in Qt that appear to be preventing mouseMove events
// firing when sent through the QTest framework. This is a work around for our version
// of Qt. In future this can hopefully be simplified. See ^1 for workaround.
// More info: Issues with mouse move in Qt
// - https://bugreports.qt.io/browse/QTBUG-5232
// - https://bugreports.qt.io/browse/QTBUG-69414
// - https://lists.qt-project.org/pipermail/development/2019-July/036873.html
void MousePressAndMove(
QWidget* widget, const QPoint& widgetScreenPosition, const QPoint& mouseDelta)
{
QPoint position = widget->mapToGlobal(widgetScreenPosition);
QPoint nextPosition = widget->mapToGlobal(widgetScreenPosition + mouseDelta);
QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, position);
// ^1 To ensure a mouse move event is fired we must call the test mouse move function
// and also send a mouse move event that matches. Each on their own do not appear to
// work - please see the links above for more context.
QTest::mouseMove(widget, nextPosition);
QMouseEvent mouseMoveEvent(
QEvent::MouseMove, QPointF(nextPosition), QPointF(nextPosition),
Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
QApplication::sendEvent(widget, &mouseMoveEvent);
}
TEST_F(SpinBoxFixture, SpinBoxMousePressAndMoveRightScrollsValue)
{
m_doubleSpinBox->setValue(10.0);
const int halfWidgetHeight = m_doubleSpinBox->height() / 2;
const QPoint widgetCenterLeftBorder = m_doubleSpinBox->pos() + QPoint(1, halfWidgetHeight);
// Check we have a valid window setup before moving the cursor
EXPECT_TRUE(m_doubleSpinBox->window()->windowHandle() != nullptr);
// Right in screen space
MousePressAndMove(m_doubleSpinBox.get(), widgetCenterLeftBorder, QPoint(11, 0));
// AzQtComponents::SpinBox::Config.pixelsPerStep is 10
EXPECT_NEAR(m_doubleSpinBox->value(), 11.0, 0.001);
}
TEST_F(SpinBoxFixture, SpinBoxMousePressAndMoveLeftScrollsValue)
{
m_doubleSpinBox->setValue(10.0);
const int halfWidgetHeight = m_doubleSpinBox->height() / 2;
const QPoint widgetCenterLeftBorder = m_doubleSpinBox->pos() + QPoint(1, halfWidgetHeight);
// Check we have a valid window setup before moving the cursor
EXPECT_TRUE(m_doubleSpinBox->window()->windowHandle() != nullptr);
// Left in screen space
MousePressAndMove(m_doubleSpinBox.get(), widgetCenterLeftBorder, QPoint(-11, 0));
// AzQtComponents::SpinBox::Config.pixelsPerStep is 10
EXPECT_NEAR(m_doubleSpinBox->value(), 9.0, 0.001);
}
TEST_F(SpinBoxFixture, SpinBoxKeyboardUpAndDownArrowsChangeValue)
{
m_intSpinBox->setValue(5);
m_intSpinBox->setFocus();
QTest::keyClick(m_intSpinBox.get(), Qt::Key_Up, Qt::NoModifier);
EXPECT_EQ(m_intSpinBox->value(), 6);
QTest::keyClick(m_intSpinBox.get(), Qt::Key_Down, Qt::NoModifier);
QTest::keyClick(m_intSpinBox.get(), Qt::Key_Down, Qt::NoModifier);
EXPECT_EQ(m_intSpinBox->value(), 4);
}
TEST_F(SpinBoxFixture, SpinBoxChangeContentsAndEnterCommitsNewValue)
{
m_doubleSpinBoxWithLineEdit->setValue(10.0);
m_doubleSpinBoxWithLineEdit->setFocus();
m_doubleSpinBoxWithLineEdit->GetLineEdit()->setText(QString("15"));
QTest::keyClick(m_doubleSpinBoxWithLineEdit.get(), Qt::Key_Enter, Qt::NoModifier);
EXPECT_NEAR(m_doubleSpinBoxWithLineEdit->value(), 15.0, 0.001);
}
TEST_F(SpinBoxFixture, SpinBoxChangeContentsAndLoseFocusCommitsNewValue)
{
m_doubleSpinBoxWithLineEdit->setValue(10.0);
m_doubleSpinBoxWithLineEdit->setFocus();
m_doubleSpinBoxWithLineEdit->GetLineEdit()->setText(QString("15"));
m_doubleSpinBoxWithLineEdit->clearFocus();
EXPECT_NEAR(m_doubleSpinBoxWithLineEdit->value(), 15.0, 0.001);
}
TEST_F(SpinBoxFixture, SpinBoxClearContentsAndEscapeReturnsToPreviousValue)
{
m_doubleSpinBoxWithLineEdit->setValue(10.0);
m_doubleSpinBoxWithLineEdit->setFocus();
m_doubleSpinBoxWithLineEdit->GetLineEdit()->clear();
QTest::keyClick(m_doubleSpinBoxWithLineEdit.get(), Qt::Key_Escape, Qt::NoModifier);
EXPECT_NEAR(m_doubleSpinBoxWithLineEdit->value(), 10.0, 0.001);
}
TEST_F(SpinBoxFixture, SpinBoxChangeContentsAndEscapeReturnsToPreviousValue)
{
m_doubleSpinBoxWithLineEdit->setValue(10.0);
m_doubleSpinBoxWithLineEdit->setFocus();
m_doubleSpinBoxWithLineEdit->GetLineEdit()->setText(QString("15"));
QTest::keyClick(m_doubleSpinBoxWithLineEdit.get(), Qt::Key_Escape, Qt::NoModifier);
EXPECT_NEAR(m_doubleSpinBoxWithLineEdit->value(), 10.0, 0.001);
EXPECT_TRUE(m_doubleSpinBoxWithLineEdit->GetLineEdit()->hasSelectedText());
}
TEST_F(SpinBoxFixture, SpinBoxSelectContentsAndEscapeKeepsFocus)
{
m_doubleSpinBox->setValue(10.0);
m_doubleSpinBox->setFocus();
m_doubleSpinBox->selectAll();
QTest::keyClick(m_doubleSpinBox.get(), Qt::Key_Escape, Qt::NoModifier);
EXPECT_TRUE(m_doubleSpinBox->hasFocus());
QTest::keyClick(m_doubleSpinBox.get(), Qt::Key_Escape, Qt::NoModifier);
EXPECT_TRUE(m_doubleSpinBox->hasFocus());
}
TEST_F(SpinBoxFixture, SpinBoxSuffixRemovedAndAppliedWithFocusChange)
{
using testing::StrEq;
m_doubleSpinBox->setSuffix("m");
m_doubleSpinBox->setValue(10.0);
// test internal logic (textFromValue() calls private StringValue())
QString value = m_doubleSpinBox->textFromValue(10.0);
EXPECT_THAT(value.toUtf8().constData(), StrEq("10.0"));
m_doubleSpinBox->setFocus();
EXPECT_THAT(m_doubleSpinBox->suffix().toUtf8().constData(), StrEq(""));
m_doubleSpinBox->clearFocus();
EXPECT_THAT(m_doubleSpinBox->suffix().toUtf8().constData(), StrEq("m"));
}
// There is logic in our AzQtComponents::SpinBoxWatcher that delays processing of the end of wheel
// events by 100msec, which used to result in a crash if the SpinBox happened to be deleted after
// the timer was started and before it was triggered. This test was added to ensure the new handling
// works correctly by no longer crashing in this scenario.
TEST_F(SpinBoxFixture, SpinBoxClearDelayedWheelTimeoutAfterDelete)
{
// The wheel movement logic won't be triggered unless the SpinBox is focused at the start
m_intSpinBox->setFocus();
// Simulate the mouse wheel scrolling
// The delta for the wheel changing doesn't matter, it just needs to be different
auto delta = QPoint(10, 10);
auto spinBox = m_intSpinBox.get();
QWheelEvent wheelEventBegin(QPoint(), QPoint(), QPoint(), QPoint(), Qt::NoButton, Qt::NoModifier, Qt::ScrollBegin, false);
QWheelEvent wheelEventUpdate(delta, delta, delta, delta, Qt::NoButton, Qt::NoModifier, Qt::ScrollUpdate, false);
QWheelEvent wheelEventEnd(QPoint(), QPoint(), QPoint(), QPoint(), Qt::NoButton, Qt::NoModifier, Qt::ScrollEnd, false);
QApplication::sendEvent(spinBox, &wheelEventBegin);
QApplication::sendEvent(spinBox, &wheelEventUpdate);
QApplication::sendEvent(spinBox, &wheelEventEnd);
// Delete the SpinBox after triggering the mouse wheel scroll
m_intSpinBox.reset();
// The timeout in question is triggered 100msec after the mouse wheel has been moved
// Waiting 200msec here to make sure it has been triggered
QTest::qWait(200);
// Verifying the SpinBox was deleted, although the true verification is that before the fix this
// test would result in a crash
EXPECT_TRUE(m_intSpinBox.get() == nullptr);
}
} // namespace UnitTest
@@ -0,0 +1,166 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerComponent.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
namespace UnitTest
{
using namespace AzToolsFramework::Thumbnailer;
class ThumbnailerTests
: public ::testing::Test
, public TraceBusRedirector
{
protected:
void SetUp() override
{
m_app.Start(m_descriptor);
// Without this, the user settings component would sometimes attempt to save
// changes on shutdown. In some cases this would cause a crash while the unit test
// was running, because the environment wasn't setup for it to save these settings.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
TraceBusRedirector::BusConnect();
AZStd::string entityName("test");
AZ::EntityId testEntityId;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
testEntityId,
&AzToolsFramework::EditorEntityContextRequestBus::Events::CreateNewEditorEntity,
entityName.c_str());
m_testEntity = AzToolsFramework::GetEntityById(testEntityId);
ASSERT_TRUE(m_testEntity);
AZ::Component* thumbnailerComponent = nullptr;
AZ::ComponentDescriptorBus::EventResult(thumbnailerComponent, azrtti_typeid<AzToolsFramework::Thumbnailer::ThumbnailerComponent>(), &AZ::ComponentDescriptorBus::Events::CreateComponent);
ASSERT_TRUE(thumbnailerComponent);
if (m_testEntity->GetState() == AZ::Entity::State::Active)
{
m_testEntity->Deactivate();
}
ASSERT_TRUE(m_testEntity->AddComponent(thumbnailerComponent));
m_testEntity->Activate();
}
void TearDown() override
{
TraceBusRedirector::BusDisconnect();
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequestBus::Events::DestroyEditorEntity,
m_testEntity->GetId());
m_app.Stop();
}
AzToolsFramework::ToolsApplication m_app;
AZ::ComponentApplication::Descriptor m_descriptor;
AZ::Entity* m_testEntity = nullptr;
};
TEST_F(ThumbnailerTests, ThumbnailerComponent_RegisterUnregisterContext)
{
constexpr const char* contextName1 = "Context1";
constexpr const char* contextName2 = "Context2";
constexpr int thumbnailSize1 = 128;
constexpr int thumbnailSize2 = 256;
auto checkHasContext = [](const char* contextName)
{
bool hasContext = false;
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::BroadcastResult(hasContext, &AzToolsFramework::Thumbnailer::ThumbnailerRequests::HasContext, contextName);
return hasContext;
};
EXPECT_FALSE(checkHasContext(contextName1));
EXPECT_FALSE(checkHasContext(contextName2));
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName1, thumbnailSize1);
EXPECT_TRUE(checkHasContext(contextName1));
EXPECT_FALSE(checkHasContext(contextName2));
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName2, thumbnailSize2);
EXPECT_TRUE(checkHasContext(contextName1));
EXPECT_TRUE(checkHasContext(contextName2));
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::UnregisterContext, contextName1);
EXPECT_FALSE(checkHasContext(contextName1));
EXPECT_TRUE(checkHasContext(contextName2));
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::UnregisterContext, contextName2);
EXPECT_FALSE(checkHasContext(contextName1));
EXPECT_FALSE(checkHasContext(contextName2));
}
TEST_F(ThumbnailerTests, ThumbnailerComponent_Deactivate_ClearTumbnailContexts)
{
constexpr const char* contextName1 = "Context1";
constexpr const char* contextName2 = "Context2";
constexpr int thumbnailSize1 = 128;
constexpr int thumbnailSize2 = 256;
auto checkHasContext = [](const char* contextName)
{
bool hasContext = false;
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::BroadcastResult(hasContext, &AzToolsFramework::Thumbnailer::ThumbnailerRequests::HasContext, contextName);
return hasContext;
};
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName1, thumbnailSize1);
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName2, thumbnailSize2);
EXPECT_TRUE(checkHasContext(contextName1));
EXPECT_TRUE(checkHasContext(contextName2));
m_testEntity->Deactivate();
m_testEntity->Activate();
EXPECT_FALSE(checkHasContext(contextName1));
EXPECT_FALSE(checkHasContext(contextName2));
}
TEST_F(ThumbnailerTests, ThumbnailerComponent_RegisterContextTwice_Assert)
{
constexpr const char* contextName1 = "Context1";
constexpr int thumbnailSize1 = 128;
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName1, thumbnailSize1);
AZ_TEST_START_TRACE_SUPPRESSION;
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName1, thumbnailSize1);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
TEST_F(ThumbnailerTests, ThumbnailerComponent_UnregisterUnknownContext_Assert)
{
AZ_TEST_START_TRACE_SUPPRESSION;
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::UnregisterContext, "ContextDoesNotExist");
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
} // namespace UnitTest
@@ -0,0 +1,113 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzCore/UnitTest/UnitTest.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace AzToolsFramework
{
struct TransformTestEntityHierarchy
{
AZ::EntityId m_parentId;
AZ::EntityId m_childId;
AZ::EntityId m_grandchild1Id;
AZ::EntityId m_grandchild2Id;
};
class EditorTransformComponentTest
: public UnitTest::ToolsApplicationFixture
{
public:
static TransformTestEntityHierarchy BuildTestHierarchy()
{
TransformTestEntityHierarchy result;
result.m_parentId = UnitTest::CreateDefaultEditorEntity("Parent");
result.m_childId = UnitTest::CreateDefaultEditorEntity("Child");
result.m_grandchild1Id = UnitTest::CreateDefaultEditorEntity("Grandchild1");
result.m_grandchild2Id = UnitTest::CreateDefaultEditorEntity("Grandchild2");
// Set parent-child relationships
AZ::TransformBus::Event(result.m_childId, &AZ::TransformBus::Events::SetParent, result.m_parentId);
AZ::TransformBus::Event(result.m_grandchild1Id, &AZ::TransformBus::Events::SetParent, result.m_childId);
AZ::TransformBus::Event(result.m_grandchild2Id, &AZ::TransformBus::Events::SetParent, result.m_childId);
return result;
}
};
TEST_F(EditorTransformComponentTest, TransformTests_EntityHasParent_WorldScaleInheritsParentScale)
{
TransformTestEntityHierarchy hierarchy = BuildTestHierarchy();
// Set scale to parent entity
const AZ::Vector3 parentScale(2.0f, 1.0f, 3.0f);
AZ::TransformBus::Event(hierarchy.m_parentId, &AZ::TransformInterface::SetLocalScale, parentScale);
// Set scale to child entity
const AZ::Vector3 childScale(5.0f, 6.0f, 10.0f);
AZ::TransformBus::Event(hierarchy.m_childId, &AZ::TransformInterface::SetLocalScale, childScale);
const AZ::Vector3 expectedScale = childScale * parentScale;
AZ::Vector3 childWorldScale = AZ::Vector3::CreateOne();
AZ::TransformBus::EventResult(childWorldScale, hierarchy.m_childId, &AZ::TransformBus::Events::GetWorldScale);
EXPECT_THAT(childWorldScale, UnitTest::IsClose(expectedScale));
}
TEST_F(EditorTransformComponentTest, TransformTests_GetChildren_DirectChildrenMatchHierarchy)
{
TransformTestEntityHierarchy hierarchy = BuildTestHierarchy();
EntityIdList children;
AZ::TransformBus::EventResult(children, hierarchy.m_parentId, &AZ::TransformBus::Events::GetChildren);
EXPECT_EQ(children.size(), 1);
EXPECT_EQ(children[0], hierarchy.m_childId);
}
TEST_F(EditorTransformComponentTest, TransformTests_GetAllDescendants_AllDescendantsMatchHierarchy)
{
TransformTestEntityHierarchy hierarchy = BuildTestHierarchy();
EntityIdList descendants;
AZ::TransformBus::EventResult(descendants, hierarchy.m_parentId, &AZ::TransformBus::Events::GetAllDescendants);
// Order of descendants here and in other test cases depends on TransformHierarchyInformationBus
// Sorting it to get predictable order and be able to verify by index
std::sort(descendants.begin(), descendants.end());
EXPECT_EQ(descendants.size(), 3);
EXPECT_EQ(descendants[0], hierarchy.m_childId);
EXPECT_EQ(descendants[1], hierarchy.m_grandchild1Id);
EXPECT_EQ(descendants[2], hierarchy.m_grandchild2Id);
}
TEST_F(EditorTransformComponentTest, TransformTests_GetEntityAndAllDescendants_AllDescendantsMatchHierarchyAndResultIncludesParentEntity)
{
TransformTestEntityHierarchy hierarchy = BuildTestHierarchy();
EntityIdList entityAndDescendants;
AZ::TransformBus::EventResult(entityAndDescendants, hierarchy.m_parentId, &AZ::TransformBus::Events::GetEntityAndAllDescendants);
std::sort(entityAndDescendants.begin(), entityAndDescendants.end());
EXPECT_EQ(entityAndDescendants.size(), 4);
EXPECT_EQ(entityAndDescendants[0], hierarchy.m_parentId);
EXPECT_EQ(entityAndDescendants[1], hierarchy.m_childId);
EXPECT_EQ(entityAndDescendants[2], hierarchy.m_grandchild1Id);
EXPECT_EQ(entityAndDescendants[3], hierarchy.m_grandchild2Id);
}
} // namespace AzToolsFramework
@@ -0,0 +1,97 @@
/*
* 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 <AzCore/Serialization/SerializeContext.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx>
#include <AzToolsFramework/API/EntityPropertyEditorRequestsBus.h>
#include <AzToolsFramework/UI/PropertyEditor/EntityIdQLineEdit.h>
#include <QApplication>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace UnitTest
{
using namespace AZ;
using namespace AzToolsFramework;
// Test widget to store an EntityIdQLineEdit
class TestEntityIdParentWidget
: public QWidget
{
public:
explicit TestEntityIdParentWidget(QWidget* parent = nullptr)
: QWidget(nullptr)
{
AZ_UNUSED(parent);
// ensure TestWidget can intercept and filter any incoming events itself
installEventFilter(this);
m_testLineEdit = new EntityIdQLineEdit(this);
}
EntityIdQLineEdit* m_testLineEdit = nullptr;
};
class EntityIdQLineEditTests
: public ToolsApplicationFixture
{
};
TEST_F(EntityIdQLineEditTests, DoubleClickWontSelectInvalidEntity)
{
AZ::Entity* entity = aznew AZ::Entity();
ASSERT_TRUE(entity != nullptr);
entity->Init();
entity->Activate();
AZ::EntityId entityId = entity->GetId();
ASSERT_TRUE(entityId.IsValid());
TestEntityIdParentWidget* widget = new TestEntityIdParentWidget(nullptr);
ASSERT_TRUE(widget != nullptr);
ASSERT_TRUE(widget->m_testLineEdit != nullptr);
// Set a valid EntityId
widget->m_testLineEdit->setFocus();
widget->m_testLineEdit->SetEntityId(entityId, {});
// Simulate double click which will cause the EntityId to be set as selected
QTest::mouseDClick(widget->m_testLineEdit, Qt::LeftButton);
// If successful we expect the label's entity to be selected.
EntityIdList selectedEntities;
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
EXPECT_EQ(selectedEntities.size(), 1) << "Double clicking on an EntityIdQLabel should only select a single entity";
EXPECT_TRUE(selectedEntities[0] == entityId) << "The selected entity is not the one that was double clicked";
selectedEntities.clear();
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selectedEntities);
// Now set an invalid EntityId
widget->m_testLineEdit->SetEntityId(AZ::EntityId(), {});
// Simulate double clicking again, which should not trigger a selection change since the EntityId is invalid
QTest::mouseDClick(widget->m_testLineEdit, Qt::LeftButton);
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
EXPECT_TRUE(selectedEntities.empty()) << "Double clicking on an EntityIdQLabel with an invalid entity ID shouldn't change anything";
delete entity;
delete widget;
}
}
@@ -0,0 +1,294 @@
/*
* 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 <AzCore/Serialization/SerializeContext.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/ComponentMode/ComponentModeCollection.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx>
#include <AzToolsFramework/API/EntityPropertyEditorRequestsBus.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityComponent.h>
#include <AzToolsFramework/ViewportSelection/EditorDefaultSelection.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/std/sort.h>
#include <QApplication>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace UnitTest
{
using namespace AZ;
using namespace AzToolsFramework;
class EntityPropertyEditorTests
: public ComponentApplication
{
public:
void SetExecutableFolder(const char* path)
{
m_exeDirectory = path;
}
void SetSettingsRegistrySpecializations(SettingsRegistryInterface::Specializations& specializations) override
{
ComponentApplication::SetSettingsRegistrySpecializations(specializations);
specializations.Append("test");
specializations.Append("entitypropertyeditor");
}
};
TEST(EntityPropertyEditorTests, PrioritySort_NonTransformAsFirstItem_TransformMovesToTopRemainderUnchanged)
{
ComponentApplication app;
AZ::Entity::ComponentArrayType unorderedComponents;
AZ::Entity::ComponentArrayType orderedComponents;
ToolsApplication::Descriptor desc;
desc.m_useExistingAllocator = true;
desc.m_enableDrilling = false;
ToolsApplication::StartupParameters startupParams;
startupParams.m_allocator = &AZ::AllocatorInstance<AZ::SystemAllocator>::Get();
Entity* systemEntity = app.Create(desc, startupParams);
// Add more than 31 components, as we are testing the case where the sort fails when there are 32 or more items.
const int numFillerItems = 32;
for (int commentIndex = 0; commentIndex < numFillerItems; commentIndex++)
{
unorderedComponents.insert(unorderedComponents.begin(), systemEntity->CreateComponent(AZ::StreamerComponent::RTTI_Type()));
}
// Add a TransformComponent at the end which should be sorted to the beginning by the priority sort.
AZ::Component* transformComponent = systemEntity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
unorderedComponents.push_back(transformComponent);
//add an AssetDatabase component at the beginning which should end up as the second item once the TransformComponent pushes it down
AZ::Component* secondComponent = systemEntity->CreateComponent(AZ::AssetManagerComponent::RTTI_Type());
unorderedComponents.insert(unorderedComponents.begin(), secondComponent);
orderedComponents = unorderedComponents;
// When this sort happens, the transformComponent should move to the top, the AssetDatabase should move to second, the order of the others should be unaltered,
// merely moved to after the AssetDatabase.
EntityPropertyEditor::SortComponentsByPriority(orderedComponents);
// Check the component arrays are intact.
EXPECT_EQ(orderedComponents.size(), unorderedComponents.size());
EXPECT_GT(orderedComponents.size(), 2);
// Check the transform is now the first component.
EXPECT_EQ(orderedComponents[0], transformComponent);
// Check the AssetDatabase is now second.
EXPECT_EQ(orderedComponents[1], secondComponent);
// Check the order of the remaining items is preserved.
int firstUnsortedFillerIndex = 1;
int firstSortedFillerIndex = 2;
for (int index = 0; index < numFillerItems; index++)
{
EXPECT_EQ(orderedComponents[index + firstSortedFillerIndex], unorderedComponents[index + firstUnsortedFillerIndex]);
}
}
void OpenPinnedInspector(const AzToolsFramework::EntityIdList& entities, EntityPropertyEditor* editor)
{
if (editor)
{
AzToolsFramework::EntityIdSet entitiesSet(entities.begin(), entities.end());
editor->SetOverrideEntityIds(entitiesSet);
}
}
class EntityPropertyEditorRequestTest
: public ToolsApplicationFixture
{
void SetUpEditorFixtureImpl() override
{
m_editor = new EntityPropertyEditor();
m_editorActions.Connect();
m_entity1 = CreateDefaultEditorEntity("Entity1");
m_entity2 = CreateDefaultEditorEntity("Entity2");
m_entity3 = CreateDefaultEditorEntity("Entity3");
m_entity4 = CreateDefaultEditorEntity("Entity4");
}
void TearDownEditorFixtureImpl() override
{
m_editorActions.Disconnect();
delete m_editor;
}
public:
EntityPropertyEditor* m_editor;
TestEditorActions m_editorActions;
EntityIdList m_entityIds;
AZ::EntityId m_entity1;
AZ::EntityId m_entity2;
AZ::EntityId m_entity3;
AZ::EntityId m_entity4;
};
TEST_F(EntityPropertyEditorRequestTest, GetSelectedEntitiesReturnsEitherSelectedEntitiesOrPinnedEntities)
{
EntityIdList entityIds;
entityIds.insert(entityIds.begin(), { m_entity1, m_entity4 });
// Set entity1 and entity4 as selected
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, entityIds);
// Find the entities that are selected
EntityIdList selectedEntityIds;
AzToolsFramework::EntityPropertyEditorRequestBus::Broadcast(
&AzToolsFramework::EntityPropertyEditorRequestBus::Events::GetSelectedEntities, selectedEntityIds);
// Make sure the correct number of entities are returned
EXPECT_EQ(selectedEntityIds.size(), 2);
// Check they are the same entities as selected above
int found = 0;
for (auto& id : selectedEntityIds)
{
if (id == m_entity1)
{
found |= 1;
}
if (id == m_entity4)
{
found |= 8;
}
}
EXPECT_EQ(found, 9);
// Clear the selected entities
entityIds.clear();
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequests::SetSelectedEntities, entityIds);
// Open the pinned Inspector with a different set of entities
entityIds.insert(entityIds.begin(), { m_entity1, m_entity2, m_entity3 });
OpenPinnedInspector(entityIds, m_editor);
// Find the entities that are selected
AzToolsFramework::EntityPropertyEditorRequestBus::Broadcast(
&AzToolsFramework::EntityPropertyEditorRequestBus::Events::GetSelectedEntities, selectedEntityIds);
// Make sure the correct number of entities are returned
EXPECT_EQ(selectedEntityIds.size(), 3);
// Check they are the same entities as selected above
found = 0;
for (auto& id : selectedEntityIds)
{
if (id == m_entity1)
{
found |= 1;
}
if (id == m_entity2)
{
found |= 2;
}
if (id == m_entity3)
{
found |= 4;
}
}
EXPECT_EQ(found, 7);
}
class LevelEntityPropertyEditorRequestTest
: public ToolsApplicationFixture
, public AzToolsFramework::EditorRequestBus::Handler
{
void SetUpEditorFixtureImpl() override
{
// Create an EntityPropertyEditor initialized to be a Level Inspector
m_levelEditor = new EntityPropertyEditor(nullptr, {}, true);
m_levelEntity = CreateDefaultEditorEntity("LevelEntity");
// Level Inspector expects to have one override entity ID, which would normally be the root slice entity.
AzToolsFramework::EntityIdSet entities;
entities.insert(m_levelEntity);
m_levelEditor->SetOverrideEntityIds(entities);
m_editorActions.Connect();
// Connect to the EditorRequestBus so that we can intercept calls checking whether or not a level is currently open.
AzToolsFramework::EditorRequestBus::Handler::BusConnect();
}
void TearDownEditorFixtureImpl() override
{
AzToolsFramework::EditorRequestBus::Handler::BusDisconnect();
m_editorActions.Disconnect();
delete m_levelEditor;
}
// Mock out this call so that we can control whether or not the Level Inspector thinks a level is open.
bool IsLevelDocumentOpen() override { return m_levelOpen; }
// These are required by implementing the EditorRequestBus
void BrowseForAssets(AssetBrowser::AssetSelectionModel& /*selection*/) override {}
int GetIconTextureIdFromEntityIconPath([[maybe_unused]] const AZStd::string& entityIconPath) override { return 0; }
bool DisplayHelpersVisible() override { return false; }
public:
EntityPropertyEditor* m_levelEditor;
TestEditorActions m_editorActions;
AZ::EntityId m_levelEntity;
bool m_levelOpen = false;
};
TEST_F(LevelEntityPropertyEditorRequestTest, GetSelectedEntitiesForLevelInspectorWhenLevelIsNotLoaded)
{
m_levelOpen = false;
// Find the entities that are selected
EntityIdList selectedEntityIds;
AzToolsFramework::EntityPropertyEditorRequestBus::Broadcast(
&AzToolsFramework::EntityPropertyEditorRequestBus::Events::GetSelectedEntities, selectedEntityIds);
// Make sure the correct number of entities are returned
EXPECT_EQ(selectedEntityIds.size(), 0);
}
TEST_F(LevelEntityPropertyEditorRequestTest, GetSelectedEntitiesForLevelInspectorWhenLevelIsLoaded)
{
m_levelOpen = true;
// Find the entities that are selected
EntityIdList selectedEntityIds;
// Find the entities that are selected
AzToolsFramework::EntityPropertyEditorRequestBus::Broadcast(
&AzToolsFramework::EntityPropertyEditorRequestBus::Events::GetSelectedEntities, selectedEntityIds);
// Make sure the correct number of entities are returned
EXPECT_EQ(selectedEntityIds.size(), 1);
EXPECT_EQ(selectedEntityIds[0], m_levelEntity);
}
}
@@ -0,0 +1,541 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
using namespace AZ;
using namespace AzToolsFramework;
using namespace AzToolsFramework::UndoSystem;
namespace UnitTest
{
class SequencePointTest
: public URSequencePoint
{
public:
SequencePointTest(AZStd::string friendlyName, URCommandID id)
: URSequencePoint(friendlyName, id)
{}
bool Changed() const override { return true; }
void Redo() override { m_redoCalled = true; }
void Undo() override { m_undoCalled = true; }
using URSequencePoint::RemoveChild;
bool m_redoCalled = false;
bool m_undoCalled = false;
};
class DifferentTypeSequencePointTest
: public URSequencePoint
{
public:
AZ_RTTI(DifferentTypeSequencePointTest, "{D7A42B6F-DCF8-443F-B4F1-57731B1D3CB8}")
DifferentTypeSequencePointTest(AZStd::string friendlyName, URCommandID id)
: URSequencePoint(friendlyName, id)
{}
bool Changed() const override { return true; }
};
///////////////////////////////////////////////////////////////////////////
// URSequencePoint
///////////////////////////////////////////////////////////////////////////
TEST(URSequencePoint, Find_IdAndTypeNotPresent_ExpectNullptr)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
auto result = parent->Find(6, AZ::Uuid::Create());
EXPECT_FALSE(result);
}
TEST(URSequencePoint, Find_TypeNotPresent_ExpectNullptr)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
auto result = parent->Find(5, AZ::Uuid::Create());
EXPECT_FALSE(result);
}
TEST(URSequencePoint, Find_IdNotPresent_ExpectNullptr)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
DifferentTypeSequencePointTest* child_2 = aznew DifferentTypeSequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
auto result = parent->Find(6, AZ::Uuid("{D7A42B6F-DCF8-443F-B4F1-57731B1D3CB8}"));
EXPECT_FALSE(result);
}
TEST(URSequencePoint, Find_MatchIsDirectChild_IdFound)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
DifferentTypeSequencePointTest* child_2 = aznew DifferentTypeSequencePointTest("Child", static_cast<AZ::u64>(2));
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
auto result = parent->Find(2, AZ::AzTypeInfo<DifferentTypeSequencePointTest>::Uuid());
EXPECT_EQ(result, child_2);
}
TEST(URSequencePoint, Find_IdIsIndirectChild_IdFound)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
DifferentTypeSequencePointTest* child_2 = aznew DifferentTypeSequencePointTest("Child", static_cast<AZ::u64>(2));
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
DifferentTypeSequencePointTest* child_1_2 = aznew DifferentTypeSequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
auto result = parent->Find(5, AZ::AzTypeInfo<DifferentTypeSequencePointTest>::Uuid());
EXPECT_EQ(result, child_1_2);
}
TEST(URSequencePoint, RemoveChild)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_4 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_5 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_4->SetParent(parent);
child_5->SetParent(parent);
auto children = parent->GetChildren();
EXPECT_EQ(children.size(), 5) << "children were not added properly";
parent->RemoveChild(child_5);
children = parent->GetChildren();
EXPECT_EQ(children.size(), 4) << "child was not removed properly";
}
TEST(URSequencePoint, SetParent_NotChildOfParent)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child = aznew SequencePointTest("Child", 1);
child->SetParent(parent);
auto children = parent->GetChildren();
EXPECT_EQ(children.size(), 1) << "child was not added properly";
EXPECT_EQ(children[0]->GetName(), child->GetName()) << "child was not added properly";
}
TEST(URSequencePoint, SetParent_AlreadyChildOfParent)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_4 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_5 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_4->SetParent(parent);
child_5->SetParent(parent);
auto children = parent->GetChildren();
EXPECT_EQ(children.size(), 5) << "children were not added properly";
child_5->SetParent(parent);
children = parent->GetChildren();
EXPECT_EQ(children.size(), 5) << "the parent did not de-dupe its children";
bool childFound = false;
for (auto tempChild : children)
{
if (tempChild->GetName() == child_5->GetName())
{
childFound = true;
break;
}
}
EXPECT_TRUE(childFound);
}
TEST(URSequencePoint, SetParent_AlreadyChildOfDifferentParent)
{
SequencePointTest* parent_1 = aznew SequencePointTest("Parent", 0);
SequencePointTest* parent_2 = aznew SequencePointTest("Parent", 1);
SequencePointTest* child = aznew SequencePointTest("Child", 5);
child->SetParent(parent_1);
auto children = parent_1->GetChildren();
EXPECT_EQ(children.size(), 1) << "child was not added properly";
child->SetParent(parent_2);
children = parent_1->GetChildren();
EXPECT_EQ(children.size(), 0) << "the original parent did not remove the child";
children = parent_2->GetChildren();
EXPECT_EQ(children[0]->GetName(), child->GetName()) << "child was not added to new parent properly";
}
TEST(URSequencePoint, RunUndo_NoChildren_UndoIsCalled)
{
SequencePointTest* object = aznew SequencePointTest("Object", 0);
object->m_undoCalled = false;
object->RunUndo();
EXPECT_TRUE(object->m_undoCalled) << "Undo was not called on the object";
}
TEST(URSequencePoint, RunUndo_HasChildren_UndoIsCalled)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 4);
parent->m_undoCalled = false;
child_1->SetParent(parent);
child_1->m_undoCalled = false;
child_2->SetParent(parent);
child_2->m_undoCalled = false;
child_1_1->SetParent(child_1);
child_1_1->m_undoCalled = false;
child_1_2->SetParent(child_1);
child_1_2->m_undoCalled = false;
parent->RunUndo();
EXPECT_TRUE(parent->m_undoCalled) << "Undo was not called on the parent";
EXPECT_TRUE(child_1->m_undoCalled) << "Undo was not called on the child";
EXPECT_TRUE(child_2->m_undoCalled) << "Undo was not called on the child";
EXPECT_TRUE(child_1_1->m_undoCalled) << "Undo was not called on the grandchild";
EXPECT_TRUE(child_1_2->m_undoCalled) << "Undo was not called on the grandchild";
}
TEST(URSequencePoint, RunRedo_NoChildren_RedoIsCalled)
{
SequencePointTest* object = aznew SequencePointTest("Object", 0);
object->m_redoCalled = false;
object->RunRedo();
EXPECT_TRUE(object->m_redoCalled) << "Redo was not called on the object";
}
TEST(URSequencePoint, RunRedo_HasChildren_RedoIsCalled)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 4);
parent->m_redoCalled = false;
child_1->SetParent(parent);
child_1->m_redoCalled = false;
child_2->SetParent(parent);
child_2->m_redoCalled = false;
child_1_1->SetParent(child_1);
child_1_1->m_redoCalled = false;
child_1_2->SetParent(child_1);
child_1_2->m_redoCalled = false;
parent->RunRedo();
EXPECT_TRUE(parent->m_redoCalled) << "Redo was not called on the parent";
EXPECT_TRUE(child_1->m_redoCalled) << "Redo was not called on the child";
EXPECT_TRUE(child_2->m_redoCalled) << "Redo was not called on the child";
EXPECT_TRUE(child_1_1->m_redoCalled) << "Redo was not called on the grandchild";
EXPECT_TRUE(child_1_2->m_redoCalled) << "Redo was not called on the grandchild";
}
TEST(URSequencePoint, SetName)
{
AZStd::string test_1("Test Point");
AZStd::string test_2("A different Test Point");
SequencePointTest* testPoint = aznew SequencePointTest("Test Point", 0);
EXPECT_EQ(testPoint->GetName(), test_1);
testPoint->SetName("A different Test Point");
EXPECT_EQ(testPoint->GetName(), test_2);
}
TEST(URSequencePoint, HasRealChildren_NoChildren_ExpectFalse)
{
SequencePointTest* testPoint = aznew SequencePointTest("Test Point", 0);
bool result = testPoint->HasRealChildren();
EXPECT_FALSE(result);
}
TEST(URSequencePoint, HasRealChildren_AllChildrenAreFake_ExpectFalse)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
bool result = parent->HasRealChildren();
EXPECT_FALSE(result);
}
TEST(URSequencePoint, HasRealChildren_OneChildIsReal_ExpectTrue)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
DifferentTypeSequencePointTest* child_3 = aznew DifferentTypeSequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
bool result = parent->HasRealChildren();
EXPECT_TRUE(result);
}
TEST(URSequencePoint, HasRealChildren_OneGrandChildIsReal_ExpectTrue)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
DifferentTypeSequencePointTest* child_1_2 = aznew DifferentTypeSequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
bool result = parent->HasRealChildren();
EXPECT_TRUE(result);
}
///////////////////////////////////////////////////////////////////////////
// UndoStack
///////////////////////////////////////////////////////////////////////////
class UndoDestructorTest : public URSequencePoint
{
public:
UndoDestructorTest(bool* completedFlag)
: URSequencePoint("UndoDestructorTest", 0)
, m_completedFlag(completedFlag)
{
*m_completedFlag = false;
}
~UndoDestructorTest()
{
*m_completedFlag = true;
}
bool Changed() const override { return true; }
private:
bool* m_completedFlag;
};
TEST(UndoStack, UndoRedoMemory)
{
UndoStack undoStack(nullptr);
bool flag = false;
undoStack.Post(aznew UndoDestructorTest(&flag));
undoStack.Undo();
undoStack.Slice();
EXPECT_EQ(flag, true);
}
class UndoIntSetter : public URSequencePoint
{
public:
UndoIntSetter(int* value, int newValue)
: URSequencePoint("UndoIntSetter", 0)
, m_value(value)
, m_newValue(newValue)
, m_oldValue(*value)
{
Redo();
}
void Undo() override
{
*m_value = m_oldValue;
}
void Redo() override
{
*m_value = m_newValue;
}
bool Changed() const override { return true; }
private:
int* m_value;
int m_newValue;
int m_oldValue;
};
TEST(UndoStack, UndoRedoSequence)
{
UndoStack undoStack(nullptr);
int tracker = 0;
undoStack.Post(aznew UndoIntSetter(&tracker, 1));
EXPECT_EQ(tracker, 1);
undoStack.Undo();
EXPECT_EQ(tracker, 0);
undoStack.Redo();
EXPECT_EQ(tracker, 1);
undoStack.Undo();
EXPECT_EQ(tracker, 0);
undoStack.Redo();
EXPECT_EQ(tracker, 1);
undoStack.Post(aznew UndoIntSetter(&tracker, 100));
EXPECT_EQ(tracker, 100);
undoStack.Undo();
EXPECT_EQ(tracker, 1);
undoStack.Undo();
EXPECT_EQ(tracker, 0);
undoStack.Redo();
EXPECT_EQ(tracker, 1);
}
TEST(UndoStack, UndoRedoLotsOfUndos)
{
UndoStack undoStack(nullptr);
int tracker = 0;
const int numUndos = 1000;
for (int i = 0; i < 1000; i++)
{
undoStack.Post(aznew UndoIntSetter(&tracker, i + 1));
EXPECT_EQ(tracker, i + 1);
}
int counter = 0;
while (undoStack.CanUndo())
{
undoStack.Undo();
counter++;
}
EXPECT_EQ(numUndos, counter);
EXPECT_EQ(tracker, 0);
counter = 0;
while (undoStack.CanRedo())
{
undoStack.Redo();
counter++;
}
EXPECT_EQ(numUndos, counter);
EXPECT_EQ(tracker, numUndos);
}
}
@@ -0,0 +1,70 @@
/*
* 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 <AzCore/Math/Transform.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/ViewportUi/ViewportUiDisplay.h>
namespace UnitTest
{
using Cluster = AzToolsFramework::ViewportUi::Internal::Cluster;
using ButtonId = AzToolsFramework::ViewportUi::ButtonId;
TEST(ClusterTest, AddButtonAddsButtonToClusterAndReturnsId)
{
auto cluster = AZStd::make_unique<Cluster>();
auto buttonId = cluster->AddButton("");
auto button = cluster->GetButton(buttonId);
EXPECT_TRUE(button != nullptr);
}
TEST(ClusterTest, SetHighlightedButtonChangesButtonStateToSelected)
{
auto cluster = AZStd::make_unique<Cluster>();
auto buttonId = cluster->AddButton("");
// check button is not highlighted by default
auto button = cluster->GetButton(buttonId);
EXPECT_FALSE(button->m_state == AzToolsFramework::ViewportUi::Internal::Button::State::Selected);
cluster->SetHighlightedButton(buttonId);
EXPECT_TRUE(button->m_state == AzToolsFramework::ViewportUi::Internal::Button::State::Selected);
}
TEST(ClusterTest, ConnectEventHandlerConnectsHandlerToButtonTriggeredEvent)
{
auto cluster = AZStd::make_unique<Cluster>();
auto buttonId = cluster->AddButton("");
// create a handler which will be triggered by the cluster
bool handlerTriggered = false;
auto testButtonId = ButtonId(buttonId);
AZ::Event<ButtonId>::Handler handler(
[&handlerTriggered, testButtonId](ButtonId buttonId)
{
if (buttonId == testButtonId)
{
handlerTriggered = true;
}
});
cluster->ConnectEventHandler(handler);
cluster->PressButton(buttonId);
EXPECT_TRUE(handlerTriggered);
}
} // namespace UnitTest
@@ -0,0 +1,231 @@
/*
* 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 <AzCore/Math/Matrix3x4.h>
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/Math/Matrix4x4.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/Viewport/ViewportTypes.h>
namespace UnitTest
{
// transform a point from screen space to world space, and then from world space back to screen space
AzFramework::ScreenPoint ScreenToWorldToScreen(
const AzFramework::ScreenPoint& screenPoint, const AzFramework::CameraState& cameraState)
{
const auto worldResult = AzFramework::ScreenToWorld(screenPoint, cameraState);
return AzFramework::WorldToScreen(worldResult, cameraState);
}
TEST(ViewportScreen, WorldToScreenAndScreenToWorldReturnsTheSameValueIdentityCameraOffsetFromOrigin)
{
using AzFramework::ScreenPoint;
const auto screenDimensions = AZ::Vector2(800.0f, 600.0f);
const auto cameraPosition = AZ::Vector3::CreateAxisY(-10.0f);
// note: nearClip is 0.1 - the world space value returned will be aligned to the near clip
// plane of the camera so use that to confirm the mapping to/from is correct
const auto cameraState = AzFramework::CreateIdentityDefaultCamera(cameraPosition, screenDimensions);
{
const auto expectedScreenPoint = ScreenPoint{600, 450};
const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState);
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
}
{
const auto expectedScreenPoint = ScreenPoint{400, 300};
const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState);
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
}
{
const auto expectedScreenPoint = ScreenPoint{0, 0};
const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState);
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
}
{
const auto expectedScreenPoint = ScreenPoint{800, 600};
const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState);
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
}
}
TEST(ViewportScreen, WorldToScreenAndScreenToWorldReturnsTheSameValueOrientatedCamera)
{
using AzFramework::ScreenPoint;
const auto screenDimensions = AZ::Vector2(1024.0f, 768.0f);
const auto cameraTransform =
AZ::Transform::CreateRotationX(AZ::DegToRad(45.0f)) * AZ::Transform::CreateRotationZ(AZ::DegToRad(90.0f));
const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions);
const auto expectedScreenPoint = ScreenPoint{200, 300};
const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState);
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
}
TEST(ViewportScreen, ScreenToWorldReturnsPositionOnNearClipPlaneInWorldSpace)
{
using AzFramework::ScreenPoint;
const auto screenDimensions = AZ::Vector2(800.0f, 600.0f);
const auto cameraTransform = AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 0.0f, 0.0f)) *
AZ::Transform::CreateRotationZ(AZ::DegToRad(-90.0f));
const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions);
const auto worldResult = AzFramework::ScreenToWorld(ScreenPoint{400, 300}, cameraState);
EXPECT_THAT(worldResult, IsClose(AZ::Vector3(10.1f, 0.0f, 0.0f)));
}
TEST(ViewportScreen, SubstractingScreenPointGivesScreenVector)
{
using AzFramework::ScreenPoint;
using AzFramework::ScreenVector;
const ScreenVector screenVector = ScreenPoint{100, 200} - ScreenPoint{10, 20};
EXPECT_EQ(screenVector, ScreenVector(90, 180));
}
TEST(ViewportScreen, AddingScreenPointAndScreenVectorGivesScreenPoint)
{
using AzFramework::ScreenPoint;
using AzFramework::ScreenVector;
const ScreenPoint screenPoint = ScreenPoint{100, 200} + ScreenVector{50, 25};
EXPECT_EQ(screenPoint, ScreenPoint(150, 225));
}
TEST(ViewportScreen, SubtractingScreenPointAndScreenVectorGivesScreenPoint)
{
using AzFramework::ScreenPoint;
using AzFramework::ScreenVector;
const ScreenPoint screenPoint = ScreenPoint{120, 200} - ScreenVector{50, 20};
EXPECT_EQ(screenPoint, ScreenPoint(70, 180));
}
TEST(ViewportScreen, AddingScreenVectorGivesScreenVector)
{
using AzFramework::ScreenPoint;
using AzFramework::ScreenVector;
const ScreenVector screenVector = ScreenVector{100, 200} + ScreenVector{50, 25};
EXPECT_EQ(screenVector, ScreenVector(150, 225));
}
TEST(ViewportScreen, SubtractingScreenVectorGivesScreenVector)
{
using AzFramework::ScreenPoint;
using AzFramework::ScreenVector;
const ScreenVector screenVector = ScreenVector{100, 200} - ScreenVector{50, 25};
EXPECT_EQ(screenVector, ScreenVector(50, 175));
}
TEST(ViewportScreen, ScreenPointAndScreenVectorConvertToVector2)
{
using AzFramework::ScreenPoint;
using AzFramework::ScreenVector;
const ScreenPoint screenPoint = ScreenPoint{100, 200};
const ScreenVector screenVector = ScreenVector{50, 25};
const AZ::Vector2 fromScreenPoint = AzFramework::Vector2FromScreenPoint(screenPoint);
const AZ::Vector2 fromScreenVector = AzFramework::Vector2FromScreenVector(screenVector);
EXPECT_THAT(fromScreenPoint, IsClose(AZ::Vector2(100.0f, 200.0f)));
EXPECT_THAT(fromScreenVector, IsClose(AZ::Vector2(50.0f, 25.0f)));
}
TEST(ViewportScreen, ScreenVectorPlusEqualsCanBeCombined)
{
using AzFramework::ScreenPoint;
using AzFramework::ScreenVector;
ScreenVector screenVector1 = ScreenVector(50, 175);
ScreenVector screenVector2 = ScreenVector(2, 4);
ScreenVector screenVector3 = ScreenVector(3, 1);
((screenVector1 += screenVector2) += screenVector3);
EXPECT_EQ(screenVector1, ScreenVector(55, 180));
}
TEST(ViewportScreen, ScreenVectorMinusEqualsCanBeCombined)
{
using AzFramework::ScreenPoint;
using AzFramework::ScreenVector;
ScreenVector screenVector1 = ScreenVector(50, 175);
ScreenVector screenVector2 = ScreenVector(2, 4);
ScreenVector screenVector3 = ScreenVector(3, 1);
((screenVector1 -= screenVector2) -= screenVector3);
EXPECT_EQ(screenVector1, ScreenVector(45, 170));
}
TEST(ViewportScreen, ScreenPointPlusEqualsScreenVectorCanBeCombined)
{
using AzFramework::ScreenPoint;
using AzFramework::ScreenVector;
ScreenPoint screenPoint = ScreenPoint(50, 175);
ScreenVector screenVector2 = ScreenVector(2, 4);
ScreenVector screenVector3 = ScreenVector(3, 1);
((screenPoint += screenVector2) += screenVector3);
EXPECT_EQ(screenPoint, ScreenPoint(55, 180));
}
TEST(ViewportScreen, ScreenPointMinusEqualsScreenVectorCanBeCombined)
{
using AzFramework::ScreenPoint;
using AzFramework::ScreenVector;
ScreenPoint screenPoint = ScreenPoint(50, 175);
ScreenVector screenVector2 = ScreenVector(2, 4);
ScreenVector screenVector3 = ScreenVector(3, 1);
((screenPoint -= screenVector2) -= screenVector3);
EXPECT_EQ(screenPoint, ScreenPoint(45, 170));
}
TEST(ViewportScreen, CanGetCameraTransformFromCameraViewAndBack)
{
const auto screenDimensions = AZ::Vector2(1024.0f, 768.0f);
const auto transform = AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisZ(5.0f)) *
AZ::Transform::CreateRotationX(AZ::DegToRad(45.0f)) * AZ::Transform::CreateRotationZ(AZ::DegToRad(90.0f));
const auto cameraState = AzFramework::CreateDefaultCamera(transform, screenDimensions);
const auto cameraTransform = AzFramework::CameraTransform(cameraState);
const auto cameraView = AzFramework::CameraView(cameraState);
const auto cameraTransformFromView = AzFramework::CameraTransformFromCameraView(cameraView);
const auto cameraViewFromTransform = AzFramework::CameraViewFromCameraTransform(cameraTransform);
EXPECT_THAT(cameraTransform, IsClose(cameraTransformFromView));
EXPECT_THAT(cameraView, IsClose(cameraViewFromTransform));
}
} // namespace UnitTest
@@ -0,0 +1,123 @@
/*
* 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 <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/ViewportUi/Cluster.h>
#include <AzToolsFramework/ViewportUi/ViewportUiCluster.h>
#include <QAction>
#include <QApplication>
#include <QKeyEvent>
#include <QWidget>
namespace UnitTest
{
using ViewportUiCluster = AzToolsFramework::ViewportUi::Internal::ViewportUiCluster;
using Cluster = AzToolsFramework::ViewportUi::Internal::Cluster;
using Button = AzToolsFramework::ViewportUi::Internal::Button;
using ButtonId = AzToolsFramework::ViewportUi::ButtonId;
TEST(ViewportUiCluster, RegisterButtonIncreasesClusterHeight)
{
auto clusterInfo = AZStd::make_shared<Cluster>();
ViewportUiCluster viewportUiCluster(clusterInfo);
viewportUiCluster.resize(viewportUiCluster.minimumSizeHint());
// need to initialize cluster with a single button or size will be invalid
viewportUiCluster.RegisterButton(AZStd::make_unique<Button>("", ButtonId(1)).get());
QSize initialSize = viewportUiCluster.size();
// add a second button to increase the size
viewportUiCluster.RegisterButton(AZStd::make_unique<Button>("", ButtonId(2)).get());
QSize finalSize = viewportUiCluster.size();
bool sizeIncrease = initialSize.width() == finalSize.width() && initialSize.height() < finalSize.height();
EXPECT_TRUE(sizeIncrease);
}
TEST(ViewportUiCluster, RemoveClusterButtonDecreasesClusterHeight)
{
auto clusterInfo = AZStd::make_shared<Cluster>();
ViewportUiCluster viewportUiCluster(clusterInfo);
viewportUiCluster.resize(viewportUiCluster.minimumSizeHint());
// need to initialize cluster with a single button or size will be invalid
viewportUiCluster.RegisterButton(AZStd::make_unique<Button>("", ButtonId(1)).get());
// add a second button to increase the size
viewportUiCluster.RegisterButton(AZStd::make_unique<Button>("", ButtonId(2)).get());
QSize initialSize = viewportUiCluster.size();
// remove the second button
viewportUiCluster.RemoveButton(ButtonId(1));
QSize finalSize = viewportUiCluster.size();
bool sizeDecrease = initialSize.width() == finalSize.width() && initialSize.height() > finalSize.height();
EXPECT_TRUE(sizeDecrease);
}
TEST(ViewportUiCluster, UpdateChangesActiveButton)
{
auto clusterInfo = AZStd::make_shared<Cluster>();
ViewportUiCluster viewportUiCluster(clusterInfo);
// register a button to the cluster
auto button = AZStd::make_unique<Button>("", ButtonId(1));
viewportUiCluster.RegisterButton(button.get());
// get the action corresponding to the button
auto widgetCallbacks = viewportUiCluster.GetWidgetCallbacks();
auto action = static_cast<QAction*>(widgetCallbacks.GetWidgets()[0].data());
// verify action is not checked by default
EXPECT_FALSE(action->isChecked());
// set the button to selected and update the ViewportUiCluster to sync
button->m_state = AzToolsFramework::ViewportUi::Internal::Button::State::Selected;
viewportUiCluster.Update();
EXPECT_TRUE(action->isChecked());
}
TEST(ViewportUiCluster, TriggeringActionTriggersClusterEventForButton)
{
auto clusterInfo = AZStd::make_shared<Cluster>();
ViewportUiCluster viewportUiCluster(clusterInfo);
// create a handler which will be triggered by the button
bool handlerTriggered = false;
auto testButtonId = ButtonId(1);
AZ::Event<ButtonId>::Handler handler(
[&handlerTriggered, testButtonId](ButtonId buttonId)
{
if (buttonId == testButtonId)
{
handlerTriggered = true;
}
});
clusterInfo->ConnectEventHandler(handler);
// register the button
auto button = AZStd::make_unique<Button>("", testButtonId);
viewportUiCluster.RegisterButton(button.get());
// trigger the action, which should activate the handler
auto widgetCallbacks = viewportUiCluster.GetWidgetCallbacks();
auto action = static_cast<QAction*>(widgetCallbacks.GetWidgets()[0].data());
action->trigger();
EXPECT_TRUE(handlerTriggered);
}
} // namespace UnitTest
@@ -0,0 +1,157 @@
/*
* 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 <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/ViewportUi/ViewportUiDisplay.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace UnitTest
{
using ViewportUiDisplay = AzToolsFramework::ViewportUi::Internal::ViewportUiDisplay;
using ViewportUiElementId = AzToolsFramework::ViewportUi::ViewportUiElementId;
using Cluster = AzToolsFramework::ViewportUi::Internal::Cluster;
// sets up a parent widget and render overlay to attach the Viewport UI to
// as well as a cluster with one button
class ViewportUiDisplayTestFixture : public ::testing::Test
{
public:
ViewportUiDisplayTestFixture() = default;
void SetUp()
{
m_cluster = AZStd::make_shared<Cluster>();
m_cluster->AddButton("");
m_parentWidget = new QWidget();
m_mockRenderOverlay = new QWidget();
}
void TearDown()
{
m_cluster.reset();
delete m_parentWidget;
delete m_mockRenderOverlay;
}
QWidget* m_parentWidget = nullptr;
QWidget* m_mockRenderOverlay = nullptr;
AZStd::shared_ptr<Cluster> m_cluster = nullptr;
};
TEST_F(ViewportUiDisplayTestFixture, ViewportUiInitializationReturnsProperlyParentedWidgets)
{
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
EXPECT_TRUE(viewportUi.GetUiMainWindow()->parent() == m_parentWidget);
EXPECT_TRUE(viewportUi.GetUiOverlay()->parent() == m_parentWidget);
}
TEST_F(ViewportUiDisplayTestFixture, InitializeUiOverlaySetsViewportUiVisibilityToFalse)
{
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.InitializeUiOverlay();
EXPECT_FALSE(viewportUi.GetUiMainWindow()->isVisible());
EXPECT_FALSE(viewportUi.GetUiOverlay()->isVisible());
}
TEST_F(ViewportUiDisplayTestFixture, RemoveViewportUiElementRemovesElementFromViewportUi)
{
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.AddCluster(m_cluster);
auto widget = viewportUi.GetViewportUiElement(m_cluster->GetViewportUiElementId());
EXPECT_TRUE(widget.get() != nullptr);
viewportUi.RemoveViewportUiElement(m_cluster->GetViewportUiElementId());
widget = viewportUi.GetViewportUiElement(m_cluster->GetViewportUiElementId());
EXPECT_TRUE(widget.get() == nullptr);
}
TEST_F(ViewportUiDisplayTestFixture, ShowViewportUiElementSetsWidgetVisibilityToTrue)
{
m_mockRenderOverlay->setVisible(true);
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.InitializeUiOverlay();
viewportUi.AddCluster(m_cluster);
viewportUi.Update();
viewportUi.ShowViewportUiElement(m_cluster->GetViewportUiElementId());
EXPECT_TRUE(viewportUi.IsViewportUiElementVisible(m_cluster->GetViewportUiElementId()));
}
TEST_F(ViewportUiDisplayTestFixture, HideViewportUiElementSetsWidgetVisibilityToFalse)
{
m_mockRenderOverlay->setVisible(true);
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.InitializeUiOverlay();
viewportUi.AddCluster(m_cluster);
viewportUi.HideViewportUiElement(m_cluster->GetViewportUiElementId());
EXPECT_FALSE(viewportUi.IsViewportUiElementVisible(m_cluster->GetViewportUiElementId()));
}
TEST_F(ViewportUiDisplayTestFixture, UpdateUiOverlayGeometryChangesGeometryToMatchViewportUiElements)
{
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.InitializeUiOverlay();
viewportUi.AddCluster(m_cluster);
viewportUi.Update();
auto widget = viewportUi.GetViewportUiElement(m_cluster->GetViewportUiElementId());
EXPECT_EQ(viewportUi.GetUiMainWindow()->mask(), widget->geometry());
}
TEST_F(ViewportUiDisplayTestFixture, UpdateSetsViewportUiInvisibleIfNoChildGeometry)
{
m_mockRenderOverlay->setVisible(true);
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.InitializeUiOverlay();
auto cluster = AZStd::make_shared<Cluster>();
cluster->AddButton("");
viewportUi.AddCluster(cluster);
viewportUi.Update();
EXPECT_TRUE(viewportUi.GetUiMainWindow()->isVisible());
viewportUi.RemoveViewportUiElement(cluster->GetViewportUiElementId());
viewportUi.Update();
EXPECT_FALSE(viewportUi.GetUiMainWindow()->isVisible());
}
TEST_F(ViewportUiDisplayTestFixture, UpdateSetsUiDimensionsToMatchRenderViewport)
{
auto geometry = QRect(25, 50, 200, 100);
m_mockRenderOverlay->setGeometry(geometry);
m_mockRenderOverlay->setVisible(true);
ViewportUiDisplay viewportUi(m_parentWidget, m_mockRenderOverlay);
viewportUi.InitializeUiOverlay();
viewportUi.Update();
EXPECT_EQ(viewportUi.GetUiOverlay()->height(), m_mockRenderOverlay->height());
EXPECT_EQ(viewportUi.GetUiOverlay()->width(), m_mockRenderOverlay->width());
}
} // namespace UnitTest
@@ -0,0 +1,191 @@
/*
* 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 <AzCore/Math/Transform.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/ViewportUi/ViewportUiManager.h>
namespace UnitTest
{
using ViewportUiDisplay = AzToolsFramework::ViewportUi::Internal::ViewportUiDisplay;
using ViewportUiElementId = AzToolsFramework::ViewportUi::ViewportUiElementId;
using Cluster = AzToolsFramework::ViewportUi::Internal::Cluster;
using ButtonId = AzToolsFramework::ViewportUi::ButtonId;
// child class of ViewportUiManager which exposes the protected cluster and viewport display
class ViewportUiManagerTestable : public AzToolsFramework::ViewportUi::ViewportUiManager
{
public:
ViewportUiManagerTestable() = default;
~ViewportUiManagerTestable() = default;
const AZStd::unordered_map<AzToolsFramework::ViewportUi::ClusterId, AZStd::shared_ptr<Cluster>>& GetClusterMap()
{
return m_clusters;
}
ViewportUiDisplay* GetViewportUiDisplay()
{
return m_viewportUi.get();
}
};
class ViewportManagerWrapper
{
public:
void Create()
{
m_viewportManager = AZStd::make_unique<ViewportUiManagerTestable>();
m_viewportManager->ConnectViewportUiBus(AzToolsFramework::ViewportUi::DefaultViewportId);
m_mockRenderOverlay = AZStd::make_unique<QWidget>();
m_parentWidget = AZStd::make_unique<QWidget>();
m_viewportManager->InitializeViewportUi(m_parentWidget.get(), m_mockRenderOverlay.get());
}
void Destroy()
{
m_viewportManager->DisconnectViewportUiBus();
m_viewportManager.reset();
m_mockRenderOverlay.reset();
m_parentWidget.reset();
}
ViewportUiManagerTestable* GetViewportManager()
{
return m_viewportManager.get();
}
QWidget* GetMockRenderOverlay()
{
return m_mockRenderOverlay.get();
}
private:
AZStd::unique_ptr<ViewportUiManagerTestable> m_viewportManager;
AZStd::unique_ptr<QWidget> m_parentWidget;
AZStd::unique_ptr<QWidget> m_mockRenderOverlay;
};
// sets up a parent widget and render overlay to attach the Viewport UI to
// as well as a cluster with one button
class ViewportUiManagerTestFixture : public ::testing::Test
{
public:
ViewportUiManagerTestFixture() = default;
ViewportManagerWrapper m_viewportManagerWrapper;
void SetUp()
{
m_viewportManagerWrapper.Create();
}
void TearDown()
{
m_viewportManagerWrapper.Destroy();
}
};
TEST_F(ViewportUiManagerTestFixture, CreateClusterAddsNewClusterAndReturnsId)
{
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto clusterEntry = m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().find(clusterId);
EXPECT_TRUE(clusterEntry != m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().end());
EXPECT_TRUE(clusterEntry->second.get() != nullptr);
}
TEST_F(ViewportUiManagerTestFixture, CreateClusterButtonAddsNewButtonAndReturnsId)
{
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto buttonId = m_viewportManagerWrapper.GetViewportManager()->CreateClusterButton(clusterId, "");
auto clusterEntry = m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().find(clusterId);
EXPECT_TRUE(clusterEntry->second->GetButton(buttonId) != nullptr);
}
TEST_F(ViewportUiManagerTestFixture, SetClusterActiveButtonSetsButtonStateToActive)
{
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto buttonId = m_viewportManagerWrapper.GetViewportManager()->CreateClusterButton(clusterId, "");
auto clusterEntry = m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().find(clusterId);
auto button = clusterEntry->second->GetButton(buttonId);
m_viewportManagerWrapper.GetViewportManager()->SetClusterActiveButton(clusterId, buttonId);
EXPECT_TRUE(button->m_state == AzToolsFramework::ViewportUi::Internal::Button::State::Selected);
}
TEST_F(ViewportUiManagerTestFixture, RegisterClusterEventHandlerConnectsHandlerToClusterEvent)
{
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto buttonId = m_viewportManagerWrapper.GetViewportManager()->CreateClusterButton(clusterId, "");
// create a handler which will be triggered by the cluster
bool handlerTriggered = false;
auto testButtonId = ButtonId(buttonId);
AZ::Event<ButtonId>::Handler handler(
[&handlerTriggered, testButtonId](ButtonId buttonId)
{
if (buttonId == testButtonId)
{
handlerTriggered = true;
}
});
auto clusterEntry = m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().find(clusterId);
auto button = clusterEntry->second->GetButton(buttonId);
// trigger the cluster
m_viewportManagerWrapper.GetViewportManager()->RegisterClusterEventHandler(clusterId, handler);
clusterEntry->second->PressButton(buttonId);
EXPECT_TRUE(handlerTriggered);
}
TEST_F(ViewportUiManagerTestFixture, RemoveClusterRemovesClusterFromViewportUi)
{
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
m_viewportManagerWrapper.GetViewportManager()->RemoveCluster(clusterId);
auto clusterEntry = m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().find(clusterId);
EXPECT_TRUE(clusterEntry == m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().end());
}
TEST_F(ViewportUiManagerTestFixture, SetClusterVisibleChangesClusterVisibility)
{
m_viewportManagerWrapper.GetMockRenderOverlay()->setVisible(true);
auto clusterId = m_viewportManagerWrapper.GetViewportManager()->CreateCluster();
auto buttonId = m_viewportManagerWrapper.GetViewportManager()->CreateClusterButton(clusterId, "");
m_viewportManagerWrapper.GetViewportManager()->Update();
m_viewportManagerWrapper.GetViewportManager()->SetClusterVisible(clusterId, false);
auto cluster = m_viewportManagerWrapper.GetViewportManager()->GetClusterMap().find(clusterId)->second;
bool visible =
m_viewportManagerWrapper.GetViewportManager()->GetViewportUiDisplay()->IsViewportUiElementVisible(cluster->GetViewportUiElementId());
EXPECT_FALSE(visible);
m_viewportManagerWrapper.GetViewportManager()->SetClusterVisible(clusterId, true);
visible =
m_viewportManagerWrapper.GetViewportManager()->GetViewportUiDisplay()->IsViewportUiElementVisible(cluster->GetViewportUiElementId());
EXPECT_TRUE(visible);
}
} // namespace UnitTest
@@ -0,0 +1,164 @@
/*
* 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 <AzCore/Math/Transform.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/ViewportUi/ViewportUiWidgetCallbacks.h>
#include <QWidget>
namespace UnitTest
{
class ViewportUiWidgetCallbacksTest : public AzToolsFramework::ViewportUi::Internal::ViewportUiWidgetCallbacks
{
public:
AZStd::vector<QPointer<QObject>>& GetWidgets()
{
return m_widgets;
}
AZStd::unordered_map<QObject*, AZStd::function<void(QPointer<QObject>)>>& GetUpdateCallbacks()
{
return m_updateCallbacks;
}
};
TEST(ViewportUiWidgetCallbacks, AddWidgetAddsToInternalVector)
{
ViewportUiWidgetCallbacksTest testWidgetManager;
EXPECT_EQ(testWidgetManager.GetWidgets().size(), 0);
QObject* mockObject = new QWidget();
testWidgetManager.AddWidget(mockObject);
EXPECT_EQ(testWidgetManager.GetWidgets().size(), 1);
EXPECT_EQ(testWidgetManager.GetWidgets()[0], mockObject);
}
TEST(ViewportUiWidgetCallbacks, AddWidgetDoesNotAddIfWidgetIsNull)
{
ViewportUiWidgetCallbacksTest testWidgetManager;
EXPECT_EQ(testWidgetManager.GetWidgets().size(), 0);
QObject* mockObject = nullptr;
testWidgetManager.AddWidget(mockObject);
EXPECT_EQ(testWidgetManager.GetWidgets().size(), 0);
}
TEST(ViewportUiWidgetCallbacks, RemoveWidgetRemovesFromInternalVector)
{
ViewportUiWidgetCallbacksTest testWidgetManager;
QObject* mockObject = new QWidget();
testWidgetManager.AddWidget(mockObject);
EXPECT_EQ(testWidgetManager.GetWidgets().size(), 1);
testWidgetManager.RemoveWidget(mockObject);
EXPECT_EQ(testWidgetManager.GetWidgets().size(), 0);
}
TEST(ViewportUiWidgetCallbacks, RegisterUpdateCallbackStoresCallbackFunction)
{
ViewportUiWidgetCallbacksTest testWidgetManager;
QObject* mockObject = new QWidget();
testWidgetManager.AddWidget(mockObject);
EXPECT_EQ(testWidgetManager.GetUpdateCallbacks().size(), 0);
auto mockFn = []([[maybe_unused]] QObject* object)
{
return;
};
testWidgetManager.RegisterUpdateCallback(mockObject, mockFn);
EXPECT_EQ(testWidgetManager.GetUpdateCallbacks().size(), 1);
}
class ViewportUiWidgetAssertFixture
: public ::testing::Test
, UnitTest::TraceBusRedirector
{
public:
void SetUp() override
{
AZ::Debug::TraceMessageBus::Handler::BusConnect();
}
void TearDown() override
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
}
};
TEST_F(ViewportUiWidgetAssertFixture, RegisterUpdateCallbackDoesNotRegisterFunctionForNotAddedObject)
{
ViewportUiWidgetCallbacksTest testWidgetManager;
QObject* mockObject = new QWidget();
auto mockFn = []([[maybe_unused]] QObject* object)
{
return;
};
AZ_TEST_START_TRACE_SUPPRESSION;
testWidgetManager.RegisterUpdateCallback(mockObject, mockFn);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
EXPECT_EQ(testWidgetManager.GetUpdateCallbacks().size(), 0);
}
TEST(ViewportUiWidgetCallbacks, UpdateCallsCallbackFunction)
{
ViewportUiWidgetCallbacksTest testWidgetManager;
QWidget* mockObject = new QWidget();
mockObject->setVisible(true);
testWidgetManager.AddWidget(mockObject);
auto mockFn = [](QObject* object)
{
static_cast<QWidget*>(object)->setVisible(false);
};
testWidgetManager.RegisterUpdateCallback(mockObject, mockFn);
testWidgetManager.Update();
EXPECT_FALSE(mockObject->isVisible());
}
TEST(ViewportUiWidgetCallbacks, UpdateRemovesDeletedObjects)
{
ViewportUiWidgetCallbacksTest testWidgetManager;
QWidget* mockObject = new QWidget();
mockObject->setVisible(true);
testWidgetManager.AddWidget(mockObject);
auto mockFn = []([[maybe_unused]] QObject* object)
{
return;
};
testWidgetManager.RegisterUpdateCallback(mockObject, mockFn);
EXPECT_EQ(testWidgetManager.GetWidgets().size(), 1);
EXPECT_EQ(testWidgetManager.GetUpdateCallbacks().size(), 1);
delete mockObject;
testWidgetManager.Update();
EXPECT_EQ(testWidgetManager.GetWidgets().size(), 0);
}
} // namespace UnitTest
@@ -0,0 +1,303 @@
/*
* 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 <AzCore/Component/TransformBus.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <AzFramework/Visibility/EntityBoundsUnionBus.h>
#include <AzFramework/Visibility/EntityVisibilityQuery.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace UnitTest
{
static auto ScreenDimensions = AZ::Vector2(1280.0f, 720.0f);
class EditorVisibilityFixture : public ToolsApplicationFixture
{
public:
void SetUpEditorFixtureImpl() override {}
void CreateEditorEntities(const size_t entityCount)
{
std::generate_n(
AZStd::back_inserter(m_editorEntityIds), entityCount,
[number = 0]() mutable
{
return CreateDefaultEditorEntity(AZStd::string::format("Entity %d", number++).c_str());
});
}
void SetupRowOfEntities(const AZ::Vector3& worldStartPosition, const AZ::Vector3& worldStepVector)
{
for (size_t entityIndex = 0; entityIndex < m_editorEntityIds.size(); ++entityIndex)
{
AZ::TransformBus::Event(
m_editorEntityIds[entityIndex], &AZ::TransformBus::Events::SetWorldTranslation,
worldStartPosition + worldStepVector * aznumeric_cast<float>(entityIndex));
}
}
AZStd::vector<AZ::EntityId> m_editorEntityIds;
};
TEST_F(EditorVisibilityFixture, VisibilityQueryReturnsEntitiesInFrustumWithNoOrientation)
{
using ::testing::UnorderedElementsAreArray;
constexpr size_t EditorEntityCount = 21;
constexpr size_t BeginVisibleEntityRangeOffset = 7;
constexpr size_t EndVisibleEntityRangeOffset = 14;
// setup row of editor entities
CreateEditorEntities(EditorEntityCount);
SetupRowOfEntities(AZ::Vector3::CreateAxisX(-20.0f), AZ::Vector3::CreateAxisX(2.0f));
// request the entity union bounds system to update
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
// create default camera looking down the negative y-axis moved just back from the origin
AzFramework::CameraState cameraState = AzFramework::CreateDefaultCamera(
AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(-5.0f)), ScreenDimensions);
// perform a visibility query based on the state of the camera
AzFramework::EntityVisibilityQuery entityVisibilityQuery;
entityVisibilityQuery.UpdateVisibility(cameraState);
// build a vector of visible entities
AZStd::vector<AZ::EntityId> visibleEditorEntityIds;
AZStd::copy(
entityVisibilityQuery.Begin(), entityVisibilityQuery.End(), AZStd::back_inserter(visibleEditorEntityIds));
// build the expected vector of entity ids (the middle portion of the row based on the centered position of the
// camera
AZStd::vector<AZ::EntityId> expectedEditorEntities;
AZStd::copy(
m_editorEntityIds.begin() + BeginVisibleEntityRangeOffset,
m_editorEntityIds.begin() + EndVisibleEntityRangeOffset, AZStd::back_inserter(expectedEditorEntities));
EXPECT_THAT(visibleEditorEntityIds, UnorderedElementsAreArray(expectedEditorEntities));
}
TEST_F(EditorVisibilityFixture, VisibilityQueryReturnsEntitiesInFrustumWithOrientationAndOffset)
{
using ::testing::UnorderedElementsAreArray;
constexpr size_t EditorEntityCount = 21;
constexpr size_t BeginVisibleEntityRangeOffset = 0;
constexpr size_t EndVisibleEntityRangeOffset = 10;
// setup row of editor entities
CreateEditorEntities(EditorEntityCount);
SetupRowOfEntities(AZ::Vector3::CreateAxisX(-20.0f), AZ::Vector3::CreateAxisX(2.0f));
// request the entity union bounds system to update
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
// create default camera looking down the negative x-axis moved along the x-axis and tilted slightly down
AzFramework::CameraState cameraState = AzFramework::CreateDefaultCamera(
AZ::Transform::CreateFromQuaternionAndTranslation(
AZ::Quaternion::CreateRotationZ(AZ::DegToRad(90.0f)) *
AZ::Quaternion::CreateRotationX(AZ::DegToRad(-25.0f)),
AZ::Vector3(2.0f, 0.0f, 5.0f)),
ScreenDimensions);
// perform a visibility query based on the state of the camera
AzFramework::EntityVisibilityQuery entityVisibilityQuery;
entityVisibilityQuery.UpdateVisibility(cameraState);
// build the expected vector of entity ids (the first 10 entities in the row)
AZStd::vector<AZ::EntityId> expectedEditorEntities;
AZStd::copy(
m_editorEntityIds.begin() + BeginVisibleEntityRangeOffset,
m_editorEntityIds.begin() + EndVisibleEntityRangeOffset, AZStd::back_inserter(expectedEditorEntities));
// build a vector of visible entities
AZStd::vector<AZ::EntityId> visibleEditorEntityIds;
AZStd::copy(
entityVisibilityQuery.Begin(), entityVisibilityQuery.End(), AZStd::back_inserter(visibleEditorEntityIds));
EXPECT_THAT(visibleEditorEntityIds, UnorderedElementsAreArray(expectedEditorEntities));
}
TEST_F(EditorVisibilityFixture, TranslatedEntityIsRemovedFromVisibilityQueryWhenOutsideFrustum)
{
using ::testing::UnorderedElementsAreArray;
constexpr size_t EditorEntityCount = 21;
constexpr size_t BeginVisibleEntityRangeOffset = 7;
constexpr size_t EndVisibleEntityRangeOffset = 14;
// setup row of editor entities
CreateEditorEntities(EditorEntityCount);
SetupRowOfEntities(AZ::Vector3::CreateAxisX(-20.0f), AZ::Vector3::CreateAxisX(2.0f));
// request the entity union bounds system to update
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
const AZ::EntityId entityIdToMove = m_editorEntityIds[10];
AZ::TransformBus::Event(
entityIdToMove, &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3::CreateAxisZ(100.0f));
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
// create default camera looking down the negative y-axis moved just back from the origin
AzFramework::CameraState cameraState = AzFramework::CreateDefaultCamera(
AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(-5.0f)), ScreenDimensions);
// perform a visibility query based on the state of the camera
AzFramework::EntityVisibilityQuery entityVisibilityQuery;
entityVisibilityQuery.UpdateVisibility(cameraState);
// build a vector of visible entities
AZStd::vector<AZ::EntityId> visibleEditorEntityIds;
AZStd::copy(
entityVisibilityQuery.Begin(), entityVisibilityQuery.End(), AZStd::back_inserter(visibleEditorEntityIds));
// build the expected vector of entity ids (the middle portion of the row based on the centered position of the
// camera
AZStd::vector<AZ::EntityId> expectedEditorEntities;
AZStd::copy(
m_editorEntityIds.begin() + BeginVisibleEntityRangeOffset,
m_editorEntityIds.begin() + EndVisibleEntityRangeOffset, AZStd::back_inserter(expectedEditorEntities));
// remove moved entity from expected vector
expectedEditorEntities.erase(
AZStd::remove(expectedEditorEntities.begin(), expectedEditorEntities.end(), entityIdToMove),
expectedEditorEntities.end());
EXPECT_THAT(visibleEditorEntityIds, UnorderedElementsAreArray(expectedEditorEntities));
}
class TestBoundComponent
: public AZ::Component
, public AzFramework::BoundsRequestBus::Handler
{
public:
AZ_COMPONENT(TestBoundComponent, "{20BB6DB0-B6C0-4D11-A963-B2884F764C4E}");
static void Reflect(AZ::ReflectContext* context);
TestBoundComponent() = default;
// BoundsRequestBus overrides
AZ::Aabb GetWorldBounds() override;
AZ::Aabb GetLocalBounds() override;
void ChangeBounds(const AZ::Aabb& localAabb);
protected:
// AZ::Component overrides ...
void Activate() override;
void Deactivate() override;
private:
AZ::Aabb m_localAabb = AZ::Aabb::CreateNull();
};
void TestBoundComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<TestBoundComponent, AZ::Component>()->Version(1);
}
}
void TestBoundComponent::Activate()
{
m_localAabb = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f));
AzFramework::BoundsRequestBus::Handler::BusConnect(GetEntityId());
}
void TestBoundComponent::Deactivate()
{
AzFramework::BoundsRequestBus::Handler::BusDisconnect();
}
AZ::Aabb TestBoundComponent::GetWorldBounds()
{
AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(worldFromLocal, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
return m_localAabb.GetTransformedAabb(worldFromLocal);
}
AZ::Aabb TestBoundComponent::GetLocalBounds()
{
return m_localAabb;
}
void TestBoundComponent::ChangeBounds(const AZ::Aabb& localAabb)
{
m_localAabb = localAabb;
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId());
}
TEST_F(EditorVisibilityFixture, UpdatedBoundsIntersectingFrustumAddsVisibleEntity)
{
using ::testing::ElementsAre;
// register new test component
GetApplication()->RegisterComponentDescriptor(TestBoundComponent::CreateDescriptor());
AZ::Entity* entity = nullptr;
const auto entityId = CreateDefaultEditorEntity("Entity", &entity);
entity->Deactivate();
auto testBoundComponent = static_cast<TestBoundComponent*>(entity->CreateComponent<TestBoundComponent>());
entity->Activate();
// move the entity just out of view (to the right of the view frustum)
AZ::TransformBus::Event(
entityId, &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3(40.0f, -3.0f, 20.0f));
// request the entity union bounds system to update
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
// create default camera looking down the positive x-axis moved to position offset from world origin
AzFramework::CameraState cameraState = AzFramework::CreateDefaultCamera(
AZ::Transform::CreateFromQuaternionAndTranslation(
AZ::Quaternion::CreateRotationZ(AZ::DegToRad(-90.0f)), AZ::Vector3(20.0f, 20.0f, 20.0f)),
ScreenDimensions);
// perform a visibility query based on the state of the camera
AzFramework::EntityVisibilityQuery entityVisibilityQuery;
entityVisibilityQuery.UpdateVisibility(cameraState);
// build a vector of visible entities
AZStd::vector<AZ::EntityId> visibleEditorEntityIds;
AZStd::copy(
entityVisibilityQuery.Begin(), entityVisibilityQuery.End(), AZStd::back_inserter(visibleEditorEntityIds));
EXPECT_TRUE(visibleEditorEntityIds.empty());
// increase the size of the bounds
testBoundComponent->ChangeBounds(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-2.5f), AZ::Vector3(2.5f)));
// perform an 'update' of the visibility system
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
entityVisibilityQuery.UpdateVisibility(cameraState);
AZStd::copy(
entityVisibilityQuery.Begin(), entityVisibilityQuery.End(), AZStd::back_inserter(visibleEditorEntityIds));
// check the entity is now visible as its bound intersects the view volume
EXPECT_THAT(visibleEditorEntityIds, ElementsAre(entityId));
}
} // namespace UnitTest
@@ -0,0 +1,90 @@
#
# 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.
#
set(FILES
Main.cpp
ArchiveTests.cpp
AssetFileInfoListComparison.cpp
AssetSeedManager.cpp
AssetSystemMocks.h
ComponentModeTests.cpp
ComponentModeTestDoubles.h
ComponentModeTestDoubles.cpp
ComponentModeTestFixture.h
ComponentModeTestFixture.cpp
EditorTransformComponentSelectionTests.cpp
EditorVertexSelectionTests.cpp
EntityIdQLabelTests.cpp
EntityInspectorTests.cpp
FingerprintingTests.cpp
LogLines.cpp
ManipulatorBoundsTests.cpp
ManipulatorCoreTests.cpp
ManipulatorViewTests.cpp
PlatformAddressedAssetCatalogTests.cpp
PropertyIntCtrlCommonTests.h
IntegerPrimtitiveTestConfig.h
QtWidgetLimitsTests.cpp
PropertyIntSliderCtrlTests.cpp
PropertyIntSpinCtrlTests.cpp
PropertyTreeEditorTests.cpp
PythonBindingTests.cpp
Slice.cpp
SliceUpgradeTestsData.h
SliceUpgradeTests.cpp
SpinBoxTests.cpp
ThumbnailerTests.cpp
UndoStack.cpp
PerforceComponentTests.cpp
Prefab/Benchmark/PrefabBenchmarkFixture.cpp
Prefab/Benchmark/PrefabBenchmarkFixture.h
Prefab/Benchmark/PrefabCreateBenchmarks.cpp
Prefab/Benchmark/PrefabInstantiateBenchmarks.cpp
Prefab/Benchmark/PrefabLoadBenchmarks.cpp
Prefab/Benchmark/PrefabUpdateInstancesBenchmarks.cpp
Prefab/MockPrefabFileIOActionValidator.cpp
Prefab/MockPrefabFileIOActionValidator.h
Prefab/PrefabInstanceToTemplatePropagatorTests.cpp
Prefab/PrefabInstantiateTests.cpp
Prefab/PrefabLoadTemplateTests.cpp
Prefab/PrefabTestComponent.cpp
Prefab/PrefabTestComponent.h
Prefab/PrefabTestData.cpp
Prefab/PrefabTestData.h
Prefab/PrefabTestDataUtils.cpp
Prefab/PrefabTestDataUtils.h
Prefab/PrefabTestDomUtils.cpp
Prefab/PrefabTestDomUtils.h
Prefab/PrefabTestFixture.cpp
Prefab/PrefabTestFixture.h
Prefab/PrefabTestUtils.h
Prefab/PrefabUpdateInstancesTests.cpp
Prefab/PrefabUpdateTemplateTests.cpp
Prefab/PrefabUpdateWithPatchesTests.cpp
Prefab/PrefabInstantiateTests.cpp
Entity/EditorEntityContextComponentTests.cpp
Entity/EditorEntitySearchComponentTests.cpp
SliceStabilityTests/SliceStabilityTestFramework.h
SliceStabilityTests/SliceStabilityTestFramework.cpp
SliceStabilityTests/SliceStabilityCreateTests.cpp
SliceStabilityTests/SliceStabilityPushTests.cpp
SliceStabilityTests/SliceStabilityReParentTests.cpp
ToolsComponents/EditorLayerComponentTests.cpp
ToolsComponents/EditorTransformComponentTests.cpp
UI/EntityPropertyEditorTests.cpp
Viewport/ViewportScreenTests.cpp
Viewport/ViewportUiClusterTests.cpp
Viewport/ViewportUiDisplayTests.cpp
Viewport/ViewportUiManagerTests.cpp
Viewport/ClusterTests.cpp
Viewport/ViewportUiWidgetManagerTests.cpp
Visibility/EditorVisibilityTests.cpp
)