You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabBehaviorTests.cpp

375 lines
16 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <PrefabBuilderTests.h>
#include <PrefabGroup/IPrefabGroup.h>
#include <PrefabGroup/PrefabGroup.h>
#include <PrefabGroup/PrefabGroupBehavior.h>
#include <AzTest/Utils.h>
#include <Tests/AssetSystemMocks.h>
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/ITransform.h>
#include <SceneAPI/SceneCore/DataTypes/Groups/IMeshGroup.h>
#include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
#include <SceneAPI/SceneCore/Events/ExportEventContext.h>
#include <SceneAPI/SceneCore/Events/ExportProductList.h>
#include <SceneAPI/SceneCore/Mocks/DataTypes/MockIGraphObject.h>
#include <SceneAPI/SceneCore/SceneCoreStandaloneAllocator.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneData/SceneDataStandaloneAllocator.h>
#include <PrefabGroup/PrefabBehaviorTests.inl>
// a mock AZ::Render::EditorMeshComponent
namespace AZ::Render
{
struct EditorMeshComponent
: public AZ::Component
{
AZ_COMPONENT(EditorMeshComponent, "{DCE68F6E-2E16-4CB4-A834-B6C2F900A7E9}");
void Activate() override {}
void Deactivate() override {}
static void Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AZ::Render::EditorMeshComponent>();
}
if (BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<EditorMeshComponent>("AZ::Render::EditorMeshComponent");
}
}
};
struct EditorMeshComponentHelper
: public AZ::ComponentDescriptorHelper<EditorMeshComponent>
{
AZ_CLASS_ALLOCATOR(EditorMeshComponentHelper, AZ::SystemAllocator, 0);
void Reflect(AZ::ReflectContext* reflection) const override
{
AZ::Render::EditorMeshComponent::Reflect(reflection);
}
};
}
namespace UnitTest
{
class PrefabBehaviorTests
: public PrefabBuilderTests
{
public:
static void SetUpTestCase()
{
// Allocator needed by SceneCore
if (!AZ::AllocatorInstance<AZ::SystemAllocator>().IsReady())
{
AZ::AllocatorInstance<AZ::SystemAllocator>().Create();
}
AZ::SceneAPI::SceneCoreStandaloneAllocator::Initialize(AZ::Environment::GetInstance());
AZ::SceneAPI::SceneDataStandaloneAllocator::Initialize(AZ::Environment::GetInstance());
}
static void TearDownTestCase()
{
AZ::SceneAPI::SceneDataStandaloneAllocator::TearDown();
AZ::SceneAPI::SceneCoreStandaloneAllocator::TearDown();
AZ::AllocatorInstance<AZ::SystemAllocator>().Destroy();
}
void SetUp() override
{
PrefabBuilderTests::SetUp();
m_prefabGroupBehavior = AZStd::make_unique<AZ::SceneAPI::Behaviors::PrefabGroupBehavior>();
m_prefabGroupBehavior->Activate();
// Mocking the asset system replacing the AssetSystem::AssetSystemComponent
AZ::Entity* systemEntity = m_app.FindEntity(AZ::SystemEntityId);
systemEntity->FindComponent<AzToolsFramework::AssetSystem::AssetSystemComponent>()->Deactivate();
using namespace testing;
ON_CALL(m_assetSystemRequestMock, GetSourceInfoBySourcePath(_, _, _)).WillByDefault([](auto* path, auto& info, auto&)
{
return PrefabBehaviorTests::OnGetSourceInfoBySourcePath(path, info);
});
m_assetSystemRequestMock.BusConnect();
m_componentDescriptorHelperEditorMeshComponent = AZStd::make_unique<AZ::Render::EditorMeshComponentHelper>();
m_componentDescriptorHelperEditorMeshComponent->Reflect(m_app.GetSerializeContext());
m_componentDescriptorHelperEditorMeshComponent->Reflect(m_app.GetBehaviorContext());
}
void TearDown() override
{
m_componentDescriptorHelperEditorMeshComponent.reset();
m_assetSystemRequestMock.BusDisconnect();
m_prefabGroupBehavior->Deactivate();
m_prefabGroupBehavior.reset();
PrefabBuilderTests::TearDown();
}
static bool OnGetSourceInfoBySourcePath(AZStd::string_view sourcePath, AZ::Data::AssetInfo& assetInfo)
{
if (sourcePath == AZStd::string_view("mock"))
{
assetInfo.m_assetId = AZ::Uuid::CreateRandom();
assetInfo.m_assetType = azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>();
assetInfo.m_relativePath = "mock/path";
assetInfo.m_sizeBytes = 0;
}
return true;
}
AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene> CreateMockScene(
const AZStd::string manifestFilename = "ManifestFilename",
const AZStd::string sourceFileName = "Source",
const AZStd::string watchFolder = "WatchFolder")
{
using namespace AZ::SceneAPI;
auto scene = AZStd::make_shared<Containers::Scene>("mock_scene");
scene->SetManifestFilename(manifestFilename);
scene->SetSource(sourceFileName, AZ::Uuid::CreateRandom());
scene->SetWatchFolder(watchFolder);
/*---------------------------------------\
Root
|
1
|
2
/ \
------3m 7
/ / / \
6 5 4t 8m-------
\ \ \
9t 10 11
\---------------------------------------*/
// Build up the graph
auto root = scene->GetGraph().GetRoot();
scene->GetGraph().SetContent(root, AZStd::make_shared<DataTypes::MockIGraphObject>(0));
auto index1 = scene->GetGraph().AddChild(root, "1", AZStd::make_shared<DataTypes::MockIGraphObject>(1));
auto index2 = scene->GetGraph().AddChild(index1, "2", AZStd::make_shared<DataTypes::MockIGraphObject>(2));
auto index3 = scene->GetGraph().AddChild(index2, "3", AZStd::make_shared<AZ::SceneData::GraphData::MeshData>());
auto index4 = scene->GetGraph().AddChild(index3, "4", AZStd::make_shared<MockTransform>());
auto index5 = scene->GetGraph().AddChild(index3, "5", AZStd::make_shared<DataTypes::MockIGraphObject>(5));
auto index6 = scene->GetGraph().AddChild(index3, "6", AZStd::make_shared<DataTypes::MockIGraphObject>(6));
auto index7 = scene->GetGraph().AddChild(index2, "7", AZStd::make_shared<DataTypes::MockIGraphObject>(7));
auto index8 = scene->GetGraph().AddChild(index7, "8", AZStd::make_shared<AZ::SceneData::GraphData::MeshData>());
auto index9 = scene->GetGraph().AddChild(index8, "9", AZStd::make_shared<MockTransform>());
auto index10 = scene->GetGraph().AddChild(index8, "10", AZStd::make_shared<DataTypes::MockIGraphObject>(10));
auto index11 = scene->GetGraph().AddChild(index8, "11", AZStd::make_shared<DataTypes::MockIGraphObject>(11));
scene->GetGraph().MakeEndPoint(index4);
scene->GetGraph().MakeEndPoint(index5);
scene->GetGraph().MakeEndPoint(index6);
scene->GetGraph().MakeEndPoint(index9);
scene->GetGraph().MakeEndPoint(index10);
scene->GetGraph().MakeEndPoint(index11);
return scene;
}
// mock classes and structures
struct MockTransform
: public AZ::SceneAPI::DataTypes::ITransform
{
AZ::Matrix3x4 m_matrix = AZ::Matrix3x4::CreateIdentity();
AZ::Matrix3x4& GetMatrix() override
{
return m_matrix;
}
const AZ::Matrix3x4& GetMatrix() const
{
return m_matrix;
}
};
struct TestPreExportEventContext
{
TestPreExportEventContext()
: m_scene("test_context")
{
using namespace AZ::SceneAPI::Events;
m_preExportEventContext = AZStd::make_unique<PreExportEventContext>(m_productList, m_outputDirectory, m_scene, "mock");
}
void SetOutputDirectory(AZStd::string outputDirectory)
{
using namespace AZ::SceneAPI::Events;
m_outputDirectory = AZStd::move(outputDirectory);
m_preExportEventContext = AZStd::make_unique<PreExportEventContext>(m_productList, m_outputDirectory, m_scene, "mock");
}
AZStd::unique_ptr<AZ::SceneAPI::Events::PreExportEventContext> m_preExportEventContext;
AZ::SceneAPI::Events::ExportProductList m_productList;
AZStd::string m_outputDirectory;
AZ::SceneAPI::Containers::Scene m_scene;
};
AZStd::unique_ptr<AZ::SceneAPI::Behaviors::PrefabGroupBehavior> m_prefabGroupBehavior;
testing::NiceMock<UnitTests::MockAssetSystemRequest> m_assetSystemRequestMock;
AZStd::unique_ptr<AZ::Render::EditorMeshComponentHelper> m_componentDescriptorHelperEditorMeshComponent;
};
TEST_F(PrefabBehaviorTests, PrefabBehavior_EmptyContextIgnored_Works)
{
auto context = TestPreExportEventContext{};
auto result = AZ::SceneAPI::Events::ProcessingResult::Failure;
AZ::SceneAPI::Events::CallProcessorBus::BroadcastResult(
result,
&AZ::SceneAPI::Events::CallProcessorBus::Events::Process,
context.m_preExportEventContext.get());
EXPECT_EQ(result, AZ::SceneAPI::Events::ProcessingResult::Ignored);
}
TEST_F(PrefabBehaviorTests, PrefabBehavior_SimplePrefab_Works)
{
auto context = TestPreExportEventContext{};
// check for the file at <temp_directory>/mock/fake_prefab.procprefab
AZ::Test::ScopedAutoTempDirectory tempDir;
context.SetOutputDirectory(tempDir.GetDirectory());
auto jsonOutcome = AZ::JsonSerializationUtils::ReadJsonString(Data::jsonPrefab);
ASSERT_TRUE(jsonOutcome);
// Register the asset to generate an AssetId in the catalog
AZ::Data::AssetId assetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, "fake_prefab.procprefab",
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(), true);
auto prefabGroup = AZStd::make_shared<AZ::SceneAPI::SceneData::PrefabGroup>();
prefabGroup.get()->SetId(AZ::Uuid::CreateRandom());
prefabGroup.get()->SetName("fake_prefab");
prefabGroup.get()->SetPrefabDom(AZStd::move(jsonOutcome.GetValue()));
context.m_scene.GetManifest().AddEntry(prefabGroup);
context.m_scene.SetSource("mock", AZ::Uuid::CreateRandom());
auto result = AZ::SceneAPI::Events::ProcessingResult::Failure;
AZ::SceneAPI::Events::CallProcessorBus::BroadcastResult(
result,
&AZ::SceneAPI::Events::CallProcessorBus::Events::Process,
context.m_preExportEventContext.get());
EXPECT_EQ(result, AZ::SceneAPI::Events::ProcessingResult::Success);
AZStd::string pathStr;
AzFramework::StringFunc::Path::ConstructFull(tempDir.GetDirectory(), "mock/fake_prefab.procprefab", pathStr, true);
if (!AZ::IO::SystemFile::Exists(pathStr.c_str()))
{
AZ_Warning("testing", false, "The product asset (%s) is missing", pathStr.c_str());
}
}
TEST_F(PrefabBehaviorTests, PrefabBehavior_UpdateManifestWithEmptyScene_DoesNotFail)
{
using namespace AZ::SceneAPI;
using namespace AZ::SceneAPI::Events;
Containers::Scene scene("empty_scene");
AssetImportRequest::ManifestAction action = AssetImportRequest::ManifestAction::ConstructDefault;
AssetImportRequest::RequestingApplication requester = {};
Behaviors::PrefabGroupBehavior prefabGroupBehavior;
ProcessingResult result = ProcessingResult::Failure;
AssetImportRequestBus::BroadcastResult(result, &AssetImportRequestBus::Events::UpdateManifest, scene, action, requester);
EXPECT_NE(result, ProcessingResult::Failure);
}
TEST_F(PrefabBehaviorTests, PrefabBehavior_UpdateManifestWithEmptyScene_Ignored)
{
using namespace AZ::SceneAPI;
using namespace AZ::SceneAPI::Events;
Containers::Scene scene("empty_scene");
AssetImportRequest::ManifestAction action = AssetImportRequest::ManifestAction::Update;
AssetImportRequest::RequestingApplication requester = {};
Behaviors::PrefabGroupBehavior prefabGroupBehavior;
ProcessingResult result = ProcessingResult::Failure;
AssetImportRequestBus::BroadcastResult(result, &AssetImportRequestBus::Events::UpdateManifest, scene, action, requester);
EXPECT_EQ(result, ProcessingResult::Ignored);
}
TEST_F(PrefabBehaviorTests, PrefabBehavior_UpdateManifestMockScene_CreatesPrefab)
{
using namespace AZ::SceneAPI;
using namespace AZ::SceneAPI::Events;
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
auto scene = CreateMockScene("Manifest", "C:/o3de/watch.folder/manifest_src_file.xml", "C:/o3de/watch.folder");
#else
auto scene = CreateMockScene("Manifest", "//o3de/watch.folder/manifest_src_file.xml", "//o3de/watch.folder");
#endif
AssetImportRequest::ManifestAction action = AssetImportRequest::ManifestAction::ConstructDefault;
AssetImportRequest::RequestingApplication requester = {};
Behaviors::PrefabGroupBehavior prefabGroupBehavior;
ProcessingResult result = ProcessingResult::Failure;
AssetImportRequestBus::BroadcastResult(result, &AssetImportRequestBus::Events::UpdateManifest, *scene, action, requester);
EXPECT_EQ(result, ProcessingResult::Success);
EXPECT_EQ(scene->GetManifest().GetEntryCount(), 3);
EXPECT_TRUE(azrtti_istypeof<AZ::SceneAPI::DataTypes::IMeshGroup>(scene->GetManifest().GetValue(0).get()));
EXPECT_TRUE(azrtti_istypeof<AZ::SceneAPI::DataTypes::IMeshGroup>(scene->GetManifest().GetValue(1).get()));
EXPECT_TRUE(azrtti_istypeof<AZ::SceneAPI::DataTypes::IPrefabGroup>(scene->GetManifest().GetValue(2).get()));
// The mesh group names are expected to be just the file name relative to the watch folder and not any absolute path
for (size_t i = 0; i < scene->GetManifest().GetEntryCount(); i++)
{
if (azrtti_istypeof<AZ::SceneAPI::DataTypes::IMeshGroup>(scene->GetManifest().GetValue(i).get()))
{
AZ::SceneAPI::DataTypes::IMeshGroup* meshGroup = reinterpret_cast<AZ::SceneAPI::DataTypes::IMeshGroup*>(scene->GetManifest().GetValue(i).get());
AZStd::string groupName = meshGroup->GetName();
EXPECT_TRUE(groupName.starts_with("default_mock_"));
}
}
}
TEST_F(PrefabBehaviorTests, PrefabBehavior_UpdateManifest_ToggleWorks)
{
using namespace AZ::SceneAPI;
using namespace AZ::SceneAPI::Events;
ASSERT_TRUE(AZ::SettingsRegistry::Get());
AZ::SettingsRegistry::Get()->Set("/O3DE/Preferences/Prefabs/CreateDefaults", false);
auto scene = CreateMockScene();
AssetImportRequest::ManifestAction action = AssetImportRequest::ManifestAction::ConstructDefault;
AssetImportRequest::RequestingApplication requester = {};
Behaviors::PrefabGroupBehavior prefabGroupBehavior;
ProcessingResult result = ProcessingResult::Failure;
AssetImportRequestBus::BroadcastResult(result, &AssetImportRequestBus::Events::UpdateManifest, *scene, action, requester);
EXPECT_EQ(result, ProcessingResult::Ignored);
EXPECT_EQ(scene->GetManifest().GetEntryCount(), 0);
}
}