Move files to AzFramework/Test and AzToolsFramework/Test accordingly

Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>
This commit is contained in:
Esteban Papp
2021-07-09 08:07:47 -07:00
parent f278bbcd65
commit afd97abdcf
62 changed files with 0 additions and 0 deletions
@@ -0,0 +1,175 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzCore/Component/Entity.h>
#include <AzFramework/Components/ComponentAdapter.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentAdapter.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
static bool s_activateCalled = false;
static bool s_deactivateCalled = false;
struct TestConfig
: public AZ::ComponentConfig
{
AZ_RTTI(TestConfig, "{835CF711-77DB-4DF2-A364-936227A7AF5F}", AZ::ComponentConfig);
uint32_t m_testValue = 0;
};
class TestController
{
public:
AZ_TYPE_INFO(TestController, "{89C1FED9-C306-4B00-9EA4-577862D9277D}");
static void Reflect(AZ::ReflectContext* context)
{
AZ_UNUSED(context);
}
TestController() = default;
TestController(const TestConfig& config):
m_config(config)
{
}
void Activate(AZ::EntityId entityId)
{
AZ_UNUSED(entityId);
s_activateCalled = true;
}
void Deactivate()
{
s_deactivateCalled = true;
}
void SetConfiguration(const TestConfig& config)
{
m_config = config;
}
const TestConfig& GetConfiguration() const
{
return m_config;
}
TestConfig m_config;
};
class TestRuntimeComponent
: public AzFramework::Components::ComponentAdapter<TestController, TestConfig>
{
public:
using BaseClass = AzFramework::Components::ComponentAdapter<TestController, TestConfig>;
AZ_COMPONENT(TestRuntimeComponent, "{136104E4-36A6-4778-AE65-065D33F87E76}", BaseClass);
TestRuntimeComponent() = default;
TestRuntimeComponent(const TestConfig& config)
: BaseClass(config)
{
}
};
class TestEditorComponent
: public AzToolsFramework::Components::EditorComponentAdapter<TestController, TestRuntimeComponent, TestConfig>
{
public:
using BaseClass = AzToolsFramework::Components::EditorComponentAdapter<TestController, TestRuntimeComponent, TestConfig>;
AZ_EDITOR_COMPONENT(TestEditorComponent, "{5FA2B1D6-E2DA-47FB-8419-B6425C37AC80}", BaseClass);
TestEditorComponent() = default;
TestEditorComponent(const TestConfig& config)
: BaseClass(config)
{
}
};
class WrappedComponentTest
: public AllocatorsFixture
{
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_testRuntimeComponentDescriptor;
AZStd::unique_ptr<AZ::ComponentDescriptor> m_testEditorComponentDescriptor;
public:
void SetUp() override
{
AllocatorsFixture::SetUp();
s_activateCalled = false;
s_deactivateCalled = false;
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
m_testRuntimeComponentDescriptor.reset(TestRuntimeComponent::CreateDescriptor());
m_testRuntimeComponentDescriptor->Reflect(&(*m_serializeContext));
m_testEditorComponentDescriptor.reset(TestEditorComponent::CreateDescriptor());
m_testEditorComponentDescriptor->Reflect(&(*m_serializeContext));
}
void TearDown() override
{
m_testEditorComponentDescriptor.reset();
m_testRuntimeComponentDescriptor.reset();
m_serializeContext.reset();
AllocatorsFixture::TearDown();
}
};
TEST_F(WrappedComponentTest, RuntimeWrappersWrapCommon)
{
AZ::Entity entity;
TestRuntimeComponent* runtimeComponent = entity.CreateComponent<TestRuntimeComponent>();
entity.Init();
entity.Activate();
EXPECT_TRUE(s_activateCalled);
entity.Deactivate();
EXPECT_TRUE(s_deactivateCalled);
TestConfig config;
config.m_testValue = 100;
EXPECT_TRUE(runtimeComponent->SetConfiguration(config));
TestConfig outConfig;
EXPECT_TRUE(runtimeComponent->GetConfiguration(outConfig));
EXPECT_EQ(config.m_testValue, outConfig.m_testValue);
}
TEST_F(WrappedComponentTest, EditorWrappersWrapCommon)
{
AZ::Entity entity;
TestEditorComponent* editorComponent = entity.CreateComponent<TestEditorComponent>();
entity.Init();
entity.Activate();
EXPECT_TRUE(s_activateCalled);
entity.Deactivate();
EXPECT_TRUE(s_deactivateCalled);
TestConfig config;
config.m_testValue = 100;
EXPECT_TRUE(editorComponent->SetConfiguration(config));
TestConfig outConfig;
EXPECT_TRUE(editorComponent->GetConfiguration(outConfig));
EXPECT_EQ(config.m_testValue, outConfig.m_testValue);
AZ::Entity gameEntity;
editorComponent->BuildGameEntity(&gameEntity);
TestRuntimeComponent* testRuntimeComponent = gameEntity.FindComponent<TestRuntimeComponent>();
EXPECT_NE(testRuntimeComponent, nullptr);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,139 @@
/*
* 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 "EntityOwnershipServiceTestFixture.h"
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Components/AzFrameworkConfigurationSystemComponent.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
namespace UnitTest
{
void EntityOwnershipServiceTestFixture::SetUpEntityOwnershipServiceTest()
{
AllocatorsTestFixture::SetUp();
AZ::ComponentApplication::Descriptor componentApplicationDescriptor;
componentApplicationDescriptor.m_useExistingAllocator = true;
componentApplicationDescriptor.m_enableDrilling = false; // we already created a memory driller for the test(AllocatorsTestFixture)
m_app = AZStd::make_unique<EntityOwnershipServiceApplication>();
m_app->Start(componentApplicationDescriptor);
// 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 EntityOwnershipServiceTestFixture::TearDownEntityOwnershipServiceTest()
{
m_app.reset();
AllocatorsTestFixture::TearDown();
}
AZ::ComponentTypeList EntityOwnershipServiceTestFixture::EntityOwnershipServiceApplication::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList defaultRequiredComponents = AzFramework::Application::GetRequiredSystemComponents();
defaultRequiredComponents.emplace_back(azrtti_typeid<AzToolsFramework::Prefab::PrefabSystemComponent>());
auto findComponentIterator = AZStd::find(defaultRequiredComponents.begin(), defaultRequiredComponents.end(),
azrtti_typeid<AzFramework::GameEntityContextComponent>());
if (findComponentIterator != defaultRequiredComponents.end())
{
defaultRequiredComponents.erase(findComponentIterator);
}
findComponentIterator = AZStd::find(defaultRequiredComponents.begin(), defaultRequiredComponents.end(),
azrtti_typeid<AzFramework::AzFrameworkConfigurationSystemComponent>());
if (findComponentIterator != defaultRequiredComponents.end())
{
defaultRequiredComponents.erase(findComponentIterator);
}
return defaultRequiredComponents;
}
AzFramework::RootSliceAsset EntityOwnershipServiceTestFixture::GetRootSliceAsset()
{
AzFramework::RootSliceAsset rootSliceAsset;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootSliceAsset,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootAsset);
return rootSliceAsset;
}
void EntityOwnershipServiceTestFixture::HandleEntitiesAdded(const AzFramework::EntityList& entityList)
{
m_entitiesAddedCallbackTriggered = true;
for (AZ::Entity* entity : entityList)
{
// If the entities are not initialized, they won't be removed from ComponentApplication during Entity destruction.
if (entity->GetState() != AZ::Entity::State::Init)
{
entity->Init();
}
}
}
void EntityOwnershipServiceTestFixture::HandleEntitiesRemoved(const AzFramework::EntityIdList&)
{
m_entityRemovedCallbackTriggered = true;
}
bool EntityOwnershipServiceTestFixture::ValidateEntities(const AzFramework::EntityList&)
{
m_validateEntitiesCallbackTriggered = true;
return m_areEntitiesValidForContext;
}
AzFramework::SliceInstantiationTicket EntityOwnershipServiceTestFixture::AddSlice(const EntityList& entityList,
const bool isAsynchronous)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
return AddSlice(entityList, isAsynchronous, sliceAsset);
}
AzFramework::SliceInstantiationTicket EntityOwnershipServiceTestFixture::AddSlice(const EntityList& entityList,
const bool isAsynchronous, AZ::Data::Asset<AZ::SliceAsset>& sliceAsset)
{
AddSliceComponentToAsset(sliceAsset, entityList);
AzFramework::SliceInstantiationTicket sliceInstantiationTicket;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(sliceInstantiationTicket,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::InstantiateSlice, sliceAsset, nullptr, nullptr);
if (!isAsynchronous)
{
AZ::TickBus::ExecuteQueuedEvents();
}
return sliceInstantiationTicket;
}
void EntityOwnershipServiceTestFixture::AddEditorSlice(
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset, const AZ::Transform& worldTransform, const EntityList& entityList)
{
AddSliceComponentToAsset(sliceAsset, entityList);
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequests::InstantiateEditorSlice, sliceAsset, worldTransform);
AZ::TickBus::ExecuteQueuedEvents();
}
void EntityOwnershipServiceTestFixture::AddSliceComponentToAsset(AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
const EntityList& entityList)
{
AZ::Entity* sliceEntity = aznew AZ::Entity();
AZ::SliceComponent* sliceComponent = sliceEntity->CreateComponent<AZ::SliceComponent>();
sliceComponent->SetSerializeContext(m_app->GetSerializeContext());
for (AZ::Entity* entity : entityList)
{
sliceComponent->AddEntity(entity);
}
sliceAsset->SetData(sliceEntity, sliceComponent);
}
}
@@ -0,0 +1,58 @@
/*
* 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 <AzCore/Asset/AssetManager.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/Slice/SliceAssetHandler.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
#include <AzFramework/Entity/SliceEntityOwnershipService.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipService.h>
namespace UnitTest
{
using EntityList = AZStd::vector<AZ::Entity*>;
class EntityOwnershipServiceTestFixture
: public AllocatorsTestFixture
{
protected:
AzFramework::RootSliceAsset GetRootSliceAsset();
void SetUpEntityOwnershipServiceTest();
void TearDownEntityOwnershipServiceTest();
AzFramework::SliceInstantiationTicket AddSlice(const EntityList& entityList, const bool isAsynchronous = false);
void AddEditorSlice(
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset, const AZ::Transform& worldTransform, const EntityList& entityList);
AzFramework::SliceInstantiationTicket AddSlice(const EntityList& entityList, const bool isAsynchronous,
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset);
void HandleEntitiesAdded(const AzFramework::EntityList& entityList);
void HandleEntitiesRemoved(const AzFramework::EntityIdList& entityIds);
bool ValidateEntities(const AzFramework::EntityList&);
void AddSliceComponentToAsset(AZ::Data::Asset<AZ::SliceAsset>& sliceAsset, const EntityList& entityList);
AZStd::unique_ptr<AzFramework::Application> m_app;
bool m_entitiesAddedCallbackTriggered = false;
bool m_entityRemovedCallbackTriggered = false;
bool m_validateEntitiesCallbackTriggered = false;
bool m_areEntitiesValidForContext = true;
class EntityOwnershipServiceApplication : public AzToolsFramework::ToolsApplication
{
public:
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
};
};
}
@@ -0,0 +1,266 @@
/*
* 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 <AzToolsFramework/Entity/SliceEditorEntityOwnershipService.h>
#include "EntityOwnershipServiceTestFixture.h"
namespace UnitTest
{
class SliceEditorEntityOwnershipTests
: public EntityOwnershipServiceTestFixture
{
public:
void SetUp() override
{
SetUpEntityOwnershipServiceTest();
m_sliceEditorEntityOwnershipService = AZStd::make_unique<AzToolsFramework::SliceEditorEntityOwnershipService>(
AZ::Uuid::CreateNull(), m_app->GetSerializeContext());
m_sliceEditorEntityOwnershipService->SetEntitiesAddedCallback([this](const AzFramework::EntityList& entityList)
{
this->HandleEntitiesAdded(entityList);
});
m_sliceEditorEntityOwnershipService->SetEntitiesRemovedCallback([this](const AzFramework::EntityIdList& entityIds)
{
this->HandleEntitiesRemoved(entityIds);
});
m_sliceEditorEntityOwnershipService->SetValidateEntitiesCallback([this](const AzFramework::EntityList& entityList)
{
return this->ValidateEntities(entityList);
});
m_sliceEditorEntityOwnershipService->Initialize();
}
void TearDown() override
{
m_sliceEditorEntityOwnershipService->Destroy();
m_sliceEditorEntityOwnershipService.reset();
TearDownEntityOwnershipServiceTest();
}
protected:
AZStd::unique_ptr<AzToolsFramework::SliceEditorEntityOwnershipService> m_sliceEditorEntityOwnershipService;
};
TEST_F(SliceEditorEntityOwnershipTests, Initialize_ResetOwnershipService_CreateRootSlice)
{
m_sliceEditorEntityOwnershipService->Reset();
EXPECT_TRUE(GetRootSliceAsset()->GetComponent() != nullptr);
}
TEST_F(SliceEditorEntityOwnershipTests, OnAssetReloaded_RootAssetReloaded_ReloadEntities)
{
// Clone the root slice asset
AZ::Data::Asset<AZ::SliceAsset> rootSliceAssetClone(GetRootSliceAsset().Get()->Clone(), AZ::Data::AssetLoadBehavior::Default);
AZ::Entity* sliceRootEntity = new AZ::Entity();
AZ::SliceComponent* sliceComponent = sliceRootEntity->CreateComponent<AZ::SliceComponent>();
sliceComponent->SetSerializeContext(m_app->GetSerializeContext());
sliceComponent->AddEntity(aznew AZ::Entity("testEntity"));
rootSliceAssetClone->SetData(sliceRootEntity, sliceComponent);
m_sliceEditorEntityOwnershipService->OnAssetReloaded(rootSliceAssetClone);
// Validate that entities-added callback is triggerted.
EXPECT_TRUE(m_entitiesAddedCallbackTriggered);
const AzFramework::EntityList& entitiesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetNewEntities();
// Validate that there is only one entity under root slice.
EXPECT_EQ(entitiesUnderRootSlice.size(), 1);
EXPECT_EQ(entitiesUnderRootSlice.at(0)->GetName(), "testEntity");
}
TEST_F(SliceEditorEntityOwnershipTests, LoadFromStream_RemapIdsFalse_IdsNotRemapped)
{
AZ::Entity* rootEntity = aznew AZ::Entity();
AZ::SliceComponent* rootSliceComponent = rootEntity->CreateComponent<AZ::SliceComponent>();
AZ::Entity* testEntity = aznew AZ::Entity();
rootSliceComponent->AddEntity(testEntity);
AZStd::vector<char> charBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char>> stream(&charBuffer);
AZ::Utils::SaveObjectToStream<AZ::Entity>(stream, AZ::ObjectStream::ST_XML, rootEntity, m_app->GetSerializeContext());
stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
EXPECT_TRUE(m_sliceEditorEntityOwnershipService->LoadFromStream(stream, false));
AZ::SliceComponent::EntityIdToEntityIdMap previousToNewIdMap;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(previousToNewIdMap,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetLoadedEntityIdMap);
// Verify that remapping of entityIds is not done by comparing the entityIds in previousToNewIdMap
EXPECT_TRUE(previousToNewIdMap.begin()->first == previousToNewIdMap.begin()->second);
delete rootEntity;
}
TEST_F(SliceEditorEntityOwnershipTests, InstantiateEditorSlice_ValidAssetProvided_SliceCreated)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AddEditorSlice(sliceAsset, AZ::Transform::CreateIdentity(), EntityList{});
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.size(), 1);
// Verify that the created slice has the same asset as the one it's provided to be created with.
EXPECT_EQ(sliceAsset, slicesUnderRootSlice.front().GetSliceAsset());
}
TEST_F(SliceEditorEntityOwnershipTests, PromoteEditorEntitiesIntoSlice_ValidEntitiesProvided_SliceCreated)
{
AZ::Entity* looseEntity = aznew AZ::Entity("testEntity");
m_sliceEditorEntityOwnershipService->AddEntity(looseEntity);
AZ::Entity* entityInSlice = aznew AZ::Entity("testEntity");
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AddSliceComponentToAsset(sliceAsset, EntityList{ entityInSlice });
AZ::SliceComponent::EntityIdToEntityIdMap looseEntityIdToSliceAssetEntityIdMap;
looseEntityIdToSliceAssetEntityIdMap.emplace(looseEntity->GetId(), entityInSlice->GetId());
// Verify that no slices exist.
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.size(), 0);
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::PromoteEditorEntitiesIntoSlice,
sliceAsset, looseEntityIdToSliceAssetEntityIdMap);
// Verify that one slice is created.
slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.size(), 1);
AZ::SliceComponent::SliceReference& sliceReference = slicesUnderRootSlice.front();
// Verify that there exists one slice instance with one entity and the correct slice asset.
ASSERT_EQ(sliceAsset, sliceReference.GetSliceAsset());
ASSERT_EQ(sliceReference.GetInstances().size(), 1);
AzFramework::EntityList entitiesOfSlice = sliceReference.GetInstances().begin()->GetInstantiated()->m_entities;
ASSERT_EQ(entitiesOfSlice.size(), 1);
// Verify that the entity in the created slice has the same id of the provided test entity.
EXPECT_EQ(entitiesOfSlice[0]->GetId(), looseEntity->GetId());
}
TEST_F(SliceEditorEntityOwnershipTests, DetachSliceEntities_ValidEntitiesProvided_EntitiesDetached)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AZ::Entity* entityInSlice = aznew AZ::Entity("testEntity");
AddEditorSlice(sliceAsset, AZ::Transform::CreateIdentity(), EntityList{ entityInSlice });
// Verify that one slice is created and it has one editor entity
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.size(), 1);
AZ::SliceComponent::SliceReference& sliceReference = slicesUnderRootSlice.front();
AzFramework::EntityList entitiesOfSlice = sliceReference.GetInstances().begin()->GetInstantiated()->m_entities;
ASSERT_EQ(entitiesOfSlice.size(), 1);
// Verify that owning slice for the editor entity exists.
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddressBeforeDetach;
AzFramework::SliceEntityRequestBus::EventResult(sliceInstanceAddressBeforeDetach, entitiesOfSlice[0]->GetId(),
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
EXPECT_TRUE(sliceInstanceAddressBeforeDetach.IsValid());
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::DetachSliceEntities,
AzToolsFramework::EntityIdList{ entitiesOfSlice[0]->GetId() });
// Verify that owning slice for the editor entity doesn't exist.
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddressAfterDetach;
AzFramework::SliceEntityRequestBus::EventResult(sliceInstanceAddressAfterDetach, entitiesOfSlice[0]->GetId(),
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
EXPECT_FALSE(sliceInstanceAddressAfterDetach.IsValid());
}
TEST_F(SliceEditorEntityOwnershipTests, DetachSliceInstances_ValidInstanceProvided_InstanceDetached)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
AddEditorSlice(sliceAsset, AZ::Transform::CreateIdentity(), EntityList{ testEntity });
// Verify that one slice exists before detaching it.
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.front().GetInstances().size(), 1);
// Verify that there are no loose entities in the editor.
EntityList looseEntitiesBeforeDetach;
m_sliceEditorEntityOwnershipService->GetNonPrefabEntities(looseEntitiesBeforeDetach);
EXPECT_TRUE(looseEntitiesBeforeDetach.size() == 0);
AZ::SliceComponent::SliceReference& sliceReference = slicesUnderRootSlice.front();
auto sliceInstanceIterator = sliceReference.GetInstances().begin();
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress(&sliceReference, &(*sliceInstanceIterator));
// Verify that there is one entity in the slice that is about to be detached.
EntityList entitiesInsliceBeforeDetach = sliceInstanceIterator->GetInstantiated()->m_entities;
EXPECT_TRUE(entitiesInsliceBeforeDetach.size() == 1);
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::DetachSliceInstances,
AZ::SliceComponent::SliceInstanceAddressSet{ sliceInstanceAddress });
// Verify that the only slice that existed is not there anymore after detaching it.
slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
EXPECT_EQ(slicesUnderRootSlice.front().GetInstances().size(), 0);
// Verify that that the detached slice entity is now a loose entity in the editor.
EntityList looseEntitiesAfterDetach;
m_sliceEditorEntityOwnershipService->GetNonPrefabEntities(looseEntitiesAfterDetach);
EXPECT_TRUE(looseEntitiesAfterDetach.size() == 1);
EXPECT_EQ(entitiesInsliceBeforeDetach[0]->GetId(), looseEntitiesAfterDetach[0]->GetId());
}
TEST_F(SliceEditorEntityOwnershipTests, RestoreSliceEntity_SliceEntityDeleted_SliceEntityRestored)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset;
sliceAsset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
AddEditorSlice(sliceAsset, AZ::Transform::CreateIdentity(), EntityList{ testEntity });
// Verify that one slice exists
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
ASSERT_EQ(slicesUnderRootSlice.front().GetInstances().size(), 1);
AZ::SliceComponent::SliceReference& sliceReference = slicesUnderRootSlice.front();
// Verify that one entity exists in the slice
EntityList entitiesOfSlice = sliceReference.GetInstances().begin()->GetInstantiated()->m_entities;
ASSERT_EQ(entitiesOfSlice.size(), 1);
// Get the slice entity ancestor and slice instance id before destroying the entity of the slice
AZ::SliceComponent::EntityAncestorList entityAncestorList;
sliceReference.GetInstanceEntityAncestry(entitiesOfSlice.front()->GetId(), entityAncestorList);
AZ::SliceComponent::SliceInstanceId sliceInstanceId = sliceReference.GetInstances().begin()->GetId();
m_sliceEditorEntityOwnershipService->DestroyEntityById(entitiesOfSlice.front()->GetId());
// Verify that no slices exists after slice entity is destroyed.
ASSERT_EQ(slicesUnderRootSlice.size(), 0);
// Restore the slice entity
AZ::SliceComponent::EntityRestoreInfo entityRestoreInfo = AZ::SliceComponent::EntityRestoreInfo(sliceAsset,
sliceInstanceId, entityAncestorList.front().m_entity->GetId(), AZ::DataPatch::FlagsMap{});
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::RestoreSliceEntity, entitiesOfSlice.front(),
entityRestoreInfo, AzToolsFramework::SliceEntityRestoreType::Deleted);
AZ::TickBus::ExecuteQueuedEvents();
// Verify that slice is restored with the same entity it had before.
ASSERT_EQ(slicesUnderRootSlice.size(), 1);
EntityList entitiesOfSliceAfterRestore = sliceReference.GetInstances().begin()->GetInstantiated()->m_entities;
ASSERT_EQ(entitiesOfSliceAfterRestore.size(), 1);
EXPECT_EQ(entitiesOfSliceAfterRestore.front()->GetId(), entitiesOfSlice.front()->GetId());
}
}
@@ -0,0 +1,406 @@
/*
* 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 <AzFramework/Entity/SliceEntityOwnershipService.h>
#include "EntityOwnershipServiceTestFixture.h"
#include <AzToolsFramework/Slice/SliceMetadataEntityContextBus.h>
namespace UnitTest
{
class SliceEntityOwnershipTests
: public EntityOwnershipServiceTestFixture
{
public:
void SetUp() override
{
SetUpEntityOwnershipServiceTest();
m_sliceEntityOwnershipService = AZStd::make_unique<AzFramework::SliceEntityOwnershipService>(AZ::Uuid::CreateNull(),
m_app->GetSerializeContext());
m_sliceEntityOwnershipService->Initialize();
m_sliceEntityOwnershipService->SetEntitiesAddedCallback([this](const AzFramework::EntityList& entityList)
{
this->HandleEntitiesAdded(entityList);
});
m_sliceEntityOwnershipService->SetEntitiesRemovedCallback([this](const AzFramework::EntityIdList& entityIds)
{
this->HandleEntitiesRemoved(entityIds);
});
m_sliceEntityOwnershipService->SetValidateEntitiesCallback([this](const AzFramework::EntityList& entityList)
{
return this->ValidateEntities(entityList);
});
}
void TearDown() override
{
m_sliceEntityOwnershipService->SetEntitiesAddedCallback(nullptr);
// In order for the death tests to work, we have to destroy the EOS early. So, don't try to destroy again.
if (m_sliceEntityOwnershipService->IsInitialized())
{
m_sliceEntityOwnershipService->Destroy();
}
m_sliceEntityOwnershipService.reset();
TearDownEntityOwnershipServiceTest();
}
protected:
AZStd::unique_ptr<AzFramework::SliceEntityOwnershipService> m_sliceEntityOwnershipService;
};
using SliceEntityOwnershipDeathTests = SliceEntityOwnershipTests;
TEST_F(SliceEntityOwnershipTests, AddEntity_InitalizedCorrectly_EntityCreated)
{
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
m_sliceEntityOwnershipService->AddEntity(testEntity);
// Validate that entities-added callback is triggerted.
EXPECT_TRUE(m_entitiesAddedCallbackTriggered);
const AzFramework::EntityList& entitiesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetNewEntities();
// Validate that there is only one entity under root slice.
EXPECT_EQ(entitiesUnderRootSlice.size(), 1);
EXPECT_EQ(entitiesUnderRootSlice.at(0)->GetName(), "testEntity");
}
TEST_F(SliceEntityOwnershipTests, DestroyEntityById_EntityAdded_EntityDestroyed)
{
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
m_sliceEntityOwnershipService->AddEntity(testEntity);
AzFramework::EntityList entitiesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetNewEntities();
// Verify that entity is added
EXPECT_EQ(entitiesUnderRootSlice.size(), 1);
EXPECT_TRUE(m_sliceEntityOwnershipService->DestroyEntityById(testEntity->GetId()));
entitiesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetNewEntities();
// Verify that entity is destroyed
EXPECT_EQ(entitiesUnderRootSlice.size(), 0);
}
TEST_F(SliceEntityOwnershipTests, GetRootSlice_RootAssetAbsent_ReturnNull)
{
m_sliceEntityOwnershipService->Destroy();
AZ::SliceComponent* rootSlice = nullptr;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
EXPECT_EQ(rootSlice, nullptr);
}
TEST_F(SliceEntityOwnershipTests, GetRootSlice_RootAssetPresent_ReturnRootSlice)
{
AZ::SliceComponent* rootSlice = nullptr;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
EXPECT_NE(rootSlice, nullptr);
}
TEST_F(SliceEntityOwnershipTests, Reset_SliceAdded_DestroySliceEntities)
{
AzFramework::EntityList entitiesToAdd = AzFramework::EntityList{ aznew AZ::Entity() };
AddSlice(entitiesToAdd);
size_t slicesCountBeforeReset = GetRootSliceAsset()->GetComponent()->GetSlices().size();
// Verify that slice exists
EXPECT_EQ(slicesCountBeforeReset, 1);
m_sliceEntityOwnershipService->Reset();
size_t slicesCountAfterReset = GetRootSliceAsset()->GetComponent()->GetSlices().size();
// Verify that slices under rootSlice were removed after reset of EntityOwnershipService.
EXPECT_EQ(slicesCountAfterReset, 0);
// Verify that call to destroy entities in the added slice occured.
EXPECT_TRUE(m_entityRemovedCallbackTriggered);
}
TEST_F(SliceEntityOwnershipTests, Reset_SliceInstantiationStarted_StopSliceInstantiation)
{
AddSlice(AzFramework::EntityList{}, true);
m_sliceEntityOwnershipService->Reset();
AZ::TickBus::ExecuteQueuedEvents();
size_t slicesCountUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices().size();
EXPECT_EQ(slicesCountUnderRootSlice, 0);
}
TEST_F(SliceEntityOwnershipTests, Reset_EntityAdded_EntityDestroyedAfterReset)
{
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
m_sliceEntityOwnershipService->AddEntity(testEntity);
m_sliceEntityOwnershipService->Reset();
const AzFramework::EntityList& entitiesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetNewEntities();
EXPECT_EQ(entitiesUnderRootSlice.size(), 0);
EXPECT_TRUE(m_entityRemovedCallbackTriggered);
}
TEST_F(SliceEntityOwnershipTests, HandleRootEntityReloadedFromStream_NoRootEntity_FailToLoadEntity)
{
bool rootEntityLoadSuccessful = false;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootEntityLoadSuccessful,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::HandleRootEntityReloadedFromStream, nullptr, false, nullptr);
EXPECT_FALSE(rootEntityLoadSuccessful);
}
TEST_F(SliceEntityOwnershipTests, HandleRootEntityReloadedFromStream_NoSliceComponent_FailToLoadEntity)
{
AZ::Entity* testEntity = aznew AZ::Entity();
// Suppress the AZ_Error thrown for not creating the root slice.
AZ_TEST_START_TRACE_SUPPRESSION;
bool rootEntityLoadSuccessful = false;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootEntityLoadSuccessful,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::HandleRootEntityReloadedFromStream, testEntity, false, nullptr);
EXPECT_FALSE(rootEntityLoadSuccessful);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
delete testEntity;
}
TEST_F(SliceEntityOwnershipTests, HandleRootEntityReloadedFromStream_RemapIdsTrue_IdsRemapped)
{
AZ::Entity* rootEntity = aznew AZ::Entity();
AZ::SliceComponent* rootSliceComponent = rootEntity->CreateComponent<AZ::SliceComponent>();
AZ::Entity* testEntity = aznew AZ::Entity();
rootSliceComponent->AddEntity(testEntity);
AZ::SliceComponent::EntityIdToEntityIdMap previousToNewIdMap;
previousToNewIdMap.emplace(testEntity->GetId(), testEntity->GetId());
bool rootEntityLoadSuccessful = false;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootEntityLoadSuccessful,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::HandleRootEntityReloadedFromStream,
rootEntity, true, &previousToNewIdMap);
EXPECT_TRUE(rootEntityLoadSuccessful);
// Verify that remapping of entityIds is done by comparing the entityIds in previousToNewIdMap
EXPECT_TRUE(previousToNewIdMap.begin()->first != previousToNewIdMap.begin()->second);
}
TEST_F(SliceEntityOwnershipTests, FindLoadedEntityIdMapping_IdsNotRemapped_EntityIdPresent)
{
AZ::Entity* rootEntity = aznew AZ::Entity();
AZ::SliceComponent* rootSliceComponent = rootEntity->CreateComponent<AZ::SliceComponent>();
AZ::Entity* testEntity = aznew AZ::Entity();
rootSliceComponent->AddEntity(testEntity);
bool rootEntityLoadSuccessful = false;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootEntityLoadSuccessful,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::HandleRootEntityReloadedFromStream, rootEntity, false, nullptr);
EXPECT_TRUE(rootEntityLoadSuccessful);
AZ::EntityId loadedEntityId;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(loadedEntityId,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::FindLoadedEntityIdMapping, testEntity->GetId());
// Verify that the entityId in the loadedEntityIdMap is same as the provided entityId, which happens when remapping is not done.
EXPECT_TRUE(loadedEntityId == testEntity->GetId());
}
TEST_F(SliceEntityOwnershipTests, FindLoadedEntityIdMapping_IdsRemapped_EntityIdAbsent)
{
AZ::Entity* rootEntity = aznew AZ::Entity();
AZ::SliceComponent* rootSliceComponent = rootEntity->CreateComponent<AZ::SliceComponent>();
AZ::Entity* testEntity = aznew AZ::Entity();
rootSliceComponent->AddEntity(testEntity);
AZ::SliceComponent::EntityIdToEntityIdMap previousToNewIdMap;
previousToNewIdMap.emplace(testEntity->GetId(), testEntity->GetId());
bool rootEntityLoadSuccessful = false;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootEntityLoadSuccessful,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::HandleRootEntityReloadedFromStream,
rootEntity, true, &previousToNewIdMap);
EXPECT_TRUE(rootEntityLoadSuccessful);
AZ::EntityId loadedEntityId;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(loadedEntityId,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::FindLoadedEntityIdMapping, testEntity->GetId());
// Verify that entityId is not present in the loadedEntityIdMap when remapping is done.
EXPECT_FALSE(loadedEntityId.IsValid());
}
TEST_F(SliceEntityOwnershipTests, OnAssetReady_RootSliceAssetReady_DoNotInstantiate)
{
m_sliceEntityOwnershipService->OnAssetReady(GetRootSliceAsset());
// Verify that validate entities callback is not triggered,
// which will only happen when an attempt to instantiate slice didn't occur.
EXPECT_FALSE(m_validateEntitiesCallbackTriggered);
}
TEST_F(SliceEntityOwnershipTests, OnAssetError_RootSliceAssetError_DoNotClearOtherSliceInstantiations)
{
AddSlice(AzFramework::EntityList{}, true);
m_sliceEntityOwnershipService->OnAssetError(GetRootSliceAsset());
// Try to finish any queued slice instantiations
AZ::TickBus::ExecuteQueuedEvents();
// Verify that slice instantiation was successful.
size_t slicesCountUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices().size();
EXPECT_EQ(slicesCountUnderRootSlice, 1);
}
TEST_F(SliceEntityOwnershipTests, OnAssetError_InstantiatingAssetError_StopSliceInstantiation)
{
AZ::Data::Asset<AZ::SliceAsset> sliceAsset1;
AZ::Data::AssetId sliceAsset1Id = AZ::Data::AssetId(AZ::Uuid::CreateRandom());
sliceAsset1.Create(sliceAsset1Id, false);
AddSlice(AzFramework::EntityList{}, true, sliceAsset1);
AZ::Data::Asset<AZ::SliceAsset> sliceAsset2;
sliceAsset2.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), false);
AddSlice(AzFramework::EntityList{}, true, sliceAsset2);
m_sliceEntityOwnershipService->OnAssetError(sliceAsset2);
// Try to finish any queued slice instantiations
AZ::TickBus::ExecuteQueuedEvents();
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
// Verify that there is only one slice under root slice
EXPECT_EQ(slicesUnderRootSlice.size(), 1);
// Verify that the slice without the asset error was instantiated
EXPECT_EQ(slicesUnderRootSlice.front().GetSliceAsset().GetId(), sliceAsset1Id);
}
TEST_F(SliceEntityOwnershipTests, InstantiateSlice_InvalidAssetId_ReturnBlankInstantiationTicket)
{
AZ::Entity* sliceEntity = aznew AZ::Entity();
AZ::SliceComponent* sliceComponent = sliceEntity->CreateComponent<AZ::SliceComponent>();
sliceComponent->SetSerializeContext(m_app->GetSerializeContext());
sliceComponent->AddEntity(aznew AZ::Entity());
// Set the asset id to null to invalidate it.
AZ::Data::Asset<AZ::SliceAsset> sliceAssetHolder = AZ::Data::AssetManager::Instance().
CreateAsset<AZ::SliceAsset>(AZ::Data::AssetId(AZ::Uuid::CreateNull()));
sliceAssetHolder.Get()->SetData(sliceEntity, sliceComponent);
AzFramework::SliceInstantiationTicket sliceInstantiationTicket;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(sliceInstantiationTicket,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::InstantiateSlice, sliceAssetHolder, nullptr, nullptr);
AZ::TickBus::ExecuteQueuedEvents();
// Verify that there is no request id or context id associated with the sliceInstantiationTicket
EXPECT_EQ(sliceInstantiationTicket.GetContextId(), AZ::Uuid::CreateNull());
EXPECT_EQ(sliceInstantiationTicket.GetRequestId(), 0);
}
TEST_F(SliceEntityOwnershipTests, InstantiateSlice_InstantiateTwoSlices_SlicesInstantiated)
{
// Add 2 slices asynchronously
AddSlice(AzFramework::EntityList{}, true);
AddSlice(AzFramework::EntityList{}, true);
AZ::TickBus::ExecuteQueuedEvents();
size_t slicesCountUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices().size();
EXPECT_EQ(slicesCountUnderRootSlice, 2);
}
TEST_F(SliceEntityOwnershipTests, CloneSliceInstance_InstantiateSlice_SliceCloned)
{
AZ::Entity* testEntity = aznew AZ::Entity("testEntity");
AddSlice(AzFramework::EntityList{ testEntity });
AZ::SliceComponent::EntityIdSet entityIdsInSlice;
GetRootSliceAsset()->GetComponent()->GetEntityIds(entityIdsInSlice);
AZ::SliceComponent* rootSlice = nullptr;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
AZ::SliceComponent::SliceInstanceAddress sourceSliceInstanceAddress = rootSlice->FindSlice(*entityIdsInSlice.begin());
AZ::SliceComponent::EntityIdToEntityIdMap entityIdToEntityIdMap;
AZ::SliceComponent::SliceInstanceAddress clonedSliceInstanceAddress;
AzFramework::SliceEntityOwnershipServiceRequestBus::BroadcastResult(clonedSliceInstanceAddress,
&AzFramework::SliceEntityOwnershipServiceRequests::CloneSliceInstance, sourceSliceInstanceAddress, entityIdToEntityIdMap);
// Verify that the entity was cloned successfully with the slice
EXPECT_EQ(clonedSliceInstanceAddress.GetInstance()->GetInstantiated()->m_entities.front()->GetName(), "testEntity");
// Verify that the source slice and the cloned slice have the same reference.
EXPECT_EQ(sourceSliceInstanceAddress.GetReference(), clonedSliceInstanceAddress.GetReference());
}
TEST_F(SliceEntityOwnershipTests, InstantiateSlice_EntitiesInvalid_SliceInstantiationFailed)
{
m_areEntitiesValidForContext = false;
AddSlice(AzFramework::EntityList{});
size_t slicesCountUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices().size();
// If entities are invalid, then slice instantiation would fail
EXPECT_EQ(slicesCountUnderRootSlice, 0);
}
TEST_F(SliceEntityOwnershipTests, CancelSliceInstantiation_SetupCorrect_SliceInstantiationCanceled)
{
AzFramework::SliceInstantiationTicket sliceInstantiationTicket = AddSlice(AzFramework::EntityList{}, true);
AzFramework::SliceEntityOwnershipServiceRequestBus::Broadcast(
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::CancelSliceInstantiation, sliceInstantiationTicket);
// This will try to finish any queued slice instantiations.
AZ::TickBus::ExecuteQueuedEvents();
size_t slicesCountUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices().size();
EXPECT_EQ(slicesCountUnderRootSlice, 0);
}
TEST_F(SliceEntityOwnershipTests, GetOwningSlice_SliceAdded_OwningSliceFetchedCorrectly)
{
AZ::SliceComponent::SliceList& slicesUnderRootSlice = GetRootSliceAsset()->GetComponent()->GetSlices();
AddSlice(AzFramework::EntityList{ aznew AZ::Entity() });
ASSERT_EQ(slicesUnderRootSlice.size(), 1);
AZ::SliceComponent::SliceReference& sliceReference = slicesUnderRootSlice.front();
ASSERT_EQ(sliceReference.GetInstances().size(), 1);
AzFramework::EntityList entitiesOfSlice = sliceReference.GetInstances().begin()->GetInstantiated()->m_entities;
ASSERT_EQ(entitiesOfSlice.size(), 1);
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
AzFramework::SliceEntityRequestBus::EventResult(sliceInstanceAddress, entitiesOfSlice.front()->GetId(),
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
// Verify that the source slice and the cloned slice have the same slice asset.
EXPECT_EQ(sliceInstanceAddress.GetReference()->GetSliceAsset(), sliceReference.GetSliceAsset());
}
TEST_F(SliceEntityOwnershipTests, GetOwningSlice_LooseEntityAdded_EntityHasNoOwningSlice)
{
AZ::Entity* testEntity = aznew AZ::Entity();
m_sliceEntityOwnershipService->AddEntity(testEntity);
AZ::SliceComponent::SliceInstanceAddress sliceInstanceAddress;
AzFramework::SliceEntityRequestBus::EventResult(sliceInstanceAddress, testEntity->GetId(),
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
// Verify that the loose entity doesn't belong to a slice instance
EXPECT_FALSE(sliceInstanceAddress.IsValid());
}
TEST_F(SliceEntityOwnershipDeathTests, AddEntity_RootSliceAssetAbsent_EntityNotCreated)
{
m_sliceEntityOwnershipService->Destroy();
AZ::Entity testEntity = AZ::Entity("testEntity");
EXPECT_DEATH(
{
m_sliceEntityOwnershipService->AddEntity(&testEntity);
}, ".*");
}
}
@@ -0,0 +1,249 @@
/*
* 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 <AzCore/Memory/AllocationRecords.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Component/EntityUtils.h>
#include <AzCore/PlatformIncl.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Asset/AssetCatalogComponent.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
//#include <AzToolsFramework/UI/Outliner/OutlinerWidget.hxx>
#include <AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.h>
#include <AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QApplication>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QFileDialog>
#include <QtCore/QTimer>
#pragma once
namespace UnitTest
{
using namespace AZ;
class EntityTestbed
: public AllocatorsFixture
, public QObject
{
public:
class TestbedApplication
: public AzToolsFramework::ToolsApplication
{
public:
AZ_CLASS_ALLOCATOR(TestbedApplication, AZ::SystemAllocator, 0);
TestbedApplication(EntityTestbed& testbed)
: m_testbed(testbed) {}
EntityTestbed& m_testbed;
};
QTimer* m_tickBusTimer = nullptr;
TestbedApplication* m_componentApplication = nullptr;
AZ::Entity* m_systemEntity = nullptr;
QApplication* m_qtApplication = nullptr;
QWidget* m_window = nullptr;
//AzToolsFramework::OutlinerWidget* m_outliner = nullptr;
AzToolsFramework::EntityPropertyEditor* m_propertyEditor = nullptr;
AZ::u32 m_entityCounter = 0;
AZ::IO::LocalFileIO m_localFileIO;
EntityTestbed()
: AllocatorsFixture()
{
}
virtual ~EntityTestbed()
{
if (m_tickBusTimer)
{
m_tickBusTimer->stop();
delete m_tickBusTimer;
m_tickBusTimer = nullptr;
}
Destroy();
}
virtual void OnSetup() {}
virtual void OnAddButtons(QHBoxLayout& layout) { (void)layout; }
virtual void OnEntityAdded(AZ::Entity& entity) { (void)entity; }
virtual void OnEntityRemoved(AZ::Entity& entity) { (void)entity; }
virtual void OnReflect(AZ::SerializeContext& context, AZ::Entity& systemEntity) { (void)context; (void)systemEntity; }
virtual void OnDestroy() {}
void Run(int argc = 0, char** argv = nullptr)
{
SetupComponentApplication();
m_qtApplication = new QApplication(argc, argv);
m_tickBusTimer = new QTimer(this);
m_qtApplication->connect(m_tickBusTimer, &QTimer::timeout,
[]()
{
AZ::TickBus::ExecuteQueuedEvents();
EBUS_EVENT(AZ::TickBus, OnTick, 0.3f, AZ::ScriptTimePoint());
}
);
m_tickBusTimer->start();
SetupUI();
OnSetup();
m_window->show();
m_qtApplication->exec();
}
void SetupUI()
{
m_window = new QWidget();
//m_outliner = aznew AzToolsFramework::OutlinerWidget(nullptr);
m_propertyEditor = aznew AzToolsFramework::EntityPropertyEditor(nullptr);
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
m_window->setMinimumHeight(600);
m_propertyEditor->setMinimumWidth(600);
//m_outliner->setMinimumWidth(100);
QVBoxLayout* leftLayout = new QVBoxLayout();
QHBoxLayout* outlinerLayout = new QHBoxLayout();
QHBoxLayout* outlinerButtonLayout = new QHBoxLayout();
//outlinerLayout->addWidget(m_outliner);
leftLayout->addLayout(outlinerLayout);
leftLayout->addLayout(outlinerButtonLayout);
QVBoxLayout* rightLayout = new QVBoxLayout();
QHBoxLayout* propertyLayout = new QHBoxLayout();
QHBoxLayout* propertyButtonLayout = new QHBoxLayout();
propertyLayout->addWidget(m_propertyEditor);
rightLayout->addLayout(propertyLayout);
rightLayout->addLayout(propertyButtonLayout);
QHBoxLayout* mainLayout = new QHBoxLayout();
m_window->setLayout(mainLayout);
mainLayout->addLayout(leftLayout, 1);
mainLayout->addLayout(rightLayout, 3);
// Add default buttons.
QPushButton* addEntity = new QPushButton(QString("Create"));
QPushButton* deleteEntities = new QPushButton(QString("Delete"));
outlinerButtonLayout->addWidget(addEntity);
outlinerButtonLayout->addWidget(deleteEntities);
m_qtApplication->connect(addEntity, &QPushButton::pressed, [ this ]() { this->AddEntity(); });
m_qtApplication->connect(deleteEntities, &QPushButton::pressed, [ this ]() { this->DeleteSelected(); });
// Test-specific buttons.
OnAddButtons(*outlinerButtonLayout);
}
void SetupComponentApplication()
{
AZ::ComponentApplication::Descriptor desc;
desc.m_enableDrilling = true;
desc.m_allocationRecords = true;
desc.m_recordingMode = AZ::Debug::AllocationRecords::RECORD_FULL;
desc.m_stackRecordLevels = 10;
desc.m_useExistingAllocator = true;
m_componentApplication = aznew TestbedApplication(*this);
AZ::IO::FileIOBase::SetInstance(&m_localFileIO);
m_componentApplication->Start(desc);
AZ::SerializeContext* serializeContext = m_componentApplication->GetSerializeContext();
serializeContext->CreateEditContext();
AzToolsFramework::Components::PropertyManagerComponent::CreateDescriptor();
const char* dir = m_componentApplication->GetExecutableFolder();
m_localFileIO.SetAlias("@assets@", dir);
m_localFileIO.SetAlias("@devassets@", dir);
}
void Destroy()
{
OnDestroy();
//delete m_outliner;
delete m_propertyEditor;
delete m_window;
delete m_qtApplication;
delete m_componentApplication;
//m_outliner = nullptr;
m_propertyEditor = nullptr;
m_window = nullptr;
m_qtApplication = nullptr;
m_componentApplication = nullptr;
if (AZ::Data::AssetManager::IsReady())
{
AZ::Data::AssetManager::Destroy();
}
AZ::IO::FileIOBase::SetInstance(nullptr);
}
void AddEntity()
{
AZStd::string entityName = AZStd::string::format("Entity%u", m_entityCounter);
AZ::EntityId entityId;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(entityId, &AzToolsFramework::EditorEntityContextRequests::CreateNewEditorEntity, entityName.c_str());
++m_entityCounter;
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
entity->Deactivate();
OnEntityAdded(*entity);
entity->Activate();
}
void DeleteSelected()
{
EBUS_EVENT(AzToolsFramework::ToolsApplicationRequests::Bus, DeleteSelected);
}
void SaveRoot()
{
const QString saveAs = QFileDialog::getSaveFileName(nullptr,
QString("Save As..."), QString("."), QString("Xml Files (*.xml)"));
if (!saveAs.isEmpty())
{
AZ::SliceComponent* rootSlice;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
rootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
AZ::Utils::SaveObjectToFile(saveAs.toUtf8().constData(), AZ::DataStream::ST_XML, rootSlice->GetEntity());
}
}
void ResetRoot()
{
EBUS_EVENT(AzToolsFramework::EditorEntityContextRequestBus, ResetEditorContext);
}
};
} // namespace UnitTest;
@@ -0,0 +1,477 @@
/*
* 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 "FrameworkApplicationFixture.h"
#include "Utils/Utils.h"
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/FileFunc/FileFunc.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzTest/AzTest.h>
#include <QTemporaryDir>
#include <QTextStream>
#include <QDir>
#include <QFileInfo>
namespace AzFramework
{
namespace FileFunc
{
namespace Internal
{
AZ::Outcome<void,AZStd::string> UpdateCfgContents(AZStd::string& cfgContents, const AZStd::list<AZStd::string>& updateRules);
AZ::Outcome<void,AZStd::string> UpdateCfgContents(AZStd::string& cfgContents, const AZStd::string& header, const AZStd::string& key, const AZStd::string& value);
AZ::Outcome<void, AZStd::string> WriteJsonToStream(const rapidjson::Document& document, AZ::IO::GenericStream& stream,
WriteJsonSettings settings = WriteJsonSettings{});
}
}
}
namespace UnitTest
{
class FileFuncTest : public ScopedAllocatorSetupFixture
{
public:
void SetUp()
{
m_prevFileIO = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FileIOBase::SetInstance(&m_fileIO);
}
void TearDown() override
{
AZ::IO::FileIOBase::SetInstance(m_prevFileIO);
}
AZ::IO::LocalFileIO m_fileIO;
AZ::IO::FileIOBase* m_prevFileIO;
};
TEST_F(FileFuncTest, UpdateCfgContents_InValidInput_Fail)
{
AZStd::string cfgContents = "[Foo]\n";
AZStd::list<AZStd::string> updateRules;
updateRules.push_back(AZStd::string("Foo/one*1"));
auto result = AzFramework::FileFunc::Internal::UpdateCfgContents(cfgContents, updateRules);
ASSERT_FALSE(result.IsSuccess());
}
TEST_F(FileFuncTest, UpdateCfgContents_ValidInput_Success)
{
AZStd::string cfgContents =
"[Foo]\n"
"one =2 \n"
"two= 3\n"
"three = 4\n"
"\n"
"[Bar]\n"
"four=3\n"
"five=3\n"
"six=3\n"
"eight=3\n";
AZStd::list<AZStd::string> updateRules;
updateRules.push_back(AZStd::string("Foo/one=1"));
updateRules.push_back(AZStd::string("Foo/two=2"));
updateRules.push_back(AZStd::string("three=3"));
auto result = AzFramework::FileFunc::Internal::UpdateCfgContents(cfgContents, updateRules);
EXPECT_TRUE(result.IsSuccess());
AZStd::string compareCfgContents =
"[Foo]\n"
"one =1\n"
"two= 2\n"
"three = 3\n"
"\n"
"[Bar]\n"
"four=3\n"
"five=3\n"
"six=3\n"
"eight=3\n";
bool equals = cfgContents.compare(compareCfgContents) == 0;
ASSERT_TRUE(equals);
}
TEST_F(FileFuncTest, UpdateCfgContents_ValidInputNewEntrySameHeader_Success)
{
AZStd::string cfgContents =
"[Foo]\n"
"one =2 \n"
"two= 3\n"
"three = 4\n";
AZStd::string header("[Foo]");
AZStd::string key("four");
AZStd::string value("4");
auto result = AzFramework::FileFunc::Internal::UpdateCfgContents(cfgContents, header, key, value);
EXPECT_TRUE(result.IsSuccess());
AZStd::string compareCfgContents =
"[Foo]\n"
"four=4\n"
"one =2 \n"
"two= 3\n"
"three = 4\n";
bool equals = cfgContents.compare(compareCfgContents) == 0;
ASSERT_TRUE(equals);
}
TEST_F(FileFuncTest, UpdateCfgContents_ValidInputNewEntryDifferentHeader_Success)
{
AZStd::string cfgContents =
";Sample Data\n"
"[Foo]\n"
"one =2 \n"
"two= 3\n"
"three = 4\n";
AZStd::list<AZStd::string> updateRules;
AZStd::string header("[Bar]");
AZStd::string key("four");
AZStd::string value("4");
auto result = AzFramework::FileFunc::Internal::UpdateCfgContents(cfgContents, header, key, value);
EXPECT_TRUE(result.IsSuccess());
AZStd::string compareCfgContents =
";Sample Data\n"
"[Foo]\n"
"one =2 \n"
"two= 3\n"
"three = 4\n"
"\n"
"[Bar]\n"
"four=4\n";
bool equals = cfgContents.compare(compareCfgContents) == 0;
ASSERT_TRUE(equals);
}
static 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;
}
TEST_F(FileFuncTest, FindFilesTest_EmptyFolder_Failure)
{
QTemporaryDir tempDir;
QDir tempPath(tempDir.path());
const char dependenciesPattern[] = "*_dependencies.xml";
bool recurse = true;
AZStd::string folderPath = tempPath.absolutePath().toStdString().c_str();
AZ::Outcome<AZStd::list<AZStd::string>, AZStd::string> result = AzFramework::FileFunc::FindFileList(folderPath.c_str(),
dependenciesPattern, recurse);
ASSERT_TRUE(result.IsSuccess());
ASSERT_EQ(result.GetValue().size(), 0);
}
TEST_F(FileFuncTest, FindFilesTest_DependenciesWildcards_Success)
{
QTemporaryDir tempDir;
QDir tempPath(tempDir.path());
const char* expectedFileNames[] = { "a_dependencies.xml","b_dependencies.xml" };
ASSERT_TRUE(CreateDummyFile(tempPath.absoluteFilePath(expectedFileNames[0]), QString("tempdata\n")));
ASSERT_TRUE(CreateDummyFile(tempPath.absoluteFilePath(expectedFileNames[1]), QString("tempdata\n")));
ASSERT_TRUE(CreateDummyFile(tempPath.absoluteFilePath("dependencies.xml"), QString("tempdata\n")));
const char dependenciesPattern[] = "*_dependencies.xml";
bool recurse = true;
AZStd::string folderPath = tempPath.absolutePath().toStdString().c_str();
AZ::Outcome<AZStd::list<AZStd::string>, AZStd::string> result = AzFramework::FileFunc::FindFileList(folderPath.c_str(),
dependenciesPattern, recurse);
ASSERT_TRUE(result.IsSuccess());
ASSERT_EQ(result.GetValue().size(), 2);
for (size_t i = 0; i < AZ_ARRAY_SIZE(expectedFileNames); ++i)
{
auto findElement = AZStd::find_if(result.GetValue().begin(), result.GetValue().end(), [&expectedFileNames, i](const AZStd::string& thisString)
{
AZStd::string thisFileName;
AzFramework::StringFunc::Path::GetFullFileName(thisString.c_str(), thisFileName);
return thisFileName == expectedFileNames[i];
});
ASSERT_NE(findElement, result.GetValue().end());
}
}
TEST_F(FileFuncTest, FindFilesTest_DependenciesWildcardsSubfolders_Success)
{
QTemporaryDir tempDir;
QDir tempPath(tempDir.path());
ASSERT_TRUE(CreateDummyFile(tempPath.absoluteFilePath("a_dependencies.xml"), QString("tempdata\n")));
ASSERT_TRUE(CreateDummyFile(tempPath.absoluteFilePath("b_dependencies.xml"), QString("tempdata\n")));
ASSERT_TRUE(CreateDummyFile(tempPath.absoluteFilePath("dependencies.xml"), QString("tempdata\n")));
const char dependenciesPattern[] = "*_dependencies.xml";
bool recurse = true;
AZStd::string folderPath = tempPath.absolutePath().toStdString().c_str();
ASSERT_TRUE(CreateDummyFile(tempPath.absoluteFilePath("subfolder1/c_dependencies.xml"), QString("tempdata\n")));
ASSERT_TRUE(CreateDummyFile(tempPath.absoluteFilePath("subfolder1/d_dependencies.xml"), QString("tempdata\n")));
ASSERT_TRUE(CreateDummyFile(tempPath.absoluteFilePath("subfolder1/dependencies.xml"), QString("tempdata\n")));
AZ::Outcome<AZStd::list<AZStd::string>, AZStd::string> result = AzFramework::FileFunc::FindFileList(folderPath.c_str(),
dependenciesPattern, recurse);
ASSERT_TRUE(result.IsSuccess());
ASSERT_EQ(result.GetValue().size(), 4);
const char* expectedFileNames[] = { "a_dependencies.xml","b_dependencies.xml", "c_dependencies.xml", "d_dependencies.xml" };
for (size_t i = 0; i < AZ_ARRAY_SIZE(expectedFileNames); ++i)
{
auto findElement = AZStd::find_if(result.GetValue().begin(), result.GetValue().end(), [&expectedFileNames, i](const AZStd::string& thisString)
{
AZStd::string thisFileName;
AzFramework::StringFunc::Path::GetFullFileName(thisString.c_str(), thisFileName);
return thisFileName == expectedFileNames[i];
});
ASSERT_NE(findElement, result.GetValue().end());
}
}
class JsonFileFuncTest
: public FrameworkApplicationFixture
{
protected:
void SetUp() override
{
FrameworkApplicationFixture::SetUp();
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
auto projectPathKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
registry->Set(projectPathKey, "AutomatedTesting");
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
m_jsonRegistrationContext = AZStd::make_unique<AZ::JsonRegistrationContext>();
m_jsonSystemComponent = AZStd::make_unique<AZ::JsonSystemComponent>();
m_serializationSettings.m_serializeContext = m_serializeContext.get();
m_serializationSettings.m_registrationContext = m_jsonRegistrationContext.get();
m_deserializationSettings.m_serializeContext = m_serializeContext.get();
m_deserializationSettings.m_registrationContext = m_jsonRegistrationContext.get();
m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get());
}
void TearDown() override
{
m_jsonRegistrationContext->EnableRemoveReflection();
m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get());
m_jsonRegistrationContext->DisableRemoveReflection();
m_jsonRegistrationContext.reset();
m_serializeContext.reset();
m_jsonSystemComponent.reset();
FrameworkApplicationFixture::TearDown();
}
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
AZStd::unique_ptr<AZ::JsonRegistrationContext> m_jsonRegistrationContext;
AZStd::unique_ptr<AZ::JsonSystemComponent> m_jsonSystemComponent;
AZ::JsonSerializerSettings m_serializationSettings;
AZ::JsonDeserializerSettings m_deserializationSettings;
};
TEST_F(JsonFileFuncTest, WriteJsonString_ValidJson_ExpectSuccess)
{
rapidjson::Document document;
document.SetObject();
document.AddMember("a", 1, document.GetAllocator());
document.AddMember("b", 2, document.GetAllocator());
document.AddMember("c", 3, document.GetAllocator());
AZStd::string expectedJsonText =
R"({
"a": 1,
"b": 2,
"c": 3
})";
expectedJsonText.erase(AZStd::remove_if(expectedJsonText.begin(), expectedJsonText.end(), ::isspace), expectedJsonText.end());
AZStd::string outString;
AZ::Outcome<void, AZStd::string> result = AzFramework::FileFunc::WriteJsonToString(document, outString);
EXPECT_TRUE(result.IsSuccess());
outString.erase(AZStd::remove_if(outString.begin(), outString.end(), ::isspace), outString.end());
EXPECT_EQ(expectedJsonText, outString) << "expected:\n" << expectedJsonText.c_str() << "\nactual:\n" << outString.c_str();
}
TEST_F(JsonFileFuncTest, WriteJsonStream_ValidJson_ExpectSuccess)
{
rapidjson::Document document;
document.SetObject();
document.AddMember("a", 1, document.GetAllocator());
document.AddMember("b", 2, document.GetAllocator());
document.AddMember("c", 3, document.GetAllocator());
AZStd::string expectedJsonText =
R"({
"a": 1,
"b": 2,
"c": 3
})";
expectedJsonText.erase(AZStd::remove_if(expectedJsonText.begin(), expectedJsonText.end(), ::isspace), expectedJsonText.end());
AZStd::vector<char> outBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char>> outStream{ &outBuffer };
AZ::Outcome<void, AZStd::string> result = AzFramework::FileFunc::Internal::WriteJsonToStream(document, outStream);
EXPECT_TRUE(result.IsSuccess());
outBuffer.push_back(0);
AZStd::string outString = outBuffer.data();
outString.erase(AZStd::remove_if(outString.begin(), outString.end(), ::isspace), outString.end());
EXPECT_EQ(expectedJsonText, outString) << "expected:\n" << expectedJsonText.c_str() << "\nactual:\n" << outString.c_str();
}
TEST_F(JsonFileFuncTest, WriteJsonFile_ValidJson_ExpectSuccess)
{
AZ::Test::ScopedAutoTempDirectory tempDir;
rapidjson::Document document;
document.SetObject();
document.AddMember("a", 1, document.GetAllocator());
document.AddMember("b", 2, document.GetAllocator());
document.AddMember("c", 3, document.GetAllocator());
AZStd::string expectedJsonText =
R"({
"a": 1,
"b": 2,
"c": 3
})";
expectedJsonText.erase(AZStd::remove_if(expectedJsonText.begin(), expectedJsonText.end(), ::isspace), expectedJsonText.end());
AZStd::string pathStr;
AzFramework::StringFunc::Path::ConstructFull(tempDir.GetDirectory(), "test.json", pathStr, true);
// Write the JSON to a file
AZ::IO::Path path(pathStr);
AZ::Outcome<void, AZStd::string> saveResult = AzFramework::FileFunc::WriteJsonFile(document, path);
EXPECT_TRUE(saveResult.IsSuccess());
// Verify that the contents of the file is what we expect
AZ::Outcome<AZStd::string, AZStd::string> readResult = AZ::Utils::ReadFile(pathStr);
EXPECT_TRUE(readResult.IsSuccess());
AZStd::string outString(readResult.TakeValue());
outString.erase(AZStd::remove_if(outString.begin(), outString.end(), ::isspace), outString.end());
EXPECT_EQ(outString, expectedJsonText);
// Clean up
AZ::IO::FileIOBase::GetInstance()->Remove(path.c_str());
}
TEST_F(JsonFileFuncTest, ReadJsonString_ValidJson_ExpectSuccess)
{
const char* jsonText =
R"(
{
"a": 1,
"b": 2,
"c": 3
})";
AZ::Outcome<rapidjson::Document, AZStd::string> result = AzFramework::FileFunc::ReadJsonFromString(jsonText);
EXPECT_TRUE(result.IsSuccess());
EXPECT_TRUE(result.GetValue().IsObject());
EXPECT_TRUE(result.GetValue().HasMember("a"));
EXPECT_TRUE(result.GetValue().HasMember("b"));
EXPECT_TRUE(result.GetValue().HasMember("c"));
EXPECT_EQ(result.GetValue()["a"].GetInt(), 1);
EXPECT_EQ(result.GetValue()["b"].GetInt(), 2);
EXPECT_EQ(result.GetValue()["c"].GetInt(), 3);
}
TEST_F(JsonFileFuncTest, ReadJsonString_InvalidJson_ErrorReportsLineNumber)
{
const char* jsonText =
R"(
{
"a": "This line is missing a comma"
"b": 2,
"c": 3
}
)";
AZ::Outcome<rapidjson::Document, AZStd::string> result = AzFramework::FileFunc::ReadJsonFromString(jsonText);
EXPECT_FALSE(result.IsSuccess());
EXPECT_TRUE(result.GetError().find("JSON parse error at line 4:") == 0);
}
TEST_F(JsonFileFuncTest, ReadJsonFile_ValidJson_ExpectSuccess)
{
AZ::Test::ScopedAutoTempDirectory tempDir;
const char* inputJsonText =
R"({
"a": 1,
"b": 2,
"c": 3
})";
rapidjson::Document expectedDocument;
expectedDocument.SetObject();
expectedDocument.AddMember("a", 1, expectedDocument.GetAllocator());
expectedDocument.AddMember("b", 2, expectedDocument.GetAllocator());
expectedDocument.AddMember("c", 3, expectedDocument.GetAllocator());
// Create test file
AZStd::string path;
AzFramework::StringFunc::Path::ConstructFull(tempDir.GetDirectory(), "test.json", path, true);
AZ::Outcome<void, AZStd::string> writeResult = AZ::Utils::WriteFile(inputJsonText, path);
EXPECT_TRUE(writeResult.IsSuccess());
// Read the JSON from the test file
AZ::Outcome<rapidjson::Document, AZStd::string> readResult = AzFramework::FileFunc::ReadJsonFile(path);
EXPECT_TRUE(readResult.IsSuccess());
EXPECT_EQ(expectedDocument, readResult.GetValue());
// Clean up
AZ::IO::FileIOBase::GetInstance()->Remove(path.c_str());
}
} // namespace UnitTest
@@ -0,0 +1,232 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
// Test that editor-components wrapped within a GenericComponentWrapper
// are moved out of the wrapper when a slice is loaded.
const char kWrappedEditorComponent[] =
R"DELIMITER(<ObjectStream version="1">
<Class name="SliceComponent" field="element" version="1" type="{AFD304E4-1773-47C8-855A-8B622398934F}">
<Class name="AZ::Component" field="BaseClass1" type="{EDFCB2CF-F75D-43BE-B26B-F35821B29247}">
<Class name="AZ::u64" field="Id" value="7737200995084371546" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
</Class>
<Class name="AZStd::vector" field="Entities" type="{2BADE35A-6F1B-4698-B2BC-3373D010020C}">
<Class name="AZ::Entity" field="element" version="2" type="{75651658-8663-478D-9090-2432DFCAFA44}">
<Class name="EntityId" field="Id" version="1" type="{6383F1D3-BB27-4E6B-A49A-6409B2059EAA}">
<Class name="AZ::u64" field="id" value="16119032733109672753" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
</Class>
<Class name="AZStd::string" field="Name" value="RigidPhysicsMesh" type="{EF8FF807-DDEE-4EB0-B678-4CA3A2C490A4}"/>
<Class name="bool" field="IsDependencyReady" value="true" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="AZStd::vector" field="Components" type="{2BADE35A-6F1B-4698-B2BC-3373D010020C}">
<Class name="GenericComponentWrapper" field="element" type="{68D358CA-89B9-4730-8BA6-E181DEA28FDE}">
<Class name="EditorComponentBase" field="BaseClass1" version="1" type="{D5346BD4-7F20-444E-B370-327ACD03D4A0}">
<Class name="AZ::Component" field="BaseClass1" type="{EDFCB2CF-F75D-43BE-B26B-F35821B29247}">
<Class name="AZ::u64" field="Id" value="11874523501682509824" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
</Class>
</Class>
<Class name="SelectionComponent" field="m_template" type="{73B724FC-43D1-4C75-ACF5-79AA8A3BF89D}">
<Class name="AZ::Component" field="BaseClass1" type="{EDFCB2CF-F75D-43BE-B26B-F35821B29247}">
<Class name="AZ::u64" field="Id" value="0" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
</Class>
</Class>
</Class>
</Class>
</Class>
</Class>
<Class name="AZStd::list" field="Prefabs" type="{B845AD64-B5A0-4CCD-A86B-3477A36779BE}"/>
<Class name="bool" field="IsDynamic" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
</Class>
</ObjectStream>)DELIMITER";
class WrappedEditorComponentTest
: public ::testing::Test
{
protected:
void SetUp() override
{
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
auto projectPathKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
registry->Set(projectPathKey, "AutomatedTesting");
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
m_app.Start(AZ::ComponentApplication::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);
m_slice.reset(AZ::Utils::LoadObjectFromBuffer<AZ::SliceComponent>(kWrappedEditorComponent, strlen(kWrappedEditorComponent) + 1));
if (m_slice)
{
if (m_slice->GetNewEntities().size() > 0)
{
m_entityFromSlice = m_slice->GetNewEntities()[0];
if (m_entityFromSlice)
{
if (m_entityFromSlice->GetComponents().size() > 0)
{
m_componentFromSlice = m_entityFromSlice->GetComponents()[0];
}
}
}
}
}
void TearDown() override
{
m_slice.reset();
m_app.Stop();
}
AzToolsFramework::ToolsApplication m_app;
AZStd::unique_ptr<AZ::SliceComponent> m_slice;
AZ::Entity* m_entityFromSlice = nullptr;
AZ::Component* m_componentFromSlice = nullptr;
};
TEST_F(WrappedEditorComponentTest, Slice_Loaded)
{
EXPECT_NE(m_slice.get(), nullptr);
}
TEST_F(WrappedEditorComponentTest, EntityFromSlice_Exists)
{
EXPECT_NE(m_entityFromSlice, nullptr);
}
TEST_F(WrappedEditorComponentTest, ComponentFromSlice_Exists)
{
EXPECT_NE(m_componentFromSlice, nullptr);
}
TEST_F(WrappedEditorComponentTest, Component_IsNotGenericComponentWrapper)
{
EXPECT_EQ(azrtti_cast<AzToolsFramework::Components::GenericComponentWrapper*>(m_componentFromSlice), nullptr);
}
// The swapped component should have adopted the GenericComponentWrapper's ComponentId.
TEST_F(WrappedEditorComponentTest, ComponentId_MatchesWrapperId)
{
EXPECT_EQ(m_componentFromSlice->GetId(), 11874523501682509824u);
}
const AZ::Uuid InGameOnlyComponentTypeId = "{1D538623-2052-464F-B0DA-D000E1520333}";
class InGameOnlyComponent
: public AZ::Component
{
public:
AZ_COMPONENT(InGameOnlyComponent, InGameOnlyComponentTypeId);
void Activate() override {}
void Deactivate() override {}
static void Reflect(AZ::ReflectContext* reflection)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
{
serializeContext->Class<InGameOnlyComponent, AZ::Component>();
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<InGameOnlyComponent>("InGame Only", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"));
}
}
}
};
const AZ::Uuid NoneEditorComponentTypeId = "{AE3454BA-D785-4EE2-A55B-A089F2B2916A}";
class NoneEditorComponent
: public AZ::Component
{
public:
AZ_COMPONENT(NoneEditorComponent, NoneEditorComponentTypeId);
void Activate() override {}
void Deactivate() override {}
static void Reflect(AZ::ReflectContext* reflection)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
{
serializeContext->Class<NoneEditorComponent, AZ::Component>();
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<NoneEditorComponent>("None Editor", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"));
}
}
}
};
class FindWrappedComponentsTest
: public ::testing::Test
{
public:
void SetUp() override
{
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
auto projectPathKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
registry->Set(projectPathKey, "AutomatedTesting");
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
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);
m_app.RegisterComponentDescriptor(InGameOnlyComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(NoneEditorComponent::CreateDescriptor());
m_entity = new AZ::Entity("Entity1");
AZ::Component* inGameOnlyComponent = nullptr;
AZ::ComponentDescriptorBus::EventResult(inGameOnlyComponent, InGameOnlyComponentTypeId, &AZ::ComponentDescriptorBus::Events::CreateComponent);
AZ::Component* genericComponent0 = aznew AzToolsFramework::Components::GenericComponentWrapper(inGameOnlyComponent);
m_entity->AddComponent(genericComponent0);
AZ::Component* noneEditorComponent = nullptr;
AZ::ComponentDescriptorBus::EventResult(noneEditorComponent, NoneEditorComponentTypeId, &AZ::ComponentDescriptorBus::Events::CreateComponent);
AZ::Component* genericComponent1 = aznew AzToolsFramework::Components::GenericComponentWrapper(noneEditorComponent);
m_entity->AddComponent(genericComponent1);
m_entity->Init();
}
void TearDown() override
{
m_app.Stop();
}
AzToolsFramework::ToolsApplication m_app;
AZ::Entity* m_entity = nullptr;
};
TEST_F(FindWrappedComponentsTest, found)
{
InGameOnlyComponent* ingameOnlyComponent = AzToolsFramework::FindWrappedComponentForEntity<InGameOnlyComponent>(m_entity);
EXPECT_NE(ingameOnlyComponent, nullptr);
NoneEditorComponent* noneEditorComponent = AzToolsFramework::FindWrappedComponentForEntity<NoneEditorComponent>(m_entity);
EXPECT_NE(noneEditorComponent, nullptr);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,128 @@
/*
* 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 <AzCore/Math/Uuid.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/SQLite/SQLiteConnection.h>
namespace UnitTest
{
using namespace AzToolsFramework;
static int s_numTablesToCreate = 100;
// we'll do about as much as we can get away with for about a second with most modern CPU
static int s_numTrialsToPerform = 10500;
class SQLiteTest
: public AllocatorsFixture
{
public:
SQLiteTest()
: AllocatorsFixture()
{
}
~SQLiteTest() = default;
void SetUp() override
{
AllocatorsFixture::SetUp();
m_database.reset(aznew SQLite::Connection());
m_randomDatabaseFileName = AZStd::string::format("%s_temp.sqlite", AZ::Uuid::CreateRandom().ToString<AZStd::string>().c_str());
m_database->Open(m_randomDatabaseFileName.c_str(), false);
}
void TearDown() override
{
m_database->Close();
m_database.reset();
AZ::IO::SystemFile::Delete(m_randomDatabaseFileName.c_str());
m_randomDatabaseFileName.set_capacity(0);
AllocatorsFixture::TearDown();
}
AZStd::string m_randomDatabaseFileName;
AZStd::unique_ptr<SQLite::Connection> m_database;
};
TEST_F(SQLiteTest, DoesTableExist_BadInputs_ShouldAssert)
{
ASSERT_TRUE(m_database->IsOpen());
// basic tests, bad input:
AZ_TEST_START_TRACE_SUPPRESSION;
EXPECT_FALSE(m_database->DoesTableExist(""));
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
AZ_TEST_START_TRACE_SUPPRESSION;
EXPECT_FALSE(m_database->DoesTableExist(nullptr));
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
// DoesTableExist had an off-by-one error in its string. It would not always crash.
// This just stress tests that function (which also tests statement creation and destruction) to ensure
// that if there is a problem with failing creation of functions, we don't crash.
TEST_F(SQLiteTest, DoesTableExist_BasicFuzzTest_BadTableNames_ShouldNotAssert_ShouldReturnFalse)
{
ASSERT_TRUE(m_database->IsOpen());
// now make up some random table names and try them out - none should exist.
AZStd::string randomJunkTableName;
randomJunkTableName.resize(16, '\0');
for (int trialNumber = 0; trialNumber < s_numTrialsToPerform; ++trialNumber)
{
for (int randomChar = 0; randomChar < 16; ++randomChar)
{
// note that this also puts characters AFTER the null, if a null appears in the mddle.
// so that if there are off by one errors they could include cruft afterwards.
randomJunkTableName[randomChar] = (char)(rand() % 256); // this will trigger invalid UTF8 decoding too
}
randomJunkTableName[0] = 'a'; // just to make sure we don't retry the null case.
EXPECT_FALSE(m_database->DoesTableExist(randomJunkTableName.c_str()));
}
}
// this makes sure that repeated calls to DoesTableExist does not cause some crazy assertion or failure
// if code is incorrect, it might, because DoesTableExists tends to create and destroy temporary statments.
// as a coincidence, this also serves as somewhat of a stress test for all the other parts of the database
// since this tests both creation of statements, execution of them, and retiring / cleaning the memory / freeing them
TEST_F(SQLiteTest, DoesTableExist_BasicStressTest_GoodTableNames_ShouldNotAssert_ShouldReturnTrue)
{
// --- SETUP PHASE ----
ASSERT_TRUE(m_database->IsOpen());
// outside scope to improve reuse memory performance.
AZStd::string randomValidTableName;
AZStd::string createDatabaseTableStatement;
for (int tableToCreate = 0; tableToCreate < s_numTablesToCreate; ++tableToCreate)
{
randomValidTableName = AZStd::string::format("testtable_%i", tableToCreate);
createDatabaseTableStatement = AZStd::string::format(
"CREATE TABLE IF NOT EXISTS %s( "
" rowID INTEGER PRIMARY KEY, "
" version INTEGER NOT NULL);", randomValidTableName.c_str());
m_database->AddStatement(randomValidTableName, createDatabaseTableStatement);
EXPECT_TRUE(m_database->ExecuteOneOffStatement(randomValidTableName.c_str()));
m_database->RemoveStatement(randomValidTableName.c_str());
}
// --- TEST PHASE ----
for (int trialNumber = 0; trialNumber < s_numTrialsToPerform; ++trialNumber)
{
randomValidTableName = AZStd::string::format("testtable_%i", rand() % s_numTablesToCreate);
EXPECT_TRUE(m_database->DoesTableExist(randomValidTableName.c_str()));
}
}
}
@@ -0,0 +1,223 @@
/*
* 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 <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Script/ScriptAsset.h>
#include <AzCore/Script/ScriptSystemComponent.h>
#include <AzCore/Script/ScriptContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/ToolsComponents/ScriptEditorComponent.h>
#include "EntityTestbed.h"
extern "C" {
# include <Lua/lualib.h>
# include <Lua/lauxlib.h>
}
namespace UnitTest
{
using namespace AZ;
using namespace AzFramework;
// Global Properties used for Testing
int mySubValue = 0;
int myReloadValue = 0;
class ScriptComponentTest
: public testing::Test
{
public:
AZ_TYPE_INFO(ScriptComponentTest, "{85CDBD49-70FF-416A-8154-B5525EDD30D4}");
void SetUp() override
{
ComponentApplication::Descriptor appDesc;
appDesc.m_memoryBlocksByteSize = 100 * 1024 * 1024;
//appDesc.m_recordsMode = AllocationRecords::RECORD_FULL;
//appDesc.m_stackRecordLevels = 20;
Entity* systemEntity = m_app.Create(appDesc);
systemEntity->CreateComponent<MemoryComponent>();
systemEntity->CreateComponent("{CAE3A025-FAC9-4537-B39E-0A800A2326DF}"); // JobManager component
systemEntity->CreateComponent<StreamerComponent>();
systemEntity->CreateComponent<AssetManagerComponent>();
systemEntity->CreateComponent("{A316662A-6C3E-43E6-BC61-4B375D0D83B4}"); // Usersettings component
systemEntity->CreateComponent<ScriptSystemComponent>();
systemEntity->Init();
systemEntity->Activate();
EBUS_EVENT_RESULT(m_scriptContext, ScriptSystemRequestBus, GetContext, DefaultScriptContextId);
EBUS_EVENT_RESULT(m_behaviorContext, AZ::ComponentApplicationBus, GetBehaviorContext);
EBUS_EVENT_RESULT(m_serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AzToolsFramework::Components::ScriptEditorComponent::CreateDescriptor(); // descriptor is deleted by app
AzToolsFramework::Components::ScriptEditorComponent::Reflect(m_serializeContext);
ScriptComponent::CreateDescriptor(); // descriptor is deleted by app
ScriptComponent::Reflect(m_serializeContext);
}
void TearDown() override
{
m_app.Destroy();
}
Data::Asset<ScriptAsset> CreateAndLoadScriptAsset(const AZStd::string& script)
{
Data::Asset<ScriptAsset> scriptAsset = Data::AssetManager::Instance().CreateAsset<ScriptAsset>(Uuid::CreateRandom());
scriptAsset.Get()->m_scriptBuffer.insert(scriptAsset.Get()->m_scriptBuffer.begin(), script.begin(), script.end());
EBUS_EVENT(Data::AssetManagerBus, OnAssetReady, scriptAsset);
m_app.Tick();
m_app.TickSystem(); // flush assets etc.
return scriptAsset;
}
static ScriptComponent* BuildGameEntity(const Data::Asset<ScriptAsset>& scriptAsset, Entity& gameEntity)
{
// We first setup the ScriptEditorComponent.
// After a script asset is loaded the ScriptEditorComponent builds the properties table.
// BuildGameEntity() hands off the properties table to the game runtime ScriptComponent.
Entity editorEntity;
auto* scriptEditorComponent = editorEntity.CreateComponent<AzToolsFramework::Components::ScriptEditorComponent>();
scriptEditorComponent->SetScript(scriptAsset);
editorEntity.Init();
editorEntity.Activate();
scriptEditorComponent->BuildGameEntity(&gameEntity);
auto* scriptComponent = gameEntity.FindComponent<ScriptComponent>();
return scriptComponent;
}
static void OverwriteScriptBuffer(Data::Asset<ScriptAsset> scriptAsset, AZStd::string newScript)
{
scriptAsset.Get()->m_scriptBuffer.insert(scriptAsset.Get()->m_scriptBuffer.begin(), newScript.begin(), newScript.end());
}
ComponentApplication m_app;
ScriptContext* m_scriptContext = nullptr;
BehaviorContext* m_behaviorContext = nullptr;
SerializeContext* m_serializeContext = nullptr;
};
TEST_F(ScriptComponentTest, ScriptInstancesCanReadButDontModifySourceTable)
{
// make sure script instances don't can read only share data, but don't modify the source table
const AZStd::string script = "test = {\
--[[test with no properties table as this should work too!]]\
state = {\
mysubstate = {\
mysubvalue = 2,\
},\
myvalue = 0,\
},\
}\
function test:OnActivate()\
self.state.mysubstate.mysubvalue = 5\
end\
return test;";
Data::Asset<ScriptAsset> scriptAsset = CreateAndLoadScriptAsset(script);
auto* entity1 = aznew Entity();
entity1->CreateComponent<ScriptComponent>()->SetScript(scriptAsset);
entity1->Init();
entity1->Activate();
auto* entity2 = aznew Entity();
entity2->CreateComponent<ScriptComponent>()->SetScript(scriptAsset);
entity2->Init();
entity2->Activate();
m_behaviorContext->Property("globalMySubValue", BehaviorValueProperty(&mySubValue));
m_scriptContext->Execute("globalMySubValue = test.state.mysubstate.mysubvalue", "Read my subvalue");
AZ_TEST_ASSERT(mySubValue == 2); // we should not have changed test. table but the instance table of each component.
delete entity1;
delete entity2;
}
TEST_F(ScriptComponentTest, ScriptReloads)
{
// Test script reload
m_behaviorContext->Property("myReloadValue", BehaviorValueProperty(&myReloadValue));
const AZStd::string script1 ="local testReload = {}\
function testReload:OnActivate()\
myReloadValue = 1\
end\
function testReload:OnDeactivate()\
myReloadValue = 0\
end\
return testReload;";
Data::Asset<ScriptAsset> scriptAsset1 = CreateAndLoadScriptAsset(script1);
auto* entity = aznew Entity();
entity->CreateComponent<ScriptComponent>()->SetScript(scriptAsset1);
entity->Init();
entity->Activate();
// test value, it should set during activation of the first script
AZ_TEST_ASSERT(myReloadValue == 1);
const AZStd::string script2 ="local testReload = {}\
function testReload:OnActivate()\
myReloadValue = 5\
end\
return testReload";
// modify the asset
Data::Asset<ScriptAsset> scriptAsset2(aznew ScriptAsset(scriptAsset1.GetId()), AZ::Data::AssetLoadBehavior::Default);
OverwriteScriptBuffer(scriptAsset2, script2);
// When reloading script assets from files, ScriptSystemComponent would clear old script caches automatically in the
// function `ScriptSystemComponent::LoadAssetData()`. But here we are changing script directly in memory, therefore we
// need to clear old cache manually.
AZ::ScriptSystemRequestBus::Broadcast(&AZ::ScriptSystemRequestBus::Events::ClearAssetReferences, scriptAsset1.GetId());
// trigger reload
Data::AssetManager::Instance().ReloadAssetFromData(scriptAsset2);
// ReloadAssetFromData is (now) a queued event
// Need to tick subsystems here to receive reload event.
m_app.Tick();
m_app.TickSystem();
// test value with the reloaded value
EXPECT_EQ(5, myReloadValue);
delete entity;
}
TEST_F(ScriptComponentTest, LuaPropertiesAreDiscovered)
{
const AZStd::string script = "local test = {\
Properties = {\
myNum = { default = 2 },\
},\
}\
function test:OnActivate()\
self.Properties.myNum = 5\
end\
return test";
const Data::Asset<ScriptAsset> scriptAsset = CreateAndLoadScriptAsset(script);
Entity editorEntity, gameEntity;
auto* scriptComponent = BuildGameEntity(scriptAsset, gameEntity);
EXPECT_NE(scriptComponent->GetScriptProperty("myNum"), nullptr);
}
} // namespace UnitTest
@@ -0,0 +1,80 @@
/*
* 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 <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Script/ScriptAsset.h>
#include <AzCore/Script/ScriptSystemComponent.h>
#include <AzCore/Script/ScriptContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/ToolsComponents/ScriptEditorComponent.h>
#include "EntityTestbed.h"
namespace UnitTest
{
using namespace AZ;
using namespace AzFramework;
class EntityScriptTest
: public EntityTestbed
{
public:
ScriptContext* m_scriptContext;
~EntityScriptTest()
{
}
void OnDestroy() override
{
delete m_scriptContext;
m_scriptContext = nullptr;
}
void run()
{
int argc = 0;
char* argv = nullptr;
Run(argc, &argv);
}
void OnReflect(AZ::SerializeContext& context, AZ::Entity& systemEntity) override
{
(void)context;
(void)systemEntity;
}
void OnSetup() override
{
m_scriptContext = aznew AZ::ScriptContext();
auto* catalogBus = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
if (catalogBus)
{
// Register asset types the asset DB should query our catalog for.
catalogBus->AddAssetType(AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid());
// Build the catalog (scan).
catalogBus->AddExtension(".lua");
}
}
void OnEntityAdded(AZ::Entity& entity) override
{
// Add your components.
entity.CreateComponent<AzToolsFramework::Components::ScriptEditorComponent>();
entity.Activate();
}
};
TEST_F(EntityScriptTest, DISABLED_Test)
{
run();
}
} // namespace UnitTest
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff