You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/AzCore/Tests/RemappableId.cpp

403 lines
17 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/IdUtils.h>
namespace UnitTest
{
using namespace AZ;
//! Unit Test for IdUtils GenerateNewIdsAndFixRefs functions for generating new unique ids
class RemappableIdTest
: public AllocatorsFixture
{
public:
RemappableIdTest()
: AllocatorsFixture()
{
}
~RemappableIdTest() override
{
}
void SetUp() override
{
AllocatorsFixture::SetUp();
m_serializeContext.reset(aznew AZ::SerializeContext());
Entity::Reflect(m_serializeContext.get());
RemapIdData::Reflect(*m_serializeContext);
RemapUuidAndEntityIdData::Reflect(*m_serializeContext);
ParentEntityContainer::Reflect(*m_serializeContext);
RootParentElementWrapper::Reflect(*m_serializeContext);
}
void TearDown() override
{
m_serializeContext.reset();
AllocatorsFixture::TearDown();
}
AZStd::unique_ptr<SerializeContext> m_serializeContext;
struct RemapIdData
{
AZ_TYPE_INFO(RemapIdData, "{0431A85A-C2CC-453A-92A7-807BCEAB46D7}");
AZ_CLASS_ALLOCATOR(RemapIdData, AZ::SystemAllocator, 0);
static void Reflect(AZ::SerializeContext& serializeContext)
{
serializeContext.Class<RemapIdData>()
->Field("m_remappableUuid", &RemapIdData::m_remappableUuid)
->Attribute(AZ::Edit::Attributes::IdGeneratorFunction, &AZ::Uuid::CreateRandom)
->Field("m_uuid1", &RemapIdData::m_uuid1)
->Field("m_uuid2", &RemapIdData::m_uuid2)
;
}
Uuid m_remappableUuid;
Uuid m_uuid1;
Uuid m_uuid2;
};
struct RemapUuidAndEntityIdData
{
AZ_TYPE_INFO(RemapUuidAndEntityIdData, "{6951A116-505C-47E0-9310-2E60D040F390}");
AZ_CLASS_ALLOCATOR(RemapUuidAndEntityIdData, AZ::SystemAllocator, 0);
// copying entities is forbidden.
RemapUuidAndEntityIdData()
{
}
RemapUuidAndEntityIdData(const RemapUuidAndEntityIdData& other)
{
m_remappableUuid = other.m_remappableUuid;
m_uuid1 = other.m_uuid1;
m_uuid2 = other.m_uuid2;
m_remappableEntityId = other.m_remappableEntityId;
m_entityId1 = other.m_entityId1;
m_entityId2 = other.m_entityId2;
m_entity.SetId(other.m_entity.GetId()); // for this test, we only copy the ID.
m_entityRef = other.m_entityRef;
}
static void Reflect(AZ::SerializeContext& serializeContext)
{
serializeContext.Class<RemapUuidAndEntityIdData>()
->Field("m_remappableUuid", &RemapUuidAndEntityIdData::m_remappableUuid)
->Attribute(AZ::Edit::Attributes::IdGeneratorFunction, &AZ::Uuid::CreateRandom)
->Field("m_uuid1", &RemapUuidAndEntityIdData::m_uuid1)
->Field("m_uuid2", &RemapUuidAndEntityIdData::m_uuid2)
->Field("m_remappbleEntityId", &RemapUuidAndEntityIdData::m_remappableEntityId)
->Attribute(AZ::Edit::Attributes::IdGeneratorFunction, &AZ::Entity::MakeId)
->Field("m_entityId1", &RemapUuidAndEntityIdData::m_entityId1)
->Field("m_entityId2", &RemapUuidAndEntityIdData::m_entityId2)
->Field("m_entity", &RemapUuidAndEntityIdData::m_entity)
->Field("m_entityRef", &RemapUuidAndEntityIdData::m_entityRef)
;
}
Uuid m_remappableUuid;
Uuid m_uuid1;
Uuid m_uuid2;
AZ::EntityId m_remappableEntityId;
AZ::EntityId m_entityId1;
AZ::EntityId m_entityId2;
AZ::Entity m_entity;
AZ::EntityId m_entityRef;
};
struct ParentEntityContainer
{
AZ_TYPE_INFO(ParentEntityContainer, "{60EFD610-4B9A-444C-9004-A651B772927F}");
AZ_CLASS_ALLOCATOR(ParentEntityContainer, AZ::SystemAllocator, 0);
static void Reflect(AZ::SerializeContext& serializeContext);
AZ::EntityId m_remappableEntityId1;
AZ::EntityId m_remappableEntityId2;
int m_beginWriteCount = 0;
int m_endWriteCount = 0;
};
struct RootParentElementWrapper
{
AZ_TYPE_INFO(RootParentElementWrapper, "{BFC46F3C-D696-4A34-B824-F18428CAA910}");
AZ_CLASS_ALLOCATOR(RootParentElementWrapper, AZ::SystemAllocator, 0);
static void Reflect(AZ::SerializeContext& serializeContext)
{
serializeContext.Class<RootParentElementWrapper>()
->Field("m_parentEntityContainer", &RootParentElementWrapper::m_parentEntityContainer)
;
}
~RootParentElementWrapper()
{
delete m_parentEntityContainer;
}
ParentEntityContainer* m_parentEntityContainer = nullptr;
};
struct ParentEntityContainerEventHandler
: public AZ::SerializeContext::IEventHandler
{
static bool s_isCheckingCallbackOrder;
void OnWriteBegin(void* classPtr) override
{
auto parentEntityContainer = reinterpret_cast<ParentEntityContainer*>(classPtr);
if (s_isCheckingCallbackOrder)
{
EXPECT_EQ(parentEntityContainer->m_beginWriteCount, parentEntityContainer->m_endWriteCount);
}
parentEntityContainer->m_beginWriteCount++;
}
void OnWriteEnd(void* classPtr) override
{
auto parentEntityContainer = reinterpret_cast<ParentEntityContainer*>(classPtr);
if (s_isCheckingCallbackOrder)
{
EXPECT_EQ(parentEntityContainer->m_beginWriteCount, parentEntityContainer->m_endWriteCount + 1);
}
parentEntityContainer->m_endWriteCount++;
}
};
};
bool RemappableIdTest::ParentEntityContainerEventHandler::s_isCheckingCallbackOrder = false;
void RemappableIdTest::ParentEntityContainer::Reflect(AZ::SerializeContext& serializeContext)
{
serializeContext.Class<ParentEntityContainer>()
->EventHandler<ParentEntityContainerEventHandler>()
->Field("m_remappableEntityId1", &ParentEntityContainer::m_remappableEntityId1)
->Attribute(AZ::Edit::Attributes::IdGeneratorFunction, &AZ::Entity::MakeId)
->Field("m_remappableEntityId2", &ParentEntityContainer::m_remappableEntityId2)
->Attribute(AZ::Edit::Attributes::IdGeneratorFunction, &AZ::Entity::MakeId)
;
}
TEST_F(RemappableIdTest, UuidRemapTest)
{
RemapIdData testData;
testData.m_remappableUuid = AZ::Uuid::CreateRandom();
testData.m_uuid1 = testData.m_remappableUuid;
testData.m_uuid2 = AZ::Uuid::CreateRandom();
EXPECT_EQ(testData.m_remappableUuid, testData.m_uuid1);
EXPECT_NE(testData.m_remappableUuid, testData.m_uuid2);
RemapIdData originalData = testData;
EXPECT_EQ(originalData.m_remappableUuid, testData.m_remappableUuid);
EXPECT_EQ(originalData.m_uuid1, testData.m_uuid1);
EXPECT_EQ(originalData.m_uuid2, testData.m_uuid2);
AZStd::unordered_map<Uuid, Uuid> remapIds;
IdUtils::Remapper<Uuid>::GenerateNewIdsAndFixRefs(&testData, remapIds, m_serializeContext.get());
EXPECT_NE(originalData.m_remappableUuid, testData.m_remappableUuid);
EXPECT_NE(originalData.m_uuid1, testData.m_uuid1);
// Uuid2 never gets remapped so it should remain the same throughout the test
EXPECT_EQ(originalData.m_uuid2, testData.m_uuid2);
// Uuid1 has the same value as the RemappableUuid so it gets updated as well
EXPECT_EQ(testData.m_remappableUuid, testData.m_uuid1);
auto remapIdsIt = remapIds.find(originalData.m_remappableUuid);
EXPECT_NE(remapIds.end(), remapIdsIt);
EXPECT_EQ(testData.m_remappableUuid, remapIdsIt->second);
}
TEST_F(RemappableIdTest, UuidAndEntityIdRemapTest)
{
RemapUuidAndEntityIdData testData;
testData.m_remappableUuid = AZ::Uuid::CreateRandom();
testData.m_uuid1 = testData.m_remappableUuid;
testData.m_uuid2 = AZ::Uuid::CreateRandom();
testData.m_remappableEntityId = AZ::Entity::MakeId();
testData.m_entityId1 = testData.m_remappableEntityId;
testData.m_entityId2 = AZ::Entity::MakeId();
testData.m_entityRef = testData.m_entity.GetId();
//Uuid Compare
EXPECT_EQ(testData.m_remappableUuid, testData.m_uuid1);
EXPECT_NE(testData.m_remappableUuid, testData.m_uuid2);
// EntityId Compare
EXPECT_EQ(testData.m_remappableEntityId, testData.m_entityId1);
EXPECT_NE(testData.m_remappableEntityId, testData.m_entityId2);
RemapUuidAndEntityIdData originalData = testData;
EXPECT_EQ(originalData.m_remappableUuid, testData.m_remappableUuid);
EXPECT_EQ(originalData.m_uuid1, testData.m_uuid1);
EXPECT_EQ(originalData.m_uuid2, testData.m_uuid2);
EXPECT_EQ(originalData.m_remappableEntityId, testData.m_remappableEntityId);
EXPECT_EQ(originalData.m_entityId1, testData.m_entityId1);
EXPECT_EQ(originalData.m_entityId2, testData.m_entityId2);
EXPECT_EQ(originalData.m_entity.GetId(), testData.m_entity.GetId());
EXPECT_EQ(originalData.m_entityRef, testData.m_entityRef);
// Remap Uuids
AZStd::unordered_map<Uuid, Uuid> remapUuids;
IdUtils::Remapper<Uuid>::GenerateNewIdsAndFixRefs(&testData, remapUuids, m_serializeContext.get());
// Remap EntityIds
AZStd::unordered_map<EntityId, EntityId> remapEntityIds;
IdUtils::Remapper<EntityId>::GenerateNewIdsAndFixRefs(&testData, remapEntityIds, m_serializeContext.get());
// Remapped Uuid Compare
// Uuid1 has the same value as the RemappableUuid so it gets updated as well
EXPECT_NE(originalData.m_remappableUuid, testData.m_remappableUuid);
EXPECT_NE(originalData.m_uuid1, testData.m_uuid1);
EXPECT_EQ(testData.m_remappableUuid, testData.m_uuid1);
// Uuid2 never gets remapped so it should remain the same throughout the test
EXPECT_EQ(originalData.m_uuid2, testData.m_uuid2);
// Remapped EntityId Compare
// EntityId1 has the same value as the RemappableEntityId so it gets updated as well
EXPECT_NE(originalData.m_remappableEntityId, testData.m_remappableEntityId);
EXPECT_NE(originalData.m_entityId1, testData.m_entityId1);
EXPECT_EQ(testData.m_remappableEntityId, testData.m_entityId1);
EXPECT_NE(originalData.m_entity.GetId(), testData.m_entity.GetId());
EXPECT_NE(originalData.m_entityRef, testData.m_entityRef);
EXPECT_EQ(testData.m_entity.GetId(), testData.m_entityRef);
// EntityId does not match the RemappableEntityId so it does not get remapped
EXPECT_EQ(originalData.m_entityId2, testData.m_entityId2);
auto remapUuidIt = remapUuids.find(originalData.m_remappableUuid);
EXPECT_NE(remapUuids.end(), remapUuidIt);
EXPECT_EQ(testData.m_remappableUuid, remapUuidIt->second);
auto remapEntityIdIt = remapEntityIds.find(originalData.m_remappableEntityId);
EXPECT_NE(remapEntityIds.end(), remapEntityIdIt);
EXPECT_EQ(testData.m_remappableEntityId, remapEntityIdIt->second);
}
TEST_F(RemappableIdTest, CloneObjectAndRemapUuidAndEntityIdTest)
{
RemapUuidAndEntityIdData testData;
testData.m_remappableUuid = AZ::Uuid::CreateRandom();
testData.m_uuid1 = testData.m_remappableUuid;
testData.m_uuid2 = AZ::Uuid::CreateRandom();
testData.m_remappableEntityId = AZ::Entity::MakeId();
testData.m_entityId1 = testData.m_remappableEntityId;
testData.m_entityId2 = AZ::Entity::MakeId();
testData.m_entityRef = testData.m_entity.GetId();
//Uuid Compare
EXPECT_EQ(testData.m_remappableUuid, testData.m_uuid1);
EXPECT_NE(testData.m_remappableUuid, testData.m_uuid2);
// EntityId Compare
EXPECT_EQ(testData.m_remappableEntityId, testData.m_entityId1);
EXPECT_NE(testData.m_remappableEntityId, testData.m_entityId2);
// Clone Object and Remap Uuids
AZStd::unordered_map<Uuid, Uuid> remapUuids;
RemapUuidAndEntityIdData* cloneData = IdUtils::Remapper<Uuid>::CloneObjectAndGenerateNewIdsAndFixRefs(&testData, remapUuids, m_serializeContext.get());
// Remap EntityIds
AZStd::unordered_map<EntityId, EntityId> remapEntityIds;
IdUtils::Remapper<EntityId>::GenerateNewIdsAndFixRefs(cloneData, remapEntityIds, m_serializeContext.get());
// Remapped Uuid Compare
// Uuid1 has the same value as the RemappableUuid so it gets updated as well
EXPECT_NE(testData.m_remappableUuid, cloneData->m_remappableUuid);
EXPECT_NE(testData.m_uuid1, cloneData->m_uuid1);
EXPECT_EQ(cloneData->m_remappableUuid, cloneData->m_uuid1);
// Uuid2 never gets remapped so it should remain the same throughout the test
EXPECT_EQ(testData.m_uuid2, cloneData->m_uuid2);
// Remapped EntityId Compare
// EntityId1 has the same value as the RemappableEntityId so it gets updated as well
EXPECT_NE(testData.m_remappableEntityId, cloneData->m_remappableEntityId);
EXPECT_NE(testData.m_entityId1, cloneData->m_entityId1);
EXPECT_EQ(cloneData->m_remappableEntityId, cloneData->m_entityId1);
EXPECT_NE(testData.m_entity.GetId(), cloneData->m_entity.GetId());
EXPECT_NE(testData.m_entityRef, cloneData->m_entityRef);
EXPECT_EQ(cloneData->m_entity.GetId(), cloneData->m_entityRef);
// EntityId does not match the RemappableEntityId so it does not get remapped
EXPECT_EQ(testData.m_entityId2, cloneData->m_entityId2);
auto remapUuidIt = remapUuids.find(testData.m_remappableUuid);
EXPECT_NE(remapUuids.end(), remapUuidIt);
EXPECT_EQ(cloneData->m_remappableUuid, remapUuidIt->second);
auto remapEntityIdIt = remapEntityIds.find(testData.m_remappableEntityId);
EXPECT_NE(remapEntityIds.end(), remapEntityIdIt);
EXPECT_EQ(cloneData->m_remappableEntityId, remapEntityIdIt->second);
delete cloneData;
}
// Test that the IdUtils::RemapIds function does not crash when invoking the IEventHandler function
// on a pointer to a class element(i.e ClassType**)
TEST_F(RemappableIdTest, OnWriteCallbacks_WhenParentHasPointer_DoesNotCrash)
{
RootParentElementWrapper rootWrapper;
rootWrapper.m_parentEntityContainer = aznew ParentEntityContainer;
auto entityIdMapper = [](const AZ::EntityId&, bool, const AZ::IdUtils::Remapper<AZ::EntityId>::IdGenerator& idGenerator)
{
return idGenerator();
};
AZ::IdUtils::Remapper<AZ::EntityId>::RemapIds(&rootWrapper, entityIdMapper, m_serializeContext.get(), true);
EXPECT_NE(0, rootWrapper.m_parentEntityContainer->m_beginWriteCount); // check that callbacks were invoked at all
}
// Test that each OnWriteBegin is paired with an OnWriteEnd
// and that we don't get two OnWriteBegins in a row
TEST_F(RemappableIdTest, OnWriteCallbacks_CalledInCorrectOrder)
{
ParentEntityContainerEventHandler::s_isCheckingCallbackOrder = true;
RootParentElementWrapper rootWrapper;
rootWrapper.m_parentEntityContainer = aznew ParentEntityContainer;
auto entityIdMapper = [](const AZ::EntityId&, bool, const AZ::IdUtils::Remapper<AZ::EntityId>::IdGenerator& idGenerator)
{
return idGenerator();
};
AZ::IdUtils::Remapper<AZ::EntityId>::RemapIds(&rootWrapper, entityIdMapper, m_serializeContext.get(), true);
EXPECT_EQ(rootWrapper.m_parentEntityContainer->m_beginWriteCount, rootWrapper.m_parentEntityContainer->m_endWriteCount);
ParentEntityContainerEventHandler::s_isCheckingCallbackOrder = false;
}
// Test that we only call OnWriteBegin once per node, even if multiple IDs on child nodes are remapped
TEST_F(RemappableIdTest, OnWriteCallbacks_WhenMultipleChildrenChanged_CalledOnceOnParent)
{
RootParentElementWrapper rootWrapper;
rootWrapper.m_parentEntityContainer = aznew ParentEntityContainer;
auto entityIdMapper = [](const AZ::EntityId&, bool, const AZ::IdUtils::Remapper<AZ::EntityId>::IdGenerator& idGenerator)
{
return idGenerator();
};
unsigned int remappedIdCount = AZ::IdUtils::Remapper<AZ::EntityId>::RemapIds(&rootWrapper, entityIdMapper, m_serializeContext.get(), true);
EXPECT_EQ(2, remappedIdCount);
EXPECT_EQ(1, rootWrapper.m_parentEntityContainer->m_beginWriteCount);
}
} // namespace UnitTest