/* * 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 #include #include #include #include #include #include namespace UnitTest { AZ::Entity* CreateEntity(const char* entityName, AZStd::initializer_list componentsToAdd = {}) { // 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(); for (auto&& component : componentsToAdd) { newEntity->AddComponent(component); } newEntity->Activate(); return newEntity; } TEST_F(PrefabBuilderTests, SourceDependencies) { static constexpr const char* ChildPrefabPath = "child.prefab"; using namespace AzToolsFramework::Prefab; AZStd::vector> childInstances; auto* prefabSystemComponentInterface = AZ::Interface::Get(); auto* prefabLoaderInterface = AZ::Interface::Get(); ASSERT_NE(prefabSystemComponentInterface, nullptr); ASSERT_NE(prefabLoaderInterface, nullptr); // Create a child entity and a prefab containing it childInstances.push_back(prefabSystemComponentInterface->CreatePrefab({CreateEntity("child")}, {}, ChildPrefabPath)); // Create a parent entity and a prefab for it, pass in the child prefab for it to reference auto parentInstance = prefabSystemComponentInterface->CreatePrefab({CreateEntity("parent")}, AZStd::move(childInstances), "parent.prefab"); AZStd::string serializedInstance; // Save to a string so we can load it as a PrefabDom and so that the nested instance becomes a Source file reference ASSERT_TRUE(prefabLoaderInterface->SaveTemplateToString(parentInstance->GetTemplateId(), serializedInstance)); AZ::Outcome readPrefabFileResult = AZ::JsonSerializationUtils::ReadJsonString(serializedInstance); ASSERT_TRUE(readPrefabFileResult.IsSuccess()); // Now that we have a PrefabDom, test extracting the Source file reference as a Source Dependency auto sourceFileDependencies = AZ::Prefab::PrefabBuilderComponent::GetSourceDependencies(readPrefabFileResult.TakeValue()); ASSERT_EQ(sourceFileDependencies.size(), 1); EXPECT_STREQ(sourceFileDependencies[0].m_sourceFileDependencyPath.c_str(), ChildPrefabPath); } TEST_F(PrefabBuilderTests, ProductDependencies) { constexpr const char* ChildPrefabPath = "child.prefab"; using namespace AzToolsFramework::Prefab; AZ::Uuid TestAssetUuid("{7725567D-D420-46C2-B481-E0F79212CD34}"); AZ::Data::AssetId TestAssetId(TestAssetUuid, 0); AZStd::vector> childInstances; auto* prefabSystemComponentInterface = AZ::Interface::Get(); ASSERT_NE(prefabSystemComponentInterface, nullptr); auto* component = aznew TestComponent(); AZ::Data::Asset asset = AZ::Data::Asset(TestAssetId, azrtti_typeid()); component->m_asset = asset; AZ::Entity* childEntity = CreateEntity("child", {component}); // Create a child prefab with an entity that has an Asset reference on it childInstances.push_back(prefabSystemComponentInterface->CreatePrefab( {childEntity}, {}, ChildPrefabPath, AZStd::unique_ptr(CreateEntity("Container")))); // Create a parent prefab that has a nested instance reference to the child prefab auto parentInstance = prefabSystemComponentInterface->CreatePrefab({CreateEntity("parent")}, AZStd::move(childInstances), "parent.prefab", AZStd::unique_ptr(CreateEntity("Container"))); AZStd::string serializedInstance; TestPrefabBuilderComponent prefabBuilderComponent; prefabBuilderComponent.Activate(); AZStd::vector jobProducts; auto&& prefabDom = prefabSystemComponentInterface->FindTemplateDom(parentInstance->GetTemplateId()); ASSERT_TRUE(prefabBuilderComponent.ProcessPrefab({AZ::Crc32("pc")}, "parent.prefab", "unused", AZ::Uuid(), prefabDom, jobProducts)); ASSERT_EQ(jobProducts.size(), 1); ASSERT_EQ(jobProducts[0].m_dependencies.size(), 1); ASSERT_EQ(jobProducts[0].m_dependencies[0].m_dependencyId, TestAssetId); prefabBuilderComponent.Deactivate(); } AZStd::pair GetFingerprint(AzToolsFramework::Prefab::PrefabDom& dom) { TestPrefabBuilderComponent prefabBuilderComponent; prefabBuilderComponent.Activate(); size_t builderFingerprint = prefabBuilderComponent.CalculateBuilderFingerprint(); size_t fingerprint = prefabBuilderComponent.CalculatePrefabFingerprint(dom); prefabBuilderComponent.Deactivate(); return {fingerprint, builderFingerprint}; } TEST_F(PrefabBuilderTests, FingerprintTest) { auto* prefabSystemComponentInterface = AZ::Interface::Get(); ASSERT_NE(prefabSystemComponentInterface, nullptr); auto* component = aznew TestComponent(); AZ::Entity* entity = CreateEntity("test", {component}); auto parentInstance = prefabSystemComponentInterface->CreatePrefab( {entity}, {}, "test.prefab", AZStd::unique_ptr(CreateEntity("Container"))); AZStd::vector jobProducts; auto&& prefabDom = prefabSystemComponentInterface->FindTemplateDom(parentInstance->GetTemplateId()); auto [v0Dom, v0Builder] = GetFingerprint(prefabDom); auto [sanityDom, sanityBuilder] = GetFingerprint(prefabDom); // Make sure the fingerprint is stable without changes ASSERT_EQ(v0Dom, sanityDom); ASSERT_EQ(v0Builder, sanityBuilder); AZ::SerializeContext* context = m_app.GetSerializeContext(); // Unreflect VersionChangingData, change its version, and reflect it again context->EnableRemoveReflection(); VersionChangingData::Reflect(context); VersionChangingData::m_version = 1; context->DisableRemoveReflection(); VersionChangingData::Reflect(context); // Get the new fingerprint and check that it changed auto [v1Dom, v1Builder] = GetFingerprint(prefabDom); ASSERT_NE(v0Dom, v1Dom); // Verify the fingerprint for the object changed ASSERT_NE(v0Builder, v1Builder); // Verify the fingerprint for the entire builder changed } AZStd::unique_ptr TestPrefabBuilderComponent::GetOutputStream(const AZ::IO::Path&) const { return AZStd::make_unique(); } void PrefabBuilderTests::SetUp() { AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; AZ::IO::FixedMaxPath enginePath; registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); AZ::ComponentApplication::Descriptor desc; m_app.Start(desc); m_app.CreateReflectionManager(); m_testComponentDescriptor = AZStd::unique_ptr{TestComponent::CreateDescriptor()}; m_testComponentDescriptor->Reflect(m_app.GetSerializeContext()); TestAsset::Reflect(m_app.GetSerializeContext()); VersionChangingData::Reflect(m_app.GetSerializeContext()); // 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::Data::AssetManager::Instance().RegisterHandler(&m_assetHandler, azrtti_typeid()); } void PrefabBuilderTests::TearDown() { AZ::Data::AssetManager::Instance().UnregisterHandler(&m_assetHandler); m_testComponentDescriptor = nullptr; m_app.Stop(); } } AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);