/* * 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 * */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace EMotionFX { template class EMotionFXTestModule : public AZ::Module { public: AZ_RTTI(EMotionFXTestModule, "{32567457-5341-4D8D-91A9-E48D8395DE65}", AZ::Module); AZ_CLASS_ALLOCATOR(EMotionFXTestModule, AZ::OSAllocator, 0); EMotionFXTestModule() { m_descriptors.insert(m_descriptors.end(), {Components::CreateDescriptor()...}); } }; template class ComponentFixtureApp : public AzFramework::Application { public: ComponentFixtureApp() { using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; constexpr auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; if(auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) { AZ::IO::FixedMaxPath enginePath; settingsRegistry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); settingsRegistry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry); } } AZ::ComponentTypeList GetRequiredSystemComponents() const override { return {azrtti_typeid()...}; } void CreateStaticModules(AZStd::vector& outModules) override { // This is intentionally bypassing the static modules that // AzFramework::Application would create. That creates more // components than these tests need. outModules.emplace_back(aznew EMotionFXTestModule()); } void StartCommon(AZ::Entity* systemEntity) override { m_systemEntity = systemEntity; AzFramework::Application::StartCommon(systemEntity); } AZ::Entity* GetSystemEntity() const { return m_systemEntity; } private: AZ::Entity* m_systemEntity = nullptr; }; //! A fixture that constructs an EMotionFX::Integration::SystemComponent /*! * This fixture can be used by any test that needs the EMotionFX runtime to * be working. It will construct all necessary allocators for EMotionFX * objects to be successfully instantiated. */ template class ComponentFixture : public UnitTest::ScopedAllocatorSetupFixture { public: void SetUp() override { UnitTest::ScopedAllocatorSetupFixture::SetUp(); PreStart(); AZ::ComponentApplication::StartupParameters startupParameters; startupParameters.m_createEditContext = true; m_app.Start(AZ::ComponentApplication::Descriptor{}, startupParameters); // 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); GetSerializeContext()->CreateEditContext(); } void TearDown() override { // If we loaded the asset catalog, call this function to release all the assets that has been loaded internally. if (HasComponentType()) { AZ::Data::AssetManager::Instance().DispatchEvents(); } GetSerializeContext()->DestroyEditContext(); // Clear the queue of messages from unit tests on our buses EMotionFX::Integration::ActorNotificationBus::ClearQueuedEvents(); UnitTest::ScopedAllocatorSetupFixture::TearDown(); } ~ComponentFixture() override { if (GetSystemEntity()->GetState() == AZ::Entity::State::Active) { GetSystemEntity()->Deactivate(); } } AZ::SerializeContext* GetSerializeContext() { return m_app.GetSerializeContext(); } AZ::Entity* GetSystemEntity() const { return m_app.GetSystemEntity(); } AZStd::string ResolvePath(const char* path) const { AZStd::string result; result.resize(AZ::IO::MaxPathLength); AZ::IO::FileIOBase::GetInstance()->ResolvePath(path, result.data(), result.size()); result.resize_no_construct(AZStd::char_traits::length(result.data())); return result; } protected: virtual AZStd::string GetAssetFolder() const { AZStd::string assetCachePath; if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) { settingsRegistry->Get(assetCachePath, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder); } return assetCachePath; } // Runs after allocators are set up but before application startup // Used by the InitSceneAPI fixture to load the SceneAPI dlls virtual void PreStart() {} template static constexpr bool HasComponentType() { // Return true if T is somewhere in the Components parameter pack // This expands to // return (((AZStd::is_same_v || AZStd::is_same_v) || AZStd::is_same_v) || ...) return ((... || AZStd::is_same_v)); } protected: // The ComponentApplication must not be a pointer, because it cannot be // dynamically allocated. Calls to new will try to use the SystemAllocator // that has not been created yet. If one is created before // ComponentApplication::Create() is called, ComponentApplication will // complain that there's already a SystemAllocator, as it tries to make one // itself. ComponentFixtureApp m_app; }; using SystemComponentFixture = ComponentFixture< AZ::MemoryComponent, AZ::AssetManagerComponent, AZ::JobManagerComponent, AZ::StreamerComponent, EMotionFX::Integration::SystemComponent >; // Use this fixture if you want to load asset catalog. Some assets (reference anim graph for example) // can only be loaded when asset catalog is loaded. using SystemComponentFixtureWithCatalog = ComponentFixture< AZ::MemoryComponent, AZ::AssetManagerComponent, AZ::JobManagerComponent, AZ::StreamerComponent, AzFramework::AssetCatalogComponent, EMotionFX::Integration::SystemComponent >; } // end namespace EMotionFX