Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "FrameworkApplicationFixture.h"
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzTest/Utils.h>
class ApplicationTest
: public UnitTest::FrameworkApplicationFixture
{
protected:
void SetUp() override
{
FrameworkApplicationFixture::SetUp();
m_application->SetAssetRoot(m_tempDirectory.GetDirectory());
}
void TearDown() override
{
FrameworkApplicationFixture::TearDown();
}
AZStd::string m_root;
AZ::Test::ScopedAutoTempDirectory m_tempDirectory;
};
TEST_F(ApplicationTest, MakePathAssetRootRelative_AbsPath_Valid)
{
AZStd::string inputPath;
AzFramework::StringFunc::Path::ConstructFull(m_tempDirectory.GetDirectory(), "TestA.txt", inputPath, true);
m_application->MakePathAssetRootRelative(inputPath);
EXPECT_EQ(inputPath, "testa.txt");
}
TEST_F(ApplicationTest, MakePathRelative_AbsPath_Valid)
{
AZStd::string inputPath;
AzFramework::StringFunc::Path::ConstructFull(m_tempDirectory.GetDirectory(), "TestA.txt", inputPath, true);
m_application->MakePathRelative(inputPath, m_tempDirectory.GetDirectory());
EXPECT_EQ(inputPath, "TestA.txt");
}
TEST_F(ApplicationTest, MakePathAssetRootRelative_AbsPath_RootLowerCase_Valid)
{
AZStd::string inputPath;
AZStd::string root = m_tempDirectory.GetDirectory();
AZStd::to_lower(root.begin(), root.end());
AzFramework::StringFunc::Path::ConstructFull(root.c_str(), "TestA.txt", inputPath, true);
m_application->MakePathAssetRootRelative(inputPath);
EXPECT_EQ(inputPath, "testa.txt");
}
TEST_F(ApplicationTest, MakePathRelative_AbsPath_RootLowerCase_Valid)
{
AZStd::string inputPath;
AZStd::string root = m_tempDirectory.GetDirectory();
AZStd::to_lower(root.begin(), root.end());
AzFramework::StringFunc::Path::ConstructFull(root.c_str(), "TestA.txt", inputPath, true);
m_application->MakePathRelative(inputPath, root.c_str());
EXPECT_EQ(inputPath, "TestA.txt");
}
TEST_F(ApplicationTest, MakePathAssetRootRelative_AbsPathWithSubFolders_Valid)
{
AZStd::string inputPath;
AzFramework::StringFunc::Path::ConstructFull(m_tempDirectory.GetDirectory(), "Foo/TestA.txt", inputPath, true);
m_application->MakePathAssetRootRelative(inputPath);
EXPECT_EQ(inputPath, "foo/testa.txt");
}
TEST_F(ApplicationTest, MakePathRelative_AbsPathWithSubFolders_Valid)
{
AZStd::string inputPath;
AzFramework::StringFunc::Path::ConstructFull(m_tempDirectory.GetDirectory(), "Foo/TestA.txt", inputPath, true);
m_application->MakePathRelative(inputPath, m_tempDirectory.GetDirectory());
EXPECT_EQ(inputPath, "Foo/TestA.txt");
}
TEST_F(ApplicationTest, MakePathAssetRootRelative_RelPath_Valid)
{
AZStd::string inputPath("TestA.txt");
m_application->MakePathAssetRootRelative(inputPath);
EXPECT_EQ(inputPath, "testa.txt");
}
TEST_F(ApplicationTest, MakePathRelative_RelPath_Valid)
{
AZStd::string inputPath("TestA.txt");
m_application->MakePathRelative(inputPath, m_tempDirectory.GetDirectory());
EXPECT_EQ(inputPath, "TestA.txt");
}
TEST_F(ApplicationTest, MakePathAssetRootRelative_RelPathWithSubFolder_Valid)
{
AZStd::string inputPath("Foo/TestA.txt");
m_application->MakePathAssetRootRelative(inputPath);
EXPECT_EQ(inputPath, "foo/testa.txt");
}
TEST_F(ApplicationTest, MakePathRelative_RelPathWithSubFolder_Valid)
{
AZStd::string inputPath("Foo/TestA.txt");
m_application->MakePathRelative(inputPath, m_tempDirectory.GetDirectory());
EXPECT_EQ(inputPath, "Foo/TestA.txt");
}
TEST_F(ApplicationTest, MakePathAssetRootRelative_RelPathStartingWithSeparator_Valid)
{
AZStd::string inputPath("//TestA.txt");
m_application->MakePathAssetRootRelative(inputPath);
EXPECT_EQ(inputPath, "testa.txt");
}
TEST_F(ApplicationTest, MakePathRelative_RelPathStartingWithSeparator_Valid)
{
AZStd::string inputPath("//TestA.txt");
m_application->MakePathRelative(inputPath, m_tempDirectory.GetDirectory());
EXPECT_EQ(inputPath, "TestA.txt");
}
TEST_F(ApplicationTest, MakePathAssetRootRelative_RelPathWithSubFolderStartingWithSeparator_Valid)
{
AZStd::string inputPath("//Foo/TestA.txt");
m_application->MakePathAssetRootRelative(inputPath);
EXPECT_EQ(inputPath, "foo/testa.txt");
}
TEST_F(ApplicationTest, MakePathRelative_RelPathWithSubFolderStartingWithSeparator_Valid)
{
AZStd::string inputPath("//Foo/TestA.txt");
m_application->MakePathRelative(inputPath, m_tempDirectory.GetDirectory());
EXPECT_EQ(inputPath, "Foo/TestA.txt");
}
@@ -0,0 +1,459 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UnitTest/UnitTest.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Archive/ArchiveFileIO.h>
#include <AzFramework/Archive/Archive.h>
#include <AzFramework/Archive/INestedArchive.h>
namespace UnitTest
{
using ArchiveCompressionParamInterface = ::testing::WithParamInterface<AZStd::tuple<
AZ::IO::INestedArchive::EPakFlags,
AZ::IO::INestedArchive::ECompressionMethods,
AZ::IO::INestedArchive::ECompressionLevels,
int, int, int>>;
class ArchiveCompressionTestFixture
: public ScopedAllocatorSetupFixture
, public ArchiveCompressionParamInterface
{
public:
ArchiveCompressionTestFixture()
: m_application { AZStd::make_unique<AzFramework::Application>() }
{}
void SetUp() override
{
m_application->Start({});
// 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 TearDown() override
{
m_application->Stop();
}
private:
AZStd::unique_ptr<AzFramework::Application> m_application;
};
auto IsPackValid(const char* path)
{
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
if (!archive)
{
return false;
}
if (!archive->OpenPack(path, AZ::IO::IArchive::FLAGS_PATH_REAL))
{
return false;
}
archive->ClosePack(path);
return true;
}
TEST_P(ArchiveCompressionTestFixture, TestArchivePacking_CompressionEmptyArchiveTest_PackIsValid)
{
// this also coincidentally tests to make sure packs inside aliases work.
AZStd::string testArchivePath = "@cache@/archivetest.pak";
AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance();
ASSERT_NE(nullptr, fileIo);
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
ASSERT_NE(nullptr, archive);
// delete test files in case they already exist
archive->ClosePack(testArchivePath.c_str());
fileIo->Remove(testArchivePath.c_str());
// ------------ BASIC TEST: Create and read Empty Archive ------------
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
}
TEST_P(ArchiveCompressionTestFixture, TestArchivePacking_CompressionFullArchive_PackIsValid)
{
// ------------ BASIC TEST: Create archive full of standard sizes (including 0) ----------------
AZStd::string testArchivePath = "@cache@/archivetest.pak";
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
auto openFlags = AZStd::get<0>(GetParam());
auto compressionMethod = AZStd::get<1>(GetParam());
auto compressionLevel = AZStd::get<2>(GetParam());
auto stepSize = AZStd::get<3>(GetParam());
auto numSteps = AZStd::get<4>(GetParam());
auto iterations = AZStd::get<5>(GetParam());
int maxSize = numSteps * stepSize;
AZStd::vector<uint8_t> checkSums;
checkSums.resize_no_construct(maxSize);
for (int pos = 0; pos < maxSize; ++pos)
{
checkSums[pos] = static_cast<uint8_t>(pos % 256);
}
auto pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
// the strategy here is to find errors related to file sizes, alignment, overwrites
// so the first test will just repeatedly write files into the pack file with varying lengths (in odd number increments from a couple KB down to 0, including 0)
AZStd::vector<uint8_t> orderedData;
for (int j = 0; j < iterations; ++j)
{
for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
{
auto fnBuffer = AZ::StringFunc::Path::FixedString::format("file-%i-%i.dat", currentSize, j);
EXPECT_TRUE(pArchive->UpdateFile(fnBuffer, checkSums.data(), currentSize, compressionMethod, compressionLevel) == 0);
}
}
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
// --------------------------------------------- read it back and verify
pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, openFlags);
EXPECT_NE(nullptr, pArchive);
for (int j = 0; j < iterations; ++j)
{
for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
{
auto fnBuffer = AZ::StringFunc::Path::FixedString::format("file-%i-%i.dat", currentSize, j);
AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
EXPECT_NE(nullptr, hand);
EXPECT_EQ(currentSize, pArchive->GetFileSize(hand));
EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
for (int pos = 0; pos < currentSize; ++pos)
{
EXPECT_EQ(pos % 256, checkSums[pos]);
}
}
}
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
}
TEST_P(ArchiveCompressionTestFixture, TestArchivePacking_CompressionWithOverridenArchiveData_PackIsValid)
{
// ---------------- MORE COMPLICATED TEST which involves overwriting elements ----------------
AZStd::string testArchivePath = "@cache@/archivetest.pak";
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
auto openFlags = AZStd::get<0>(GetParam());
auto compressionMethod = AZStd::get<1>(GetParam());
auto compressionLevel = AZStd::get<2>(GetParam());
auto stepSize = AZStd::get<3>(GetParam());
auto numSteps = AZStd::get<4>(GetParam());
auto iterations = AZStd::get<5>(GetParam());
int maxSize = numSteps * stepSize;
AZStd::vector<uint8_t> checkSums;
checkSums.resize_no_construct(maxSize);
for (int pos = 0; pos < maxSize; ++pos)
{
checkSums[pos] = static_cast<uint8_t>(pos % 256);
}
auto pArchive = archive->OpenArchive(testArchivePath.c_str());
EXPECT_NE(nullptr, pArchive);
for (int j = 0; j < iterations; ++j)
{
for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
{
auto fnBuffer = AZ::StringFunc::Path::FixedString::format("file-%i-%i.dat", currentSize, j);
EXPECT_TRUE(pArchive->UpdateFile(fnBuffer, checkSums.data(), currentSize, compressionMethod, compressionLevel) == 0);
}
}
// overwrite the first and last iterations with files that are half their original size.
for (int j = 0; j < iterations; ++j)
{
for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
{
int newSize = currentSize; // more will become zero
if (j != 1)
{
newSize = newSize / 2; // the second iteration overwrites files with exactly the same size.
}
auto fnBuffer = AZ::StringFunc::Path::FixedString::format("file-%i-%i.dat", currentSize, j);
// before we overwrite it, ensure that the element is correctly resized:
AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
EXPECT_NE(nullptr, hand);
EXPECT_EQ(currentSize, pArchive->GetFileSize(hand));
EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
for (int pos = 0; pos < currentSize; ++pos)
{
EXPECT_EQ(pos % 256, checkSums[pos]);
}
// now overwrite it:
EXPECT_EQ(0, pArchive->UpdateFile(fnBuffer, checkSums.data(), newSize, compressionMethod, compressionLevel));
// after overwriting it ensure that the pack contains the updated info:
hand = pArchive->FindFile(fnBuffer);
EXPECT_NE(nullptr, hand);
EXPECT_EQ(newSize, pArchive->GetFileSize(hand));
EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
for (int pos = 0; pos < newSize; ++pos)
{
EXPECT_EQ(pos % 256, checkSums[pos]);
}
}
}
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
// -------------------------------------------------------------------------------------------
// read it back and verify
pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, openFlags);
EXPECT_NE(nullptr, pArchive);
for (int j = 0; j < iterations; ++j)
{
for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
{
auto fnBuffer = AZ::StringFunc::Path::FixedString::format("file-%i-%i.dat", currentSize, j);
int newSize = currentSize; // more will become zero
if (j != 1)
{
newSize = newSize / 2; // the middle iteration overwrites files with exactly the same size.
}
AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
EXPECT_NE(nullptr, hand);
EXPECT_EQ(newSize, pArchive->GetFileSize(hand));
EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
for (int pos = 0; pos < newSize; ++pos)
{
EXPECT_EQ(pos % 256, checkSums[pos]);
}
}
}
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
}
TEST_P(ArchiveCompressionTestFixture, TestArchivePacking_CompressionWithScatteredUpdatesAndNewFiles_PackIsValid)
{
// ---------- scattered test --------------
// in this next test, we're going to update only some elements, to make sure it reads existing data okay
// we want to make at least one element shrink and one element grow, adjacent to other files
// this will include files that become zero size, and also includes new files that were not there before
AZStd::string testArchivePath = "@cache@/archivetest.pak";
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
auto openFlags = AZStd::get<0>(GetParam());
auto compressionMethod = AZStd::get<1>(GetParam());
auto compressionLevel = AZStd::get<2>(GetParam());
auto stepSize = AZStd::get<3>(GetParam());
auto numSteps = AZStd::get<4>(GetParam());
auto iterations = AZStd::get<5>(GetParam());
int maxSize = numSteps * stepSize;
AZStd::vector<uint8_t> checkSums;
checkSums.resize_no_construct(maxSize);
for (int pos = 0; pos < maxSize; ++pos)
{
checkSums[pos] = static_cast<uint8_t>(pos % 256);
}
// first, reset the pack to the original state:
auto pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
for (int j = 0; j < iterations; ++j)
{
for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
{
char fnBuffer[AZ_MAX_PATH_LEN];
azsnprintf(fnBuffer, AZ_MAX_PATH_LEN, "file-%i-%i.dat", static_cast<int>(currentSize), j);
EXPECT_TRUE(pArchive->UpdateFile(fnBuffer, checkSums.data(), currentSize, compressionMethod, compressionLevel) == 0);
}
}
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
pArchive = archive->OpenArchive(testArchivePath.c_str());
EXPECT_NE(nullptr, pArchive);
// replace a scattering of the files:
int writeCount = 0;
for (int j = 0; j < iterations + 1; ++j) // note: an extra iteration to generate new files
{
char fnBuffer[AZ_MAX_PATH_LEN];
for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
{
azsnprintf(fnBuffer, AZ_MAX_PATH_LEN, "file-%i-%i.dat", static_cast<int>(currentSize), j);
++writeCount;
if (writeCount % 4 == 0)
{
if (j != iterations) // the last one wont be there
{
// don't do anything for every fourth file, but we do make sure its there:
AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
EXPECT_NE(nullptr, hand);
EXPECT_EQ(currentSize, pArchive->GetFileSize(hand));
EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
}
continue;
}
int newSize = currentSize;
if (writeCount % 4 == 1)
{
newSize = newSize * 2;
}
else if (writeCount % 4 == 2)
{
newSize = newSize / 2;
}
else if (writeCount % 4 == 3)
{
newSize = 0;
}
if (newSize > maxSize)
{
newSize = maxSize; // don't blow our buffer!
}
// overwrite it:
EXPECT_TRUE(pArchive->UpdateFile(fnBuffer, checkSums.data(), newSize, compressionMethod, compressionLevel) == 0);
// after overwriting it ensure that the pack contains the updated info:
AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
EXPECT_NE(nullptr, hand);
EXPECT_EQ(newSize, pArchive->GetFileSize(hand));
EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
for (int pos = 0; pos < newSize; ++pos)
{
EXPECT_EQ(pos % 256, checkSums[pos]);
}
}
}
EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
// -------------------------------------------------------------------------------------------
// read it back and verify
pArchive = archive->OpenArchive(testArchivePath.c_str(), nullptr, openFlags);
EXPECT_NE(nullptr, pArchive);
writeCount = 0;
for (int j = 0; j < iterations + 1; ++j) // make sure the extra iteration is there.
{
char fnBuffer[AZ_MAX_PATH_LEN];
for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
{
++writeCount;
int newSize = currentSize;
if (writeCount % 4 == 1)
{
newSize = newSize * 2;
}
else if (writeCount % 4 == 2)
{
newSize = newSize / 2;
}
else if (writeCount % 4 == 3)
{
newSize = 0;
}
else if (writeCount % 4 == 0)
{
if (j == iterations) // the last one wont be there
{
continue;
}
}
if (newSize > maxSize)
{
newSize = maxSize; // don't blow our buffer!
}
azsnprintf(fnBuffer, AZ_MAX_PATH_LEN, "file-%i-%i.dat", static_cast<int>(currentSize), j);
// check it:
AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
EXPECT_NE(nullptr, hand);
EXPECT_EQ(newSize, pArchive->GetFileSize(hand));
EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
for (int pos = 0; pos < newSize; ++pos)
{
EXPECT_TRUE(checkSums[pos] == (pos % 256));
}
}
}
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
}
INSTANTIATE_TEST_CASE_P(
ArchiveCompression,
ArchiveCompressionTestFixture,
::testing::Values(
std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST, 777, 7, 1),
std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTER, 777, 7, 1),
std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_NORMAL, 777, 7, 1),
std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST, 777, 7, 1),
std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST, 777, 7, 1),
std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTER, 777, 7, 1),
std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_NORMAL, 777, 7, 1),
std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST, 777, 7, 1),
std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_STORE, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_STORE, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST, 1111, 10, 1),
std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST, 1111, 10, 1)
));
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,628 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "FrameworkApplicationFixture.h"
#include <atomic>
#include <gtest/gtest.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzFramework/Network/AssetProcessorConnection.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
// This is the type and payload sent from A to B
const AZ::u32 ABType = 0x86;
const char* ABPayload = "Hello World";
const AZ::u32 ABPayloadSize = azlossy_caster(strlen(ABPayload));
// This is the type sent from A to B with no payload
const AZ::u32 ABNoPayloadType = 0x69;
// This is the type and payload sent from B to A
const AZ::u32 BAType = 0xffff0000;
const char* BAPayload = "When in the Course of human events it becomes necessary for one people to dissolve the political bands which have connected them with another and to assume among the powers of the earth, the separate and equal station to which the Laws of Nature and of Nature's God entitle them, a decent respect to the opinions of mankind requires that they should declare the causes which impel them to the separation.";
const AZ::u32 BAPayloadSize = azlossy_caster(strlen(BAPayload));
// how long before tests fail when expecting a connection.
// normally, connections to localhost happen immediately (microseconds), so this is just for when things
// go wrong. In normal test runs, we'll be yielding and waiting very short
// amounts of time (milliseconds) instead of the full 15 seconds.
const int secondsMaxConnectionAttempt = 15;
// the longest time it should be conceivable for a message to take to send.
// most messages will arrive within microseconds, but if the machine is really busy it could take
// a couple orders of magnitude longer. Nothing in these tests waits for this full duration
// unless a test is failing, so the actual runtime of the tests should be milliseconds.
const int millisecondsForSend = 5000;
class APConnectionTest
: public UnitTest::FrameworkApplicationFixture
{
protected:
void SetUp() override
{
FrameworkApplicationFixture::SetUp();
}
void TearDown() override
{
FrameworkApplicationFixture::TearDown();
}
bool WaitForConnectionStateToBeEqual(AzFramework::AssetSystem::AssetProcessorConnection& connectionObject, AzFramework::SocketConnection::EConnectionState desired)
{
int tries = 0;
auto started = AZStd::chrono::system_clock::now();
while (connectionObject.GetConnectionState() != desired )
{
auto seconds_passed = AZStd::chrono::seconds(AZStd::chrono::system_clock::now() - started).count();
if (seconds_passed > secondsMaxConnectionAttempt)
{
break;
}
AZStd::this_thread::yield();
}
return connectionObject.GetConnectionState() == desired;
}
bool WaitForConnectionStateToNotBeEqual(AzFramework::AssetSystem::AssetProcessorConnection& connectionObject, AzFramework::SocketConnection::EConnectionState notDesired)
{
auto started = AZStd::chrono::system_clock::now();
while (connectionObject.GetConnectionState() == notDesired)
{
auto seconds_passed = AZStd::chrono::seconds(AZStd::chrono::system_clock::now() - started).count();
if (seconds_passed > secondsMaxConnectionAttempt)
{
break;
}
AZStd::this_thread::yield();
}
return connectionObject.GetConnectionState() != notDesired;
}
};
TEST_F(APConnectionTest, TestAddRemoveCallbacks)
{
using namespace AzFramework;
// This is connection A
AssetSystem::AssetProcessorConnection apConnection;
apConnection.m_unitTesting = true;
// This is connection B
AssetSystem::AssetProcessorConnection apListener;
apListener.m_unitTesting = true;
std::atomic_uint BAMessageCallbackCount;
BAMessageCallbackCount = 0;
std::atomic_uint ABMessageCallbackCount;
ABMessageCallbackCount = 0;
AZStd::binary_semaphore messageArrivedSemaphore;
// once we disconnect, we'll set this atomic to ensure no message arrives after disconnection
AZStd::atomic_bool failIfMessageArrivesAB = {false};
AZStd::atomic_bool failIfMessageArrivesBA = {false};
// Connection A is expecting the above type and payload from B, therefore it is B->A, BA
auto BACallbackHandle = apConnection.AddMessageHandler(BAType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* data, AZ::u32 dataLength) -> void
{
EXPECT_FALSE(failIfMessageArrivesBA.load());
EXPECT_EQ(typeId, BAType);
EXPECT_EQ(BAPayloadSize, dataLength);
EXPECT_TRUE(!strncmp(reinterpret_cast<const char*>(data), BAPayload, dataLength));
++BAMessageCallbackCount;
messageArrivedSemaphore.release();
});
// Connection B is expecting the above type and payload from A, therefore it is A->B, AB
auto ABCallbackHandle = apListener.AddMessageHandler(ABType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* data, AZ::u32 dataLength) -> void
{
EXPECT_FALSE(failIfMessageArrivesAB.load());
EXPECT_EQ(typeId, ABType);
EXPECT_EQ(ABPayloadSize, dataLength);
EXPECT_TRUE(!strncmp(reinterpret_cast<const char*>(data), ABPayload, dataLength));
++ABMessageCallbackCount;
messageArrivedSemaphore.release();
});
// Test listening
EXPECT_EQ(apListener.GetConnectionState(), SocketConnection::EConnectionState::Disconnected);
bool listenResult = apListener.Listen(11112);
EXPECT_TRUE(listenResult);
// Wait some time for the connection to start listening, since it doesn't actually call listen() immediately.
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Listening));
EXPECT_EQ(apListener.GetConnectionState(), SocketConnection::EConnectionState::Listening);
// Test connect success
EXPECT_EQ(apConnection.GetConnectionState(), SocketConnection::EConnectionState::Disconnected);
// This is blocking, should connect
bool connectResult = apConnection.Connect("127.0.0.1", 11112);
EXPECT_TRUE(connectResult);
// Wait some time for the connection to negotiate, only after negotiation succeeds is it actually considered connected,,
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Connected));
// Check listener for success - by this time the listener should also be considered connected.
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Connected));
//
// Send first set, ensure we got 1 each
//
// Send message from A to B
apConnection.SendMsg(ABType, ABPayload, ABPayloadSize);
// Wait some time to allow message to send
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_EQ(ABMessageCallbackCount, 1);
// Send message from B to A
apListener.SendMsg(BAType, BAPayload, BAPayloadSize);
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_EQ(BAMessageCallbackCount, 1);
//
// Send second set, ensure we got 2 each (didn't auto-remove or anything crazy)
//
// Send message from A to B
apConnection.SendMsg(ABType, ABPayload, ABPayloadSize);
// Wait some time to allow message to send
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_EQ(ABMessageCallbackCount, 2);
// Send message from B to A
apListener.SendMsg(BAType, BAPayload, BAPayloadSize);
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_EQ(BAMessageCallbackCount, 2);
// Remove callbacks
// after removing a listener, we expect no further messages to arrive.
apConnection.RemoveMessageHandler(BAType, BACallbackHandle);
failIfMessageArrivesBA = true;
apListener.RemoveMessageHandler(ABType, ABCallbackHandle);
failIfMessageArrivesAB = true;
// the below 2 lines send a message while nobody is connected as a listener.
// it may not fail immediately but will cause a cascade later, which is better than
// waiting for some large timeout in the test.
apConnection.SendMsg(ABType, ABPayload, ABPayloadSize);
apListener.SendMsg(BAType, BAPayload, BAPayloadSize);
// Disconnect A
// which flushes and will cause any traps to spring.
bool disconnectResult = apConnection.Disconnect(true);
EXPECT_TRUE(disconnectResult);
// Disconnect B
disconnectResult = apListener.Disconnect(true);
EXPECT_TRUE(disconnectResult);
// Verify A and B are disconnected
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Disconnected));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
}
TEST_F(APConnectionTest, TestAddRemoveCallbacks_RemoveDuringCallback_DoesNotCrash)
{
using namespace AzFramework;
// This is connection A
AssetSystem::AssetProcessorConnection apConnection;
apConnection.m_unitTesting = true;
// This is connection B
AssetSystem::AssetProcessorConnection apListener;
apListener.m_unitTesting = true;
std::atomic_uint BAMessageCallbackCount;
BAMessageCallbackCount = 0;
std::atomic_uint ABMessageCallbackCount;
ABMessageCallbackCount = 0;
AZStd::binary_semaphore messageArrivedSemaphore;
// establish connection
EXPECT_TRUE(apListener.Listen(11112));
EXPECT_TRUE(apConnection.Connect("127.0.0.1", 11112));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Connected));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Connected));
//
// Now try adding listeners that remove themselves during callback
//
// Connection A is expecting the above type and payload from B, therefore it is B->A, BA
// we set a trap here - after we first get this message, we are removing the handler
// so that it should not ever fire again, and we assert that its false.
AZStd::atomic_bool failIfWeGetCalledAgainBA = {false};
SocketConnection::TMessageCallbackHandle SelfRemovingBACallbackHandle = SocketConnection::s_invalidCallbackHandle;
SelfRemovingBACallbackHandle = apConnection.AddMessageHandler(BAType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* data, AZ::u32 dataLength) -> void
{
EXPECT_FALSE(failIfWeGetCalledAgainBA.load());
EXPECT_EQ(typeId, BAType);
EXPECT_EQ(BAPayloadSize, dataLength);
EXPECT_TRUE(!strncmp(reinterpret_cast<const char*>(data), BAPayload, dataLength));
++BAMessageCallbackCount;
apConnection.RemoveMessageHandler(BAType, SelfRemovingBACallbackHandle);
failIfWeGetCalledAgainBA = true;
messageArrivedSemaphore.release();
});
// Connection B is expecting the above type and payload from A, therefore it is A->B, AB
AZStd::atomic_bool failIfWeGetCalledAgainAB = {false};
SocketConnection::TMessageCallbackHandle SelfRemovingABCallbackHandle = SocketConnection::s_invalidCallbackHandle;
SelfRemovingABCallbackHandle = apListener.AddMessageHandler(ABType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* data, AZ::u32 dataLength) -> void
{
EXPECT_FALSE(failIfWeGetCalledAgainAB.load());
EXPECT_EQ(typeId, ABType);
EXPECT_EQ(ABPayloadSize, dataLength);
EXPECT_TRUE(!strncmp(reinterpret_cast<const char*>(data), ABPayload, dataLength));
++ABMessageCallbackCount;
apListener.RemoveMessageHandler(ABType, SelfRemovingABCallbackHandle);
failIfWeGetCalledAgainAB = true;
messageArrivedSemaphore.release();
});
// Send message, should be at 1 each
// Send message from A to B
apConnection.SendMsg(ABType, ABPayload, ABPayloadSize);
// Wait some time to allow message to send
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_EQ(ABMessageCallbackCount, 1);
// Send message from B to A
apListener.SendMsg(BAType, BAPayload, BAPayloadSize);
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_EQ(BAMessageCallbackCount, 1);
// the callback has disconnected, so sending additional messages should NOT result in the callback
// being called.
// we send some additional messages, as a "trap", if the callbacks fire, then the
// above callback functions will trigger their asserts.
apConnection.SendMsg(ABType, ABPayload, ABPayloadSize);
apListener.SendMsg(BAType, BAPayload, BAPayloadSize);
// disconnect fully, which flushes sender queue and reciever queue and will cause any traps to spring!
EXPECT_TRUE(apConnection.Disconnect(true));
EXPECT_TRUE(apListener.Disconnect(true));
// Verify A and B are disconnected
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Disconnected));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
}
TEST_F(APConnectionTest, TestAddRemoveCallbacks_AddDuringCallback_DoesNotCrash)
{
using namespace AzFramework;
// This is connection A
AssetSystem::AssetProcessorConnection apConnection;
apConnection.m_unitTesting = true;
// This is connection B
AssetSystem::AssetProcessorConnection apListener;
apListener.m_unitTesting = true;
std::atomic_uint BAMessageCallbackCount;
BAMessageCallbackCount = 0;
std::atomic_uint ABMessageCallbackCount;
ABMessageCallbackCount = 0;
AZStd::binary_semaphore messageArrivedSemaphore;
// establish connection
EXPECT_TRUE(apListener.Listen(11112));
EXPECT_TRUE(apConnection.Connect("127.0.0.1", 11112));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Connected));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Connected));
//
// Now try adding listeners that add more listeners during callback
//
// Connection A is expecting the above type and payload from B, therefore it is B->A, BA
SocketConnection::TMessageCallbackHandle SecondAddedBACallbackHandle = SocketConnection::s_invalidCallbackHandle;
SocketConnection::TMessageCallbackHandle AddingBACallbackHandle = SocketConnection::s_invalidCallbackHandle;
// set some traps so that if things call more than once, its a failure:
AZStd::atomic_bool AddingBACallbackFailIfCalledAgain = {false};
AZStd::atomic_bool AddingBACallbackFailIfCalledAgain_inner = {false};
AddingBACallbackHandle = apConnection.AddMessageHandler(BAType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* data, AZ::u32 dataLength) -> void
{
EXPECT_FALSE(AddingBACallbackFailIfCalledAgain.load());
EXPECT_EQ(typeId, BAType);
EXPECT_EQ(BAPayloadSize, dataLength);
EXPECT_TRUE(!strncmp(reinterpret_cast<const char*>(data), BAPayload, dataLength));
++BAMessageCallbackCount;
apConnection.RemoveMessageHandler(BAType, AddingBACallbackHandle);
AddingBACallbackFailIfCalledAgain = true;
SecondAddedBACallbackHandle = apConnection.AddMessageHandler(BAType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* data, AZ::u32 dataLength) -> void
{
EXPECT_FALSE(AddingBACallbackFailIfCalledAgain_inner.load());
EXPECT_EQ(typeId, BAType);
EXPECT_EQ(BAPayloadSize, dataLength);
EXPECT_TRUE(!strncmp(reinterpret_cast<const char*>(data), BAPayload, dataLength));
++BAMessageCallbackCount;
apConnection.RemoveMessageHandler(BAType, SecondAddedBACallbackHandle);
AddingBACallbackFailIfCalledAgain_inner = true;
messageArrivedSemaphore.release();
});
messageArrivedSemaphore.release();
});
// Connection B is expecting the above type and payload from A, therefore it is A->B, AB
AZStd::atomic_bool AddingABCallbackFailIfCalledAgain = {false};
AZStd::atomic_bool AddingABCallbackFailIfCalledAgain_inner = {false};
SocketConnection::TMessageCallbackHandle SecondAddedABCallbackHandle = SocketConnection::s_invalidCallbackHandle;
SocketConnection::TMessageCallbackHandle AddingABCallbackHandle = SocketConnection::s_invalidCallbackHandle;
AddingABCallbackHandle = apListener.AddMessageHandler(ABType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* data, AZ::u32 dataLength) -> void
{
EXPECT_FALSE(AddingABCallbackFailIfCalledAgain.load());
EXPECT_EQ(typeId, ABType);
EXPECT_EQ(ABPayloadSize, dataLength);
EXPECT_TRUE(!strncmp(reinterpret_cast<const char*>(data), ABPayload, dataLength));
++ABMessageCallbackCount;
apListener.RemoveMessageHandler(ABType, AddingABCallbackHandle);
AddingABCallbackFailIfCalledAgain = true;
messageArrivedSemaphore.release();
SecondAddedABCallbackHandle = apListener.AddMessageHandler(ABType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* data, AZ::u32 dataLength) -> void
{
EXPECT_FALSE(AddingABCallbackFailIfCalledAgain_inner.load());
EXPECT_EQ(typeId, ABType);
EXPECT_EQ(ABPayloadSize, dataLength);
EXPECT_TRUE(!strncmp(reinterpret_cast<const char*>(data), ABPayload, dataLength));
++ABMessageCallbackCount;
apListener.RemoveMessageHandler(ABType, SecondAddedABCallbackHandle);
AddingABCallbackFailIfCalledAgain_inner = true;
messageArrivedSemaphore.release();
});
});
// Send message, should be at 1 each
// Send message from A to B
apConnection.SendMsg(ABType, ABPayload, ABPayloadSize);
// Wait some time to allow message to send
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_EQ(ABMessageCallbackCount, 1);
// Send message from B to A
apListener.SendMsg(BAType, BAPayload, BAPayloadSize);
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_EQ(BAMessageCallbackCount, 1);
// Send message, should be at 2 each since the handlers
// have been replaced with the ones created in the callback
// Send message from A to B
apConnection.SendMsg(ABType, ABPayload, ABPayloadSize);
// Wait some time to allow message to send
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_EQ(ABMessageCallbackCount, 2);
// Send message from B to A
apListener.SendMsg(BAType, BAPayload, BAPayloadSize);
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_EQ(BAMessageCallbackCount, 2);
// Send message, we don't wait for these as our listeners should be disconnected, but there
// are traps set to make sure they dont call.
// since we flush on disconnect, these traps will activate.
apConnection.SendMsg(ABType, ABPayload, ABPayloadSize);
apListener.SendMsg(BAType, BAPayload, BAPayloadSize);
// Disconnect A
// which flushes and will cause any traps to spring.
bool disconnectResult = apConnection.Disconnect(true);
EXPECT_TRUE(disconnectResult);
// Disconnect B
disconnectResult = apListener.Disconnect(true);
EXPECT_TRUE(disconnectResult);
// Verify A and B are disconnected
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Disconnected));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
}
TEST_F(APConnectionTest, TestConnection)
{
using namespace AzFramework;
// This is connection A
AssetSystem::AssetProcessorConnection apConnection;
apConnection.m_unitTesting = true;
// This is connection B
AssetSystem::AssetProcessorConnection apListener;
apListener.m_unitTesting = true;
bool ABMessageSuccess = false;
bool ABNoPayloadMessageSuccess = false;
bool BAMessageSuccess = false;
AZStd::binary_semaphore messageArrivedSemaphore;
// Connection A is expecting the above type and payload from B, therefore it is B->A, BA
apConnection.AddMessageHandler(BAType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* data, AZ::u32 dataLength) -> void
{
EXPECT_EQ(typeId, BAType);
EXPECT_EQ(BAPayloadSize, dataLength);
EXPECT_TRUE(!strncmp(reinterpret_cast<const char*>(data), BAPayload, dataLength));
BAMessageSuccess = true;
messageArrivedSemaphore.release();
});
// Connection B is expecting the above type and payload from A, therefore it is A->B, AB
apListener.AddMessageHandler(ABType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* data, AZ::u32 dataLength) -> void
{
EXPECT_EQ(typeId, ABType);
EXPECT_EQ(ABPayloadSize, dataLength);
EXPECT_TRUE(!strncmp(reinterpret_cast<const char*>(data), ABPayload, dataLength));
ABMessageSuccess = true;
messageArrivedSemaphore.release();
});
// Connection B is expecting the above type and no payload from A, therefore it is A->B, AB
apListener.AddMessageHandler(ABNoPayloadType, [&](AZ::u32 typeId, AZ::u32 /*serial*/, const void* /*data*/, AZ::u32 dataLength) -> void
{
EXPECT_EQ(typeId, ABNoPayloadType);
EXPECT_EQ(dataLength, 0);
ABNoPayloadMessageSuccess = true;
messageArrivedSemaphore.release();
});
// Test connection coming online first
EXPECT_TRUE(apConnection.GetConnectionState() == SocketConnection::EConnectionState::Disconnected);
bool connectResult = apConnection.Connect("127.0.0.1", 11120);
EXPECT_TRUE(connectResult);
// during the connect/disconnect/reconnect loop, the status of the connection rapidly oscillates
// between "connecting" and "disconnecting" as it tries, fails, and sets up to try again.
// Since the connection attempt starts as disconnected (checked before the connect), here we will
// check that it transitions to a state different than disconnected (connecting/disconnecting) and then
// it gets back to disconnected once the attempts are exhausted
EXPECT_TRUE(WaitForConnectionStateToNotBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
// Test listening on separate port. This is NOT the port that the connecting one is trying to reach.
EXPECT_TRUE(apListener.GetConnectionState() == SocketConnection::EConnectionState::Disconnected);
EXPECT_TRUE(apListener.Listen(54321));
// we should end up listening, not connected.
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Listening));
// since we listened on the 'wrong' port, we should not see a successful connection:
EXPECT_NE(apConnection.GetConnectionState(), SocketConnection::EConnectionState::Connected);
// Disconnect listener from wrong port
EXPECT_TRUE(apListener.Disconnect());
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Disconnected));
// Listen with correct port
EXPECT_TRUE(apListener.Listen(11120));
// Wait some time for apConnection to connect (it has to finish negotiation)
// Also the listener needs to tick and connect
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Connected));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Connected));
// Send message from A to B
apConnection.SendMsg(ABType, ABPayload, ABPayloadSize);
// Wait some time to allow message to send
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_TRUE(ABMessageSuccess);
// Send message from B to A
apListener.SendMsg(BAType, BAPayload, BAPayloadSize);
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_TRUE(BAMessageSuccess);
// Send no payload message from A to B
apConnection.SendMsg(ABNoPayloadType, nullptr, 0);
messageArrivedSemaphore.try_acquire_for(AZStd::chrono::milliseconds(millisecondsForSend));
EXPECT_TRUE(ABNoPayloadMessageSuccess);
EXPECT_TRUE(apConnection.Disconnect(true));
EXPECT_TRUE(apListener.Disconnect(true));
// Verify they've disconnected
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Disconnected));
}
TEST_F(APConnectionTest, TestReconnect)
{
using namespace AzFramework;
// This is connection A
AssetSystem::AssetProcessorConnection apConnection;
apConnection.m_unitTesting = true;
// This is connection B
AssetSystem::AssetProcessorConnection apListener;
apListener.m_unitTesting = true;
// Test listening - listen takes a moment to actually start listening:
EXPECT_TRUE(apListener.GetConnectionState() == SocketConnection::EConnectionState::Disconnected);
bool listenResult = apListener.Listen(11120);
EXPECT_TRUE(listenResult);
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Listening));
// Test connect success
EXPECT_TRUE(apConnection.GetConnectionState() == SocketConnection::EConnectionState::Disconnected);
bool connectResult = apConnection.Connect("127.0.0.1", 11120);
EXPECT_TRUE(connectResult);
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Connected));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Connected));
// Disconnect B
bool disconnectResult = apListener.Disconnect();
EXPECT_TRUE(disconnectResult);
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Disconnected));
// disconncting the listener should kick out the other end:
// note that the listener was the ONLY one we told to disconnect
// the other end (the apConnection) is likely to be in a retry state - so it wont be connected, but it also won't necessarily
// be disconnected, connecting, etc.
EXPECT_TRUE(WaitForConnectionStateToNotBeEqual(apConnection, SocketConnection::EConnectionState::Connected));
// start listening again
listenResult = apListener.Listen(11120);
EXPECT_TRUE(listenResult);
// once we start listening, the ap connection should autoconnect very shortly:
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Connected));
// at that point, both sides should consider themselves cyonnected (the listener, too)
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Connected));
// now disconnect A without waiting for it to finish
disconnectResult = apConnection.Disconnect();
EXPECT_TRUE(disconnectResult);
// wait for it to finish
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
// ensure that B rebinds and starts listening again
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Listening));
// reconnect manually from A -> B
connectResult = apConnection.Connect("127.0.0.1", 11120);
EXPECT_TRUE(connectResult);
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Connected));
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Connected));
// disconnect everything, starting with B to ensure that reconnect thread exits on disconnect
disconnectResult = apListener.Disconnect();
EXPECT_TRUE(disconnectResult);
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Disconnected));
// note that the listener was the ONLY one we told to disconnect!
// the other end (the apConnection) is likely to be in a retry state (ie, one of the states that is not connected)
EXPECT_TRUE(WaitForConnectionStateToNotBeEqual(apConnection, SocketConnection::EConnectionState::Connected));
// disconnect A
disconnectResult = apConnection.Disconnect(true); // we're not going to wait, so we do a final disconnect here (true)
EXPECT_TRUE(disconnectResult);
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
}
@@ -0,0 +1,304 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "FrameworkApplicationFixture.h"
#include <AzCore/Component/Component.h>
#include <AzFramework/Entity/BehaviorEntity.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
// some fake components to test with
static const AZ::TypeId HatComponentTypeId = "{EADEF936-E987-4BF3-9651-A42251827628}";
class HatConfig : public AZ::ComponentConfig
{
public:
AZ_RTTI(HatConfig, "{A3129800-43DF-48CA-9BC3-77632241B8ED}", ComponentConfig);
float m_brimWidth = 1.f;
};
class HatComponent : public AZ::Component
{
public:
AZ_COMPONENT(HatComponent, HatComponentTypeId);
static void Reflect(AZ::ReflectContext*) {}
void Activate() override {}
void Deactivate() override {}
bool ReadInConfig(const AZ::ComponentConfig* baseConfig)
{
if (auto config = azrtti_cast<const HatConfig*>(baseConfig))
{
m_config = *config;
return true;
}
return false;
}
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
{
if (auto outConfig = azrtti_cast<HatConfig*>(outBaseConfig))
{
*outConfig = m_config;
return true;
}
return false;
}
HatConfig m_config;
};
static const AZ::TypeId EarComponentTypeId = "{1F741BC1-451F-445F-891B-1204D6A434D0}";
class EarComponent : public AZ::Component
{
public:
AZ_COMPONENT(EarComponent, EarComponentTypeId);
static void Reflect(AZ::ReflectContext*) {}
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) { services.push_back(AZ::Crc32("EarService")); }
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services) { services.push_back(AZ::Crc32("EarService")); }
void Activate() override {}
void Deactivate() override {}
};
static const AZ::TypeId DeactivateDuringActivationComponentTypeId = "{E18A3FFE-FA61-4682-A6C2-FB065D5DDDD2}";
class DeactivateDuringActivationComponent : public AZ::Component
{
public:
AZ_COMPONENT(DeactivateDuringActivationComponent , DeactivateDuringActivationComponentTypeId);
static void Reflect(AZ::ReflectContext*) {}
void Activate() override
{
AzFramework::BehaviorEntity behaviorEntity{ GetEntityId() };
behaviorEntity.Deactivate();
}
void Deactivate() override {}
};
class BehaviorEntityTest
: public UnitTest::FrameworkApplicationFixture
{
protected:
void SetUp() override
{
m_appDescriptor.m_enableScriptReflection = true;
FrameworkApplicationFixture::SetUp();
m_application->RegisterComponentDescriptor(HatComponent::CreateDescriptor());
m_application->RegisterComponentDescriptor(EarComponent::CreateDescriptor());
m_application->RegisterComponentDescriptor(DeactivateDuringActivationComponent::CreateDescriptor());
AzFramework::GameEntityContextRequestBus::BroadcastResult(m_rawEntity, &AzFramework::GameEntityContextRequestBus::Events::CreateGameEntity, "Hat");
m_behaviorEntity = AzFramework::BehaviorEntity(m_rawEntity->GetId());
}
void TearDown() override
{
FrameworkApplicationFixture::TearDown();
}
AZ::Entity* m_rawEntity = nullptr;
AzFramework::BehaviorEntity m_behaviorEntity;
};
TEST_F(BehaviorEntityTest, FixtureSanityCheck_Succeeds)
{
EXPECT_NE(nullptr, m_rawEntity);
}
TEST_F(BehaviorEntityTest, GetName_Succeeds)
{
EXPECT_EQ(m_rawEntity->GetName(), m_behaviorEntity.GetName());
}
TEST_F(BehaviorEntityTest, SetName_Succeeds)
{
AZStd::string targetName = "Colden";
m_behaviorEntity.SetName(targetName.c_str());
EXPECT_EQ(targetName, m_rawEntity->GetName());
}
TEST_F(BehaviorEntityTest, GetOwningContextId_MatchesGameEntityContextId)
{
AzFramework::EntityContextId gameEntityContextId = AzFramework::EntityContextId::CreateNull();
AzFramework::GameEntityContextRequestBus::BroadcastResult(gameEntityContextId, &AzFramework::GameEntityContextRequestBus::Events::GetGameEntityContextId);
EXPECT_EQ(m_behaviorEntity.GetOwningContextId(), gameEntityContextId);
EXPECT_FALSE(m_behaviorEntity.GetOwningContextId().IsNull());
}
TEST_F(BehaviorEntityTest, Exists_ForActualEntity_True)
{
EXPECT_TRUE(m_behaviorEntity.Exists());
}
TEST_F(BehaviorEntityTest, Exists_ForDeletedEntity_False)
{
delete m_rawEntity;
EXPECT_FALSE(m_behaviorEntity.Exists());
}
TEST_F(BehaviorEntityTest, IsActivated_ForNewEntity_False)
{
EXPECT_EQ(AZ::Entity::State::Init, m_rawEntity->GetState()); // sanity check
EXPECT_FALSE(m_behaviorEntity.IsActivated());
}
TEST_F(BehaviorEntityTest, IsActivated_ForActivatedEntity_True)
{
m_rawEntity->Activate();
EXPECT_EQ(AZ::Entity::State::Active, m_rawEntity->GetState()); // sanity check
EXPECT_TRUE(m_behaviorEntity.IsActivated());
}
TEST_F(BehaviorEntityTest, Activate_Succeeds)
{
m_behaviorEntity.Activate();
EXPECT_EQ(AZ::Entity::State::Active, m_rawEntity->GetState());
}
TEST_F(BehaviorEntityTest, Deactivate_ForActivatedEntity_Succeeds)
{
m_rawEntity->Activate();
EXPECT_EQ(AZ::Entity::State::Active, m_rawEntity->GetState()); // sanity check
m_behaviorEntity.Deactivate();
EXPECT_EQ(AZ::Entity::State::Init, m_rawEntity->GetState());
}
TEST_F(BehaviorEntityTest, Deactivate_ForActivatingEntity_SucceedsOneTickLater)
{
// this component calls BehaviorEntity::Deactivate() during Activate().
// activate should succeed, and a deactivate should be queued on TickBus
m_rawEntity->CreateComponent(DeactivateDuringActivationComponentTypeId);
m_rawEntity->Activate();
EXPECT_EQ(AZ::Entity::State::Active, m_rawEntity->GetState());
m_application->Tick();
EXPECT_EQ(AZ::Entity::State::Init, m_rawEntity->GetState());
}
TEST_F(BehaviorEntityTest, CreateComponent_Succeeds)
{
AzFramework::BehaviorComponentId componentId = m_behaviorEntity.CreateComponent(HatComponentTypeId);
EXPECT_TRUE(componentId.IsValid());
AZ::Component* rawComponent = m_rawEntity->FindComponent(componentId);
EXPECT_NE(nullptr, rawComponent);
EXPECT_EQ(HatComponentTypeId, azrtti_typeid(rawComponent));
}
TEST_F(BehaviorEntityTest, CreateComponent_WithNonexistentType_Fails)
{
// We expect an assert in Entity::CreateComponent()
UnitTest::TestRunner::Instance().StartAssertTests();
AzFramework::BehaviorComponentId componentId = m_behaviorEntity.CreateComponent(AZ::TypeId::CreateNull());
UnitTest::TestRunner::Instance().StopAssertTests();
EXPECT_FALSE(componentId.IsValid());
EXPECT_EQ(0, m_rawEntity->GetComponents().size());
}
TEST_F(BehaviorEntityTest, CreateComponent_WithIncompatibleType_Fails)
{
AzFramework::BehaviorComponentId componentId1 = m_behaviorEntity.CreateComponent(EarComponentTypeId);
AzFramework::BehaviorComponentId componentId2 = m_behaviorEntity.CreateComponent(EarComponentTypeId);
EXPECT_TRUE(componentId1.IsValid());
EXPECT_FALSE(componentId2.IsValid());
EXPECT_EQ(1, m_rawEntity->GetComponents().size());
EXPECT_NE(nullptr, m_rawEntity->FindComponent(componentId1));
}
TEST_F(BehaviorEntityTest, DestroyComponent_Succeeds)
{
AZ::Component* rawComponent = m_rawEntity->CreateComponent(HatComponentTypeId);
EXPECT_NE(nullptr, rawComponent); // sanity check
bool destroyed = m_behaviorEntity.DestroyComponent(rawComponent->GetId());
EXPECT_TRUE(destroyed);
EXPECT_TRUE(m_rawEntity->GetComponents().empty());
}
TEST_F(BehaviorEntityTest, GetComponents_ReturnsAllComponents)
{
AZ::Component* rawComponent1 = m_rawEntity->CreateComponent(HatComponentTypeId);
AZ::Component* rawComponent2 = m_rawEntity->CreateComponent(EarComponentTypeId);
AZStd::vector<AzFramework::BehaviorComponentId> componentIds = m_behaviorEntity.GetComponents();
EXPECT_EQ(2, componentIds.size());
EXPECT_NE(componentIds.end(), AZStd::find(componentIds.begin(), componentIds.end(), AzFramework::BehaviorComponentId(rawComponent1->GetId())));
EXPECT_NE(componentIds.end(), AZStd::find(componentIds.begin(), componentIds.end(), AzFramework::BehaviorComponentId(rawComponent2->GetId())));
}
TEST_F(BehaviorEntityTest, FindComponentOfType_Succeeds)
{
m_rawEntity->CreateComponent(HatComponentTypeId);
AZ::Component* rawComponent2 = m_rawEntity->CreateComponent(EarComponentTypeId);
AzFramework::BehaviorComponentId foundComponentId = m_behaviorEntity.FindComponentOfType(azrtti_typeid(rawComponent2));
EXPECT_EQ(rawComponent2->GetId(), foundComponentId);
}
TEST_F(BehaviorEntityTest, FindComponentOfType_ForNonexistentComponent_ReturnsInvalidComponentId)
{
AzFramework::BehaviorComponentId foundComponentId = m_behaviorEntity.FindComponentOfType(HatComponentTypeId);
EXPECT_FALSE(foundComponentId.IsValid());
}
TEST_F(BehaviorEntityTest, FindAllComponentsOfType_Succeeds)
{
m_rawEntity->CreateComponent(HatComponentTypeId);
AZ::Component* rawComponent2 = m_rawEntity->CreateComponent(EarComponentTypeId);
m_rawEntity->CreateComponent(HatComponentTypeId);
AZ::Component* rawComponent4 = m_rawEntity->CreateComponent(EarComponentTypeId);
AZStd::vector<AzFramework::BehaviorComponentId> componentIds = m_behaviorEntity.FindAllComponentsOfType(EarComponentTypeId);
EXPECT_EQ(2, componentIds.size());
EXPECT_NE(componentIds.end(), AZStd::find(componentIds.begin(), componentIds.end(), AzFramework::BehaviorComponentId(rawComponent2->GetId())));
EXPECT_NE(componentIds.end(), AZStd::find(componentIds.begin(), componentIds.end(), AzFramework::BehaviorComponentId(rawComponent4->GetId())));
}
TEST_F(BehaviorEntityTest, GetComponentType_Succeeds)
{
AZ::Component* rawComponent = m_rawEntity->CreateComponent(HatComponentTypeId);
EXPECT_EQ(azrtti_typeid(rawComponent), m_behaviorEntity.GetComponentType(rawComponent->GetId()));
}
TEST_F(BehaviorEntityTest, GetComponentName_Succeeds)
{
AZ::Component* rawComponent = m_rawEntity->CreateComponent(HatComponentTypeId);
EXPECT_EQ(m_behaviorEntity.GetComponentName(rawComponent->GetId()), "HatComponent");
}
TEST_F(BehaviorEntityTest, SetComponentConfiguration_Succeeds)
{
HatComponent* rawComponent = m_rawEntity->CreateComponent<HatComponent>();
HatConfig customConfig;
customConfig.m_brimWidth = 5.f;
bool configSuccess = m_behaviorEntity.SetComponentConfiguration(rawComponent->GetId(), customConfig);
EXPECT_TRUE(configSuccess);
EXPECT_EQ(customConfig.m_brimWidth, rawComponent->m_config.m_brimWidth);
}
TEST_F(BehaviorEntityTest, GetComponentConfiguration_Succeeds)
{
HatComponent* rawComponent = m_rawEntity->CreateComponent<HatComponent>();
rawComponent->m_config.m_brimWidth = 12.f;
HatConfig retrievedConfig;
bool configSuccess = m_behaviorEntity.GetComponentConfiguration(rawComponent->GetId(), retrievedConfig);
EXPECT_TRUE(configSuccess);
EXPECT_EQ(rawComponent->m_config.m_brimWidth, retrievedConfig.m_brimWidth);
}
+199
View File
@@ -0,0 +1,199 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace UnitTest
{
using namespace AZ;
using namespace AzFramework;
//! Unit Test for testing Base64 Encode/Decode functions
class Base64Test
: public AllocatorsFixture
{
public:
Base64Test()
: AllocatorsFixture()
{
}
void SetUp() override
{
AllocatorsFixture::SetUp();
AllocatorInstance<PoolAllocator>::Create();
AllocatorInstance<ThreadPoolAllocator>::Create();
ComponentApplication::Descriptor desc;
desc.m_useExistingAllocator = true;
desc.m_enableDrilling = false; // we already created a memory driller for the test (AllocatorsFixture)
m_app.Create(desc);
}
void TearDown() override
{
m_app.Destroy();
AllocatorInstance<PoolAllocator>::Destroy();
AllocatorInstance<ThreadPoolAllocator>::Destroy();
AllocatorsFixture::TearDown();
}
virtual ~Base64Test()
{
}
ComponentApplication m_app;
};
TEST_F(Base64Test, EmptyStringEncodeTest)
{
AZStd::string value;
AZStd::string encodedString = AzFramework::StringFunc::Base64::Encode(reinterpret_cast<AZ::u8*>(value.data()), value.size());
EXPECT_EQ("", encodedString);
}
//! Test vectors from the Base-N encodings rfc https://tools.ietf.org/html/rfc4648#section-10
TEST_F(Base64Test, Rfc4648EncodeTest)
{
AZStd::string value = "f";
AZStd::string encodedString = AzFramework::StringFunc::Base64::Encode(reinterpret_cast<AZ::u8*>(value.data()), value.size());
EXPECT_EQ("Zg==", encodedString);
value = "fo";
encodedString = AzFramework::StringFunc::Base64::Encode(reinterpret_cast<AZ::u8*>(value.data()), value.size());
EXPECT_EQ("Zm8=", encodedString);
value = "foo";
encodedString = AzFramework::StringFunc::Base64::Encode(reinterpret_cast<AZ::u8*>(value.data()), value.size());
EXPECT_EQ("Zm9v", encodedString);
value = "foob";
encodedString = AzFramework::StringFunc::Base64::Encode(reinterpret_cast<AZ::u8*>(value.data()), value.size());
EXPECT_EQ("Zm9vYg==", encodedString);
value = "fooba";
encodedString = AzFramework::StringFunc::Base64::Encode(reinterpret_cast<AZ::u8*>(value.data()), value.size());
EXPECT_EQ("Zm9vYmE=", encodedString);
value = "foobar";
encodedString = AzFramework::StringFunc::Base64::Encode(reinterpret_cast<AZ::u8*>(value.data()), value.size());
EXPECT_EQ("Zm9vYmFy", encodedString);
}
//! Test vectors from the Base-N encodings rfc https://tools.ietf.org/html/rfc4648#section-10
TEST_F(Base64Test, Rfc4648DecodeTest)
{
AZStd::vector<AZ::u8> decodedVector;
AZStd::string value = "Zg==";
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedVector, value.data(), value.size()));
size_t strLen = AZStd::min(AZ_ARRAY_SIZE("f") - 1, decodedVector.size());
EXPECT_EQ(0, memcmp("f", decodedVector.data(), strLen));
value = "Zm8=";
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedVector, value.data(), value.size()));
strLen = AZStd::min(AZ_ARRAY_SIZE("fo") - 1, decodedVector.size());
EXPECT_EQ(0, memcmp("fo", decodedVector.data(), strLen));
value = "Zm9v";
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedVector, value.data(), value.size()));
strLen = AZStd::min(AZ_ARRAY_SIZE("foo") - 1, decodedVector.size());
EXPECT_EQ(0, memcmp("foo", decodedVector.data(), strLen));
value = "Zm9vYg==";
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedVector, value.data(), value.size()));
strLen = AZStd::min(AZ_ARRAY_SIZE("foob") - 1, decodedVector.size());
EXPECT_EQ(0, memcmp("foob", decodedVector.data(), strLen));
value = "Zm9vYmE=";
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedVector, value.data(), value.size()));
strLen = AZStd::min(AZ_ARRAY_SIZE("fooba") - 1, decodedVector.size());
EXPECT_EQ(0, memcmp("fooba", decodedVector.data(), strLen));
value = "Zm9vYmFy";
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedVector, value.data(), value.size()));
strLen = AZStd::min(AZ_ARRAY_SIZE("foobar") - 1, decodedVector.size());
EXPECT_EQ(0, memcmp("foobar", decodedVector.data(), strLen));
}
//! Test RFC 4648 Binary https://tools.ietf.org/html/rfc4648#page-12
TEST_F(Base64Test, Rfc4648BinaryEncodeTest)
{
const AZ::u8 binaryValue[] = { 0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e };
AZStd::string encodedString = AzFramework::StringFunc::Base64::Encode(binaryValue, AZ_ARRAY_SIZE(binaryValue));
EXPECT_EQ("FPucA9l+", encodedString);
const AZ::u8 binaryValue2[] = { 0x14, 0xfb, 0x9c, 0x03, 0xd9 };
encodedString = AzFramework::StringFunc::Base64::Encode(binaryValue2, AZ_ARRAY_SIZE(binaryValue2));
EXPECT_EQ("FPucA9k=", encodedString);
const AZ::u8 binaryValue3[] = { 0x14, 0xfb, 0x9c, 0x03 };
encodedString = AzFramework::StringFunc::Base64::Encode(binaryValue3, AZ_ARRAY_SIZE(binaryValue3));
EXPECT_EQ("FPucAw==", encodedString);
EXPECT_EQ("TlVMAEluU3RyaW5n", AzFramework::StringFunc::Base64::Encode(reinterpret_cast<const AZ::u8*>("NUL\0InString"), AZ_ARRAY_SIZE("NUL\0InString") - 1));
}
//! Test RFC 4648 Binary https://tools.ietf.org/html/rfc4648#page-12
TEST_F(Base64Test, Rfc4648BinaryDecodeTest)
{
const AZ::u8 expectedBinaryValue[] = { 0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e };
const AZ::u8 expectedBinaryValue2[] = { 0x14, 0xfb, 0x9c, 0x03, 0xd9 };
const AZ::u8 expectedBinaryValue3[] = { 0x14, 0xfb, 0x9c, 0x03 };
const AZ::u8 expectedBinaryValue4[] = { 'N','U', 'L', '\0', 'I', 'n', 'S', 't', 'r', 'i', 'n', 'g' };
AZStd::vector<AZ::u8> decodedVector;
const char textValue[] = "FPucA9l+";
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedVector, textValue, AZ_ARRAY_SIZE(textValue) - 1));
size_t vecLen = AZStd::min(AZ_ARRAY_SIZE(expectedBinaryValue), decodedVector.size());
EXPECT_EQ(0, memcmp(expectedBinaryValue, decodedVector.data(), vecLen));
const char textValue2[] = "FPucA9k=";
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedVector, textValue2, AZ_ARRAY_SIZE(textValue2) - 1));
vecLen = AZStd::min(AZ_ARRAY_SIZE(expectedBinaryValue2), decodedVector.size());
EXPECT_EQ(0, memcmp(expectedBinaryValue2, decodedVector.data(), vecLen));
const char textValue3[] = "FPucAw==";
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedVector, textValue3, AZ_ARRAY_SIZE(textValue3) - 1));
vecLen = AZStd::min(AZ_ARRAY_SIZE(expectedBinaryValue3), decodedVector.size());
EXPECT_EQ(0, memcmp(expectedBinaryValue3, decodedVector.data(), vecLen));
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedVector, "TlVMAEluU3RyaW5n", strlen("TlVMAEluU3RyaW5n")));
vecLen = AZStd::min(AZ_ARRAY_SIZE(expectedBinaryValue4), decodedVector.size());
EXPECT_EQ(0, memcmp(expectedBinaryValue4, decodedVector.data(), vecLen));
}
TEST_F(Base64Test, EmptyStringDecodeTest)
{
AZStd::vector<AZ::u8> decodedString;
const char value[] = "";
EXPECT_TRUE(AzFramework::StringFunc::Base64::Decode(decodedString, value, AZ_ARRAY_SIZE(value) - 1));
EXPECT_EQ(0, decodedString.size());
}
TEST_F(Base64Test, ErrorDecodeTest)
{
AZStd::vector<AZ::u8> expectedVector{ 'T', 'e', 'x', 't', 0xDF };
AZStd::vector<AZ::u8> decodedString = expectedVector;
const char value[] = "NotMultpleOf4";
EXPECT_FALSE(AzFramework::StringFunc::Base64::Decode(decodedString, value, AZ_ARRAY_SIZE(value) - 1));
EXPECT_EQ(expectedVector, decodedString);
const char value2[] = "Bad" "\x00" "Data@^&=";
EXPECT_FALSE(AzFramework::StringFunc::Base64::Decode(decodedString, value2, AZ_ARRAY_SIZE(value2) - 1));
EXPECT_EQ(expectedVector, decodedString);
}
}
+68
View File
@@ -0,0 +1,68 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
add_subdirectory(Physics)
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
ly_get_list_relative_pal_filename(common_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/Common)
ly_add_target(
NAME AzFrameworkTestShared STATIC
NAMESPACE AZ
FILES_CMAKE
framework_shared_tests_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
AZ::AzFramework
)
ly_add_target(
NAME ProcessLaunchTest EXECUTABLE
NAMESPACE AZ
FILES_CMAKE
process_launch_test_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
AZ::AzFramework
)
ly_add_target(
NAME Framework.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE AZ
FILES_CMAKE
frameworktests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
${pal_dir}
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AzToolsFramework
AZ::AzTestShared
RUNTIME_DEPENDENCIES
AZ::ProcessLaunchTest
)
ly_add_googletest(
NAME AZ::Framework.Tests
)
endif()
+203
View File
@@ -0,0 +1,203 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Math/Transform.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AZTestShared/Math/MathTestHelpers.h>
#include <AzCore/Math/SimdMath.h>
#include <AzCore/Math/Matrix4x4.h>
namespace UnitTest
{
class CameraStateFixture
: public ::testing::Test
{
public:
void SetUp() override
{
m_cameraState = AzFramework::CreateDefaultCamera(AZ::Transform::CreateIdentity(), AZ::Vector2(1024, 768));
}
AzFramework::CameraState m_cameraState;
};
class Translation
: public CameraStateFixture
, public ::testing::WithParamInterface<AZStd::tuple<float, float, float>>
{
};
class Rotation
: public CameraStateFixture
, public ::testing::WithParamInterface<AZStd::tuple<float, float, float>>
{
};
class WorldFromViewMatrix
: public CameraStateFixture
, public ::testing::WithParamInterface<AZStd::tuple<AZ::Vector3, AZ::Vector3, AZ::Vector2>>
{
};
class PerspectiveMatrix
: public CameraStateFixture
, public ::testing::WithParamInterface<AZStd::tuple<float, float, float, float>>
{
};
// Taken from Atom::MatrixUtils for testing purposes, this can be removed if MakePerspectiveFovMatrixRH makes it into AZ
static AZ::Matrix4x4 MakePerspectiveMatrixRH(float fovY, float aspectRatio, float nearClip, float farClip)
{
float sinFov, cosFov;
AZ::SinCos(0.5f * fovY, sinFov, cosFov);
float yScale = cosFov / sinFov; //cot(fovY/2)
float xScale = yScale / aspectRatio;
AZ::Matrix4x4 out;
out.SetRow(0, xScale, 0.f, 0.f, 0.f );
out.SetRow(1, 0.f, yScale, 0.f, 0.f );
out.SetRow(2, 0.f, 0.f, farClip / (nearClip - farClip), nearClip*farClip / (nearClip - farClip) );
out.SetRow(3, 0.f, 0.f, -1.f, 0.f );
return out;
}
TEST_P(Translation, Permutation)
{
// Given a position
const auto [x, y, z] = GetParam();
const AZ::Vector3 expectedPosition(x, y, z);
// Set the camera state's transform to the expected position
AzFramework::SetCameraTransform(m_cameraState, AZ::Transform::CreateTranslation(expectedPosition));
// Expect the camera state transform's position to be expected position
EXPECT_THAT(m_cameraState.m_position, IsCloseTolerance(expectedPosition, 0.01f));
// Expect the camera state transform's orientation to be identity
EXPECT_THAT(m_cameraState.m_forward, IsCloseTolerance(AZ::Vector3::CreateAxisY(), 0.01f));
EXPECT_THAT(m_cameraState.m_up, IsCloseTolerance(AZ::Vector3::CreateAxisZ(), 0.01f));
EXPECT_THAT(m_cameraState.m_side, IsCloseTolerance(AZ::Vector3::CreateAxisX(), 0.01f));
}
INSTANTIATE_TEST_CASE_P(
CameraState,
Translation,
testing::Combine(
testing::Values(0.0f, 1.0f),
testing::Values(0.0f, 1.0f),
testing::Values(0.0f, 1.0f))
);
TEST_P(Rotation, Permutation)
{
int expectedErrors = -1;
AZ_TEST_START_TRACE_SUPPRESSION;
// Given an orientation derived from the look at points
const auto [x, y, z] = GetParam();
const AZ::Vector3 from = AZ::Vector3::CreateZero();
const AZ::Vector3 to = AZ::Vector3(x, y, z);
const AZ::Transform expectedTransform = AZ::Transform::CreateLookAt(from, AZ::Vector3(x, y, z));
// Set the camera's rotation to the expected orientation
AzFramework::SetCameraTransform(m_cameraState, expectedTransform);
// Expect the camera state transform's position to be identity
EXPECT_THAT(m_cameraState.m_position, IsCloseTolerance(AZ::Vector3::CreateZero(), 0.01f));
if (from.IsClose(to, 0.001f))
{
// Expect one error to be generated by AZ::Transform::CreateLookAt() due to from and to being the same position
expectedErrors = 1;
// If the look at points yield an invalid orientation, expect identity rotation basis vectors
EXPECT_THAT(m_cameraState.m_forward, IsCloseTolerance(AZ::Vector3::CreateAxisY(), 0.01f));
EXPECT_THAT(m_cameraState.m_up, IsCloseTolerance(AZ::Vector3::CreateAxisZ(), 0.01f));
EXPECT_THAT(m_cameraState.m_side, IsCloseTolerance(AZ::Vector3::CreateAxisX(), 0.01f));
}
else
{
// Expect no errors to be generated by AZ::Transform::CreateLookAt()
expectedErrors = 0;
// If the look at points yield a valid orientation, expect the rotation basis vectors to be that of the orientation
EXPECT_THAT(m_cameraState.m_forward, IsCloseTolerance(expectedTransform.GetBasisY(), 0.01f));
EXPECT_THAT(m_cameraState.m_up, IsCloseTolerance(expectedTransform.GetBasisZ(), 0.01f));
EXPECT_THAT(m_cameraState.m_side, IsCloseTolerance(expectedTransform.GetBasisX(), 0.01f));
}
AZ_TEST_STOP_TRACE_SUPPRESSION(expectedErrors);
}
INSTANTIATE_TEST_CASE_P(
CameraState,
Rotation,
testing::Combine(
testing::Values(0.0f, 1.0f),
testing::Values(0.0f, 1.0f),
testing::Values(0.0f, 1.0f))
);
TEST_P(WorldFromViewMatrix, Permutation)
{
auto [translation, eulerRotation, viewportSize] = GetParam();
// Quaternion::CreateFromEulerAnglesDegrees takes a non-const Vector3&
AZ::Vector3 eulerRotationCopy = eulerRotation;
AZ::Quaternion rotation = AZ::Quaternion::CreateFromEulerAnglesDegrees(eulerRotationCopy);
AZ::Matrix4x4 worldFromView = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(rotation, translation);
m_cameraState = AzFramework::CreateCameraFromWorldFromViewMatrix(worldFromView, viewportSize);
EXPECT_EQ(m_cameraState.m_viewportSize, viewportSize);
EXPECT_THAT(m_cameraState.m_position, IsCloseTolerance(translation, 0.01f));
// Translate back into a quaternion to safely compare rotations
auto decomposedCameraStateRotation = AZ::Quaternion::CreateFromBasis(m_cameraState.m_side, m_cameraState.m_forward, m_cameraState.m_up);
auto rotationDelta = 1.f - decomposedCameraStateRotation.Dot(decomposedCameraStateRotation);
EXPECT_NEAR(rotationDelta, 0.f, 0.01f);
}
INSTANTIATE_TEST_CASE_P(
CameraState,
WorldFromViewMatrix,
testing::Combine(
testing::Values(AZ::Vector3{0.f, 0.f, 0.f}, AZ::Vector3{100.f, 0.f, 0.f}, AZ::Vector3{-5.f,10.f,-1.f}),
testing::Values(AZ::Vector3{0.f, 0.f, 0.f}, AZ::Vector3{90.f, 0.f, 0.f}, AZ::Vector3{-45.f, -45.f, -45.f}),
testing::Values(AZ::Vector2{100.f, 100.f})
)
);
TEST_P(PerspectiveMatrix, Permutation)
{
auto [fovY, aspectRatio, nearClip, farClip] = GetParam();
AZ::Matrix4x4 clipFromView = MakePerspectiveMatrixRH(fovY, aspectRatio, nearClip, farClip);
AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(m_cameraState, clipFromView);
EXPECT_NEAR(m_cameraState.m_nearClip, nearClip, 0.01f);
EXPECT_NEAR(m_cameraState.m_farClip, farClip, 1.f);
EXPECT_NEAR(m_cameraState.m_fovOrZoom, fovY, 0.01f);
}
INSTANTIATE_TEST_CASE_P(
CameraState,
PerspectiveMatrix,
testing::Combine(
testing::Values(1.f, 0.5f, 2.f),
testing::Values(1.f, 16.f / 9.f),
testing::Values(1.f, 50.f),
testing::Values(100.f, 10000.f)
)
);
} // namespace UnitTest
@@ -0,0 +1,180 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#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
+123
View File
@@ -0,0 +1,123 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Math/Uuid.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/Slice/SliceAssetHandler.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Entity/EntityContext.h>
namespace UnitTest
{
using namespace AZ;
using namespace AzFramework;
class EntityContextBasicTest
: public ScopedAllocatorSetupFixture
, public EntityContextEventBus::Handler
{
public:
EntityContextBasicTest()
{
}
void SetUp() override
{
AllocatorInstance<PoolAllocator>::Create();
AllocatorInstance<ThreadPoolAllocator>::Create();
Data::AssetManager::Descriptor desc;
Data::AssetManager::Create(desc);
}
virtual ~EntityContextBasicTest()
{
}
void TearDown() override
{
Data::AssetManager::Destroy();
AllocatorInstance<PoolAllocator>::Destroy();
AllocatorInstance<ThreadPoolAllocator>::Destroy();
}
void run()
{
ComponentApplication app;
ComponentApplication::Descriptor desc;
desc.m_useExistingAllocator = true;
desc.m_enableDrilling = false; // we already created a memory driller for the test (AllocatorsFixture)
app.Create(desc);
Data::AssetManager::Instance().RegisterHandler(aznew SliceAssetHandler(app.GetSerializeContext()), AZ::AzTypeInfo<AZ::SliceAsset>::Uuid());
AZ::Uuid entityContextId = AZ::Uuid::CreateRandom();
AZStd::unique_ptr<SliceEntityOwnershipService> m_entityOwnershipService =
AZStd::make_unique<AzFramework::SliceEntityOwnershipService>(entityContextId, app.GetSerializeContext());
EntityContext context(entityContextId, AZStd::move(m_entityOwnershipService), app.GetSerializeContext());
context.InitContext();
EntityContextEventBus::Handler::BusConnect(context.GetContextId());
AZ::Entity* entity = context.CreateEntity("MyEntity");
AZ_TEST_ASSERT(entity); // Should have created the entity.
AZ_TEST_ASSERT(m_createEntityEvents == 1);
AZ::Uuid contextId = AZ::Uuid::CreateNull();
EBUS_EVENT_ID_RESULT(contextId, entity->GetId(), EntityIdContextQueryBus, GetOwningContextId);
AZ_TEST_ASSERT(contextId == context.GetContextId()); // Context properly associated with entity?
AZ_TEST_ASSERT(context.DestroyEntity(entity));
AZ_TEST_ASSERT(m_destroyEntityEvents == 1);
AZ::Entity* sliceEntity = aznew AZ::Entity();
AZ::SliceComponent* sliceComponent = sliceEntity->CreateComponent<AZ::SliceComponent>();
sliceComponent->SetSerializeContext(app.GetSerializeContext());
sliceComponent->AddEntity(aznew AZ::Entity());
Data::Asset<SliceAsset> sliceAssetHolder = Data::AssetManager::Instance().CreateAsset<SliceAsset>(Data::AssetId(Uuid::CreateRandom()));
SliceAsset* sliceAsset = sliceAssetHolder.Get();
sliceAsset->SetData(sliceEntity, sliceComponent);
EntityContextEventBus::Handler::BusDisconnect(context.GetContextId());
app.Destroy();
}
void OnEntityContextCreateEntity(AZ::Entity& entity) override
{
(void)entity;
++m_createEntityEvents;
}
void OnEntityContextDestroyEntity(const AZ::EntityId& entity) override
{
(void)entity;
++m_destroyEntityEvents;
}
size_t m_createEntityEvents = 0;
size_t m_destroyEntityEvents = 0;
};
TEST_F(EntityContextBasicTest, Test)
{
run();
}
}
@@ -0,0 +1,132 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "EntityOwnershipServiceTestFixture.h"
#include <AzCore/UserSettings/UserSettingsComponent.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();
auto findComponentIterator = AZStd::find(defaultRequiredComponents.begin(), defaultRequiredComponents.end(),
azrtti_typeid<AzFramework::GameEntityContextComponent>());
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,63 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#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,271 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#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,411 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#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);
}, ".*");
}
}
+255
View File
@@ -0,0 +1,255 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#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_componentApplication->SetAssetRoot(dir);
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;
+475
View File
@@ -0,0 +1,475 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#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/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();
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
+945
View File
@@ -0,0 +1,945 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/string/string.h>
#include <AzCore/PlatformIncl.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/FileOperations.h>
#include <time.h>
#include <AzTest/Utils.h>
#include <AzFrameworkTests_Traits_Platform.h>
#if AZ_TRAIT_USE_WINDOWS_FILE_API
#include <sys/stat.h>
#include <io.h>
#endif
using namespace AZ;
using namespace AZ::IO;
using namespace AZ::Debug;
namespace PathUtil
{
AZStd::string AddSlash(const AZStd::string& path)
{
if (path.empty() || path[path.length() - 1] == '/')
{
return path;
}
if (path[path.length() - 1] == '\\')
{
return path.substr(0, path.length() - 1) + "/";
}
return path + "/";
}
}
namespace UnitTest
{
class NameMatchesFilterTest
: public AllocatorsFixture
{
public:
void run()
{
AZ_TEST_ASSERT(NameMatchesFilter("hello", "hello") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "he?l?") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "he???") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "he*") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "he*o") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "?*?o") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "h?*?") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "h?*?o") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "h?*?o?") == false);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "h***o*") == true);
AZ_TEST_ASSERT(NameMatchesFilter("something", "some??") == false);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "?????*") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "????*") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "h??*") == true);
AZ_TEST_ASSERT(NameMatchesFilter("hello", "??L*") == true);
AZ_TEST_ASSERT(NameMatchesFilter("anything", "**") == true);
AZ_TEST_ASSERT(NameMatchesFilter("any.thing", "*") == true);
AZ_TEST_ASSERT(NameMatchesFilter("anything", "") == false);
AZ_TEST_ASSERT(NameMatchesFilter("system.pak", "*.pak") == true);
AZ_TEST_ASSERT(NameMatchesFilter("system.pakx", "*.pak") == false);
AZ_TEST_ASSERT(NameMatchesFilter("system.pa", "*.pak") == false);
AZ_TEST_ASSERT(NameMatchesFilter("system.pak.3", "*.pak.*") == true);
AZ_TEST_ASSERT(NameMatchesFilter("system.pa.pak", "*.pak") == true);
AZ_TEST_ASSERT(NameMatchesFilter("log1234.log", "log????.log") == true);
AZ_TEST_ASSERT(NameMatchesFilter("log1234.log", "log?????.log") == false);
AZ_TEST_ASSERT(NameMatchesFilter("log151234.log", "log*.log") == true);
AZ_TEST_ASSERT(NameMatchesFilter(".pak", "*.pak") == true);
AZ_TEST_ASSERT(NameMatchesFilter("", "*.pak") == false);
AZ_TEST_ASSERT(NameMatchesFilter("", "") == true);
AZ_TEST_ASSERT(NameMatchesFilter("test.test", "????.????") == true);
AZ_TEST_ASSERT(NameMatchesFilter("testatest", "????.????") == false);
}
};
TEST_F(NameMatchesFilterTest, Test)
{
run();
}
/**
* FileIOStream test
*/
class FileIOStreamTest
: public AllocatorsFixture
{
public:
AZ::IO::LocalFileIO m_fileIO;
AZ::IO::FileIOBase* m_prevFileIO;
FileIOStreamTest()
{
}
void SetUp() override
{
AllocatorsFixture::SetUp();
m_prevFileIO = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FileIOBase::SetInstance(&m_fileIO);
}
~FileIOStreamTest()
{
}
void TearDown() override
{
AZ::IO::FileIOBase::SetInstance(m_prevFileIO);
AllocatorsFixture::TearDown();
}
void run()
{
AZ::Test::ScopedAutoTempDirectory tempDir;
char fileIOTestPath[AZ::IO::MaxPathLength];
azsnprintf(fileIOTestPath, AZ::IO::MaxPathLength, "%s/fileiotest.txt", tempDir.GetDirectory());
FileIOStream stream(fileIOTestPath, AZ::IO::OpenMode::ModeWrite);
AZ_TEST_ASSERT(stream.IsOpen());
char output[256];
azsnprintf(output, sizeof(output), "magic string");
AZ_TEST_ASSERT(strlen(output) + 1 == stream.Write(strlen(output) + 1, output));
stream.Close();
stream.Open(fileIOTestPath, AZ::IO::OpenMode::ModeRead);
AZ_TEST_ASSERT(stream.IsOpen());
AZ_TEST_ASSERT(strlen(output) + 1 == stream.Read(strlen(output) + 1, output));
AZ_TEST_ASSERT(strcmp(output, "magic string") == 0);
stream.Close();
}
};
TEST_F(FileIOStreamTest, Test)
{
run();
}
namespace LocalFileIOTest
{
class FolderFixture
: public ScopedAllocatorSetupFixture
{
public:
AZStd::string m_root;
AZStd::string folderName;
AZStd::string deepFolder;
AZStd::string extraFolder;
AZStd::string fileRoot;
AZStd::string file01Name;
AZStd::string file02Name;
AZStd::string file03Name;
int m_randomFolderKey = 0;
FolderFixture()
{
}
void ChooseRandomFolder()
{
char currentDir[AZ_MAX_PATH_LEN];
AZ::Utils::GetExecutableDirectory(currentDir, AZ_MAX_PATH_LEN);
folderName = currentDir;
folderName.append("/temp");
m_root = folderName;
if (folderName.size() > 0)
{
folderName = PathUtil::AddSlash(folderName);
}
AZStd::string tempName = AZStd::string::format("tmp%08x", m_randomFolderKey);
folderName.append(tempName.c_str());
folderName = PathUtil::AddSlash(folderName);
AZStd::replace(folderName.begin(), folderName.end(), '\\', '/');
// Make sure the drive letter is capitalized
if (folderName.size() > 2)
{
if (folderName[1] == ':')
{
folderName[0] = static_cast<char>(toupper(folderName[0]));
}
}
deepFolder = folderName;
deepFolder.append("test");
deepFolder = PathUtil::AddSlash(deepFolder);
deepFolder.append("subdir");
extraFolder = deepFolder;
extraFolder = PathUtil::AddSlash(extraFolder);
extraFolder.append("subdir2");
// make a couple files there, and in the root:
fileRoot = PathUtil::AddSlash(extraFolder);
}
void SetUp() override
{
// lets use a random temp folder name
srand(clock());
m_randomFolderKey = rand();
LocalFileIO local;
do
{
ChooseRandomFolder();
++m_randomFolderKey;
} while (local.IsDirectory(fileRoot.c_str()));
file01Name = fileRoot + "file01.txt";
file02Name = fileRoot + "file02.asdf";
file03Name = fileRoot + "test123.wha";
}
void TearDown() override
{
if ((!folderName.empty())&&(strstr(folderName.c_str(), "/temp") != nullptr))
{
// cleanup!
LocalFileIO local;
local.DestroyPath(folderName.c_str());
}
}
void CreateTestFiles()
{
LocalFileIO local;
AZ_TEST_ASSERT(local.CreatePath(fileRoot.c_str()));
AZ_TEST_ASSERT(local.IsDirectory(fileRoot.c_str()));
for (const AZStd::string& filename : { file01Name, file02Name, file03Name })
{
#ifdef AZ_COMPILER_MSVC
FILE* tempFile;
fopen_s(&tempFile, filename.c_str(), "wb");
#else
FILE* tempFile = fopen(filename.c_str(), "wb");
#endif
fwrite("this is just a test", 1, 19, tempFile);
fclose(tempFile);
}
}
};
class DirectoryTest
: public FolderFixture
{
public:
void run()
{
LocalFileIO local;
AZ_TEST_ASSERT(!local.Exists(folderName.c_str()));
AZStd::string longPathCreateTest = folderName;
longPathCreateTest.append("one");
longPathCreateTest = PathUtil::AddSlash(longPathCreateTest);
longPathCreateTest.append("two");
longPathCreateTest = PathUtil::AddSlash(longPathCreateTest);
longPathCreateTest.append("three");
AZ_TEST_ASSERT(!local.Exists(longPathCreateTest.c_str()));
AZ_TEST_ASSERT(!local.IsDirectory(longPathCreateTest.c_str()));
AZ_TEST_ASSERT(local.CreatePath(longPathCreateTest.c_str()));
AZ_TEST_ASSERT(local.IsDirectory(longPathCreateTest.c_str()));
AZ_TEST_ASSERT(!local.Exists(deepFolder.c_str()));
AZ_TEST_ASSERT(!local.IsDirectory(deepFolder.c_str()));
AZ_TEST_ASSERT(local.CreatePath(deepFolder.c_str()));
AZ_TEST_ASSERT(local.IsDirectory(deepFolder.c_str()));
AZ_TEST_ASSERT(local.Exists(deepFolder.c_str()));
AZ_TEST_ASSERT(local.CreatePath(deepFolder.c_str()));
AZ_TEST_ASSERT(local.Exists(deepFolder.c_str()));
}
};
TEST_F(DirectoryTest, Test)
{
run();
}
class ReadWriteTest
: public FolderFixture
{
public:
void run()
{
LocalFileIO local;
AZ_TEST_ASSERT(!local.Exists(fileRoot.c_str()));
AZ_TEST_ASSERT(!local.IsDirectory(fileRoot.c_str()));
AZ_TEST_ASSERT(local.CreatePath(fileRoot.c_str()));
AZ_TEST_ASSERT(local.IsDirectory(fileRoot.c_str()));
FILE* tempFile = nullptr;
azfopen(&tempFile, file01Name.c_str(), "wb");
fwrite("this is just a test", 1, 19, tempFile);
fclose(tempFile);
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
AZ_TEST_ASSERT(!local.Open("", AZ::IO::OpenMode::ModeWrite, fileHandle));
AZ_TEST_ASSERT(fileHandle == AZ::IO::InvalidHandle);
// test size without opening:
AZ::u64 fs = 0;
AZ_TEST_ASSERT(local.Size(file01Name.c_str(), fs));
AZ_TEST_ASSERT(fs == 19);
fileHandle = AZ::IO::InvalidHandle;
AZ::u64 modTimeA = local.ModificationTime(file01Name.c_str());
AZ_TEST_ASSERT(modTimeA != 0);
// test invalid handle ops:
AZ_TEST_ASSERT(!local.Seek(fileHandle, 0, AZ::IO::SeekType::SeekFromStart));
AZ_TEST_ASSERT(!local.Close(fileHandle));
AZ_TEST_ASSERT(!local.Eof(fileHandle));
AZ_TEST_ASSERT(!local.Flush(fileHandle));
AZ_TEST_ASSERT(!local.ModificationTime(fileHandle));
AZ_TEST_ASSERT(!local.Read(fileHandle, 0, 0, false));
AZ_TEST_ASSERT(!local.Tell(fileHandle, fs));
AZ_TEST_ASSERT(!local.Exists((file01Name + "notexist").c_str()));
AZ_TEST_ASSERT(local.Exists(file01Name.c_str()));
AZ_TEST_ASSERT(!local.IsReadOnly(file01Name.c_str()));
AZ_TEST_ASSERT(!local.IsDirectory(file01Name.c_str()));
// test reads and seeks.
AZ_TEST_ASSERT(local.Open(file01Name.c_str(), AZ::IO::OpenMode::ModeRead, fileHandle));
AZ_TEST_ASSERT(fileHandle != AZ::IO::InvalidHandle);
// use this again later...
AZ::u64 modTimeB = local.ModificationTime(fileHandle);
AZ_TEST_ASSERT(modTimeB != 0);
static const size_t testStringLen = 256;
char testString[testStringLen] = { 0 };
// test size on open handle:
fs = 0;
AZ_TEST_ASSERT(local.Size(fileHandle, fs));
AZ_TEST_ASSERT(fs == 19);
// test size without opening, after its already open:
fs = 0;
AZ_TEST_ASSERT(local.Size(file01Name.c_str(), fs));
AZ_TEST_ASSERT(fs == 19);
AZ::u64 offs = 0;
AZ_TEST_ASSERT(local.Tell(fileHandle, offs));
AZ_TEST_ASSERT(offs == 0);
AZ_TEST_ASSERT(local.Seek(fileHandle, 5, AZ::IO::SeekType::SeekFromStart));
AZ_TEST_ASSERT(!local.Eof(fileHandle));
AZ::u64 actualBytesRead = 0;
// situation
// this is just a test
// ^-------------
// 15 chars
AZ_TEST_ASSERT(local.Tell(fileHandle, offs));
AZ_TEST_ASSERT(offs == 5);
AZ_TEST_ASSERT(!local.Eof(fileHandle));
AZ_TEST_ASSERT(local.Read(fileHandle, testString, testStringLen, false, &actualBytesRead));
AZ_TEST_ASSERT(actualBytesRead == 14);
AZ_TEST_ASSERT(strncmp(testString, "is just a test", 14) == 0);
AZ_TEST_ASSERT(local.Eof(fileHandle));
// this is just a test
// ^
AZ_TEST_ASSERT(local.Seek(fileHandle, -5, AZ::IO::SeekType::SeekFromCurrent));
// this is just a test
// ^----
AZ_TEST_ASSERT(local.Tell(fileHandle, offs));
AZ_TEST_ASSERT(offs == 14);
AZ_TEST_ASSERT(!local.Eof(fileHandle));
AZ_TEST_ASSERT(local.Read(fileHandle, testString, testStringLen, false, &actualBytesRead));
AZ_TEST_ASSERT(actualBytesRead == 5);
AZ_TEST_ASSERT(strncmp(testString, " test", 5) == 0);
AZ_TEST_ASSERT(local.Eof(fileHandle));
// this is just a test
// ^
AZ_TEST_ASSERT(local.Seek(fileHandle, -6, AZ::IO::SeekType::SeekFromEnd));
// this is just a test
// ^---
AZ_TEST_ASSERT(local.Tell(fileHandle, offs));
AZ_TEST_ASSERT(offs == 13);
AZ_TEST_ASSERT(!local.Eof(fileHandle));
AZ_TEST_ASSERT(local.Read(fileHandle, testString, 4, true, &actualBytesRead));
AZ_TEST_ASSERT(actualBytesRead == 4);
AZ_TEST_ASSERT(strncmp(testString, "a te", 4) == 0);
AZ_TEST_ASSERT(local.Tell(fileHandle, offs));
AZ_TEST_ASSERT(offs == 17);
AZ_TEST_ASSERT(!local.Eof(fileHandle));
// fail when not enough bytes:
AZ_TEST_ASSERT(!local.Read(fileHandle, testString, testStringLen, true, &actualBytesRead));
AZ_TEST_ASSERT(local.Eof(fileHandle));
AZ_TEST_ASSERT(local.Close(fileHandle));
fileHandle = AZ::IO::InvalidHandle;
}
};
TEST_F(ReadWriteTest, Test)
{
run();
}
class PermissionsTest
: public FolderFixture
{
public:
void run()
{
LocalFileIO local;
CreateTestFiles();
#if AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST
#if AZ_TRAIT_USE_WINDOWS_FILE_API
_chmod(file01Name.c_str(), _S_IREAD);
#else
chmod(file01Name.c_str(), S_IRUSR | S_IRGRP | S_IROTH);
#endif
AZ_TEST_ASSERT(local.IsReadOnly(file01Name.c_str()));
#if AZ_TRAIT_USE_WINDOWS_FILE_API
_chmod(file01Name.c_str(), _S_IREAD | _S_IWRITE);
#else
chmod(file01Name.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
#endif
#endif
AZ_TEST_ASSERT(!local.IsReadOnly(file01Name.c_str()));
}
};
TEST_F(PermissionsTest, Test)
{
run();
}
class CopyMoveTests
: public FolderFixture
{
public:
void run()
{
LocalFileIO local;
AZ_TEST_ASSERT(local.CreatePath(fileRoot.c_str()));
AZ_TEST_ASSERT(local.IsDirectory(fileRoot.c_str()));
{
#ifdef AZ_COMPILER_MSVC
FILE* tempFile;
fopen_s(&tempFile, file01Name.c_str(), "wb");
#else
FILE* tempFile = fopen(file01Name.c_str(), "wb");
#endif
fwrite("this is just a test", 1, 19, tempFile);
fclose(tempFile);
}
// make sure attributes are copied (such as modtime) even if they're copied:
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500));
AZ_TEST_ASSERT(local.Copy(file01Name.c_str(), file02Name.c_str()));
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500));
AZ_TEST_ASSERT(local.Copy(file01Name.c_str(), file03Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file01Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file02Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file03Name.c_str()));
AZ_TEST_ASSERT(!local.DestroyPath(file01Name.c_str())); // you may not destroy files.
AZ_TEST_ASSERT(!local.DestroyPath(file02Name.c_str()));
AZ_TEST_ASSERT(!local.DestroyPath(file03Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file01Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file02Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file03Name.c_str()));
AZ::u64 f1s = 0;
AZ::u64 f2s = 0;
AZ::u64 f3s = 0;
AZ_TEST_ASSERT(local.Size(file01Name.c_str(), f1s));
AZ_TEST_ASSERT(local.Size(file02Name.c_str(), f2s));
AZ_TEST_ASSERT(local.Size(file03Name.c_str(), f3s));
AZ_TEST_ASSERT(f1s == f2s);
AZ_TEST_ASSERT(f1s == f3s);
// Copying over top other files is allowed
SystemFile file;
EXPECT_TRUE(file.Open(file01Name.c_str(), SystemFile::SF_OPEN_WRITE_ONLY));
file.Write("this is just a test that is longer", 34);
file.Close();
// make sure attributes are copied (such as modtime) even if they're copied:
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500));
EXPECT_TRUE(local.Copy(file01Name.c_str(), file02Name.c_str()));
f1s = 0;
f2s = 0;
f3s = 0;
EXPECT_TRUE(local.Size(file01Name.c_str(), f1s));
EXPECT_TRUE(local.Size(file02Name.c_str(), f2s));
EXPECT_TRUE(local.Size(file03Name.c_str(), f3s));
EXPECT_EQ(f1s, f2s);
EXPECT_NE(f1s, f3s);
}
};
TEST_F(CopyMoveTests, Test)
{
run();
}
class ModTimeTest
: public FolderFixture
{
public:
void run()
{
AZ::IO::LocalFileIO local;
CreateTestFiles();
AZ::u64 modTimeC = 0;
AZ::u64 modTimeD = 0;
modTimeC = local.ModificationTime(file02Name.c_str());
modTimeD = local.ModificationTime(file03Name.c_str());
// make sure modtimes are in ascending order (at least)
AZ_TEST_ASSERT(modTimeD >= modTimeC);
// now touch some of the files. This is also how we test append mode, and write mode.
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
AZ_TEST_ASSERT(local.Open(file02Name.c_str(), AZ::IO::OpenMode::ModeAppend | AZ::IO::OpenMode::ModeBinary, fileHandle));
AZ_TEST_ASSERT(fileHandle != AZ::IO::InvalidHandle);
AZ_TEST_ASSERT(local.Write(fileHandle, "more", 4));
AZ_TEST_ASSERT(local.Close(fileHandle));
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500));
// No-append-mode
AZ_TEST_ASSERT(local.Open(file03Name.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, fileHandle));
AZ_TEST_ASSERT(fileHandle != AZ::IO::InvalidHandle);
AZ_TEST_ASSERT(local.Write(fileHandle, "more", 4));
AZ_TEST_ASSERT(local.Close(fileHandle));
modTimeC = local.ModificationTime(file02Name.c_str());
modTimeD = local.ModificationTime(file03Name.c_str());
AZ_TEST_ASSERT(modTimeD > modTimeC);
AZ::u64 f1s = 0;
AZ::u64 f2s = 0;
AZ::u64 f3s = 0;
AZ_TEST_ASSERT(local.Size(file01Name.c_str(), f1s));
AZ_TEST_ASSERT(local.Size(file02Name.c_str(), f2s));
AZ_TEST_ASSERT(local.Size(file03Name.c_str(), f3s));
AZ_TEST_ASSERT(f2s == f1s + 4);
AZ_TEST_ASSERT(f3s == 4);
}
};
TEST_F(ModTimeTest, Test)
{
run();
}
class FindFilesTest
: public FolderFixture
{
public:
void run()
{
AZ::IO::LocalFileIO local;
CreateTestFiles();
AZStd::vector<AZStd::string> resultFiles;
bool foundOK = local.FindFiles(fileRoot.c_str(), "*",
[&](const char* filePath) -> bool
{
resultFiles.push_back(filePath);
return false; // early out!
});
AZ_TEST_ASSERT(foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 1);
resultFiles.clear();
foundOK = local.FindFiles(fileRoot.c_str(), "*",
[&](const char* filePath) -> bool
{
resultFiles.push_back(filePath);
return true; // continue iterating
});
AZ_TEST_ASSERT(foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 3);
// note: following tests accumulate more files without clearing resultfiles.
foundOK = local.FindFiles(fileRoot.c_str(), "*.txt",
[&](const char* filePath) -> bool
{
resultFiles.push_back(filePath);
return true; // continue iterating
});
AZ_TEST_ASSERT(foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 4);
foundOK = local.FindFiles(fileRoot.c_str(), "file*.asdf",
[&](const char* filePath) -> bool
{
resultFiles.push_back(filePath);
return true; // continue iterating
});
AZ_TEST_ASSERT(foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 5);
foundOK = local.FindFiles(fileRoot.c_str(), "asaf.asdf",
[&](const char* filePath) -> bool
{
resultFiles.push_back(filePath);
return true; // continue iterating
});
AZ_TEST_ASSERT(foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 5);
resultFiles.clear();
// test to make sure directories show up:
foundOK = local.FindFiles(deepFolder.c_str(), "*",
[&](const char* filePath) -> bool
{
resultFiles.push_back(filePath);
return true; // continue iterating
});
// canonicalize the name in the same way that find does.
//AZStd::replace() extraFolder.replace('\\', '/'); FIXME PPATEL
AZ_TEST_ASSERT(foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 1);
AZ_TEST_ASSERT(resultFiles[0] == extraFolder);
resultFiles.clear();
foundOK = local.FindFiles("o:137787621!@#$%^&&**())_+[])_", "asaf.asdf",
[&](const char* filePath) -> bool
{
resultFiles.push_back(filePath);
return true; // continue iterating
});
AZ_TEST_ASSERT(!foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 0);
AZStd::string file04Name = fileRoot + "test.wha";
// test rename
AZ_TEST_ASSERT(local.Rename(file03Name.c_str(), file04Name.c_str()));
AZ_TEST_ASSERT(!local.Rename(file03Name.c_str(), file04Name.c_str()));
AZ_TEST_ASSERT(local.Rename(file04Name.c_str(), file04Name.c_str())); // this is valid and ok
AZ_TEST_ASSERT(local.Exists(file04Name.c_str()));
AZ_TEST_ASSERT(!local.Exists(file03Name.c_str()));
AZ_TEST_ASSERT(!local.IsDirectory(file04Name.c_str()));
AZ::u64 f3s = 0;
AZ_TEST_ASSERT(local.Size(file04Name.c_str(), f3s));
AZ_TEST_ASSERT(f3s == 19);
// deep destroy directory:
AZ_TEST_ASSERT(local.DestroyPath(folderName.c_str()));
AZ_TEST_ASSERT(!local.Exists(folderName.c_str()));
}
};
TEST_F(FindFilesTest, Test)
{
run();
}
using AliasTest = FolderFixture;
TEST_F(AliasTest, Test)
{
AZ::IO::LocalFileIO local;
// test aliases
local.SetAlias("@test@", folderName.c_str());
const char* testDest1 = local.GetAlias("@test@");
AZ_TEST_ASSERT(testDest1 != nullptr);
const char* testDest2 = local.GetAlias("@NOPE@");
AZ_TEST_ASSERT(testDest2 == nullptr);
testDest1 = local.GetAlias("@test@"); // try with different case
AZ_TEST_ASSERT(testDest1 != nullptr);
// test resolving
const char* aliasTestPath = "@test@\\some\\path\\somefile.txt";
char aliasResolvedPath[AZ_MAX_PATH_LEN];
bool resolveDidWork = local.ResolvePath(aliasTestPath, aliasResolvedPath, AZ_MAX_PATH_LEN);
AZ_TEST_ASSERT(resolveDidWork);
AZStd::string expectedResolvedPath = folderName + "some/path/somefile.txt";
AZ_TEST_ASSERT(aliasResolvedPath == expectedResolvedPath);
// more resolve path tests with invalid inputs
const char* testPath = nullptr;
char* testResolvedPath = nullptr;
resolveDidWork = local.ResolvePath(testPath, aliasResolvedPath, AZ_MAX_PATH_LEN);
AZ_TEST_ASSERT(!resolveDidWork);
resolveDidWork = local.ResolvePath(aliasTestPath, testResolvedPath, AZ_MAX_PATH_LEN);
AZ_TEST_ASSERT(!resolveDidWork);
resolveDidWork = local.ResolvePath(aliasTestPath, aliasResolvedPath, 0);
AZ_TEST_ASSERT(!resolveDidWork);
// Test that sending in a too small output path fails,
// if the output buffer is smaller than the string being resolved
size_t SMALLER_THAN_PATH_BEING_RESOLVED = strlen(aliasTestPath) - 1;
AZ_TEST_START_TRACE_SUPPRESSION;
resolveDidWork = local.ResolvePath(aliasTestPath, aliasResolvedPath, SMALLER_THAN_PATH_BEING_RESOLVED);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
AZ_TEST_ASSERT(!resolveDidWork);
// Test that sending in a too small output path fails,
// if the output buffer is too small to hold the resolved path
size_t SMALLER_THAN_FINAL_RESOLVED_PATH = expectedResolvedPath.length() - 1;
resolveDidWork = local.ResolvePath(aliasTestPath, aliasResolvedPath, SMALLER_THAN_FINAL_RESOLVED_PATH);
AZ_TEST_ASSERT(!resolveDidWork);
// test clearing an alias
local.ClearAlias("@test@");
testDest1 = local.GetAlias("@test@");
AZ_TEST_ASSERT(testDest1 == nullptr);;
}
TEST_F(AliasTest, ResolvePath_PathViewOverload_Succeeds)
{
AZ::IO::LocalFileIO local;
local.SetAlias("@test@", folderName.c_str());
AZ::IO::PathView aliasTestPath = "@test@\\some\\path\\somefile.txt";
AZ::IO::FixedMaxPath aliasResolvedPath;
ASSERT_TRUE(local.ResolvePath(aliasResolvedPath, aliasTestPath));
const auto expectedResolvedPath = AZ::IO::FixedMaxPathString::format("%ssome/path/somefile.txt", folderName.c_str());
EXPECT_STREQ(expectedResolvedPath.c_str(), aliasResolvedPath.c_str());
AZStd::optional<AZ::IO::FixedMaxPath> optionalResolvedPath = local.ResolvePath(aliasTestPath);
ASSERT_TRUE(optionalResolvedPath);
EXPECT_STREQ(expectedResolvedPath.c_str(), optionalResolvedPath->c_str());
}
TEST_F(AliasTest, ResolvePath_PathViewOverloadWithEmptyPath_Fails)
{
AZ::IO::LocalFileIO local;
local.SetAlias("@test@", folderName.c_str());
AZ::IO::FixedMaxPath aliasResolvedPath;
EXPECT_FALSE(local.ResolvePath(aliasResolvedPath, {}));
}
TEST_F(AliasTest, ConvertToAlias_PathViewOverloadContainingExactAliasPath_Succeeds)
{
AZ::IO::LocalFileIO local;
AZ::IO::FixedMaxPathString aliasFolder;
EXPECT_TRUE(local.ConvertToAbsolutePath("/temp", aliasFolder.data(), aliasFolder.capacity()));
aliasFolder.resize_no_construct(AZStd::char_traits<char>::length(aliasFolder.data()));
local.SetAlias("@test@", aliasFolder.c_str());
AZ::IO::FixedMaxPath aliasPath;
ASSERT_TRUE(local.ConvertToAlias(aliasPath, AZ::IO::PathView(aliasFolder)));
EXPECT_STREQ("@test@", aliasPath.c_str());
AZStd::optional<AZ::IO::FixedMaxPath> optionalAliasPath = local.ConvertToAlias(AZ::IO::PathView(aliasFolder));
ASSERT_TRUE(optionalAliasPath);
EXPECT_STREQ("@test@", optionalAliasPath->c_str());
}
TEST_F(AliasTest, ConvertToAlias_PathViewOverloadStartingWithAliasPath_Succeeds)
{
AZ::IO::LocalFileIO local;
AZ::IO::FixedMaxPathString aliasFolder;
EXPECT_TRUE(local.ConvertToAbsolutePath("/temp", aliasFolder.data(), aliasFolder.capacity()));
aliasFolder.resize_no_construct(AZStd::char_traits<char>::length(aliasFolder.data()));
local.SetAlias("@test@", aliasFolder.c_str());
const auto testPath = AZ::IO::FixedMaxPathString::format("%s/Dir", aliasFolder.c_str());
AZ::IO::FixedMaxPath aliasPath;
ASSERT_TRUE(local.ConvertToAlias(aliasPath, AZ::IO::PathView(testPath)));
EXPECT_STREQ("@test@/Dir", aliasPath.c_str());
}
TEST_F(AliasTest, ConvertToAlias_PathViewOverloadInputPathWithoutPathSeparatorAndStartWithAliasPath_DoesNotSubstituteAlias)
{
AZ::IO::LocalFileIO local;
AZ::IO::FixedMaxPathString aliasFolder;
EXPECT_TRUE(local.ConvertToAbsolutePath("/temp", aliasFolder.data(), aliasFolder.capacity()));
aliasFolder.resize_no_construct(AZStd::char_traits<char>::length(aliasFolder.data()));
local.SetAlias("@test@", aliasFolder.c_str());
// Because there is no trailing path separator, the input path is really "/tempDir"
// Therefore the "/temp" alias shouldn't match as an alias should match a full directory
const auto testPath = AZ::IO::FixedMaxPathString::format("%sDir", aliasFolder.c_str());
AZ::IO::FixedMaxPath aliasPath{ testPath };
EXPECT_TRUE(local.ConvertToAlias(aliasPath, AZ::IO::PathView(testPath)));
EXPECT_STREQ(testPath.c_str(), aliasPath.c_str());
}
TEST_F(AliasTest, ConvertToAlias_PathViewOverloadWithTooLongPath_ReturnsFalse)
{
AZ::IO::LocalFileIO local;
AZ::IO::FixedMaxPathString aliasFolder;
EXPECT_TRUE(local.ConvertToAbsolutePath("/temp", aliasFolder.data(), aliasFolder.capacity()));
aliasFolder.resize_no_construct(AZStd::char_traits<char>::length(aliasFolder.data()));
local.SetAlias("@LongAliasThatIsLong@", aliasFolder.c_str());
AZStd::string path = static_cast<AZStd::string_view>(aliasFolder);
path.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
// The length of "@alias@" is longer than the aliased path
// Therefore ConvertToAlias should fail due to not being able to fit the alias in the buffer
path.append(AZ::IO::MaxPathLength, 'a');
AZ::IO::FixedMaxPath aliasPath;
AZ_TEST_START_TRACE_SUPPRESSION;
EXPECT_FALSE(local.ConvertToAlias(aliasPath, AZ::IO::PathView(path)));
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
class SmartMoveTests
: public FolderFixture
{
public:
void run()
{
LocalFileIO localFileIO;
AZ::IO::FileIOBase::SetInstance(&localFileIO);
AZStd::string path;
AzFramework::StringFunc::Path::GetFullPath(file01Name.c_str(), path);
AZ_TEST_ASSERT(localFileIO.CreatePath(path.c_str()));
AzFramework::StringFunc::Path::GetFullPath(file02Name.c_str(), path);
AZ_TEST_ASSERT(localFileIO.CreatePath(path.c_str()));
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
localFileIO.Open(file01Name.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, fileHandle);
localFileIO.Write(fileHandle, "DummyFile", 9);
localFileIO.Close(fileHandle);
AZ::IO::HandleType fileHandle1 = AZ::IO::InvalidHandle;
localFileIO.Open(file02Name.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, fileHandle1);
localFileIO.Write(fileHandle1, "TestFile", 8);
localFileIO.Close(fileHandle1);
fileHandle1 = AZ::IO::InvalidHandle;
localFileIO.Open(file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1);
static const size_t testStringLen = 256;
char testString[testStringLen] = { 0 };
localFileIO.Read(fileHandle1, testString, testStringLen);
localFileIO.Close(fileHandle1);
AZ_TEST_ASSERT(strncmp(testString, "TestFile", 8) == 0);
// try swapping files when none of the files are in use
AZ_TEST_ASSERT(AZ::IO::SmartMove(file01Name.c_str(), file02Name.c_str()));
fileHandle1 = AZ::IO::InvalidHandle;
localFileIO.Open(file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1);
testString[0] = '\0';
localFileIO.Read(fileHandle1, testString, testStringLen);
localFileIO.Close(fileHandle1);
AZ_TEST_ASSERT(strncmp(testString, "DummyFile", 9) == 0);
//try swapping files when source file is not present, this should fail
AZ_TEST_ASSERT(!AZ::IO::SmartMove(file01Name.c_str(), file02Name.c_str()));
fileHandle = AZ::IO::InvalidHandle;
localFileIO.Open(file01Name.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, fileHandle);
localFileIO.Write(fileHandle, "TestFile", 8);
localFileIO.Close(fileHandle);
#if AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN
fileHandle1 = AZ::IO::InvalidHandle;
localFileIO.Open(file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1);
testString[0] = '\0';
localFileIO.Read(fileHandle1, testString, testStringLen);
// try swapping files when the destination file is open for read only,
// since window is unable to move files that are open for read, this will fail.
AZ_TEST_ASSERT(!AZ::IO::SmartMove(file01Name.c_str(), file02Name.c_str()));
localFileIO.Close(fileHandle1);
#endif
fileHandle = AZ::IO::InvalidHandle;
localFileIO.Open(file01Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle);
// try swapping files when the source file is open for read only
AZ_TEST_ASSERT(AZ::IO::SmartMove(file01Name.c_str(), file02Name.c_str()));
localFileIO.Close(fileHandle);
fileHandle1 = AZ::IO::InvalidHandle;
localFileIO.Open(file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1);
testString[0] = '\0';
localFileIO.Read(fileHandle1, testString, testStringLen);
AZ_TEST_ASSERT(strncmp(testString, "TestFile", 8) == 0);
localFileIO.Close(fileHandle1);
localFileIO.Remove(file01Name.c_str());
localFileIO.Remove(file02Name.c_str());
localFileIO.DestroyPath(m_root.c_str());
AZ::IO::FileIOBase::SetInstance(nullptr);
}
};
TEST_F(SmartMoveTests, Test)
{
run();
}
}
} // namespace UnitTest
+403
View File
@@ -0,0 +1,403 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/FileTag/FileTag.h>
#include <AzFramework/FileTag/FileTagBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AZTestShared/Utils/Utils.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <fstream>
namespace UnitTest
{
const char DummyFile[] = "dummy.txt";
const char AnotherDummyFile[] = "Foo/Dummy.txt";
const char DummyPattern[] = R"(^(.+)_([a-z]+)\..+$)";
const char MatchingPatternFile[] = "Foo/dummy_abc.txt";
const char NonMatchingPatternFile[] = "Foo/dummy_a8c.txt";
const char DummyWildcard[] = "?oo.txt";
const char MatchingWildcardFile[] = "Foo.txt";
const char NonMatchingWildcardFile[] = "Test.txt";
const char* DummyFileTags[] = { "A", "B", "C", "D", "E", "F", "G" };
const char* DummyFileTagsLowerCase[] = { "a", "b", "c", "d", "e", "f", "g" };
enum DummyFileTagIndex
{
AIdx = 0,
BIdx,
CIdx,
DIdx,
EIdx,
FIdx,
GIdx
};
const char ExcludeFile[] = "Exclude";
const char IncludeFile[] = "Include";
class FileTagQueryManagerTest : public AzFramework::FileTag::FileTagQueryManager
{
public:
friend class GTEST_TEST_CLASS_NAME_(FileTagTest, FileTags_QueryFilePlusPatternMatch_Valid);
friend class GTEST_TEST_CLASS_NAME_(FileTagTest, FileTags_RemoveTag_Valid);
FileTagQueryManagerTest(AzFramework::FileTag::FileTagType fileTagType)
:AzFramework::FileTag::FileTagQueryManager(fileTagType)
{
}
void ClearData()
{
m_fileTagsMap.clear();
m_patternTagsMap.clear();
}
};
class FileTagTest
: public AllocatorsFixture
{
public:
void SetUp() override
{
AllocatorsFixture::SetUp();
m_data = AZStd::make_unique<StaticData>();
using namespace AzFramework::FileTag;
AZ::ComponentApplication::Descriptor desc;
desc.m_enableDrilling = false;
m_data->m_application.Start(desc);
const char* testAssetRoot = m_tempDirectory.GetDirectory();
// 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_data->m_localFileIO = AZStd::make_unique<AZ::IO::LocalFileIO>();
m_data->m_priorFileIO = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_data->m_localFileIO.get());
AZ::IO::FileIOBase::GetInstance()->SetAlias("@assets@", testAssetRoot);
m_data->m_excludeFileQueryManager = AZStd::make_unique<FileTagQueryManagerTest>(FileTagType::Exclude);
m_data->m_includeFileQueryManager = AZStd::make_unique<FileTagQueryManagerTest>(FileTagType::Include);
AZStd::vector<AZStd::string> excludedFileTags = { DummyFileTags[DummyFileTagIndex::AIdx], DummyFileTags[DummyFileTagIndex::BIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.AddFileTags(DummyFile, FileTagType::Exclude, excludedFileTags).IsSuccess());
AZStd::vector<AZStd::string> includedFileTags = { DummyFileTags[DummyFileTagIndex::CIdx], DummyFileTags[DummyFileTagIndex::DIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.AddFileTags(AnotherDummyFile, FileTagType::Include, includedFileTags).IsSuccess());
AZStd::vector<AZStd::string> excludedPatternTags = { DummyFileTags[DummyFileTagIndex::EIdx], DummyFileTags[DummyFileTagIndex::FIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.AddFilePatternTags(DummyPattern, FilePatternType::Regex, FileTagType::Exclude, excludedPatternTags).IsSuccess());
AZStd::vector<AZStd::string> includedWildcardTags = { DummyFileTags[DummyFileTagIndex::GIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.AddFilePatternTags(DummyWildcard, FilePatternType::Wildcard, FileTagType::Include, includedWildcardTags).IsSuccess());
AzFramework::StringFunc::Path::Join(testAssetRoot, AZStd::string::format("%s.%s", ExcludeFile, FileTagAsset::Extension()).c_str(), m_data->m_excludeFile);
AzFramework::StringFunc::Path::Join(testAssetRoot, AZStd::string::format("%s.%s", IncludeFile, FileTagAsset::Extension()).c_str(), m_data->m_includeFile);
EXPECT_TRUE(m_data->m_fileTagManager.Save(FileTagType::Exclude, m_data->m_excludeFile));
EXPECT_TRUE(m_data->m_fileTagManager.Save(FileTagType::Include, m_data->m_includeFile));
EXPECT_TRUE(m_data->m_excludeFileQueryManager->Load(m_data->m_excludeFile));
EXPECT_TRUE(m_data->m_includeFileQueryManager->Load(m_data->m_includeFile));
AzFramework::StringFunc::Path::Join(testAssetRoot, "test_dependencies.xml", m_data->m_engineDependenciesFile);
std::ofstream outFile(m_data->m_engineDependenciesFile.c_str(), std::ofstream::out | std::ofstream::app);
outFile << "<EngineDependencies versionnumber=\"1.0.0\"><Dependency path=\"Foo\\Dummy.txt\" optional=\"true\"/><Dependency path=\"Foo/dummy_abc.txt\" optional=\"false\"/></EngineDependencies>";
outFile.close();
}
void TearDown() override
{
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_data->m_priorFileIO);
m_data->m_application.Stop();
m_data.reset();
AllocatorsFixture::TearDown();
}
struct StaticData
{
AzFramework::Application m_application;
AzFramework::FileTag::FileTagManager m_fileTagManager;
AZStd::unique_ptr<FileTagQueryManagerTest> m_excludeFileQueryManager;
AZStd::unique_ptr<FileTagQueryManagerTest> m_includeFileQueryManager;
AZ::IO::FileIOBase* m_priorFileIO = nullptr;
AZStd::unique_ptr<AZ::IO::FileIOBase> m_localFileIO;
AZStd::string m_excludeFile;
AZStd::string m_includeFile;
AZStd::string m_engineDependenciesFile;
};
AZStd::unique_ptr<StaticData> m_data;
AZ::Test::ScopedAutoTempDirectory m_tempDirectory;
};
TEST_F(FileTagTest, FileTags_QueryFile_Valid)
{
AZStd::set<AZStd::string> tags = m_data->m_excludeFileQueryManager->GetTags(DummyFile);
ASSERT_EQ(tags.size(), 2);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::AIdx]), 1);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::BIdx]), 1);
tags = m_data->m_includeFileQueryManager->GetTags(DummyFile);
ASSERT_EQ(tags.size(), 0);
tags = m_data->m_includeFileQueryManager->GetTags(AnotherDummyFile);
ASSERT_EQ(tags.size(), 2);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::CIdx]), 1);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::DIdx]), 1);
tags = m_data->m_excludeFileQueryManager->GetTags(AnotherDummyFile);
ASSERT_EQ(tags.size(), 0);
}
TEST_F(FileTagTest, FileTags_QueryByAbsoluteFilePath_Valid)
{
AZStd::string absoluteDummyFilePath = DummyFile;
EXPECT_TRUE(AzFramework::StringFunc::AssetDatabasePath::Join("@assets@", absoluteDummyFilePath.c_str(), absoluteDummyFilePath));
AZStd::set<AZStd::string> tags = m_data->m_excludeFileQueryManager->GetTags(absoluteDummyFilePath);
ASSERT_EQ(tags.size(), 2);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::AIdx]), 1);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::BIdx]), 1);
tags = m_data->m_includeFileQueryManager->GetTags(absoluteDummyFilePath);
ASSERT_EQ(tags.size(), 0);
AZStd::string absoluteAnotherDummyFilePath = AnotherDummyFile;
EXPECT_TRUE(AzFramework::StringFunc::AssetDatabasePath::Join("@assets@", absoluteAnotherDummyFilePath.c_str(), absoluteAnotherDummyFilePath));
tags = m_data->m_includeFileQueryManager->GetTags(absoluteAnotherDummyFilePath);
ASSERT_EQ(tags.size(), 2);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::CIdx]), 1);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::DIdx]), 1);
tags = m_data->m_excludeFileQueryManager->GetTags(absoluteAnotherDummyFilePath);
ASSERT_EQ(tags.size(), 0);
}
TEST_F(FileTagTest, FileTags_QueryTagsDefinedForFilePathWithAlias_Valid)
{
using namespace AzFramework::FileTag;
// Set the customized alias
AZStd::string customizedAliasFilePath;
const char* assetsAlias = AZ::IO::FileIOBase::GetInstance()->GetAlias("@assets@");
AzFramework::StringFunc::AssetDatabasePath::Join(assetsAlias, "foo", customizedAliasFilePath);
AZ::IO::FileIOBase::GetInstance()->SetAlias("@customizedalias@", customizedAliasFilePath.c_str());
// Add tags for a file path with this customzied alias
AZStd::string DummyFileWithcustomizedAlias;
EXPECT_TRUE(AzFramework::StringFunc::AssetDatabasePath::Join("@customizedalias@", "dummy.txt", DummyFileWithcustomizedAlias));
AZStd::vector<AZStd::string> excludedFileTags = { DummyFileTags[DummyFileTagIndex::CIdx], DummyFileTags[DummyFileTagIndex::DIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.AddFileTags(DummyFileWithcustomizedAlias, FileTagType::Exclude, excludedFileTags).IsSuccess());
EXPECT_TRUE(m_data->m_fileTagManager.Save(FileTagType::Exclude, m_data->m_excludeFile));
m_data->m_excludeFileQueryManager->ClearData();
EXPECT_TRUE(m_data->m_excludeFileQueryManager->Load(m_data->m_excludeFile));
// Query the file and verify the tags were added successfully
AZStd::set<AZStd::string> tags = m_data->m_excludeFileQueryManager->GetTags(AnotherDummyFile);
ASSERT_EQ(tags.size(), 2);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::CIdx]), 1);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::DIdx]), 1);
}
TEST_F(FileTagTest, FileTags_QueryPattern_Valid)
{
AZStd::set<AZStd::string> tags = m_data->m_excludeFileQueryManager->GetTags(MatchingPatternFile);
ASSERT_EQ(tags.size(), 2);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::EIdx]), 1);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::FIdx]), 1);
tags = m_data->m_includeFileQueryManager->GetTags(MatchingPatternFile);
ASSERT_EQ(tags.size(), 0);
tags = m_data->m_excludeFileQueryManager->GetTags(NonMatchingPatternFile);
ASSERT_EQ(tags.size(), 0);
tags = m_data->m_includeFileQueryManager->GetTags(NonMatchingPatternFile);
ASSERT_EQ(tags.size(), 0);
}
TEST_F(FileTagTest, FileTags_QueryWildcard_Valid)
{
AZStd::set<AZStd::string> tags = m_data->m_includeFileQueryManager->GetTags(MatchingWildcardFile);
ASSERT_EQ(tags.size(), 1);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::GIdx]), 1);
tags = m_data->m_excludeFileQueryManager->GetTags(MatchingWildcardFile);
ASSERT_EQ(tags.size(), 0);
tags = m_data->m_excludeFileQueryManager->GetTags(NonMatchingWildcardFile);
ASSERT_EQ(tags.size(), 0);
tags = m_data->m_includeFileQueryManager->GetTags(NonMatchingWildcardFile);
ASSERT_EQ(tags.size(), 0);
}
TEST_F(FileTagTest, FileTags_LoadEngineDependencies_AddToExcludeFile)
{
m_data->m_excludeFileQueryManager->ClearData();
EXPECT_TRUE(m_data->m_excludeFileQueryManager->LoadEngineDependencies(m_data->m_engineDependenciesFile));
AZStd::string normalizedFilePath = AnotherDummyFile;
EXPECT_TRUE(AzFramework::StringFunc::Path::Normalize(normalizedFilePath));
AZStd::set<AZStd::string> outputTags = m_data->m_excludeFileQueryManager->GetTags(normalizedFilePath);
EXPECT_EQ(outputTags.size(), 2);
EXPECT_EQ(outputTags.count("ignore"), 1);
EXPECT_EQ(outputTags.count("productdependency"), 1);
normalizedFilePath = MatchingPatternFile;
EXPECT_TRUE(AzFramework::StringFunc::Path::Normalize(normalizedFilePath));
outputTags = m_data->m_excludeFileQueryManager->GetTags(normalizedFilePath);
EXPECT_EQ(outputTags.size(), 0);
}
TEST_F(FileTagTest, FileTags_QueryFilePlusPatternMatch_Valid)
{
using namespace AzFramework::FileTag;
AZStd::vector<AZStd::string> inputTags = { DummyFileTags[DummyFileTagIndex::GIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.AddFileTags(MatchingWildcardFile, FileTagType::Exclude, inputTags).IsSuccess());
inputTags = { DummyFileTags[DummyFileTagIndex::AIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.AddFilePatternTags(DummyWildcard, FilePatternType::Wildcard, FileTagType::Exclude, inputTags).IsSuccess());
EXPECT_TRUE(m_data->m_fileTagManager.Save(FileTagType::Exclude, m_data->m_excludeFile));
m_data->m_excludeFileQueryManager->ClearData();
EXPECT_TRUE(m_data->m_excludeFileQueryManager->Load(m_data->m_excludeFile));
AZStd::set<AZStd::string> outputTags = m_data->m_excludeFileQueryManager->GetTags(MatchingWildcardFile);
EXPECT_EQ(outputTags.size(), 2);
EXPECT_EQ(outputTags.count(DummyFileTagsLowerCase[DummyFileTagIndex::AIdx]), 1);
EXPECT_EQ(outputTags.count(DummyFileTagsLowerCase[DummyFileTagIndex::GIdx]), 1);
}
TEST_F(FileTagTest, FileTags_RemoveTag_Valid)
{
using namespace AzFramework::FileTag;
AZStd::set<AZStd::string> tags = m_data->m_excludeFileQueryManager->GetTags(DummyFile);
ASSERT_EQ(tags.size(), 2);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::AIdx]), 1);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::BIdx]), 1);
//remove Tag A
AZStd::vector<AZStd::string> excludedFileTags = { DummyFileTags[DummyFileTagIndex::AIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.RemoveFileTags(DummyFile, FileTagType::Exclude, excludedFileTags).IsSuccess());
EXPECT_TRUE(m_data->m_fileTagManager.Save(FileTagType::Exclude, m_data->m_excludeFile));
m_data->m_excludeFileQueryManager->ClearData();
EXPECT_TRUE(m_data->m_excludeFileQueryManager->Load(m_data->m_excludeFile));
tags = m_data->m_excludeFileQueryManager->GetTags(DummyFile);
ASSERT_EQ(tags.size(), 1);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::BIdx]), 1);
//remove Tag B
excludedFileTags = { DummyFileTags[DummyFileTagIndex::BIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.RemoveFileTags(DummyFile, FileTagType::Exclude, excludedFileTags).IsSuccess());
EXPECT_TRUE(m_data->m_fileTagManager.Save(FileTagType::Exclude, m_data->m_excludeFile));
m_data->m_excludeFileQueryManager->ClearData();
EXPECT_TRUE(m_data->m_excludeFileQueryManager->Load(m_data->m_excludeFile));
tags = m_data->m_excludeFileQueryManager->GetTags(DummyFile);
ASSERT_EQ(tags.size(), 0);
}
TEST_F(FileTagTest, FileTags_Matching_Valid)
{
AZStd::vector<AZStd::string> matchFileTags = { DummyFileTags[DummyFileTagIndex::AIdx], DummyFileTags[DummyFileTagIndex::BIdx] };
bool match = m_data->m_excludeFileQueryManager->Match(DummyFile, matchFileTags);
EXPECT_TRUE(match);
matchFileTags = { DummyFileTags[DummyFileTagIndex::AIdx] };
match = m_data->m_excludeFileQueryManager->Match(DummyFile, matchFileTags);
EXPECT_TRUE(match);
matchFileTags = { DummyFileTags[DummyFileTagIndex::BIdx] };
match = m_data->m_excludeFileQueryManager->Match(DummyFile, matchFileTags);
EXPECT_TRUE(match);
matchFileTags = { DummyFileTags[DummyFileTagIndex::CIdx] };
match = m_data->m_excludeFileQueryManager->Match(DummyFile, matchFileTags);
EXPECT_FALSE(match);
}
TEST_F(FileTagTest, FileTags_ValidateError_Ok)
{
using namespace AzFramework::FileTag;
AZStd::set<AZStd::string> tags = m_data->m_excludeFileQueryManager->GetTags(DummyFile);
ASSERT_EQ(tags.size(), 2);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::AIdx]), 1);
EXPECT_EQ(tags.count(DummyFileTagsLowerCase[DummyFileTagIndex::BIdx]), 1);
// file tags already exists
AZStd::vector<AZStd::string> excludedFileTags = { DummyFileTags[DummyFileTagIndex::AIdx], DummyFileTags[DummyFileTagIndex::BIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.AddFileTags(DummyFile, FileTagType::Exclude, excludedFileTags).IsSuccess());
//remove Tag C which does not exist
excludedFileTags = { DummyFileTags[DummyFileTagIndex::CIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.RemoveFileTags(DummyFile, FileTagType::Exclude, excludedFileTags).IsSuccess());
// Invalid FilePattern type
AZStd::vector<AZStd::string> excludedPatternTags = { DummyFileTags[DummyFileTagIndex::EIdx], DummyFileTags[DummyFileTagIndex::FIdx] };
EXPECT_FALSE(m_data->m_fileTagManager.RemoveFilePatternTags(DummyPattern, FilePatternType::Wildcard, FileTagType::Exclude, excludedPatternTags).IsSuccess());
// Removing a FilePattern that does not exits
const char pattern[] = R"(^(.+)_([a-z0-9]+)\..+$)";
excludedPatternTags = { DummyFileTags[DummyFileTagIndex::EIdx], DummyFileTags[DummyFileTagIndex::FIdx] };
EXPECT_FALSE(m_data->m_fileTagManager.RemoveFilePatternTags(pattern, FilePatternType::Regex, FileTagType::Exclude, excludedPatternTags).IsSuccess());
}
}
@@ -0,0 +1,86 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/typetraits/alignment_of.h>
#include <AzCore/std/typetraits/aligned_storage.h>
#include <AzFramework/Application/Application.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace AZ
{
class Entity;
}
namespace UnitTest
{
/**
* Test fixture that starts up an AzFramework::Application.
*/
class FrameworkApplicationFixture
: public ::testing::Test
{
protected:
// HACK: Special Application that excludes UserSettingsComponent.
// For some reason unit tests for different branches will read/write the same UserSettings.xml file,
// but those tests may have different versions of serialization code for writing UserSettings.xml, thus
// causing version conflicts. Ideally unit tests should not interact with physical files on disk, after
// we fix this problem NoUserSettingsApplication should be removed, and we can use AzFramework::Application directly.
class NoUserSettingsApplication
: public AzFramework::Application
{
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
AZ::ComponentTypeList components = AzFramework::Application::GetRequiredSystemComponents();
AZ::ComponentTypeList::iterator componentItr = AZStd::find(components.begin(), components.end(), azrtti_typeid<AZ::UserSettingsComponent>());
if (componentItr != components.end())
{
components.erase(componentItr);
}
return components;
}
};
void SetUp() override
{
m_appDescriptor.m_allocationRecords = true;
m_appDescriptor.m_allocationRecordsSaveNames = true;
m_appDescriptor.m_recordingMode = AZ::Debug::AllocationRecords::Mode::RECORD_FULL;
m_application = new (AZStd::addressof(m_applicationBuffer)) NoUserSettingsApplication();
m_application->Start(m_appDescriptor, m_appStartupParams);
}
void TearDown() override
{
m_application->~Application();
m_application = nullptr;
// Reset so next test can assume blank slate.
m_appStartupParams = AzFramework::Application::StartupParameters();
m_appDescriptor = AzFramework::Application::Descriptor();
}
// Customize the descriptor before SetUp() to affect the application's startup.
AzFramework::Application::Descriptor m_appDescriptor;
// Customize the startup params before SetUp() to affect the application's startup.
AzFramework::Application::StartupParameters m_appStartupParams;
// Can't store on the stack because the object must be properly destroyed on shutdown.
// Can't use unique_ptr yet because the allocators aren't up yet.
AZStd::aligned_storage<sizeof(NoUserSettingsApplication), AZStd::alignment_of<NoUserSettingsApplication>::value>::type m_applicationBuffer;
AzFramework::Application* m_application;
};
}
+113
View File
@@ -0,0 +1,113 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/Component/ComponentApplication.h>
namespace UnitTest
{
using namespace AZ;
class SetRestoreFileIOBaseRAII
{
public:
SetRestoreFileIOBaseRAII(AZ::IO::FileIOBase& fileIO)
: m_prevFileIO(AZ::IO::FileIOBase::GetInstance())
{
AZ::IO::FileIOBase::SetInstance(&fileIO);
}
~SetRestoreFileIOBaseRAII()
{
AZ::IO::FileIOBase::SetInstance(m_prevFileIO);
}
private:
AZ::IO::FileIOBase* m_prevFileIO;
};
class GenAppDescriptors
: public AllocatorsTestFixture
{
public:
void run()
{
struct Config
{
const char* platformName;
const char* configName;
const char* libSuffix;
};
ComponentApplication app;
SerializeContext serializeContext;
AZ::ComponentApplication::Descriptor::Reflect(&serializeContext, &app);
AZ::Entity::Reflect(&serializeContext);
DynamicModuleDescriptor::Reflect(&serializeContext);
AZ::Entity dummySystemEntity(AZ::SystemEntityId, "SystemEntity");
const Config config = {"Platform", "Config", "libSuffix"};
AZ::ComponentApplication::Descriptor descriptor;
if (config.libSuffix && config.libSuffix[0])
{
FakePopulateModules(descriptor, config.libSuffix);
}
const AZStd::string filename = AZStd::string::format("LYConfig_%s%s.xml", config.platformName, config.configName);
IO::FileIOStream stream(filename.c_str(), IO::OpenMode::ModeWrite);
ObjectStream* objStream = ObjectStream::Create(&stream, serializeContext, ObjectStream::ST_XML);
bool descWriteOk = objStream->WriteClass(&descriptor);
(void)descWriteOk;
AZ_Warning("ComponentApplication", descWriteOk, "Failed to write memory descriptor to application descriptor file %s!", filename.c_str());
bool entityWriteOk = objStream->WriteClass(&dummySystemEntity);
(void)entityWriteOk;
AZ_Warning("ComponentApplication", entityWriteOk, "Failed to write system entity to application descriptor file %s!", filename.c_str());
bool flushOk = objStream->Finalize();
(void)flushOk;
AZ_Warning("ComponentApplication", flushOk, "Failed finalizing application descriptor file %s!", filename.c_str());
}
void FakePopulateModules(AZ::ComponentApplication::Descriptor& desc, const char* libSuffix)
{
static const char* modules[] =
{
"LySystemModule",
};
if (desc.m_modules.empty())
{
for (const char* module : modules)
{
desc.m_modules.push_back();
desc.m_modules.back().m_dynamicLibraryPath = module;
desc.m_modules.back().m_dynamicLibraryPath += libSuffix;
}
}
}
};
TEST_F(GenAppDescriptors, Test)
{
AZ::IO::LocalFileIO fileIO;
SetRestoreFileIOBaseRAII restoreFileIOScope(fileIO);
run();
}
}
@@ -0,0 +1,224 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/Serialization/Utils.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
{
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
{
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);
}
+58
View File
@@ -0,0 +1,58 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzTest/AzTest.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <GridMate/Session/Session.h>
namespace UnitTest
{
class MockSession
: public GridMate::GridSession
{
public:
MockSession(GridMate::SessionService* service)
: GridSession(service)
{
}
void SetReplicaManager(GridMate::ReplicaManager* replicaManager)
{
m_replicaMgr = replicaManager;
}
MOCK_METHOD4(CreateRemoteMember, GridMate::GridMember*(const GridMate::string&, GridMate::ReadBuffer&, GridMate::RemotePeerMode, GridMate::ConnectionID));
MOCK_METHOD1(OnSessionParamChanged, void(const GridMate::GridSessionParam&));
MOCK_METHOD1(OnSessionParamRemoved, void(const GridMate::string&));
};
class MockSessionService
: public GridMate::SessionService
{
public:
MockSessionService()
: SessionService(GridMate::SessionServiceDesc())
{
}
~MockSessionService()
{
m_activeSearches.clear();
m_gridMate = nullptr;
}
MOCK_CONST_METHOD0(IsReady, bool());
};
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,151 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "GridMocks.h"
#include <GridMate/Replica/Interest/BitmaskInterestHandler.h>
#include <GridMate/Replica/Interest/InterestManager.h>
#include <GridMate/Replica/Interest/ProximityInterestHandler.h>
#include <AzFramework/Network/InterestManagerComponent.h>
#include <AzCore/Socket/AzSocket.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
using testing::_;
class MockInterestManagerEvents
: public AzFramework::InterestManagerEventsBus::Handler
{
public:
MockInterestManagerEvents()
{
BusConnect();
}
virtual ~MockInterestManagerEvents()
{
BusDisconnect();
}
MOCK_METHOD1(OnInterestManagerActivate, void(GridMate::InterestManager* im));
MOCK_METHOD1(OnInterestManagerDeactivate, void(GridMate::InterestManager* im));
};
class InterestManagerComponentFixture
: public AllocatorsFixture
{
public:
InterestManagerComponentFixture()
: AllocatorsFixture()
{
}
~InterestManagerComponentFixture()
{
}
void SetUp() override
{
AZ::AzSock::Startup();
AllocatorsFixture::SetUp();
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Create();
m_gridMate = GridMate::GridMateCreate(GridMate::GridMateDesc());
m_carrier = GridMate::DefaultCarrier::Create(GridMate::CarrierDesc(), m_gridMate);
m_sessionService = AZStd::make_unique<UnitTest::MockSessionService>();
m_gridSession = AZStd::make_unique<UnitTest::MockSession>(m_sessionService.get());
m_replicaManagerDesc.m_carrier = m_carrier;
m_replicaManagerDesc.m_myPeerId = AZ::Crc32(testing::UnitTest::GetInstance()->current_test_info()->test_case_name());
m_replicaManagerDesc.m_roles = GridMate::ReplicaMgrDesc::Role_SyncHost;
m_replicaManager = AZStd::make_unique<GridMate::ReplicaManager>();
m_replicaManager->Init(m_replicaManagerDesc);
m_gridSession->SetReplicaManager(m_replicaManager.get());
}
void TearDown() override
{
m_gridSession = nullptr;
m_sessionService = nullptr;
m_replicaManager->Shutdown();
m_replicaManager = nullptr;
m_carrier->Shutdown();
delete m_carrier;
GridMate::GridMateDestroy(m_gridMate);
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Destroy();
AllocatorsFixture::TearDown();
AZ::AzSock::Cleanup();
}
AZStd::unique_ptr<UnitTest::MockSessionService> m_sessionService;
AZStd::unique_ptr<UnitTest::MockSession> m_gridSession;
GridMate::IGridMate* m_gridMate;
GridMate::Carrier* m_carrier;
GridMate::ReplicaMgrDesc m_replicaManagerDesc;
AZStd::unique_ptr<GridMate::ReplicaManager> m_replicaManager;
};
TEST_F(InterestManagerComponentFixture, TestNetworkSessionDeactivate)
{
// Using StrictMock here will ensure that the test fails if any of the events fire (as no EXPECT_CALL has been set).
testing::StrictMock<MockInterestManagerEvents> interestManagerEvents;
AzFramework::InterestManagerComponent interestManagerComponent;
// This will connect the component to the NetBindingSystemEventsBus
interestManagerComponent.Activate();
// Ensure that the interest manager component handles receiving OnNetworkSessionDeactivated for a session that was never activated.
// This can happen in the event of a client failing to connect to a host.
AzFramework::NetBindingSystemEventsBus::Broadcast(
&AzFramework::NetBindingSystemEvents::OnNetworkSessionDeactivated, m_gridSession.get());
interestManagerComponent.Deactivate();
}
TEST_F(InterestManagerComponentFixture, TestNetworkSessionActivateAndDeactivate)
{
// Using StrictMock here will ensure that the test fails if any of the events fire (as no EXPECT_CALL has been set).
testing::StrictMock<MockInterestManagerEvents> interestManagerEvents;
AzFramework::InterestManagerComponent interestManagerComponent;
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<GridMate::BitmaskInterestChunk>();
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<GridMate::ProximityInterestChunk>();
// This will connect the component to the NetBindingSystemEventsBus
interestManagerComponent.Activate();
// Golden path test that the interest manager component behaves as expected under normal conditions
// (receiving OnNetworkSessionActivated followed by OnNetworkSessionDeactivated).
testing::Expectation activationEvent = EXPECT_CALL(interestManagerEvents, OnInterestManagerActivate(_))
.Times(1);
AzFramework::NetBindingSystemEventsBus::Broadcast(
&AzFramework::NetBindingSystemEvents::OnNetworkSessionActivated, m_gridSession.get());
EXPECT_CALL(interestManagerEvents, OnInterestManagerDeactivate(_))
.Times(1)
.After(activationEvent);
AzFramework::NetBindingSystemEventsBus::Broadcast(
&AzFramework::NetBindingSystemEvents::OnNetworkSessionDeactivated, m_gridSession.get());
interestManagerComponent.Deactivate();
}
}
+214
View File
@@ -0,0 +1,214 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Windowing/NativeWindow.h>
using namespace AZ;
using namespace AzFramework;
namespace UnitTest
{
using NativeWindowTest = AllocatorsFixture;
class NativeWindowListener
: public WindowNotificationBus::Handler
{
public:
NativeWindowListener(NativeWindowHandle windowHandle)
: m_windowHandle(windowHandle)
{
AzFramework::WindowNotificationBus::Handler::BusConnect(m_windowHandle);
}
~NativeWindowListener()
{
AzFramework::WindowNotificationBus::Handler::BusDisconnect(m_windowHandle);
}
// WindowNotificationBus::Handler overrides...
void OnWindowResized(uint32_t width, uint32_t height)
{
AZ_UNUSED(width);
AZ_UNUSED(height);
m_wasOnWindowResizedReceived = true;
}
void OnWindowClosed()
{
m_wasOnWindowClosedReceived = true;
}
NativeWindowHandle m_windowHandle = nullptr;
bool m_wasOnWindowResizedReceived = false;
bool m_wasOnWindowClosedReceived = false;
};
// Test that a window can be created and will start in the non-active state
#if AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS
TEST_F(NativeWindowTest, DISABLED_CreateWindow)
#else
TEST_F(NativeWindowTest, CreateWindow)
#endif // AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS
{
const uint32_t PosX = 0;
const uint32_t PosY = 0;
const uint32_t Width = 1280;
const uint32_t Height = 720;
WindowGeometry geometry(PosX, PosY, Width, Height);
AZStd::unique_ptr<NativeWindow> nativeWindow = AZStd::make_unique<AzFramework::NativeWindow>(
"Test Window", geometry);
EXPECT_FALSE(nativeWindow == nullptr) << "NativeWindow was not allocated correctly.";
EXPECT_FALSE(nativeWindow->IsActive()) << "NativeWindow was in active state after construction.";
}
// Test that a window can be created and activated
#if AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS
TEST_F(NativeWindowTest, DISABLED_ActivateWindow)
#else
TEST_F(NativeWindowTest, ActivateWindow)
#endif // AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS
{
const uint32_t PosX = 0;
const uint32_t PosY = 0;
const uint32_t Width = 1280;
const uint32_t Height = 720;
WindowGeometry geometry(PosX, PosY, Width, Height);
AZStd::unique_ptr<NativeWindow> nativeWindow = AZStd::make_unique<AzFramework::NativeWindow>(
"Test Window", geometry);
EXPECT_FALSE(nativeWindow == nullptr) << "NativeWindow was not allocated correctly.";
nativeWindow->Activate();
EXPECT_TRUE(nativeWindow->IsActive()) << "NativeWindow was in inactive state after Activate called.";
// The window will get deactivated automatically when the NativeWindow is destructed
}
// Test that a window can be created, activated and deactivated
#if AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS
TEST_F(NativeWindowTest, DISABLED_DectivateWindow)
#else
TEST_F(NativeWindowTest, DectivateWindow)
#endif // AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS
{
const uint32_t PosX = 0;
const uint32_t PosY = 0;
const uint32_t Width = 1280;
const uint32_t Height = 720;
WindowGeometry geometry(PosX, PosY, Width, Height);
AZStd::unique_ptr<NativeWindow> nativeWindow = AZStd::make_unique<AzFramework::NativeWindow>(
"Test Window", geometry);
EXPECT_FALSE(nativeWindow == nullptr) << "NativeWindow was not allocated correctly.";
nativeWindow->Activate();
EXPECT_TRUE(nativeWindow->IsActive()) << "NativeWindow was in inactive state after Activate called.";
nativeWindow->Deactivate();
EXPECT_FALSE(nativeWindow->IsActive()) << "NativeWindow was in active state after Deactivate called.";
}
// Test that a window responds to the GetClientAreaSize bus request
#if AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS
TEST_F(NativeWindowTest, DISABLED_GetClientAreaSize)
#else
TEST_F(NativeWindowTest, GetClientAreaSize)
#endif // AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS
{
const uint32_t PosX = 0;
const uint32_t PosY = 0;
const uint32_t Width = 1280;
const uint32_t Height = 720;
WindowGeometry geometry(PosX, PosY, Width, Height);
AZStd::unique_ptr<NativeWindow> nativeWindow = AZStd::make_unique<AzFramework::NativeWindow>(
"Test Window", geometry);
EXPECT_FALSE(nativeWindow == nullptr) << "NativeWindow was not allocated correctly.";
nativeWindow->Activate();
EXPECT_TRUE(nativeWindow->IsActive()) << "NativeWindow was in inactive state after activation.";
NativeWindowHandle windowHandle = nativeWindow->GetWindowHandle();
WindowSize windowSize;
WindowRequestBus::EventResult(windowSize, windowHandle, &WindowRequestBus::Events::GetClientAreaSize);
EXPECT_TRUE(windowSize.m_width > 0) << "NativeWindow was created with wrong geometry.";
EXPECT_TRUE(windowSize.m_height > 0) << "NativeWindow was created with wrong geometry.";
EXPECT_TRUE(windowSize.m_width <= geometry.m_width) << "NativeWindow was created with wrong geometry.";
EXPECT_TRUE(windowSize.m_height <= geometry.m_height) << "NativeWindow was created with wrong geometry.";
}
// Test that a window sends the correct notifications
#if AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS
TEST_F(NativeWindowTest, DISABLED_Notifications)
#else
TEST_F(NativeWindowTest, Notifications)
#endif // AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS
{
const uint32_t PosX = 0;
const uint32_t PosY = 0;
const uint32_t Width = 1280;
const uint32_t Height = 720;
WindowGeometry geometry(PosX, PosY, Width, Height);
AZStd::unique_ptr<NativeWindow> nativeWindow = AZStd::make_unique<AzFramework::NativeWindow>(
"Test Window", geometry);
EXPECT_FALSE(nativeWindow == nullptr) << "NativeWindow was not allocated correctly.";
NativeWindowHandle windowHandle = nativeWindow->GetWindowHandle();
EXPECT_FALSE(windowHandle == nullptr) << "NativeWindow has invalid handle.";
NativeWindowListener listener(windowHandle);
EXPECT_FALSE(listener.m_wasOnWindowResizedReceived) << "No notifications should be received yet.";
EXPECT_FALSE(listener.m_wasOnWindowClosedReceived) << "No notifications should be received yet.";
nativeWindow->Activate();
WindowSize windowSize;
WindowRequestBus::EventResult(windowSize, windowHandle, &WindowRequestBus::Events::GetClientAreaSize);
bool windowSizeChanged = windowSize.m_width != geometry.m_width || windowSize.m_height != geometry.m_height;
EXPECT_TRUE(nativeWindow->IsActive()) << "NativeWindow was in inactive state after activation.";
EXPECT_TRUE(listener.m_wasOnWindowResizedReceived || !windowSizeChanged) << "Expected the OnWindowResized notification to have occurred.";
EXPECT_FALSE(listener.m_wasOnWindowClosedReceived) << "Did not expect the OnWindowClosed notification to have occurred.";
listener.m_wasOnWindowResizedReceived = false;
nativeWindow->Deactivate();
EXPECT_FALSE(nativeWindow->IsActive()) << "NativeWindow was in active state after deactivation.";
EXPECT_FALSE(listener.m_wasOnWindowResizedReceived) << "Did not expect the OnWindowResized notification to have occurred.";
EXPECT_TRUE(listener.m_wasOnWindowClosedReceived) << "Expected the OnWindowClosed notification to have occurred.";
}
} // namespace UnitTest
+600
View File
@@ -0,0 +1,600 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/containers/ring_buffer.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
#include <AzFramework/Network/NetBindable.h>
#include <GridMate/GridMate.h>
#include <GridMate/Session/LANSession.h>
#include <GridMate/Replica/ReplicaChunkDescriptor.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <GridMate/Replica/ReplicaFunctions.h>
#include <GridMate/Replica/DataSet.h>
#include <GridMate/Replica/RemoteProcedureCall.h>
#include <GridMate/Serialize/DataMarshal.h>
#include <GridMate/Serialize/CompressionMarshal.h>
#include <GridMate/Carrier/Utils.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/Memory/AllocationRecords.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
namespace UnitTest
{
#if 0
using namespace AZ;
/**
*/
class NetBindingTestComponent
: public AZ::Component
, public AzFramework::NetBindable
{
friend class NetBindingComponentChunk;
public:
AZ_COMPONENT(NetBindingTestComponent, "{DE5CF1C0-B4B6-4BB0-86FE-936B400871E0}", AzFramework::NetBindable);
protected:
class NetChunk
: public GridMate::ReplicaChunk
{
public:
AZ_CLASS_ALLOCATOR(NetChunk, AZ::SystemAllocator, 0);
static const char* GetChunkName() { return "NetBindingTestComponent::NetChunk"; }
bool IsReplicaMigratable() override { return false; }
};
///////////////////////////////////////////////////////////////////////
// NetBindable
GridMate::ReplicaChunkPtr GetNetworkBinding() override
{
AZ_TracePrintf("NetBinding", "NetBindingTestComponent::GetNetworkBinding()\n");
m_chunk = GridMate::CreateReplicaChunk<NetChunk>();
AZ_Assert(m_chunk, "Failed to create NetBindingTestComponent::NetChunk!");
return m_chunk;
}
void SetNetworkBinding(GridMate::ReplicaChunkPtr binding) override
{
AZ_TracePrintf("NetBinding", "NetBindingTestComponent::SetNetworkBinding()\n");
AZ_TEST_ASSERT(binding);
AZ_TEST_ASSERT(binding->GetDescriptor()->GetChunkTypeId() == GridMate::ReplicaChunkClassId(NetChunk::GetChunkName()));
m_chunk = AZStd::static_pointer_cast<NetChunk>(binding);
}
void UnbindFromNetwork() override
{
if (m_chunk)
{
AZ_TracePrintf("NetBinding", "NetBindingTestComponent::UnbindFromNetwork()\n");
m_chunk = nullptr;
}
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// AZ::Component
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<NetBindingTestComponent, AZ::Component, AzFramework::NetBindable>()
;
}
// We also need to register the chunk type, and this would be a good time to do so.
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<NetChunk>();
}
void Activate() override
{
AZ_TracePrintf("NetBinding", "NetBindingTestComponent::Activate()\n");
}
void Deactivate() override
{
AZ_TracePrintf("NetBinding", "NetBindingTestComponent::Deactivate()\n");
UnbindFromNetwork();
}
///////////////////////////////////////////////////////////////////////
AZStd::intrusive_ptr<NetChunk> m_chunk;
};
/**
* Fakes the behavior of NetBindingSystemContextData on the host side
*/
class FakeNetBindingContextChunk
: public GridMate::ReplicaChunk
{
public:
AZ_CLASS_ALLOCATOR(FakeNetBindingContextChunk, AZ::SystemAllocator, 0);
static const char* GetChunkName() { return "NetBindingSystemContextData"; } // We are pretending to be a NetBindingSystemContextData
FakeNetBindingContextChunk()
: m_bindingContextSequence("BindingContextSequence", AzFramework::UnspecifiedNetBindingContextSequence)
{
}
bool IsReplicaMigratable() override { return true; }
GridMate::DataSet<AZ::u32, GridMate::VlqU32Marshaler> m_bindingContextSequence;
};
/*
* NetBindingSystemComponentLifecycleTest
*/
class NetBindingSystemComponentLifecycleTest
: public GridMate::SessionEventBus::Handler
, public AzFramework::NetBindingHandlerBus::Handler
{
public:
void OnSessionCreated(GridMate::GridSession* session) override
{
if (session == m_session)
{
if (session->IsHost())
{
EBUS_EVENT(AzFramework::NetBindingSystemBus, OnNetworkSessionActivated, session);
}
}
}
void OnSessionJoined(GridMate::GridSession* session) override
{
if (session == m_session)
{
EBUS_EVENT(AzFramework::NetBindingSystemBus, OnNetworkSessionActivated, session);
}
}
void OnSessionDelete(GridMate::GridSession* session)
{
if (session == m_session)
{
EBUS_EVENT(AzFramework::NetBindingSystemBus, OnNetworkSessionDeactivated, session);
m_session = nullptr;
}
}
void BindToNetwork(GridMate::ReplicaPtr bindTo) override
{
// Verify that BindToNetwork() is not called more than once
AZ_TEST_ASSERT(!m_receivedBindEvent);
m_receivedBindEvent = true;
// Test that now we should be binding to the network
bool shouldBindToNetwork = false;
EBUS_EVENT_RESULT(shouldBindToNetwork, AzFramework::NetBindingSystemBus, ShouldBindToNetwork);
AZ_TEST_ASSERT(shouldBindToNetwork);
// Verify that the context sequence is no longer unspecified
AzFramework::NetBindingContextSequence contextSequence = AzFramework::UnspecifiedNetBindingContextSequence;
EBUS_EVENT_RESULT(contextSequence, AzFramework::NetBindingSystemBus, GetCurrentContextSequence);
AZ_TEST_ASSERT(contextSequence != AzFramework::UnspecifiedNetBindingContextSequence);
}
void UnbindFromNetwork() override
{
// Verify that UnbindFromNetwork() is not called more than once
AZ_TEST_ASSERT(!m_receivedUnbindEvent);
m_receivedUnbindEvent = true;
}
void run()
{
// Setup
AZ::ComponentApplication app;
AZ::ComponentApplication::Descriptor appDesc;
appDesc.m_recordsMode = AZ::Debug::AllocationRecords::RECORD_FULL;
AZ::Entity* systemEntity = app.Create(appDesc);
app.RegisterComponentDescriptor(AzFramework::NetBindingSystemComponent::CreateDescriptor());
app.RegisterComponentDescriptor(AzFramework::GameEntityContextComponent::CreateDescriptor());
systemEntity->Init();
systemEntity->CreateComponent<AZ::MemoryComponent>();
systemEntity->CreateComponent<AZ::AssetManagerComponent>();
systemEntity->CreateComponent<AzFramework::GameEntityContextComponent>();
systemEntity->CreateComponent<AzFramework::NetBindingSystemComponent>();
systemEntity->Activate();
AzFramework::NetBindingHandlerBus::Handler::BusConnect();
GridMate::GridMateDesc gridMateDesc;
GridMate::IGridMate* gridMate = GridMate::GridMateCreate(gridMateDesc);
GridMate::GridMateAllocatorMP::Descriptor allocDesc;
allocDesc.m_stackRecordLevels = 15;
allocDesc.m_custom = &AZ::AllocatorInstance<AZ::SystemAllocator>::Get();
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Create(allocDesc);
if (AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Get().GetRecords())
{
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Get().GetRecords()->SetMode(AZ::Debug::AllocationRecords::RECORD_FULL);
}
GridMate::StartGridMateService<GridMate::LANSessionService>(gridMate, GridMate::SessionServiceDesc());
GridMate::SessionEventBus::Handler::BusConnect(gridMate);
// Test offline behavior
{
bool shouldBindToNetwork = true;
EBUS_EVENT_RESULT(shouldBindToNetwork, AzFramework::NetBindingSystemBus, ShouldBindToNetwork);
AZ_TEST_ASSERT(!shouldBindToNetwork);
AzFramework::NetBindingContextSequence offlineContextSequence = 0xBADF00D;
EBUS_EVENT_RESULT(offlineContextSequence, AzFramework::NetBindingSystemBus, GetCurrentContextSequence);
AZ_TEST_ASSERT(offlineContextSequence == AzFramework::UnspecifiedNetBindingContextSequence);
}
// Test host-side behavior
{
m_receivedBindEvent = m_receivedUnbindEvent = false;
// Host a session
GridMate::CarrierDesc carrierDesc;
carrierDesc.m_enableDisconnectDetection = true;
GridMate::LANSessionParams sessionParams;
sessionParams.m_numPublicSlots = 10;
sessionParams.m_flags = 0;
sessionParams.m_port = HOST_PORT;
sessionParams.m_params[sessionParams.m_numParams].m_id = "filter";
sessionParams.m_params[sessionParams.m_numParams].m_value = GridMate::Utils::GetMachineAddress();
sessionParams.m_numParams++;
m_session = gridMate->GetMultiplayerService()->HostSession(&sessionParams, carrierDesc);
int nFrame = 0;
while (m_session)
{
if (nFrame == 10)
{
// Verify that BindToNetwork() has been called
AZ_TEST_ASSERT(m_receivedBindEvent);
// Verify that we have a valid context sequence
AzFramework::NetBindingContextSequence contextSequence1 = AzFramework::UnspecifiedNetBindingContextSequence;
EBUS_EVENT_RESULT(contextSequence1, AzFramework::NetBindingSystemBus, GetCurrentContextSequence);
AZ_TEST_ASSERT(contextSequence1 != AzFramework::UnspecifiedNetBindingContextSequence);
EBUS_EVENT(AzFramework::GameEntityContextRequestBus, ResetGameContext);
// Verify that the context sequence was incremented
AzFramework::NetBindingContextSequence contextSequence2 = contextSequence1;
EBUS_EVENT_RESULT(contextSequence2, AzFramework::NetBindingSystemBus, GetCurrentContextSequence);
AZ_TEST_ASSERT(contextSequence2 != AzFramework::UnspecifiedNetBindingContextSequence);
AZ_TEST_ASSERT(contextSequence2 > contextSequence1);
m_session->Leave(false);
}
app.Tick();
gridMate->Update();
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
nFrame++;
}
// Verify that we should no longer bind to the network
bool shouldBindToNetwork = true;
EBUS_EVENT_RESULT(shouldBindToNetwork, AzFramework::NetBindingSystemBus, ShouldBindToNetwork);
AZ_TEST_ASSERT(!shouldBindToNetwork);
// Verify that the context sequence was reset to unspecified
AzFramework::NetBindingContextSequence offlineContextSequence = 0xBADF00D;
EBUS_EVENT_RESULT(offlineContextSequence, AzFramework::NetBindingSystemBus, GetCurrentContextSequence);
AZ_TEST_ASSERT(offlineContextSequence == AzFramework::UnspecifiedNetBindingContextSequence);
}
// Test nonhost-side behavior by faking the behavior on the host side and then joining the host session.
{
m_receivedBindEvent = m_receivedUnbindEvent = false;
// Host a session
GridMate::CarrierDesc carrierDesc;
carrierDesc.m_enableDisconnectDetection = true;
GridMate::LANSessionParams sessionParams;
sessionParams.m_numPublicSlots = 10;
sessionParams.m_flags = 0;
sessionParams.m_port = HOST_PORT;
sessionParams.m_params[sessionParams.m_numParams].m_id = "filter";
sessionParams.m_params[sessionParams.m_numParams].m_value = GridMate::Utils::GetMachineAddress();
sessionParams.m_numParams++;
GridMate::GridSession* hostSession = gridMate->GetMultiplayerService()->HostSession(&sessionParams, carrierDesc);
// Add the fake context replica on the host and set the context sequence to 1
GridMate::ReplicaPtr replica = GridMate::Replica::CreateReplica("Potato");
FakeNetBindingContextChunk* contextChunk = GridMate::CreateReplicaChunk<FakeNetBindingContextChunk>();
replica->AttachReplicaChunk(contextChunk);
while (!hostSession->IsReady())
{
gridMate->Update();
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
}
hostSession->GetReplicaMgr()->AddMaster(replica);
contextChunk->m_bindingContextSequence.Set(1);
int nFrame = 0;
while (m_session)
{
if (nFrame == 10)
{
// Join the hosted session
GridMate::SessionIdInfo sessionInfo;
sessionInfo.m_sessionId = hostSession->GetId();
m_session = gridMate->GetMultiplayerService()->JoinSession(&sessionInfo, GridMate::JoinParams(), carrierDesc);
}
if (nFrame == 20)
{
// Verify that BindToNetwork() has been called
AZ_TEST_ASSERT(m_receivedBindEvent);
// Verify that we have a valid context sequence
AzFramework::NetBindingContextSequence contextSequence = AzFramework::UnspecifiedNetBindingContextSequence;
EBUS_EVENT_RESULT(contextSequence, AzFramework::NetBindingSystemBus, GetCurrentContextSequence);
AZ_TEST_ASSERT(contextSequence == contextChunk->m_bindingContextSequence.Get());
// Simulate a context switch on the host
contextChunk->m_bindingContextSequence.Set(contextChunk->m_bindingContextSequence.Get() + 1);
}
if (nFrame == 30)
{
// Verify that the context sequence was incremented
AzFramework::NetBindingContextSequence contextSequence = AzFramework::UnspecifiedNetBindingContextSequence;
EBUS_EVENT_RESULT(contextSequence, AzFramework::NetBindingSystemBus, GetCurrentContextSequence);
AZ_TEST_ASSERT(contextSequence == contextChunk->m_bindingContextSequence.Get());
hostSession->Leave(false);
}
app.Tick();
gridMate->Update();
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
nFrame++;
}
// Verify that we should no longer bind to the network
bool shouldBindToNetwork = true;
EBUS_EVENT_RESULT(shouldBindToNetwork, AzFramework::NetBindingSystemBus, ShouldBindToNetwork);
AZ_TEST_ASSERT(!shouldBindToNetwork);
// Verify that the context sequence was reset to unspecified
AzFramework::NetBindingContextSequence offlineContextSequence = 0xBADF00D;
EBUS_EVENT_RESULT(offlineContextSequence, AzFramework::NetBindingSystemBus, GetCurrentContextSequence);
AZ_TEST_ASSERT(offlineContextSequence == AzFramework::UnspecifiedNetBindingContextSequence);
}
// Clean up
GridMate::SessionEventBus::Handler::BusDisconnect();
GridMate::GridMateDestroy(gridMate);
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Destroy();
AzFramework::NetBindingHandlerBus::Handler::BusDisconnect();
app.Destroy();
}
static const int HOST_PORT = 5000;
GridMate::GridSession* m_session;
bool m_receivedBindEvent;
bool m_receivedUnbindEvent;
};
/*
* NetBindingFeatureTest (requires two instances)
*/
class NetBindingFeatureTest
: public GridMate::SessionEventBus::Handler
{
public:
void OnSessionCreated(GridMate::GridSession* session) override
{
if (session == m_session)
{
if (session->IsHost())
{
EBUS_EVENT(AzFramework::NetBindingSystemBus, OnNetworkSessionActivated, session);
}
}
}
void OnSessionJoined(GridMate::GridSession* session) override
{
if (session == m_session)
{
EBUS_EVENT(AzFramework::NetBindingSystemBus, OnNetworkSessionActivated, session);
}
}
void OnSessionDelete(GridMate::GridSession* session)
{
if (session == m_session)
{
EBUS_EVENT(AzFramework::NetBindingSystemBus, OnNetworkSessionDeactivated, session);
m_session = nullptr;
}
}
void OnGridSearchComplete(GridMate::GridSearch* results) override
{
if (results == m_search)
{
GridMate::CarrierDesc carrierDesc;
carrierDesc.m_enableDisconnectDetection = true;
// Create an entity before we get in the session
AZ_TracePrintf("NetBinding", "Spawning master entity...\n");
AZ::Entity* newEntity = nullptr;
newEntity = aznew Entity;
newEntity->CreateComponent<NetBindingTestComponent>();
newEntity->CreateComponent<AzFramework::NetBindingComponent>();
newEntity->Init();
newEntity->Activate();
m_entities.push_back(newEntity);
if (results->GetNumResults() == 0)
{
// Host a session instead
GridMate::LANSessionParams sessionParams;
sessionParams.m_numPublicSlots = 10;
sessionParams.m_flags = 0;
sessionParams.m_port = HOST_PORT;
sessionParams.m_params[sessionParams.m_numParams].m_id = "filter";
sessionParams.m_params[sessionParams.m_numParams].m_value = GridMate::Utils::GetMachineAddress();
sessionParams.m_numParams++;
m_session = m_gridMate->GetMultiplayerService()->HostSession(&sessionParams, carrierDesc);
m_search->Release();
}
else
{
// Join the session
GridMate::JoinParams joinParams;
m_session = m_gridMate->GetMultiplayerService()->JoinSession(results->GetResult(0), joinParams, carrierDesc);
}
m_search = nullptr;
}
}
void OnMemberJoined(GridMate::GridSession* session, GridMate::GridMember* member) override
{
if (session == m_session)
{
if (session->IsHost())
{
if (member != session->GetMyMember())
{
// Spawn an entity after session creation
AZ_TracePrintf("NetBinding", "Spawning master entity...\n");
AZ::Entity* newEntity = nullptr;
EBUS_EVENT_RESULT(newEntity, AzFramework::GameEntityContextRequestBus, CreateGameEntity, "ReplicatedEntity2");
newEntity->CreateComponent<NetBindingTestComponent>();
newEntity->CreateComponent<AzFramework::NetBindingComponent>();
newEntity->Init();
newEntity->Activate();
m_entities.push_back(newEntity);
}
}
}
}
void run()
{
m_gridMate = nullptr;
m_session = nullptr;
AZ::ComponentApplication app;
AZ::ComponentApplication::Descriptor appDesc;
AZ::Entity* systemEntity = app.Create(appDesc);
app.RegisterComponentDescriptor(AzFramework::NetBindingSystemComponent::CreateDescriptor());
app.RegisterComponentDescriptor(AzFramework::NetBindingComponent::CreateDescriptor());
app.RegisterComponentDescriptor(NetBindingTestComponent::CreateDescriptor());
app.RegisterComponentDescriptor(AzFramework::GameEntityContextComponent::CreateDescriptor());
systemEntity->Init();
systemEntity->CreateComponent<AZ::MemoryComponent>();
systemEntity->CreateComponent<AZ::AssetManagerComponent>();
systemEntity->CreateComponent<AzFramework::GameEntityContextComponent>();
systemEntity->CreateComponent<AzFramework::NetBindingSystemComponent>();
systemEntity->Activate();
GridMate::GridMateDesc gridMateDesc;
m_gridMate = GridMate::GridMateCreate(gridMateDesc);
GridMate::GridMateAllocatorMP::Descriptor allocDesc;
allocDesc.m_custom = &AZ::AllocatorInstance<AZ::SystemAllocator>::Get();
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Create(allocDesc);
GridMate::StartGridMateService<GridMate::LANSessionService>(m_gridMate, GridMate::SessionServiceDesc());
GridMate::SessionEventBus::Handler::BusConnect(m_gridMate);
// Search for an existing session
// If a session is not found, we will host a session from within the search callback.
{
GridMate::LANSearchParams searchParams;
searchParams.m_serverPort = HOST_PORT;
searchParams.m_params[searchParams.m_numParams].m_id = "filter";
searchParams.m_params[searchParams.m_numParams].m_value = GridMate::Utils::GetMachineAddress();
searchParams.m_params[searchParams.m_numParams].m_op = GridMate::GridSessionSearchOperators::SSO_OPERATOR_EQUAL;
searchParams.m_numParams++;
m_search = m_gridMate->GetMultiplayerService()->StartGridSearch(&searchParams);
while (m_search)
{
m_gridMate->Update();
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
}
}
// Tick for a while
//static int nTicks = 100;
for (int i = 0; m_session; ++i)
{
if (m_session->IsHost())
{
if (i > 4000 && m_session->GetNumberOfMembers() == 1)
{
m_session->Leave(false);
}
}
m_gridMate->Update();
app.Tick();
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
}
GridMate::SessionEventBus::Handler::BusDisconnect();
GridMate::GridMateDestroy(m_gridMate);
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Destroy();
for (AZ::Entity* entity : m_entities)
{
AzFramework::EntityContextId contextId = AzFramework::EntityContextId::CreateNull();
EBUS_EVENT_ID_RESULT(contextId, entity->GetId(), AzFramework::EntityIdContextQueryBus, GetOwningContextId);
if (contextId.IsNull())
{
delete entity;
}
else
{
EBUS_EVENT(AzFramework::GameEntityContextRequestBus, DestroyGameEntity, entity);
}
}
app.Destroy();
}
static const int HOST_PORT = 6000;
GridMate::IGridMate* m_gridMate;
GridMate::GridSession* m_session;
GridMate::GridSearch* m_search;
AZStd::fixed_vector<AZ::Entity*, 10> m_entities;
};
#endif
}
AZ_TEST_SUITE(NetBinding)
//AZ_TEST(UnitTest::NetBindingSystemComponentLifecycleTest)
//AZ_TEST(UnitTest::NetBindingFeatureTest)
AZ_TEST_SUITE_END
+325
View File
@@ -0,0 +1,325 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_UNITTEST_NETBINDINGMOCKS_H
#define AZCORE_UNITTEST_NETBINDINGMOCKS_H
#include <AzTest/AzTest.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Entity/SliceGameEntityOwnershipServiceBus.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzFramework/Network/NetBindingHandlerBus.h>
namespace UnitTest
{
class MockGameEntityContext
: public AzFramework::GameEntityContextRequestBus::Handler
, public AzFramework::SliceGameEntityOwnershipServiceRequestBus::Handler
{
public:
MockGameEntityContext()
{
AzFramework::GameEntityContextRequestBus::Handler::BusConnect();
AzFramework::SliceGameEntityOwnershipServiceRequestBus::Handler::BusConnect();
}
~MockGameEntityContext()
{
AzFramework::SliceGameEntityOwnershipServiceRequestBus::Handler::BusDisconnect();
AzFramework::GameEntityContextRequestBus::Handler::BusDisconnect();
}
MOCK_METHOD3(InstantiateDynamicSlice, AzFramework::SliceInstantiationTicket(const AZ::Data::Asset<AZ::Data::AssetData>&, const AZ::Transform&, const AZ::IdUtils::Remapper<AZ::EntityId>::IdMapper&));
MOCK_METHOD0(GetGameEntityContextId, AzFramework::EntityContextId());
MOCK_METHOD1(CreateGameEntity, AZ::Entity*(const char*));
MOCK_METHOD1(AddGameEntity, void (AZ::Entity*));
MOCK_METHOD1(DestroyGameEntity, void (const AZ::EntityId&));
MOCK_METHOD1(DestroyGameEntityAndDescendants, void (const AZ::EntityId&));
MOCK_METHOD1(ActivateGameEntity, void (const AZ::EntityId&));
MOCK_METHOD1(DeactivateGameEntity, void (const AZ::EntityId&));
MOCK_METHOD1(DestroyDynamicSliceByEntity, bool (const AZ::EntityId&));
MOCK_METHOD2(LoadFromStream, bool (AZ::IO::GenericStream&, bool));
MOCK_METHOD0(ResetGameContext, void ());
MOCK_METHOD1(GetEntityName, AZStd::string (const AZ::EntityId&));
MOCK_METHOD1(DestroySliceByEntity, bool(const AZ::EntityId&));
MOCK_METHOD1(CreateGameEntityForBehaviorContext, AzFramework::BehaviorEntity (const char *));
MOCK_METHOD1(CancelDynamicSliceInstantiation, void (const AzFramework::SliceInstantiationTicket &));
};
class MockNetBindingSystemContextData
: public AzFramework::NetBindingSystemContextData
{
public:
AZ_CLASS_ALLOCATOR(MockNetBindingSystemContextData, AZ::SystemAllocator, 0);
static const char* GetChunkName()
{
return "MockNetBindingSystemContextData";
}
MOCK_METHOD1(OnAttachedToReplica, void (GridMate::Replica*));
MOCK_METHOD1(OnDetachedFromReplica, void (GridMate::Replica*));
MOCK_METHOD1(UpdateChunk, void (const GridMate::ReplicaContext&));
MOCK_METHOD1(UpdateFromChunk, void (const GridMate::ReplicaContext&));
MOCK_METHOD2(AcceptChangeOwnership, bool (GridMate::PeerId, const GridMate::ReplicaContext&));
MOCK_METHOD1(OnReplicaChangeOwnership, void (const GridMate::ReplicaContext&));
MOCK_METHOD0(IsUpdateFromReplicaEnabled, bool ());
MOCK_CONST_METHOD1(ShouldSendToPeer, bool (GridMate::ReplicaPeer*));
MOCK_METHOD1(CalculateDirtyDataSetMask, AZ::u32 (GridMate::MarshalContext&));
MOCK_METHOD1(OnDataSetChanged, void (const GridMate::DataSetBase&));
MOCK_METHOD2(Marshal, void (GridMate::MarshalContext&, AZ::u32));
MOCK_METHOD2(Unmarshal, void (GridMate::UnmarshalContext&, AZ::u32));
MOCK_METHOD0(IsReplicaMigratable, bool ());
MOCK_METHOD0(IsBroadcast, bool ());
MOCK_METHOD1(OnReplicaActivate, void (const GridMate::ReplicaContext&));
MOCK_METHOD1(OnReplicaDeactivate, void (const GridMate::ReplicaContext&));
/**
* \brief Helper method for GoogleMock to call NetBindingSystemContextData::OnReplicaActivate
*/
void Base_OnReplicaActivate(const GridMate::ReplicaContext& rc)
{
NetBindingSystemContextData::OnReplicaActivate(rc);
}
MOCK_METHOD0(GetReplicaManager, GridMate::ReplicaManager* ());
MOCK_METHOD0(ShouldBindToNetwork, bool ());
};
class MockReplicaManager
: public GridMate::ReplicaManager
{
public:
MOCK_METHOD2(OnIncomingConnection, void (GridMate::Carrier*, GridMate::ConnectionID));
MOCK_METHOD3(OnFailedToConnect, void (GridMate::Carrier*, GridMate::ConnectionID, GridMate::CarrierDisconnectReason));
MOCK_METHOD3(OnDriverError, void (GridMate::Carrier*, GridMate::ConnectionID, const GridMate::DriverError&));
MOCK_METHOD3(OnSecurityError, void (GridMate::Carrier*, GridMate::ConnectionID, const GridMate::SecurityError&));
MOCK_METHOD1(Destroy, bool (GridMate::Replica*));
MOCK_METHOD2(GetReplicaContext, void (const GridMate::Replica*, GridMate::ReplicaContext&));
MOCK_METHOD2(OnConnectionEstablished, void (GridMate::Carrier*, GridMate::ConnectionID));
MOCK_METHOD3(OnDisconnect, void (GridMate::Carrier*, GridMate::ConnectionID, GridMate::CarrierDisconnectReason));
MOCK_METHOD3(OnRateChange, void (GridMate::Carrier*, GridMate::ConnectionID, AZ::u32));
MOCK_METHOD1(FindReplica, GridMate::ReplicaPtr (GridMate::ReplicaId));
};
class MockAssetHandler
: public AZ::Data::AssetHandler
{
public:
AZ_CLASS_ALLOCATOR(MockAssetHandler, AZ::SystemAllocator, 0)
MOCK_METHOD2(CreateAsset, AZ::Data::AssetPtr (const AZ::Data::AssetId&, const AZ::Data::AssetType&));
MOCK_METHOD3(LoadAssetData, AZ::Data::AssetHandler::LoadResult (
const AZ::Data::Asset<AZ::Data::AssetData>&,
AZStd::shared_ptr<AZ::Data::AssetDataStream>,
const AZ::Data::AssetFilterCB&));
MOCK_METHOD2(SaveAssetData, bool (const AZ::Data::Asset<AZ::Data::AssetData>&, AZ::IO::GenericStream*));
MOCK_METHOD3(InitAsset, void (const AZ::Data::Asset<AZ::Data::AssetData>&, bool, bool));
MOCK_METHOD1(DestroyAsset, void (AZ::Data::AssetPtr));
MOCK_METHOD1(GetHandledAssetTypes, void (AZStd::vector<AZ::Data::AssetType>&));
MOCK_CONST_METHOD1(CanHandleAsset, bool (const AZ::Data::AssetId&));
};
class MockAsset
: public AZ::DynamicSliceAsset
{
public:
AZ_RTTI(MockAsset, "{78ABC204-452E-4621-A552-F04D3ABF1690}", DynamicSliceAsset);
MockAsset(const AZ::Data::AssetId& assetId = AZ::Data::AssetId())
: DynamicSliceAsset(assetId)
{
}
~MockAsset() = default;
};
class MockSliceReference
: public AZ::SliceComponent::SliceReference
{
public:
using SliceReference::SliceReference;
MOCK_METHOD1(CreateInstance, AZ::SliceComponent::SliceInstance*(const AZ::IdUtils::Remapper<AZ::EntityId>::IdMapper&));
MOCK_METHOD2(CloneInstance, AZ::SliceComponent::SliceInstance*(AZ::SliceComponent::SliceInstance*, AZ::SliceComponent::EntityIdToEntityIdMap&));
MOCK_METHOD1(FindInstance, AZ::SliceComponent::SliceInstance*(const AZ::SliceComponent::SliceInstanceId&));
MOCK_METHOD1(RemoveInstance, bool(AZ::SliceComponent::SliceInstance*));
MOCK_METHOD3(RemoveEntity, bool(AZ::EntityId, bool, AZ::SliceComponent::SliceInstance*));
MOCK_CONST_METHOD0(GetInstances, const AZ::SliceComponent::SliceReference::SliceInstances&());
MOCK_CONST_METHOD0(GetSliceAsset, const AZ::Data::Asset<AZ::SliceAsset>& ());
MOCK_CONST_METHOD0(GetSliceComponent, AZ::SliceComponent*());
MOCK_CONST_METHOD0(IsInstantiated, bool ());
MOCK_CONST_METHOD3(GetInstanceEntityAncestry, bool(const AZ::EntityId&, AZ::SliceComponent::EntityAncestorList&, AZ::u32));
MOCK_METHOD0(ComputeDataPatch, void());
};
class MockSliceInstance
: public AZ::SliceComponent::SliceInstance
{
public:
using SliceInstance::SliceInstance;
void SetMockInstantiatedContainer(AZ::SliceComponent::InstantiatedContainer* newContainer)
{
m_instantiated = newContainer;
for (AZ::Entity* entity : m_instantiated->m_entities)
{
m_entityIdToBaseCache.insert(AZStd::make_pair(entity->GetId(), entity->GetId()));
}
for (AZ::Entity* entity : m_instantiated->m_entities)
{
m_baseToNewEntityIdMap.insert(AZStd::make_pair(entity->GetId(), entity->GetId()));
}
}
MOCK_CONST_METHOD0(GetInstantiated, const AZ::SliceComponent::InstantiatedContainer*());
MOCK_CONST_METHOD0(GetDataPatch, const AZ::DataPatch&());
MOCK_CONST_METHOD0(GetDataFlags, const AZ::SliceComponent::DataFlagsPerEntity&());
MOCK_METHOD0(GetDataFlags, AZ::SliceComponent::DataFlagsPerEntity&());
MOCK_CONST_METHOD0(GetEntityIdMap, const AZ::SliceComponent::EntityIdToEntityIdMap& ());
MOCK_CONST_METHOD0(GetEntityIdToBaseMap, const AZ::SliceComponent::EntityIdToEntityIdMap& ());
MOCK_CONST_METHOD0(GetId, const AZ::SliceComponent::SliceInstanceId& ());
MOCK_CONST_METHOD0(GetMetadataEntity, AZ::Entity* ());
};
class MockEntity
: public AZ::Entity
{
public:
~MockEntity() override {}
MOCK_METHOD0(Init, void ());
MOCK_METHOD0(Activate, void ());
MOCK_METHOD0(Deactivate, void ());
/**
* \brief Helper method for GoogleMock to call base class method
*/
void Base_Init()
{
Entity::Init();
}
/**
* \brief Helper method for GoogleMock to mark an entity as activated
*/
void Base_Activate()
{
m_state = State::Active;
}
/**
* \brief Helper method for GoogleMock to mark an entity as deactivated
*/
void Base_Deactivate()
{
m_state = State::Init;
}
};
class MockComponentApplication
: public AZ::ComponentApplicationBus::Handler
{
public:
MockComponentApplication()
{
AZ::ComponentApplicationBus::Handler::BusConnect();
}
~MockComponentApplication()
{
AZ::ComponentApplicationBus::Handler::BusDisconnect();
}
AZStd::vector<AZ::Entity*> m_mockEntities;
bool AddEntity(AZ::Entity* entity) override
{
const auto it = AZStd::find(m_mockEntities.begin(), m_mockEntities.end(), entity);
if (it == m_mockEntities.end())
{
m_mockEntities.push_back(entity);
return true;
}
return false;
}
AZ::Entity* FindEntity(const AZ::EntityId& id) override
{
const auto it = AZStd::find_if(m_mockEntities.begin(), m_mockEntities.end(), [id](AZ::Entity* entity)
{
return entity->GetId() == id;
});
if (it != m_mockEntities.end())
{
return *it;
}
return nullptr;
}
MOCK_METHOD0(Destroy, void ());
MOCK_METHOD1(RegisterComponentDescriptor, void (const AZ::ComponentDescriptor*));
MOCK_METHOD1(UnregisterComponentDescriptor, void (const AZ::ComponentDescriptor*));
MOCK_METHOD1(RemoveEntity, bool (AZ::Entity*));
MOCK_METHOD1(DeleteEntity, bool (const AZ::EntityId&));
MOCK_METHOD1(GetEntityName, AZStd::string (const AZ::EntityId&));
MOCK_METHOD1(EnumerateEntities, void (const ComponentApplicationRequests::EntityCallback&));
MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ());
MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ());
MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ());
MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ());
MOCK_CONST_METHOD0(GetAppRoot, const char* ());
MOCK_CONST_METHOD0(GetExecutableFolder, const char* ());
MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ());
MOCK_METHOD0(GetTickDeltaTime, float ());
MOCK_METHOD0(GetTimeAtCurrentTick, AZ::ScriptTimePoint ());
MOCK_METHOD1(Tick, void (float));
MOCK_METHOD0(TickSystem, void ());
MOCK_CONST_METHOD0(GetRequiredSystemComponents, AZ::ComponentTypeList ());
MOCK_METHOD1(ResolveModulePath, void (AZ::OSString&));
MOCK_METHOD0(RegisterCoreComponents, void ());
MOCK_METHOD1(Reflect, void (AZ::ReflectContext*));
MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&));
};
class MockBindingComponent
: public AZ::Component
, public AzFramework::NetBindingHandlerBus::Handler
{
public:
AZ_COMPONENT(MockBindingComponent, "{8393809A-3256-4865-97A9-1CCA43073B4A}", NetBindingHandlerInterface);
static void Reflect(AZ::ReflectContext*) {}
MOCK_METHOD0(Init, void ());
MOCK_METHOD0(Activate, void ());
MOCK_METHOD0(Deactivate, void ());
MOCK_METHOD1(ReadInConfig, bool (const AZ::ComponentConfig*));
MOCK_CONST_METHOD1(WriteOutConfig, bool (AZ::ComponentConfig*));
MOCK_METHOD1(BindToNetwork, void (GridMate::ReplicaPtr));
MOCK_METHOD0(UnbindFromNetwork, void ());
MOCK_METHOD0(IsEntityBoundToNetwork, bool ());
MOCK_METHOD0(IsEntityAuthoritative, bool ());
MOCK_METHOD0(MarkAsLevelSliceEntity, void ());
MOCK_METHOD1(SetSliceInstanceId, void (const AZ::SliceComponent::SliceInstanceId&));
MOCK_METHOD1(SetReplicaPriority, void (GridMate::ReplicaPriority));
MOCK_METHOD1(RequestEntityChangeOwnership, void (GridMate::PeerId));
MOCK_CONST_METHOD0(GetReplicaPriority, GridMate::ReplicaPriority ());
};
}
#endif // AZCORE_UNITTEST_NETBINDINGMOCKS_H
@@ -0,0 +1,605 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/ComponentApplication.h>
#include <AzFramework/Network/NetBindingSystemImpl.h>
#include <AzFramework/Network/NetBindable.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Memory/AllocationRecords.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <GridMate/Serialize/CompressionMarshal.h>
#include <GridMate/Replica/ReplicaFunctions.h>
#include <AzCore/Asset/AssetManager.h>
#include "NetBindingMocks.h"
#include <gmock/gmock-matchers.h>
#include <gmock/gmock-more-actions.h>
#include <gmock/gmock-spec-builders.h>
#include <AzCore/Slice/SliceComponent.h>
namespace UnitTest
{
using namespace AZ;
using namespace AzFramework;
using namespace GridMate;
class NetBindingWithSlicesTest
: public ScopedAllocatorSetupFixture
{
public:
const NetBindingContextSequence k_fakeContextSeq = 1;
const AZ::SliceComponent::SliceInstanceId k_fakeSliceInstanceId = Uuid::CreateRandom();
const AZ::SliceComponent::SliceInstanceId k_fakeSliceInstanceId_Another = Uuid::CreateRandom();
SliceInstantiationTicket m_sliceTicket = SliceInstantiationTicket(EntityContextId::CreateName("Test"), 1);
const Data::AssetId k_fakeAssetId = Data::AssetId(Uuid::CreateRandom(), 0);
const EntityId k_fakeEntityId_One = EntityId(9001);
const ReplicaId k_repId_One = 1001;
const EntityId k_fakeEntityId_Two = EntityId(9002);
const ReplicaId k_repId_Two = 1002;
AZStd::unique_ptr<NetBindingSystemImpl> m_netBindingImpl;
AZStd::unique_ptr<MockComponentApplication> m_componentApplication;
AZStd::unique_ptr<SerializeContext> m_applicationContext;
AZStd::unique_ptr<MockGameEntityContext> m_gameEntityMock;
AZStd::unique_ptr<MockReplicaManager> m_replicaManagerMock;
ReplicaPtr m_replicaMock;
ComponentDescriptor* m_netBindingSystemComponentDescriptor = nullptr;
AZStd::intrusive_ptr<MockNetBindingSystemContextData> m_contextChunkMock;
MockAssetHandler* m_myAssetHandlerAndCatalog = nullptr; // owned by AssetManager
AZStd::unique_ptr<MockAsset> m_fakeAsset;
const float k_wayOverSliceTimeout = NetBindingSystemImpl::s_sliceBindingTimeout.count() * 2.f;
const float k_smallStep = 0.1f;
void SetUpFakeAssetManager()
{
using namespace testing;
const Data::AssetManager::Descriptor desc;
Data::AssetManager::Create(desc);
m_myAssetHandlerAndCatalog = aznew NiceMock<MockAssetHandler>;
ON_CALL(*m_myAssetHandlerAndCatalog, CreateAsset(_, _))
.WillByDefault(Invoke([this](const Data::AssetId&, const Data::AssetType&) -> Data::AssetPtr
{
m_fakeAsset = AZStd::make_unique<NiceMock<MockAsset>>(k_fakeAssetId);
return m_fakeAsset.get();
}));
ON_CALL(*m_myAssetHandlerAndCatalog, DestroyAsset(_))
.WillByDefault(Invoke([this](const Data::AssetPtr asset)
{
EXPECT_EQ(asset, m_fakeAsset.get());
m_fakeAsset.reset();
}));
Data::AssetManager::Instance().RegisterHandler(m_myAssetHandlerAndCatalog, AzTypeInfo<DynamicSliceAsset>::Uuid());
Data::AssetManager::Instance().RegisterHandler(m_myAssetHandlerAndCatalog, AzTypeInfo<MockAsset>::Uuid());
}
void SetUp() override
{
using namespace testing;
m_applicationContext.reset(aznew SerializeContext());
AllocatorInstance<GridMateAllocatorMP>::Create();
AllocatorInstance<ThreadPoolAllocator>::Create();
DefaultValue<SliceInstantiationTicket>::Set(m_sliceTicket);
m_gameEntityMock = AZStd::make_unique<NiceMock<MockGameEntityContext>>();
m_componentApplication = AZStd::make_unique<NiceMock<MockComponentApplication>>();
ON_CALL(*m_componentApplication, GetSerializeContext())
.WillByDefault(Invoke([this]()
{
return m_applicationContext.get();
}));
ON_CALL(*m_gameEntityMock, GetGameEntityContextId())
.WillByDefault(Return(EntityContextId::CreateRandom()));
m_netBindingSystemComponentDescriptor = NetBindingSystemComponent::CreateDescriptor();
ReplicaChunkDescriptorTable::Get().RegisterChunkType<MockNetBindingSystemContextData>();
m_contextChunkMock.reset(CreateReplicaChunk<NiceMock<MockNetBindingSystemContextData>>());
ON_CALL(*m_contextChunkMock, ShouldBindToNetwork())
.WillByDefault(Return(true));
m_replicaManagerMock = AZStd::make_unique<NiceMock<MockReplicaManager>>();
ON_CALL(*m_contextChunkMock, GetReplicaManager())
.WillByDefault(Invoke([this]()
{
return m_replicaManagerMock.get();
}));
m_replicaMock = Replica::CreateReplica("unittest");
ON_CALL(*m_replicaManagerMock, FindReplica(_))
.WillByDefault(Invoke([this](ReplicaId id) -> ReplicaPtr
{
AZ_UNUSED(id);
return m_replicaMock;
}));
ON_CALL(*m_contextChunkMock, OnReplicaActivate(_))
.WillByDefault(Invoke(m_contextChunkMock.get(), &MockNetBindingSystemContextData::Base_OnReplicaActivate));
m_netBindingImpl = AZStd::make_unique<AzFramework::NetBindingSystemImpl>();
m_netBindingImpl->Init();
m_contextChunkMock->OnReplicaActivate(ReplicaContext(nullptr, TimeContext()));
SetUpFakeAssetManager();
}
void TearDown() override
{
Data::AssetManager::Destroy();
m_replicaMock.reset();
m_replicaManagerMock.reset();
m_contextChunkMock.reset();
m_fakeAsset.reset();
m_netBindingImpl->Shutdown();
m_netBindingImpl.reset();
ReplicaChunkDescriptorTable::Get().UnregisterReplicaChunkDescriptor(ReplicaChunkClassId(MockNetBindingSystemContextData::GetChunkName()));
m_netBindingSystemComponentDescriptor->ReleaseDescriptor();
m_componentApplication.reset();
m_gameEntityMock.reset();
AllocatorInstance<GridMateAllocatorMP>::Destroy();
AllocatorInstance<ThreadPoolAllocator>::Destroy();
m_applicationContext.reset();
}
};
TEST_F(NetBindingWithSlicesTest, SameSliceInstanceId_InstantiateDynamicSlice_CallOnce)
{
using namespace testing;
EXPECT_CALL(*m_gameEntityMock, InstantiateDynamicSlice(_, _, _))
.Times(1);
EXPECT_CALL(*m_gameEntityMock, CancelDynamicSliceInstantiation(_))
.Times(1);
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_One;
spawnContext.m_staticEntityId = k_fakeEntityId_One;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId; // both mock entities come from the same slice
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_One, spawnContext);
}
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_Two;
spawnContext.m_staticEntityId = k_fakeEntityId_Two;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId; // both mock entities come from the same slice
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_Two, spawnContext);
}
// this should kick off NetBindingSystemImpl::ProcessBindRequests
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
}
TEST_F(NetBindingWithSlicesTest, DifferentSliceInstanceId_InstantiateDynamicSlice_CalledTwice)
{
using namespace testing;
EXPECT_CALL(*m_gameEntityMock, InstantiateDynamicSlice(_, _, _))
.Times(2);
EXPECT_CALL(*m_gameEntityMock, CancelDynamicSliceInstantiation(_))
.Times(2);
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_One;
spawnContext.m_staticEntityId = k_fakeEntityId_One;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId;
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_One, spawnContext);
}
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_Two;
spawnContext.m_staticEntityId = k_fakeEntityId_Two;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId_Another; // different slice entity
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_Two, spawnContext);
}
// this should kick off NetBindingSystemImpl::ProcessBindRequests
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
}
TEST_F(NetBindingWithSlicesTest, AssetManagerDestroyed_InstantiateDynamicSlice_NotCalled)
{
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_One;
spawnContext.m_staticEntityId = k_fakeEntityId_One;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId; // both mock entities come from the same slice
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_One, spawnContext);
}
Data::AssetManager::Destroy();
// this should kick off NetBindingSystemImpl::ProcessBindRequests, but InstantiateDynamicSlice will not be called
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
}
class ExtendedBindingWithSlicesTest
: public NetBindingWithSlicesTest
{
public:
void SetUp() override
{
NetBindingWithSlicesTest::SetUp();
}
void TearDown() override
{
using namespace testing;
EXPECT_CALL(*m_gameEntityMock, DestroyGameEntity(k_fakeEntityId_One))
.Times(AtMost(1));
EXPECT_CALL(*m_gameEntityMock, DestroyGameEntity(k_fakeEntityId_Two))
.Times(AtMost(1));
NetBindingWithSlicesTest::TearDown();
}
class InstantiateMockSlice
{
public:
explicit InstantiateMockSlice(ExtendedBindingWithSlicesTest* parent)
{
using namespace testing;
m_mockSliceRef = AZStd::make_unique<MockSliceReference>();
m_mockSliceInstance = AZStd::make_unique<MockSliceInstance>();
// container owns the entities and will delete them
auto mockContainer = AZStd::make_unique<SliceComponent::InstantiatedContainer>();
auto binding1 = AZStd::make_unique<NiceMock<MockBindingComponent>>();
mockContainer->m_entities.push_back(CreateMockEntity(parent->k_fakeEntityId_One, binding1.release()));
auto binding2 = AZStd::make_unique<NiceMock<MockBindingComponent>>();
mockContainer->m_entities.push_back(CreateMockEntity(parent->k_fakeEntityId_Two, binding2.release()));
m_mockSliceInstance->SetMockInstantiatedContainer(mockContainer.release());
SliceComponent::SliceInstanceAddress sliceInstanceAddress(m_mockSliceRef.get(), m_mockSliceInstance.get());
// This will pass our mock slice to NetBindingSystem
EBUS_EVENT_ID(parent->m_sliceTicket, SliceInstantiationResultBus, OnSlicePreInstantiate, parent->k_fakeAssetId, sliceInstanceAddress);
EBUS_EVENT_ID(parent->m_sliceTicket, SliceInstantiationResultBus, OnSliceInstantiated, parent->k_fakeAssetId, sliceInstanceAddress);
}
Entity* CreateMockEntity(const EntityId& id, Component* optional = nullptr)
{
using namespace testing;
auto mock = AZStd::make_unique<NiceMock<MockEntity>>();
mock->SetId(EntityId(id));
if (optional)
{
mock->AddComponent(optional); // entity owns the component
}
ON_CALL(*mock, Init())
.WillByDefault(Invoke(mock.get(), &MockEntity::Base_Init));
mock->Init();
ON_CALL(*mock, Activate())
.WillByDefault(Invoke(mock.get(), &MockEntity::Base_Activate));
ON_CALL(*mock, Deactivate())
.WillByDefault(Invoke(mock.get(), &MockEntity::Base_Deactivate));
return mock.release();
}
AZStd::unique_ptr<MockSliceReference> m_mockSliceRef;
AZStd::unique_ptr<MockSliceInstance> m_mockSliceInstance;
};
AZStd::unique_ptr<InstantiateMockSlice> m_slice;
void CreateMockSlice()
{
m_slice = AZStd::make_unique<InstantiateMockSlice>(this);
}
};
TEST_F(ExtendedBindingWithSlicesTest, ActiveSlice_EntitiesThatWerentBounded_StayDeactivated)
{
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_One;
spawnContext.m_staticEntityId = k_fakeEntityId_One;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId;
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_One, spawnContext);
}
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
CreateMockSlice();
MockEntity* mock1 = static_cast<MockEntity*>(m_componentApplication->FindEntity(k_fakeEntityId_One));
EXPECT_CALL(*mock1, Activate()).
Times(1);
MockEntity* mock2 = static_cast<MockEntity*>(m_componentApplication->FindEntity(k_fakeEntityId_Two));
EXPECT_CALL(*mock2, Activate()).
Times(0);
EXPECT_CALL(*m_gameEntityMock, DestroyGameEntity(k_fakeEntityId_Two))
.Times(0);
// Now it should time out the slice handler and the second entity should remain deactivated since we didn't give binding request for it
EBUS_EVENT(AZ::TickBus, OnTick, k_wayOverSliceTimeout, AZ::ScriptTimePoint());
}
TEST_F(ExtendedBindingWithSlicesTest, ActiveSlice_SpawnSecondEntity_AfterLongDelay_InSameSlicenInstance)
{
EXPECT_CALL(*m_gameEntityMock, DestroyGameEntity(k_fakeEntityId_Two))
.Times(0);
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_One;
spawnContext.m_staticEntityId = k_fakeEntityId_One;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId;
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_One, spawnContext);
}
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
CreateMockSlice();
MockEntity* mock2 = static_cast<MockEntity*>(m_componentApplication->FindEntity(k_fakeEntityId_Two));
EXPECT_CALL(*mock2, Activate()).
Times(0);
// This should not trigger removal of the second entity yet
auto halfTimeoutInSeconds = AZStd::chrono::seconds(NetBindingSystemImpl::s_sliceBindingTimeout).count() * 10.f;
EBUS_EVENT(AZ::TickBus, OnTick, halfTimeoutInSeconds, AZ::ScriptTimePoint());
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_Two;
spawnContext.m_staticEntityId = k_fakeEntityId_Two;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId; // both mock entities come from the same slice
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_Two, spawnContext);
}
EXPECT_CALL(*mock2, Activate()).
Times(1);
// This should give net binding system time to bind the second entity
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
// Let the slice timeout, this should lead to no destruction since both entities ought to have been bound by now
EBUS_EVENT(AZ::TickBus, OnTick, k_wayOverSliceTimeout, AZ::ScriptTimePoint());
}
TEST_F(ExtendedBindingWithSlicesTest, ActiveSlice_DespawnLastEntity_DespawnWholeSliceAfterTimeout)
{
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_One;
spawnContext.m_staticEntityId = k_fakeEntityId_One;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId;
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_One, spawnContext);
}
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
CreateMockSlice();
MockEntity* mock1 = static_cast<MockEntity*>(m_componentApplication->FindEntity(k_fakeEntityId_One));
EXPECT_CALL(*mock1, Activate()).
Times(1);
// This should not trigger removal of the second entity yet
auto halfTimeoutInSeconds = AZStd::chrono::seconds(NetBindingSystemImpl::s_sliceBindingTimeout).count() * 10.f;
EBUS_EVENT(AZ::TickBus, OnTick, halfTimeoutInSeconds, AZ::ScriptTimePoint());
EXPECT_CALL(*mock1, Deactivate()).
Times(1);
EBUS_EVENT(NetBindingSystemBus, UnbindGameEntity, k_fakeEntityId_One, k_fakeSliceInstanceId);
EXPECT_CALL(*m_gameEntityMock, DestroyGameEntity(k_fakeEntityId_One))
.Times(1);
EXPECT_CALL(*m_gameEntityMock, DestroyGameEntity(k_fakeEntityId_Two))
.Times(1);
EBUS_EVENT(AZ::TickBus, OnTick, k_wayOverSliceTimeout, AZ::ScriptTimePoint());
}
TEST_F(ExtendedBindingWithSlicesTest, ActiveSlice_DespawnLastEntityBeforeSliceInstantiation_DespawnWholeSlice)
{
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_One;
spawnContext.m_staticEntityId = k_fakeEntityId_One;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId;
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_One, spawnContext);
}
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
EBUS_EVENT(NetBindingSystemBus, UnbindGameEntity, k_fakeEntityId_One, k_fakeSliceInstanceId);
CreateMockSlice();
MockEntity* mock1 = static_cast<MockEntity*>(m_componentApplication->FindEntity(k_fakeEntityId_One));
EXPECT_CALL(*mock1, Activate()).
Times(0);
auto halfTimeoutInSeconds = AZStd::chrono::seconds(NetBindingSystemImpl::s_sliceBindingTimeout).count() * 10.f;
EBUS_EVENT(AZ::TickBus, OnTick, halfTimeoutInSeconds, AZ::ScriptTimePoint());
EBUS_EVENT(AZ::TickBus, OnTick, k_wayOverSliceTimeout, AZ::ScriptTimePoint());
}
TEST_F(ExtendedBindingWithSlicesTest, ActiveSlice_ReuseEntity)
{
EXPECT_CALL(*m_gameEntityMock, DestroyGameEntity(k_fakeEntityId_Two))
.Times(0);
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_One;
spawnContext.m_staticEntityId = k_fakeEntityId_One;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId;
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_One, spawnContext);
}
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_Two;
spawnContext.m_staticEntityId = k_fakeEntityId_Two;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId; // both mock entities come from the same slice
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_Two, spawnContext);
}
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
CreateMockSlice();
MockEntity* mock2 = static_cast<MockEntity*>(m_componentApplication->FindEntity(k_fakeEntityId_Two));
EXPECT_CALL(*mock2, Activate()).
Times(1);
EBUS_EVENT(AZ::TickBus, OnTick, k_wayOverSliceTimeout, AZ::ScriptTimePoint());
EXPECT_CALL(*mock2, Deactivate()).
Times(1);
// some time later the second entity goes away and comes back
EBUS_EVENT(NetBindingSystemBus, UnbindGameEntity, k_fakeEntityId_Two, k_fakeSliceInstanceId);
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_Two;
spawnContext.m_staticEntityId = k_fakeEntityId_Two;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId; // both mock entities come from the same slice
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_Two, spawnContext);
}
// The same entity should be activated for the second time
EXPECT_CALL(*mock2, Activate()).
Times(1); // Note, Google Mock treats each expect_call separately and satisfies them separately. That's why it's 1 here, despite being a second call.
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
}
TEST_F(ExtendedBindingWithSlicesTest, SliceFailedToSpawn)
{
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_One;
spawnContext.m_staticEntityId = k_fakeEntityId_One;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId;
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_One, spawnContext);
}
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
EBUS_EVENT_ID(m_sliceTicket, SliceInstantiationResultBus, OnSliceInstantiationFailed, k_fakeAssetId);
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
EXPECT_TRUE(m_componentApplication->FindEntity(k_fakeEntityId_One) == nullptr);
}
TEST_F(ExtendedBindingWithSlicesTest, SliceSpawned_AfterTimeout)
{
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = k_fakeContextSeq;
spawnContext.m_sliceAssetId = k_fakeAssetId;
spawnContext.m_runtimeEntityId = k_fakeEntityId_One;
spawnContext.m_staticEntityId = k_fakeEntityId_One;
spawnContext.m_sliceInstanceId = k_fakeSliceInstanceId;
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, k_repId_One, spawnContext);
}
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
EBUS_EVENT(AZ::TickBus, OnTick, k_wayOverSliceTimeout, AZ::ScriptTimePoint());
CreateMockSlice();
MockEntity* mock1 = static_cast<MockEntity*>(m_componentApplication->FindEntity(k_fakeEntityId_One));
EXPECT_CALL(*mock1, Activate()).
Times(1);
EBUS_EVENT(AZ::TickBus, OnTick, k_smallStep, AZ::ScriptTimePoint());
}
}
+801
View File
@@ -0,0 +1,801 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Network/NetworkContext.h>
#include <AzFramework/Application/Application.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <GridMate/Replica/DataSet.h>
#include <GridMate/Replica/RemoteProcedureCall.h>
#include <GridMate/Serialize/DataMarshal.h>
#include <GridMate/Serialize/UtilityMarshal.h>
#include <GridMate/Serialize/ContainerMarshal.h>
#include <GridMate/Replica/ReplicaMgr.h>
#include <AzFramework/Network/InterestManagerComponent.h>
namespace UnitTest
{
using namespace AZ;
using namespace AzFramework;
class TestComponentExternalChunk
: public AZ::Component
, public NetBindable
{
public:
AZ_COMPONENT(TestComponentExternalChunk, "{73BB3B15-7C4D-4BD5-9568-F3B2DCBC7725}", AZ::Component);
static void Reflect(ReflectContext* context);
void Init() override
{
NetBindable::NetInit();
}
void Activate() override {}
void Deactivate() override {}
bool SetPos(float x, float y, const RpcContext&)
{
m_x = x;
m_y = y;
return true;
}
void OnFloatChanged(const float&, const TimeContext&)
{
m_floatChanged = true;
}
bool m_floatChanged = false;
private:
float m_x = 0, m_y = 0;
};
class TestComponentReplicaChunk
: public ReplicaChunkBase
, public ReplicaChunkInterface
{
public:
GM_CLASS_ALLOCATOR(TestComponentReplicaChunk);
static const char* GetChunkName() { return "TestComponentReplicaChunk"; }
bool IsReplicaMigratable() override { return true; }
public:
TestComponentReplicaChunk()
: m_int("m_int", 42)
, m_float("m_float", 96.4f)
, SetInt("SetInt")
, SetPos("SetPos")
{
}
bool SetIntImpl(int newValue, const RpcContext&)
{
m_int.Set(newValue);
return true;
}
DataSet<int> m_int;
DataSet<float>::BindInterface<TestComponentExternalChunk, &TestComponentExternalChunk::OnFloatChanged> m_float;
GridMate::Rpc<GridMate::RpcArg<int, Marshaler<int> > >::BindInterface<TestComponentReplicaChunk, &TestComponentReplicaChunk::SetIntImpl> SetInt;
GridMate::Rpc<GridMate::RpcArg<float>, GridMate::RpcArg<float> >::BindInterface<TestComponentExternalChunk, &TestComponentExternalChunk::SetPos> SetPos;
};
void TestComponentExternalChunk::Reflect(ReflectContext* context)
{
NetworkContext* netContext = azrtti_cast<NetworkContext*>(context);
if (netContext)
{
netContext->Class<TestComponentExternalChunk>()
->Chunk<TestComponentReplicaChunk>()
->Field("m_int", &TestComponentReplicaChunk::m_int)
->Field("m_float", &TestComponentReplicaChunk::m_float)
->RPC("SetInt", &TestComponentReplicaChunk::SetInt)
->RPC("SetPos", &TestComponentReplicaChunk::SetPos);
}
}
class TestComponentAutoChunk
: public AZ::Component
, public NetBindable
{
public:
enum TestEnum
{
TEST_Value0 = 0,
TEST_Value1 = 1,
TEST_Value255 = 255
};
AZ_COMPONENT(TestComponentAutoChunk, "{003FD1BC-8456-43D5-9879-1B3804327A4F}", AZ::Component);
static void Reflect(ReflectContext* context)
{
NetworkContext* netContext = azrtti_cast<NetworkContext*>(context);
if (netContext)
{
netContext->Class<TestComponentAutoChunk>()
->Field("m_int", &TestComponentAutoChunk::m_int)
->Field("m_float", &TestComponentAutoChunk::m_float)
->Field("m_enum", &TestComponentAutoChunk::m_enum)
->RPC("SetInt", &TestComponentAutoChunk::SetInt)
->CtorData("CtorInt", &TestComponentAutoChunk::GetCtorInt, &TestComponentAutoChunk::SetCtorInt)
->CtorData("CtorVec", &TestComponentAutoChunk::GetCtorVec, &TestComponentAutoChunk::SetCtorVec);
}
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<TestComponentAutoChunk, AZ::Component>()
->Version(1)
->Field("m_int", &TestComponentAutoChunk::m_int)
->Field("m_float", &TestComponentAutoChunk::m_float)
->Field("m_enum", &TestComponentAutoChunk::m_enum)
->Field("ctorInt", &TestComponentAutoChunk::m_ctorInt)
->Field("ctorVec", &TestComponentAutoChunk::m_ctorVec);
}
}
void Init() override
{
NetBindable::NetInit();
}
void Activate() override {}
void Deactivate() override {}
void SetNetworkBinding(ReplicaChunkPtr chunk) override {}
void UnbindFromNetwork() override {}
bool SetIntImpl(int val, const RpcContext&)
{
m_int = val;
return true;
}
void OnFloatChanged(const float&, const TimeContext&)
{
}
int GetCtorInt() const { return m_ctorInt; }
void SetCtorInt(const int& ctorInt) { m_ctorInt = ctorInt; }
AZStd::vector<int>& GetCtorVec() { return m_ctorVec; }
void SetCtorVec(const AZStd::vector<int>& vec) { m_ctorVec = vec; }
int m_ctorInt;
AZStd::vector<int> m_ctorVec;
Field<int> m_int;
BoundField<float, TestComponentAutoChunk, &TestComponentAutoChunk::OnFloatChanged> m_float;
Field<TestEnum, GridMate::ConversionMarshaler<AZ::u8, TestEnum> > m_enum;
Rpc<int>::Binder<TestComponentAutoChunk, &TestComponentAutoChunk::SetIntImpl> SetInt;
};
class NetContextReflectionTest
: public AllocatorsTestFixture
{
public:
void SetUp() override
{
AllocatorsTestFixture::SetUp();
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Create();
}
void TearDown() override
{
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Destroy();
AllocatorsTestFixture::TearDown();
}
void run()
{
AzFramework::Application app;
AzFramework::Application::Descriptor appDesc;
appDesc.m_recordingMode = Debug::AllocationRecords::RECORD_NO_RECORDS;
appDesc.m_allocationRecords = false;
appDesc.m_enableDrilling = false;
app.Start(appDesc);
// 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);
AzFramework::NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_TEST_ASSERT(netContext);
AZ::ComponentDescriptor* descTestComponentExternalChunk = TestComponentExternalChunk::CreateDescriptor();
app.RegisterComponentDescriptor(descTestComponentExternalChunk);
AZ::ComponentDescriptor* descTestComponentAutoChunk = TestComponentAutoChunk::CreateDescriptor();
app.RegisterComponentDescriptor(descTestComponentAutoChunk);
AZ::Entity* testEntity = aznew AZ::Entity("TestEntity");
testEntity->Init();
testEntity->CreateComponent<TestComponentAutoChunk>();
testEntity->CreateComponent<TestComponentExternalChunk>();
testEntity->Activate();
// test field binding/auto reflection/creation
{
TestComponentAutoChunk* testComponent = testEntity->FindComponent<TestComponentAutoChunk>();
AZ_TEST_ASSERT(testComponent);
testComponent->SetInt(2048); // should happen locally
AZ_TEST_ASSERT(testComponent->m_int == 2048);
ReplicaChunkPtr chunk = testComponent->GetNetworkBinding();
AZ_TEST_ASSERT(chunk);
GridMate::ReplicaChunkDescriptor* desc = chunk->GetDescriptor();
AZ_TEST_ASSERT(desc);
testComponent->m_ctorInt = 8192;
for (int n = 0; n < 16; ++n)
{
testComponent->m_ctorVec.push_back(n);
}
GridMate::WriteBufferDynamic wb(GridMate::EndianType::IgnoreEndian);
desc->MarshalCtorData(chunk.get(), wb);
{
// Create a chunk from the recorded ctor data, ensure that it stores
// the ctor data in preparation for copying it to the instance
GridMate::TimeContext tc;
GridMate::ReplicaContext rc(nullptr, tc);
GridMate::ReadBuffer rb(wb.GetEndianType(), wb.Get(), wb.Size());
GridMate::UnmarshalContext ctx(rc);
ctx.m_hasCtorData = true;
ctx.m_iBuf = &rb;
ReplicaChunkPtr chunk2 = desc->CreateFromStream(ctx);
AZ_TEST_ASSERT(chunk2); // ensure a new chunk was created
ReflectedReplicaChunkBase* refChunk = static_cast<ReflectedReplicaChunkBase*>(chunk2.get());
AZ_TEST_ASSERT(refChunk->m_ctorBuffer.Size() == sizeof(int) + sizeof(AZ::u16) + (sizeof(int) * testComponent->m_ctorVec.size()));
}
{
// discard a ctor data stream and ensure that the stream is emptied
GridMate::TimeContext tc;
GridMate::ReplicaContext rc(nullptr, tc);
GridMate::ReadBuffer rb(wb.GetEndianType(), wb.Get(), wb.Size());
GridMate::UnmarshalContext ctx(rc);
ctx.m_hasCtorData = true;
ctx.m_iBuf = &rb;
desc->DiscardCtorStream(ctx);
AZ_TEST_ASSERT(rb.IsEmptyIgnoreTrailingBits()); // should have discarded the whole stream
}
{
// Make another chunk and bind it to a new component and make sure the ctor data matches
AZ::Entity* testEntity2 = aznew AZ::Entity("TestEntity2");
testEntity2->Init();
testEntity2->CreateComponent<TestComponentAutoChunk>();
testEntity2->Activate();
GridMate::TimeContext tc;
GridMate::ReplicaContext rc(nullptr, tc);
GridMate::ReadBuffer rb(wb.GetEndianType(), wb.Get(), wb.Size());
GridMate::UnmarshalContext ctx(rc);
ctx.m_hasCtorData = true;
ctx.m_iBuf = &rb;
ReplicaChunkPtr chunk2 = desc->CreateFromStream(ctx);
TestComponentAutoChunk* testComponent2 = testEntity2->FindComponent<TestComponentAutoChunk>();
netContext->Bind(testComponent2, chunk2, NetworkContextBindMode::NonAuthoritative);
// Ensure values match after ctor data is applied
AZ_TEST_ASSERT(testComponent2->m_ctorInt == testComponent->m_ctorInt);
AZ_TEST_ASSERT(testComponent2->m_ctorVec == testComponent->m_ctorVec);
}
testComponent->SetInt(4096);
AZ_TEST_ASSERT(testComponent->m_int == 4096);
testComponent->m_int = 42; // now it should change
AZ_TEST_ASSERT(testComponent->m_int == 42);
testComponent->m_enum = TestComponentAutoChunk::TEST_Value1;
chunk.reset(); // should cause netContext->DestroyReplicaChunk()
}
// test chunk binding/creation
{
TestComponentExternalChunk* testComponent = testEntity->FindComponent<TestComponentExternalChunk>();
AZ_TEST_ASSERT(testComponent);
ReplicaChunkPtr chunk = testComponent->GetNetworkBinding();
AZ_TEST_ASSERT(chunk);
TestComponentReplicaChunk* testChunk = static_cast<TestComponentReplicaChunk*>(chunk.get());
// for now, this will throw a warning, but will at least attempt the dispatch
testChunk->SetPos(42.0f, 96.0f);
AZ_TEST_ASSERT(testComponent->m_floatChanged == false);
testChunk->m_float.Set(1024.0f);
// I would like to test that the notify fired, but without a Replica, cant :(
testComponent->UnbindFromNetwork();
chunk.reset(); ///// CRASHES FROM HERE
}
// test serialization of NetBindable::Fields
{
TestComponentAutoChunk* testComponent = testEntity->FindComponent<TestComponentAutoChunk>();
AZStd::vector<AZ::u8> buffer;
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8> > saveStream(&buffer);
bool saved = AZ::Utils::SaveObjectToStream(saveStream, AZ::DataStream::ST_XML, testComponent);
AZ_TEST_ASSERT(saved);
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8> > loadStream(&buffer);
TestComponentAutoChunk* testCopy = AZ::Utils::LoadObjectFromStream<TestComponentAutoChunk>(loadStream);
AZ_TEST_ASSERT(testCopy);
delete testCopy;
}
testEntity->Deactivate();
delete testEntity;
descTestComponentExternalChunk->ReleaseDescriptor();
descTestComponentAutoChunk->ReleaseDescriptor();
app.Stop();
}
};
TEST_F(NetContextReflectionTest, Test)
{
run();
}
template <typename ComponentType>
class NetContextFixture
: public ::testing::Test
{
public:
NetContextFixture() = default;
~NetContextFixture() = default;
void SetUp() override
{
AZ::AllocatorInstance<SystemAllocator>::Create();
m_app = AZStd::make_unique<AzFramework::Application>();
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);
AzFramework::NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_TEST_ASSERT(netContext);
m_descTestComponentAutoChunk = ComponentType::CreateDescriptor();
m_app->RegisterComponentDescriptor(m_descTestComponentAutoChunk);
m_entity = AZStd::make_unique<AZ::Entity>("TestEntity");
m_entity->Init();
m_entity->CreateComponent<ComponentType>();
m_entity->Activate();
}
void TearDown() override
{
m_descTestComponentAutoChunk->ReleaseDescriptor();
m_entity->Deactivate();
m_entity.reset();
m_app->Stop();
m_app.reset();
AZ::AllocatorInstance<SystemAllocator>::Destroy();
}
void RunTest()
{
const ComponentType* testComponent = m_entity->FindComponent<ComponentType>();
AZStd::vector<AZ::u8> buffer;
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8> > saveStream(&buffer);
const bool saved = AZ::Utils::SaveObjectToStream(saveStream, AZ::DataStream::ST_XML, testComponent);
AZ_TEST_ASSERT(saved);
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8> > loadStream(&buffer);
const AZStd::unique_ptr<ComponentType> testCopy(AZ::Utils::LoadObjectFromStream<ComponentType>(loadStream));
AZ_TEST_ASSERT(testCopy);
}
AZStd::unique_ptr<AzFramework::Application> m_app;
AZStd::unique_ptr<AZ::Entity> m_entity;
AZ::ComponentDescriptor* m_descTestComponentAutoChunk = nullptr;
};
class TestComponent_EmptyNetContext
: public AZ::Component
, public NetBindable
{
public:
AZ_COMPONENT(TestComponent_EmptyNetContext, "{B1E2E2DD-DA70-4D59-A185-AF9A5CCF1574}", AZ::Component, NetBindable);
static void Reflect(ReflectContext* context)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<TestComponent_EmptyNetContext, AZ::Component>()
->Version(1);
}
if (NetworkContext* netContext = azrtti_cast<NetworkContext*>(context))
{
netContext->Class<TestComponent_EmptyNetContext>();
}
}
void Activate() override {}
void Deactivate() override {}
};
using NetContextEmpty = NetContextFixture<TestComponent_EmptyNetContext>;
TEST_F(NetContextEmpty, SerializationTests)
{
RunTest();
}
template<typename FieldType>
class TestComponent_OneField
: public AZ::Component
, public NetBindable
{
public:
AZ_COMPONENT(TestComponent_OneField, "{A7BCDBEF-3D4F-4D04-A6FA-DF48D4B66ABE}", AZ::Component, NetBindable);
using ThisComponentType = TestComponent_OneField<FieldType>;
static void Reflect(ReflectContext* context)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<TestComponent_OneField, AZ::Component>()
->Field("Field", &TestComponent_OneField::m_field)
->Version(1);
}
if (NetworkContext* netContext = azrtti_cast<NetworkContext*>(context))
{
netContext->Class<TestComponent_OneField>()
->Field("Field", &TestComponent_OneField::m_field);
}
}
void Activate() override {}
void Deactivate() override {}
Field<FieldType> m_field;
};
TYPED_TEST_CASE_P(NetContextFixture);
TYPED_TEST_P(NetContextFixture, SerializationTests)
{
this->RunTest();
}
REGISTER_TYPED_TEST_CASE_P(NetContextFixture, SerializationTests);
/*
* Testing the basic common types.
*/
using CommonTypes = ::testing::Types<
TestComponent_OneField<bool>,
TestComponent_OneField<float>,
TestComponent_OneField<AZ::u32>,
TestComponent_OneField<AZ::EntityId>,
TestComponent_OneField<AZ::Vector2>,
TestComponent_OneField<AZ::Vector3>,
TestComponent_OneField<AZ::Quaternion>
>;
INSTANTIATE_TYPED_TEST_CASE_P(NetContextCommonSerialization, NetContextFixture, CommonTypes);
/*
* And some less common types.
*/
using LessCommonTypes = ::testing::Types<
TestComponent_OneField<AZStd::string>,
TestComponent_OneField<AZ::Transform>,
TestComponent_OneField<AZ::Color>,
TestComponent_OneField<AZStd::vector<int>>,
TestComponent_OneField<AZ::Uuid>
>;
INSTANTIATE_TYPED_TEST_CASE_P(NetContextLessCommonSerialization, NetContextFixture, LessCommonTypes);
/*
* Next up are marshal and unmarshal tests.
*/
template <typename ComponentType>
class NetContextMarshalFixture
: public UnitTest::AllocatorsTestFixture
{
public:
NetContextMarshalFixture() = default;
~NetContextMarshalFixture() = default;
void SetUp() override
{
UnitTest::AllocatorsTestFixture::SetUp();
AZ::AllocatorInstance<GridMate::GridMateAllocator>::Create();
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Create();
m_app = AZStd::make_unique<AzFramework::Application>();
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);
AzFramework::NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_TEST_ASSERT(netContext);
m_descTestComponentAutoChunk = ComponentType::CreateDescriptor();
m_app->RegisterComponentDescriptor(m_descTestComponentAutoChunk);
m_entityFrom = AZStd::make_unique<AZ::Entity>("TestEntityFrom");
m_entityFrom->Init();
m_componentFrom = m_entityFrom->CreateComponent<ComponentType>();
m_entityFrom->Activate();
m_entityTo = AZStd::make_unique<AZ::Entity>("TestEntityTo");
m_entityTo->Init();
m_componentTo = m_entityTo->CreateComponent<ComponentType>();
m_entityTo->Activate();
}
void MarshalUnMarshal()
{
AzFramework::NetworkContext* netContext = nullptr;
NetSystemRequestBus::BroadcastResult(netContext, &NetSystemRequestBus::Events::GetNetworkContext);
AZ_TEST_ASSERT(netContext);
ComponentType* testComponent = m_entityFrom->FindComponent<ComponentType>();
AZ_TEST_ASSERT(testComponent);
ReplicaChunkPtr chunk = testComponent->GetNetworkBinding();
AZ_TEST_ASSERT(chunk);
m_outReplica = AZStd::make_unique<GridMate::Replica>("ReplicaTo");
{
m_outManager = AZStd::make_unique<GridMate::ReplicaManager>();
m_outPeer = AZStd::make_unique<GridMate::ReplicaPeer>(m_outManager.get());
GridMate::WriteBufferDynamic wb(GridMate::EndianType::IgnoreEndian);
{
GridMate::TimeContext tc;
const GridMate::ReplicaContext rc(nullptr, tc);
GridMate::MarshalContext mc(GridMate::ReplicaMarshalFlags::FullSync, &wb, nullptr, rc);
mc.m_peer = m_outPeer.get();
mc.m_rm = m_outManager.get();
chunk->Debug_PrepareData(wb.GetEndianType(), GridMate::ReplicaMarshalFlags::FullSync);
chunk->Debug_Marshal(mc, 0);
}
// and now unmarshal into the other entity
{
GridMate::TimeContext tc;
const GridMate::ReplicaContext rc(nullptr, tc);
GridMate::ReadBuffer rb(wb.GetEndianType(), wb.Get(), wb.Size());
GridMate::UnmarshalContext ctx(rc);
ctx.m_hasCtorData = false;
ctx.m_iBuf = &rb;
ctx.m_peer = m_outPeer.get();
ctx.m_rm = m_outManager.get();
m_outReplicaChunk = chunk->GetDescriptor()->CreateFromStream(ctx);
m_outReplicaChunk->Debug_AttachedToReplica(m_outReplica.get());
ctx.m_peer->Debug_Add(m_outReplica.get());
m_outReplicaChunk->Debug_Unmarshal(ctx, 0);
/*
* Note the order: unmarshal first to populate the chunk with data, then apply it to a component.
* The expectation is that the valid will apply to NetBindable::Field without being overwritten.
*/
m_componentTo->SetNetworkBinding(m_outReplicaChunk);
// the main test body can now test for the equality
}
}
}
void TearDown() override
{
m_outReplicaChunk.reset();
m_outManager.reset();
m_outPeer.reset();
m_outReplica.release(); // Replica is held by as an intrusive pointer in @m_outPeer and is destroyed there.
if (m_entityFrom)
{
m_entityFrom->Deactivate();
m_entityFrom.reset();
}
if (m_entityTo)
{
m_entityTo->Deactivate();
m_entityTo.reset();
}
m_descTestComponentAutoChunk->ReleaseDescriptor();
m_app->Stop();
m_app.reset();
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Destroy();
AZ::AllocatorInstance<GridMate::GridMateAllocator>::Destroy();
UnitTest::AllocatorsTestFixture::TearDown();
}
AZStd::unique_ptr<AzFramework::Application> m_app;
AZStd::unique_ptr<AZ::Entity> m_entityFrom;
AZStd::unique_ptr<AZ::Entity> m_entityTo;
ComponentType* m_componentFrom = nullptr;
ComponentType* m_componentTo = nullptr;
AZ::ComponentDescriptor* m_descTestComponentAutoChunk = nullptr;
GridMate::ReplicaChunkPtr m_outReplicaChunk;
AZStd::unique_ptr<GridMate::Replica> m_outReplica;
AZStd::unique_ptr<GridMate::ReplicaManager> m_outManager;
AZStd::unique_ptr<GridMate::ReplicaPeer> m_outPeer;
};
using NetContextVector3 = NetContextMarshalFixture<TestComponent_OneField<AZ::Vector3>>;
TEST_F(NetContextVector3, SerializationTests)
{
const Vector3 value = AZ::Vector3::CreateAxisZ( 1.f );
m_componentFrom->m_field = value;
MarshalUnMarshal();
AZ_TEST_ASSERT(m_componentTo->m_field.Get() == value);
}
/*
* Now the same test but with NetBindable::BoundField<>
*/
template<typename FieldType>
class TestComponent_OneBoundField
: public AZ::Component
, public NetBindable
{
public:
AZ_COMPONENT(TestComponent_OneBoundField, "{2B283821-41DF-46BB-BE8E-66EF7301B62A}", AZ::Component, NetBindable);
using ThisComponentType = TestComponent_OneBoundField<FieldType>;
static void Reflect(ReflectContext* context)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ThisComponentType, AZ::Component>()
->Field("Field", &ThisComponentType::m_boundField)
->Version(1);
}
if (NetworkContext* netContext = azrtti_cast<NetworkContext*>(context))
{
netContext->Class<ThisComponentType>()
->Field("Field", &ThisComponentType::m_boundField);
}
}
void Activate() override {}
void Deactivate() override {}
void OnBoundFieldChanged( const FieldType&, const GridMate::TimeContext& ) {}
BoundField<FieldType, ThisComponentType, &ThisComponentType::OnBoundFieldChanged> m_boundField;
};
using NetContextBoundVector2 = NetContextMarshalFixture<TestComponent_OneBoundField<AZ::Vector2>>;
TEST_F(NetContextBoundVector2, SerializationTests)
{
const Vector2 value = AZ::Vector2::CreateAxisX( 4.f );
m_componentFrom->m_boundField = value;
MarshalUnMarshal();
AZ_TEST_ASSERT(m_componentTo->m_boundField.Get() == value);
}
TEST_F(NetContextBoundVector2, Delete_Authoritative_Entity)
{
using ThisComponentType = TestComponent_OneBoundField<AZ::Vector2>;
AzFramework::NetworkContext* netContext = nullptr;
NetSystemRequestBus::BroadcastResult(netContext, &NetSystemRequestBus::Events::GetNetworkContext);
AZ_TEST_ASSERT(netContext);
ThisComponentType* testComponent = m_entityFrom->FindComponent<ThisComponentType>();
AZ_TEST_ASSERT(testComponent);
ReplicaChunkPtr chunk = testComponent->GetNetworkBinding();
// Testing early deletion of an entity on the server.
m_entityFrom->Deactivate();
m_entityFrom.reset();
// This test passes if it doesn't crash on cleanup.
chunk.reset();
}
template<typename FieldType>
class TestComponent_OneBoundField_ServerCallback
: public AZ::Component
, public NetBindable
{
public:
AZ_COMPONENT(TestComponent_OneBoundField_ServerCallback, "{74F5B232-0544-45CA-B207-9846052ED1AD}", AZ::Component, NetBindable);
using ThisComponentType = TestComponent_OneBoundField_ServerCallback<FieldType>;
static void Reflect(ReflectContext* context)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ThisComponentType, AZ::Component>()
->Field("Field", &ThisComponentType::m_boundField)
->Version(1);
}
if (NetworkContext* netContext = azrtti_cast<NetworkContext*>(context))
{
netContext->Class<ThisComponentType>()
->Field("Field", &ThisComponentType::m_boundField);
}
}
void Activate() override {}
void Deactivate() override {}
void OnBoundFieldChanged( const FieldType&, const GridMate::TimeContext& )
{
++m_callbacksInvokeCount;
}
AZ::u8 m_callbacksInvokeCount = 0;
BoundField<FieldType, ThisComponentType, &ThisComponentType::OnBoundFieldChanged> m_boundField;
};
using NetContextBoundVector2WithCallbackCount = NetContextMarshalFixture<TestComponent_OneBoundField_ServerCallback<AZ::Vector2>>;
TEST_F(NetContextBoundVector2WithCallbackCount, BoundField_Invoke_OnServer_Test)
{
MarshalUnMarshal();
m_componentFrom->m_callbacksInvokeCount = 0; // resetting the count
const Vector2 value = AZ::Vector2::CreateAxisX( 4.f );
m_componentFrom->m_boundField = value;
AZ_TEST_ASSERT(m_componentFrom->m_callbacksInvokeCount == 1);
}
}
+552
View File
@@ -0,0 +1,552 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "TestTypes.h"
#include <AzCore/Math/Random.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzFramework/Network/EntityIdMarshaler.h>
#include <AzFramework/Network/DynamicSerializableFieldMarshaler.h>
#include <GridMate/Serialize/Buffer.h>
namespace UnitTest
{
template<class T>
class MarshalerTester
: public AllocatorsFixture
{
public:
MarshalerTester()
: m_writeBuffer(GridMate::EndianType::BigEndian)
, m_readBuffer(GridMate::EndianType::BigEndian)
{
}
void SetUp() override
{
AllocatorsFixture::SetUp();
m_random.SetSeed(AZStd::chrono::milliseconds().count());
}
void PopulateReadBuffer()
{
m_readBuffer = GridMate::ReadBuffer(m_writeBuffer.GetEndianType(), m_writeBuffer.Get(), m_writeBuffer.Size());
}
AZ::SimpleLcgRandom m_random;
GridMate::Marshaler<T> m_marshaler;
GridMate::WriteBufferStatic<> m_writeBuffer;
GridMate::ReadBuffer m_readBuffer;
};
// EntityIdMarshalerTest
typedef MarshalerTester<AZ::EntityId> EntityIdMarshalerTest;
TEST_F(EntityIdMarshalerTest, SingleMarshalUnmarshalTest_EquivalentEmptyValue)
{
AZ::EntityId initialId;
m_marshaler.Marshal(m_writeBuffer, initialId);
PopulateReadBuffer();
AZ::EntityId receivedId;
m_marshaler.Unmarshal(receivedId, m_readBuffer);
EXPECT_EQ(initialId,receivedId);
EXPECT_FALSE(receivedId.IsValid());
}
TEST_F(EntityIdMarshalerTest, SingleMarshalUnmarshalTest_EquivalentRandomValue)
{
AZ::EntityId initialId = AZ::EntityId(m_random.GetRandom());
m_marshaler.Marshal(m_writeBuffer, initialId);
PopulateReadBuffer();
AZ::EntityId receivedId;
m_marshaler.Unmarshal(receivedId, m_readBuffer);
EXPECT_EQ(initialId,receivedId);
}
TEST_F(EntityIdMarshalerTest, MultipleMarshalUnmarshalTest_EquivalentEmptyRandomEmptyRandomValueChain)
{
AZ::EntityId sentId1_empty;
AZ::EntityId sentId2_random = AZ::EntityId(m_random.GetRandom());
AZ::EntityId sentId3_empty;
AZ::EntityId sentId4_random = AZ::EntityId(m_random.GetRandom());
m_marshaler.Marshal(m_writeBuffer, sentId1_empty);
m_marshaler.Marshal(m_writeBuffer, sentId2_random);
m_marshaler.Marshal(m_writeBuffer, sentId3_empty);
m_marshaler.Marshal(m_writeBuffer, sentId4_random);
PopulateReadBuffer();
AZ::EntityId receivedId1_empty;
AZ::EntityId receivedId2_random;
AZ::EntityId receivedId3_empty;
AZ::EntityId receivedId4_random;
m_marshaler.Unmarshal(receivedId1_empty, m_readBuffer);
m_marshaler.Unmarshal(receivedId2_random, m_readBuffer);
m_marshaler.Unmarshal(receivedId3_empty, m_readBuffer);
m_marshaler.Unmarshal(receivedId4_random, m_readBuffer);
EXPECT_EQ(sentId1_empty, receivedId1_empty);
EXPECT_EQ(sentId2_random, receivedId2_random);
EXPECT_EQ(sentId3_empty, receivedId3_empty);
EXPECT_EQ(sentId4_random, receivedId4_random);
}
// AZ::DynamicSerializableFieldMarshaler
class FooSerializable
{
public:
AZ_RTTI(FooSerializable, "{A60F0B2B-6085-4FF1-BD17-A0B0143BB03D}");
AZ_CLASS_ALLOCATOR(FooSerializable, AZ::SystemAllocator,0);
static void Reflect(AZ::SerializeContext& serializeContext)
{
serializeContext.Class<FooSerializable>()
->Version(1)
->Field("IntValue", &FooSerializable::m_intValue)
->Field("FloatValue", &FooSerializable::m_floatValue)
;
}
FooSerializable()
: m_intValue(0)
, m_floatValue(0.0f)
{
}
bool operator==(const FooSerializable& other) const
{
return m_intValue == other.m_intValue && AZ::IsClose(m_floatValue, other.m_floatValue,0.0001f);
}
AZ::u32 m_intValue;
float m_floatValue;
};
class BarSerializable
{
public:
AZ_RTTI(BarSerializable, "{2389C23F-D247-420B-A385-71AB8455CD2E}");
AZ_CLASS_ALLOCATOR(BarSerializable, AZ::SystemAllocator,0);
static void Reflect(AZ::SerializeContext& serializeContext)
{
serializeContext.Class<BarSerializable>()
->Version(1)
->Field("LongValue", &BarSerializable::m_longValue)
->Field("DoubleValue", &BarSerializable::m_doubleValue)
;
}
BarSerializable()
: m_longValue(0)
, m_doubleValue(0.0)
{
}
bool operator==(const BarSerializable& other) const
{
return m_longValue == other.m_longValue && AZ::IsClose(m_doubleValue,other.m_doubleValue,0.0001);
}
long m_longValue;
double m_doubleValue;
};
class ComplexSerializable
{
public:
AZ_RTTI(ComplexSerializable,"{055CB45C-702C-499F-8221-E9ABB21CF1D4}");
AZ_CLASS_ALLOCATOR(ComplexSerializable, AZ::SystemAllocator,0);
static void Reflect(AZ::SerializeContext& serializeContext)
{
serializeContext.Class<ComplexSerializable>()
->Version(1)
->Field("FooSerializable",&ComplexSerializable::m_fooField)
->Field("BarSerializable",&ComplexSerializable::m_barField)
;
}
bool operator==(const ComplexSerializable& other) const
{
return m_fooField == other.m_fooField && m_barField == other.m_barField;
}
FooSerializable m_fooField;
BarSerializable m_barField;
};
class DynamicSerializableFieldMarshalerTest
: public MarshalerTester<AZ::DynamicSerializableField>
, public AZ::ComponentApplicationBus::Handler
{
public:
DynamicSerializableFieldMarshalerTest()
: MarshalerTester<AZ::DynamicSerializableField>()
{
}
void SetUp() override
{
MarshalerTester<AZ::DynamicSerializableField>::SetUp();
FooSerializable::Reflect(m_serializeContext);
BarSerializable::Reflect(m_serializeContext);
ComplexSerializable::Reflect(m_serializeContext);
// Create the Marshaler with access to our custom serialize context.
m_marshaler = GridMate::Marshaler<AZ::DynamicSerializableField>(&m_serializeContext);
AZ::ComponentApplicationBus::Handler::BusConnect();
}
void TearDown() override
{
MarshalerTester<AZ::DynamicSerializableField>::TearDown();
AZ::ComponentApplicationBus::Handler::BusDisconnect();
}
FooSerializable* GenerateFooSerializable()
{
FooSerializable* field = new FooSerializable();
RandomizeFooSerializable((*field));
return field;
}
void RandomizeFooSerializable(FooSerializable& serializable)
{
serializable.m_intValue = m_random.GetRandom();
serializable.m_floatValue = m_random.GetRandomFloat();
}
BarSerializable* GenerateBarSerializable()
{
BarSerializable* field = new BarSerializable();
return field;
}
void RandomizeBarSerializable(BarSerializable& serializable)
{
serializable.m_longValue = static_cast<long>(m_random.GetRandom());
serializable.m_doubleValue = static_cast<double>(m_random.GetRandomFloat());
}
ComplexSerializable* GenerateComplexSerializable()
{
ComplexSerializable* complexField = new ComplexSerializable();
RandomizeFooSerializable(complexField->m_fooField);
RandomizeBarSerializable(complexField->m_barField);
return complexField;
}
// Used Component Application Methods
AZ::SerializeContext* GetSerializeContext() { return &m_serializeContext; }
// Unused ComponentApplication methods
void RegisterComponentDescriptor(const AZ::ComponentDescriptor* descriptor) override { (void)descriptor; AZ_Assert(false,"Unsupported method in Unit Test"); }
void UnregisterComponentDescriptor(const AZ::ComponentDescriptor* descriptor) override { (void)descriptor; AZ_Assert(false,"Unsupported method in Unit Test"); }
AZ::ComponentApplication* GetApplication() override { AZ_Assert(false,"Unsupported method in Unit Test"); return nullptr; }
bool AddEntity(AZ::Entity* entity) override { (void)entity; AZ_Assert(false,"Unsupported method in Unit Test"); return false; }
bool RemoveEntity(AZ::Entity* entity) override { (void)entity; AZ_Assert(false,"Unsupported method in Unit Test"); return false; }
bool DeleteEntity(const AZ::EntityId& id) override { (void)id; AZ_Assert(false,"Unsupported method in Unit Test"); return false; }
AZ::Entity* FindEntity(const AZ::EntityId& id) override { (void)id; AZ_Assert(false,"Unsupported method in Unit Test"); return nullptr; }
void EnumerateEntities(const EntityCallback& callback) override { (void)callback; AZ_Assert(false,"Unsupported method in Unit Test"); }
AZ::BehaviorContext* GetBehaviorContext() override { AZ_Assert(false,"Unsupported method in Unit Test"); return nullptr; }
const char* GetAppRoot() override { AZ_Assert(false,"Unsupported method in Unit Test"); return nullptr; }
const char* GetExecutableFolder() override { AZ_Assert(false,"Unsupported method in Unit Test"); return nullptr; }
AZ::Debug::DrillerManager* GetDrillerManager() override { AZ_Assert(false,"Unsupported method in Unit Test"); return nullptr; }
void ReloadModule(const char* moduleFullPath) override { (void)moduleFullPath; AZ_Assert(false,"Unsupported method in Unit Test"); }
AZ::SerializeContext m_serializeContext;
};
TEST_F(DynamicSerializableFieldMarshalerTest, SingleMarshalUnmarshalTest_EquivalentEmptyValue)
{
AZ::DynamicSerializableField sentField;
m_marshaler.Marshal(m_writeBuffer, sentField);
PopulateReadBuffer();
AZ::DynamicSerializableField receivedField;
m_marshaler.Unmarshal(receivedField,m_readBuffer);
EXPECT_TRUE(sentField.IsEqualTo(receivedField, &m_serializeContext));
}
TEST_F(DynamicSerializableFieldMarshalerTest, SingleMarshalUnmarshalTest_EquivalentFooValue)
{
AZ::DynamicSerializableField sentField;
FooSerializable* fooSerializable = GenerateFooSerializable();
sentField.Set(fooSerializable);
m_marshaler.Marshal(m_writeBuffer, sentField);
PopulateReadBuffer();
AZ::DynamicSerializableField receivedField;
m_marshaler.Unmarshal(receivedField,m_readBuffer);
EXPECT_TRUE(sentField.IsEqualTo(receivedField, &m_serializeContext));
sentField.DestroyData(&m_serializeContext);
receivedField.DestroyData(&m_serializeContext);
}
TEST_F(DynamicSerializableFieldMarshalerTest, SingleMarshalUnmarshalTest_EquivalentBarValue)
{
AZ::DynamicSerializableField sentField;
BarSerializable* barSerializable = GenerateBarSerializable();
sentField.Set(barSerializable);
m_marshaler.Marshal(m_writeBuffer, sentField);
PopulateReadBuffer();
AZ::DynamicSerializableField receivedField;
m_marshaler.Unmarshal(receivedField,m_readBuffer);
EXPECT_TRUE(sentField.IsEqualTo(receivedField, &m_serializeContext));
sentField.DestroyData(&m_serializeContext);
receivedField.DestroyData(&m_serializeContext);
}
TEST_F(DynamicSerializableFieldMarshalerTest, SingleMarshalUnmarshalTest_EquivalentComplexValue)
{
AZ::DynamicSerializableField sentField;
ComplexSerializable* complexSerializable = GenerateComplexSerializable();
sentField.Set(complexSerializable);
m_marshaler.Marshal(m_writeBuffer, sentField);
PopulateReadBuffer();
AZ::DynamicSerializableField receivedField;
m_marshaler.Unmarshal(receivedField,m_readBuffer);
EXPECT_TRUE(sentField.IsEqualTo(receivedField, &m_serializeContext));
sentField.DestroyData(&m_serializeContext);
receivedField.DestroyData(&m_serializeContext);
}
TEST_F(DynamicSerializableFieldMarshalerTest, MultipleMarshalUnmarshalTest_EmptyEmptyChainEquivalentValue)
{
AZ::DynamicSerializableField sentField1;
AZ::DynamicSerializableField sentField2;
m_marshaler.Marshal(m_writeBuffer, sentField1);
m_marshaler.Marshal(m_writeBuffer, sentField2);
PopulateReadBuffer();
AZ::DynamicSerializableField receivedField1;
AZ::DynamicSerializableField receivedField2;
m_marshaler.Unmarshal(receivedField1,m_readBuffer);
m_marshaler.Unmarshal(receivedField2,m_readBuffer);
EXPECT_TRUE(sentField1.IsEqualTo(receivedField1, &m_serializeContext));
EXPECT_TRUE(sentField2.IsEqualTo(receivedField2, &m_serializeContext));
sentField1.DestroyData(&m_serializeContext);
sentField2.DestroyData(&m_serializeContext);
receivedField1.DestroyData(&m_serializeContext);
receivedField2.DestroyData(&m_serializeContext);
}
TEST_F(DynamicSerializableFieldMarshalerTest, MultipleMarshalUnmarshalTest_FooBarComplexChainEquivalentValue)
{
AZ::DynamicSerializableField sentField1;
FooSerializable* fooSerializable = GenerateFooSerializable();
sentField1.Set(fooSerializable);
AZ::DynamicSerializableField sentField2;
BarSerializable* barSerializable = GenerateBarSerializable();
sentField2.Set(barSerializable);
AZ::DynamicSerializableField sentField3;
ComplexSerializable* complexSerializable = GenerateComplexSerializable();
sentField3.Set(complexSerializable);
m_marshaler.Marshal(m_writeBuffer, sentField1);
m_marshaler.Marshal(m_writeBuffer, sentField2);
m_marshaler.Marshal(m_writeBuffer, sentField3);
PopulateReadBuffer();
AZ::DynamicSerializableField receivedField1;
AZ::DynamicSerializableField receivedField2;
AZ::DynamicSerializableField receivedField3;
m_marshaler.Unmarshal(receivedField1, m_readBuffer);
m_marshaler.Unmarshal(receivedField2, m_readBuffer);
m_marshaler.Unmarshal(receivedField3, m_readBuffer);
EXPECT_TRUE(sentField1.IsEqualTo(receivedField1, &m_serializeContext));
EXPECT_TRUE(sentField2.IsEqualTo(receivedField2, &m_serializeContext));
EXPECT_TRUE(sentField3.IsEqualTo(receivedField3, &m_serializeContext));
sentField1.DestroyData(&m_serializeContext);
sentField2.DestroyData(&m_serializeContext);
sentField3.DestroyData(&m_serializeContext);
receivedField1.DestroyData(&m_serializeContext);
receivedField2.DestroyData(&m_serializeContext);
receivedField3.DestroyData(&m_serializeContext);
}
TEST_F(DynamicSerializableFieldMarshalerTest, MultipleMarshalUnmarshalTest_EmptyFooEmptyBarEmptyComplexChainEquivalentValue)
{
AZ::DynamicSerializableField emptyField;
AZ::DynamicSerializableField sentField1;
FooSerializable* fooSerializable = GenerateFooSerializable();
sentField1.Set(fooSerializable);
AZ::DynamicSerializableField sentField2;
BarSerializable* barSerializable = GenerateBarSerializable();
sentField2.Set(barSerializable);
AZ::DynamicSerializableField sentField3;
ComplexSerializable* complexSerializable = GenerateComplexSerializable();
sentField3.Set(complexSerializable);
m_marshaler.Marshal(m_writeBuffer, emptyField);
m_marshaler.Marshal(m_writeBuffer, sentField1);
m_marshaler.Marshal(m_writeBuffer, emptyField);
m_marshaler.Marshal(m_writeBuffer, sentField2);
m_marshaler.Marshal(m_writeBuffer, emptyField);
m_marshaler.Marshal(m_writeBuffer, sentField3);
m_marshaler.Marshal(m_writeBuffer, emptyField);
PopulateReadBuffer();
AZ::DynamicSerializableField receivedEmptyField1;
AZ::DynamicSerializableField receivedField1;
AZ::DynamicSerializableField receivedEmptyField2;
AZ::DynamicSerializableField receivedField2;
AZ::DynamicSerializableField receivedEmptyField3;
AZ::DynamicSerializableField receivedField3;
AZ::DynamicSerializableField receivedEmptyField4;
m_marshaler.Unmarshal(receivedEmptyField1, m_readBuffer);
m_marshaler.Unmarshal(receivedField1, m_readBuffer);
m_marshaler.Unmarshal(receivedEmptyField2, m_readBuffer);
m_marshaler.Unmarshal(receivedField2, m_readBuffer);
m_marshaler.Unmarshal(receivedEmptyField3, m_readBuffer);
m_marshaler.Unmarshal(receivedField3, m_readBuffer);
m_marshaler.Unmarshal(receivedEmptyField4, m_readBuffer);
EXPECT_TRUE(emptyField.IsEqualTo(receivedEmptyField1, &m_serializeContext));
EXPECT_TRUE(sentField1.IsEqualTo(receivedField1, &m_serializeContext));
EXPECT_TRUE(emptyField.IsEqualTo(receivedEmptyField2, &m_serializeContext));
EXPECT_TRUE(sentField2.IsEqualTo(receivedField2, &m_serializeContext));
EXPECT_TRUE(emptyField.IsEqualTo(receivedEmptyField3, &m_serializeContext));
EXPECT_TRUE(sentField3.IsEqualTo(receivedField3, &m_serializeContext));
EXPECT_TRUE(emptyField.IsEqualTo(receivedEmptyField4, &m_serializeContext));
emptyField.DestroyData(&m_serializeContext);
sentField1.DestroyData(&m_serializeContext);
sentField2.DestroyData(&m_serializeContext);
sentField3.DestroyData(&m_serializeContext);
receivedEmptyField1.DestroyData(&m_serializeContext);
receivedField1.DestroyData(&m_serializeContext);
receivedEmptyField2.DestroyData(&m_serializeContext);
receivedField2.DestroyData(&m_serializeContext);
receivedEmptyField3.DestroyData(&m_serializeContext);
receivedField3.DestroyData(&m_serializeContext);
receivedEmptyField4.DestroyData(&m_serializeContext);
}
TEST_F(DynamicSerializableFieldMarshalerTest, MultipleMarshalUnmarshalTest_RandomChainEquivalentValue)
{
// Need to watch out for the size of the WriteBuffer. It's about ~2048 bytes, at worst case here, I'll write ~100 Bytes to the field per test object)
// So I need to keep this ~20 elements.
int numValues = 5 + m_random.GetRandom()%10;
AZStd::vector< AZ::DynamicSerializableField > sentValues;
AZStd::vector< AZ::DynamicSerializableField > receivedValues;
sentValues.resize(numValues);
receivedValues.resize(numValues);
for (auto& currentField : sentValues)
{
int value = m_random.GetRandom() % 4;
switch (value)
{
case 0:
{
currentField.Set(GenerateFooSerializable());
}
break;
case 1:
{
currentField.Set(GenerateBarSerializable());
}
break;
case 2:
{
currentField.Set(GenerateComplexSerializable());
}
break;
case 3:
default:
// Empty field
break;
}
}
for (auto& currentField : sentValues)
{
m_marshaler.Marshal(m_writeBuffer,currentField);
}
PopulateReadBuffer();
for (auto& currentField : receivedValues)
{
m_marshaler.Unmarshal(currentField,m_readBuffer);
}
for (unsigned int i=0; i < sentValues.size(); ++i)
{
AZ::DynamicSerializableField& sentField = sentValues[i];
AZ::DynamicSerializableField& receivedField = receivedValues[i];
EXPECT_TRUE(sentField.IsEqualTo(receivedField, &m_serializeContext));
sentField.DestroyData(&m_serializeContext);
receivedField.DestroyData(&m_serializeContext);
}
}
}
@@ -0,0 +1,330 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Visibility/OctreeSystemComponent.h>
#if defined(HAVE_BENCHMARK)
#include <random>
#include <benchmark/benchmark.h>
namespace Benchmark
{
class BM_Octree
: public benchmark::Fixture
{
public:
void SetUp([[maybe_unused]] const ::benchmark::State& state) override
{
// Create the SystemAllocator if not available
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
m_ownsSystemAllocator = true;
}
m_octreeSystemComponent = new AzFramework::OctreeSystemComponent;
m_dataArray.resize(1000000);
m_queryDataArray.resize(1000);
const unsigned int seed = 1;
std::mt19937_64 rng(seed);
std::uniform_real_distribution<float> unif;
std::generate(m_dataArray.begin(), m_dataArray.end(), [&unif, &rng]()
{
AzFramework::VisibilityEntry data;
AZ::Vector3 aabbMin = AZ::Vector3(unif(rng), unif(rng), unif(rng)) * 8000.0f;
AZ::Vector3 aabbMax = AZ::Vector3(unif(rng), unif(rng), unif(rng)).GetAbs() * 50.0f + aabbMin;
data.m_internalNode = nullptr;
data.m_internalNodeIndex = 0;
data.m_boundingVolume = AZ::Aabb::CreateFromMinMax(aabbMin, aabbMax);
data.m_userData = nullptr;
data.m_typeFlags = AzFramework::VisibilityEntry::TYPE_None;
return data;
});
std::generate(m_queryDataArray.begin(), m_queryDataArray.end(), [&unif, &rng]()
{
QueryData data;
AZ::Vector3 aabbMin = AZ::Vector3(unif(rng), unif(rng), unif(rng)) * 8000.0f;
AZ::Vector3 aabbMax = AZ::Vector3(unif(rng), unif(rng), unif(rng)).GetAbs() * 250.0f + aabbMin;
AZ::Vector3 frustumCenter = AZ::Vector3(unif(rng), unif(rng), unif(rng)) * 8000.0f;
AZ::Quaternion quaternion = AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(unif(rng), unif(rng), unif(rng)).GetNormalized(), unif(rng));
data.aabb = AZ::Aabb::CreateFromMinMax(aabbMin, aabbMax);
data.sphere = AZ::Sphere(AZ::Vector3(unif(rng), unif(rng), unif(rng)) * 8000.0f, unif(rng) * 250.0f);
data.frustum = AZ::Frustum(AZ::ViewFrustumAttributes(
AZ::Transform::CreateFromQuaternionAndTranslation(quaternion, frustumCenter), 1.0f,
2.0f * atanf(0.5f), unif(rng) * 10.0f, unif(rng) * 1000.0f));
return data;
});
}
void TearDown([[maybe_unused]] const ::benchmark::State& state) override
{
delete m_octreeSystemComponent;
m_dataArray.clear();
m_dataArray.shrink_to_fit();
m_queryDataArray.clear();
m_queryDataArray.shrink_to_fit();
// Destroy system allocator only if it was created by this environment
if (m_ownsSystemAllocator)
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
}
void InsertEntries(uint32_t entryCount)
{
AzFramework::IVisibilitySystem* visSystem = AZ::Interface<AzFramework::IVisibilitySystem>::Get();
for (uint32_t i = 0; i < entryCount; ++i)
{
visSystem->InsertOrUpdateEntry(m_dataArray[i]);
}
}
void RemoveEntries(uint32_t entryCount)
{
AzFramework::IVisibilitySystem* visSystem = AZ::Interface<AzFramework::IVisibilitySystem>::Get();
for (uint32_t i = 0; i < entryCount; ++i)
{
visSystem->RemoveEntry(m_dataArray[i]);
}
}
struct QueryData
{
AZ::Aabb aabb;
AZ::Sphere sphere;
AZ::Frustum frustum;
};
bool m_ownsSystemAllocator = false;
AZStd::vector<AzFramework::VisibilityEntry> m_dataArray;
AZStd::vector<QueryData> m_queryDataArray;
AzFramework::OctreeSystemComponent* m_octreeSystemComponent;
};
BENCHMARK_F(BM_Octree, InsertDelete1000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 1000;
for (auto _ : state)
{
InsertEntries(EntryCount);
RemoveEntries(EntryCount);
}
}
BENCHMARK_F(BM_Octree, InsertDelete10000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 10000;
for (auto _ : state)
{
InsertEntries(EntryCount);
RemoveEntries(EntryCount);
}
}
BENCHMARK_F(BM_Octree, InsertDelete100000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 100000;
for (auto _ : state)
{
InsertEntries(EntryCount);
RemoveEntries(EntryCount);
}
}
BENCHMARK_F(BM_Octree, InsertDelete1000000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 1000000;
for (auto _ : state)
{
InsertEntries(EntryCount);
RemoveEntries(EntryCount);
}
}
BENCHMARK_F(BM_Octree, EnumerateAabb1000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 1000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.aabb, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateAabb10000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 10000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.aabb, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateAabb100000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 100000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.aabb, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateAabb1000000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 1000000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.aabb, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateSphere1000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 1000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.sphere, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateSphere10000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 10000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.sphere, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateSphere100000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 100000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.sphere, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateSphere1000000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 1000000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.sphere, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateFrustum1000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 1000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.frustum, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateFrustum10000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 10000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.frustum, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateFrustum100000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 100000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.frustum, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
BENCHMARK_F(BM_Octree, EnumerateFrustum1000000)(benchmark::State& state)
{
constexpr uint32_t EntryCount = 1000000;
InsertEntries(EntryCount);
for (auto _ : state)
{
for (auto& queryData : m_queryDataArray)
{
m_octreeSystemComponent->Enumerate(queryData.frustum, [](const AzFramework::IVisibilitySystem::NodeData&) {});
}
}
RemoveEntries(EntryCount);
}
}
#endif
+340
View File
@@ -0,0 +1,340 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/Console.h>
#include <AzFramework/Visibility/OctreeSystemComponent.h>
#include <random>
using namespace AzFramework;
namespace UnitTest
{
class OctreeTests
: public AllocatorsFixture
{
public:
void SetUp() override
{
AllocatorsFixture::SetUp();
m_console = aznew AZ::Console();
AZ::Interface<AZ::IConsole>::Register(m_console);
m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead());
m_console->GetCvarValue("bg_octreeNodeMaxEntries", m_savedMaxEntries);
m_console->GetCvarValue("bg_octreeNodeMinEntries", m_savedMinEntries);
m_console->GetCvarValue("bg_octreeMaxWorldExtents", m_savedBounds);
// To ease unit testing, configure the octreeSystemComponent to only allow one entry per node
m_console->PerformCommand("bg_octreeNodeMaxEntries 1");
m_console->PerformCommand("bg_octreeNodeMinEntries 1");
m_console->PerformCommand("bg_octreeMaxWorldExtents 1"); // Create a -1,-1,-1 to 1,1,1 world volume
m_octreeSystemComponent = new OctreeSystemComponent;
}
void TearDown() override
{
// Restore octreeSystemComponent cvars for any future tests or benchmarks that might get executed
AZStd::string commandString;
commandString.format("bg_octreeNodeMaxEntries %u", m_savedMaxEntries);
m_console->PerformCommand(commandString.c_str());
commandString.format("bg_octreeNodeMinEntries %u", m_savedMinEntries);
m_console->PerformCommand(commandString.c_str());
commandString.format("bg_octreeMaxWorldExtents %f", m_savedBounds);
m_console->PerformCommand(commandString.c_str());
delete m_octreeSystemComponent;
AZ::Interface<AZ::IConsole>::Unregister(m_console);
delete m_console;
AllocatorsFixture::TearDown();
}
OctreeSystemComponent* m_octreeSystemComponent = nullptr;
uint32_t m_savedMaxEntries = 0;
uint32_t m_savedMinEntries = 0;
float m_savedBounds = 0.0f;
AZ::Console* m_console = nullptr;
};
TEST_F(OctreeTests, InsertDeleteSingleEntry)
{
AzFramework::VisibilityEntry visEntry;
visEntry.m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3::CreateZero(), AZ::Vector3::CreateOne());
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry);
EXPECT_TRUE(visEntry.m_internalNode != nullptr);
EXPECT_TRUE(visEntry.m_internalNodeIndex == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 1);
m_octreeSystemComponent->RemoveEntry(visEntry);
EXPECT_TRUE(visEntry.m_internalNode == nullptr);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 0);
}
TEST_F(OctreeTests, InsertDeleteSplitMerge)
{
AzFramework::VisibilityEntry visEntry[3];
visEntry[0].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.9f), AZ::Vector3(-0.6f));
visEntry[1].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.1f), AZ::Vector3( 0.4f));
visEntry[2].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.6f), AZ::Vector3( 0.9f));
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry[0]);
EXPECT_TRUE(visEntry[0].m_internalNode != nullptr);
EXPECT_TRUE(visEntry[0].m_internalNodeIndex == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 1);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1);
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry[1]); // This should force a split of the root node
EXPECT_TRUE(visEntry[1].m_internalNode != nullptr);
EXPECT_TRUE(visEntry[1].m_internalNodeIndex == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 2);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1 + m_octreeSystemComponent->GetChildNodeCount());
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry[2]); // This should force a split of the roots +/+/+ child node
EXPECT_TRUE(visEntry[2].m_internalNode != nullptr);
EXPECT_TRUE(visEntry[2].m_internalNodeIndex == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 3);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1 + (2 * m_octreeSystemComponent->GetChildNodeCount()));
m_octreeSystemComponent->RemoveEntry(visEntry[2]);
EXPECT_TRUE(visEntry[2].m_internalNode == nullptr);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 2);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1 + m_octreeSystemComponent->GetChildNodeCount());
m_octreeSystemComponent->RemoveEntry(visEntry[1]);
EXPECT_TRUE(visEntry[1].m_internalNode == nullptr);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 1);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1);
m_octreeSystemComponent->RemoveEntry(visEntry[0]);
EXPECT_TRUE(visEntry[0].m_internalNode == nullptr);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 0);
}
TEST_F(OctreeTests, UpdateSingleEntry)
{
AzFramework::VisibilityEntry visEntry;
visEntry.m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3::CreateZero(), AZ::Vector3::CreateOne());
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry);
EXPECT_TRUE(visEntry.m_internalNode != nullptr);
EXPECT_TRUE(visEntry.m_internalNodeIndex == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 1);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1);
visEntry.m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f));
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry);
EXPECT_TRUE(visEntry.m_internalNode != nullptr);
EXPECT_TRUE(visEntry.m_internalNodeIndex == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 1);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1);
m_octreeSystemComponent->RemoveEntry(visEntry);
EXPECT_TRUE(visEntry.m_internalNode == nullptr);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1);
}
TEST_F(OctreeTests, UpdateSplitMerge)
{
AzFramework::VisibilityEntry visEntry[3];
visEntry[0].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.9f), AZ::Vector3(-0.6f));
visEntry[1].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.1f), AZ::Vector3( 0.4f));
visEntry[2].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.6f), AZ::Vector3( 0.9f));
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry[0]);
EXPECT_TRUE(visEntry[0].m_internalNode != nullptr);
EXPECT_TRUE(visEntry[0].m_internalNodeIndex == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 1);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1);
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry[1]); // This should force a split of the root node
EXPECT_TRUE(visEntry[1].m_internalNode != nullptr);
EXPECT_TRUE(visEntry[1].m_internalNodeIndex == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 2);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1 + m_octreeSystemComponent->GetChildNodeCount());
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry[2]); // This should force a split of the roots +/+/+ child node
EXPECT_TRUE(visEntry[2].m_internalNode != nullptr);
EXPECT_TRUE(visEntry[2].m_internalNodeIndex == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 3);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1 + (2 * m_octreeSystemComponent->GetChildNodeCount()));
visEntry[1].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.9f), AZ::Vector3(-0.6f));
visEntry[2].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.1f), AZ::Vector3( 0.4f));
visEntry[0].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.6f), AZ::Vector3( 0.9f));
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry[0]);
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry[1]);
m_octreeSystemComponent->InsertOrUpdateEntry(visEntry[2]);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 3);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1 + (2 * m_octreeSystemComponent->GetChildNodeCount()));
m_octreeSystemComponent->RemoveEntry(visEntry[2]);
EXPECT_TRUE(visEntry[2].m_internalNode == nullptr);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 2);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1 + m_octreeSystemComponent->GetChildNodeCount());
m_octreeSystemComponent->RemoveEntry(visEntry[1]);
EXPECT_TRUE(visEntry[1].m_internalNode == nullptr);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 1);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1);
m_octreeSystemComponent->RemoveEntry(visEntry[0]);
EXPECT_TRUE(visEntry[0].m_internalNode == nullptr);
EXPECT_TRUE(m_octreeSystemComponent->GetEntryCount() == 0);
EXPECT_TRUE(m_octreeSystemComponent->GetNodeCount() == 1);
}
void AppendEntries(AZStd::vector<VisibilityEntry*>& gatheredEntries, const AzFramework::IVisibilitySystem::NodeData& nodeData)
{
gatheredEntries.insert(gatheredEntries.end(), nodeData.m_entries.begin(), nodeData.m_entries.end());
}
template <typename BoundType>
void EnumerateSingleEntryHelper(OctreeSystemComponent* octreeSystemComponent, const BoundType& bounds)
{
AzFramework::VisibilityEntry visEntry;
visEntry.m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3::CreateZero(), AZ::Vector3::CreateOne());
AZStd::vector<VisibilityEntry*> gatheredEntries;
octreeSystemComponent->Enumerate(bounds, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.empty());
octreeSystemComponent->InsertOrUpdateEntry(visEntry);
octreeSystemComponent->Enumerate(bounds, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.size() == 1);
EXPECT_TRUE(gatheredEntries[0] == &visEntry);
visEntry.m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f));
octreeSystemComponent->InsertOrUpdateEntry(visEntry);
gatheredEntries.clear();
octreeSystemComponent->Enumerate(bounds, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.size() == 1);
EXPECT_TRUE(gatheredEntries[0] == &visEntry);
octreeSystemComponent->RemoveEntry(visEntry);
gatheredEntries.clear();
octreeSystemComponent->Enumerate(bounds, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.empty());
}
TEST_F(OctreeTests, EnumerateSphereSingleEntry)
{
AZ::Sphere bounds = AZ::Sphere::CreateUnitSphere();
EnumerateSingleEntryHelper(m_octreeSystemComponent, bounds);
}
TEST_F(OctreeTests, EnumerateAabbSingleEntry)
{
AZ::Aabb bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-1.0f), AZ::Vector3(1.0f));
EnumerateSingleEntryHelper(m_octreeSystemComponent, bounds);
}
TEST_F(OctreeTests, EnumerateFrustumSingleEntry)
{
AZ::Vector3 frustumOrigin = AZ::Vector3(0.0f, -2.0f, 0.0f);
AZ::Quaternion frustumDirection = AZ::Quaternion::CreateIdentity();
AZ::Transform frustumTransform = AZ::Transform::CreateFromQuaternionAndTranslation(frustumDirection, frustumOrigin);
AZ::Frustum bounds = AZ::Frustum(AZ::ViewFrustumAttributes(frustumTransform, 1.0f, 2.0f * atanf(0.5f), 1.0f, 3.0f));
EnumerateSingleEntryHelper(m_octreeSystemComponent, bounds);
}
// bound1 should cover the entire spatial hash
// bound2 should not cross into the positive Y-axis
// bound3 should only intersect the region inside 0.6, 0.6, 0.6 to 0.9, 0.9, 0.9
template <typename BoundType>
void EnumerateMultipleEntriesHelper(OctreeSystemComponent* octreeSystemComponent, const BoundType& bound1, const BoundType& bound2, const BoundType& bound3)
{
AZStd::vector<VisibilityEntry*> gatheredEntries;
AzFramework::VisibilityEntry visEntry[3];
visEntry[0].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.9f), AZ::Vector3(-0.6f));
visEntry[1].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.1f), AZ::Vector3( 0.4f));
visEntry[2].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.6f), AZ::Vector3( 0.9f));
octreeSystemComponent->InsertOrUpdateEntry(visEntry[0]);
octreeSystemComponent->InsertOrUpdateEntry(visEntry[1]);
octreeSystemComponent->InsertOrUpdateEntry(visEntry[2]);
gatheredEntries.clear();
octreeSystemComponent->Enumerate(bound1, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.size() == 3);
gatheredEntries.clear();
octreeSystemComponent->Enumerate(bound2, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.size() == 1);
EXPECT_TRUE(gatheredEntries[0] == &(visEntry[0]));
gatheredEntries.clear();
octreeSystemComponent->Enumerate(bound3, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.size() == 1);
EXPECT_TRUE(gatheredEntries[0] == &(visEntry[2]));
visEntry[1].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.9f), AZ::Vector3(-0.6f));
visEntry[2].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.1f), AZ::Vector3( 0.4f));
visEntry[0].m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.6f), AZ::Vector3( 0.9f));
octreeSystemComponent->InsertOrUpdateEntry(visEntry[0]);
octreeSystemComponent->InsertOrUpdateEntry(visEntry[1]);
octreeSystemComponent->InsertOrUpdateEntry(visEntry[2]);
gatheredEntries.clear();
octreeSystemComponent->Enumerate(bound1, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.size() == 3);
gatheredEntries.clear();
octreeSystemComponent->Enumerate(bound2, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.size() == 1);
EXPECT_TRUE(gatheredEntries[0] == &(visEntry[1]));
gatheredEntries.clear();
octreeSystemComponent->Enumerate(bound3, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.size() == 1);
EXPECT_TRUE(gatheredEntries[0] == &(visEntry[0]));
octreeSystemComponent->RemoveEntry(visEntry[0]);
octreeSystemComponent->RemoveEntry(visEntry[1]);
octreeSystemComponent->RemoveEntry(visEntry[2]);
gatheredEntries.clear();
octreeSystemComponent->Enumerate(bound1, [&gatheredEntries](const AzFramework::IVisibilitySystem::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
EXPECT_TRUE(gatheredEntries.empty());
}
TEST_F(OctreeTests, EnumerateSphereMultipleEntries)
{
AZ::Sphere bound1 = AZ::Sphere::CreateUnitSphere();
AZ::Sphere bound2 = AZ::Sphere(AZ::Vector3(-0.5f), 0.5f);
AZ::Sphere bound3 = AZ::Sphere(AZ::Vector3(0.75f), 0.2f);
EnumerateMultipleEntriesHelper(m_octreeSystemComponent, bound1, bound2, bound3);
}
TEST_F(OctreeTests, EnumerateAabbMultipleEntries)
{
AZ::Aabb bound1 = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-1.0f), AZ::Vector3( 1.0f));
AZ::Aabb bound2 = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-1.0f), AZ::Vector3(-0.5f));
AZ::Aabb bound3 = AZ::Aabb::CreateFromMinMax(AZ::Vector3( 0.6f), AZ::Vector3( 0.9f));
EnumerateMultipleEntriesHelper(m_octreeSystemComponent, bound1, bound2, bound3);
}
TEST_F(OctreeTests, EnumerateFrustumMultipleEntries)
{
AZ::Vector3 frustumOrigin = AZ::Vector3(0.0f, -2.0f, 0.0f);
AZ::Quaternion frustumDirection = AZ::Quaternion::CreateIdentity();
AZ::Transform frustumTransform = AZ::Transform::CreateFromQuaternionAndTranslation(frustumDirection, frustumOrigin);
AZ::Frustum bound1 = AZ::Frustum(AZ::ViewFrustumAttributes(frustumTransform, 1.0f, 2.0f * atanf(0.5f), 1.0f, 3.0f));
AZ::Frustum bound2 = AZ::Frustum(AZ::ViewFrustumAttributes(frustumTransform, 1.0f, 2.0f * atanf(0.5f), 1.0f, 2.0f));
AZ::Frustum bound3 = AZ::Frustum(AZ::ViewFrustumAttributes(frustumTransform, 1.0f, 2.0f * atanf(0.5f), 2.6f, 2.9f));
EnumerateMultipleEntriesHelper(m_octreeSystemComponent, bound1, bound2, bound3);
}
}
@@ -0,0 +1,27 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME AzPhysicsTests HEADERONLY
NAMESPACE AZ
FILES_CMAKE
azphysicstests_files.cmake
INCLUDE_DIRECTORIES
INTERFACE
..
BUILD_DEPENDENCIES
INTERFACE
AZ::AzFramework
)
endif()
@@ -0,0 +1,988 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
namespace Physics
{
TEST_F(PhysicsComponentBusTest, SetLinearDamping_DynamicSphere_MoreDampedBodyFallsSlower)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f);
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::SetLinearDamping, 0.1f);
Physics::RigidBodyRequestBus::Event(sphereB->GetId(),
&Physics::RigidBodyRequests::SetLinearDamping, 0.2f);
UpdateDefaultWorld(60);
float dampingA, dampingB;
Physics::RigidBodyRequestBus::EventResult(dampingA, sphereA->GetId(),
&Physics::RigidBodyRequests::GetLinearDamping);
Physics::RigidBodyRequestBus::EventResult(dampingB, sphereB->GetId(),
&Physics::RigidBodyRequests::GetLinearDamping);
EXPECT_NEAR(dampingA, 0.1f, 1e-3f);
EXPECT_NEAR(dampingB, 0.2f, 1e-3f);
float zA = GetPositionElement(sphereA, 2);
float zB = GetPositionElement(sphereB, 2);
EXPECT_GT(zB, zA);
AZ::Vector3 vA = AZ::Vector3::CreateZero();
AZ::Vector3 vB = AZ::Vector3::CreateZero();
Physics::RigidBodyRequestBus::EventResult(vA, sphereA->GetId(),
&Physics::RigidBodyRequests::GetLinearVelocity);
Physics::RigidBodyRequestBus::EventResult(vB, sphereB->GetId(),
&Physics::RigidBodyRequests::GetLinearVelocity);
EXPECT_GT(static_cast<float>(vA.GetLength()), static_cast<float>(vB.GetLength()));
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, SetLinearDampingNegative_DynamicSphere_NegativeValueRejected)
{
ErrorHandler errorHandler("Negative linear damping value");
auto sphere = AddSphereEntity(AZ::Vector3::CreateZero(), 0.5f);
float damping = 0.0f, initialDamping = 0.0f;
Physics::RigidBodyRequestBus::EventResult(initialDamping, sphere->GetId(),
&Physics::RigidBodyRequests::GetLinearDamping);
// a negative damping value should be rejected and the damping should remain at its previous value
Physics::RigidBodyRequestBus::Event(sphere->GetId(),
&Physics::RigidBodyRequests::SetLinearDamping, -0.1f);
Physics::RigidBodyRequestBus::EventResult(damping, sphere->GetId(),
&Physics::RigidBodyRequests::GetLinearDamping);
EXPECT_NEAR(damping, initialDamping, 1e-3f);
EXPECT_TRUE(errorHandler.GetWarningCount() > 0);
delete sphere;
}
TEST_F(PhysicsComponentBusTest, SetAngularDamping_DynamicSphere_MoreDampedBodyRotatesSlower)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 1.0f), 0.5f);
auto sphereB = AddSphereEntity(AZ::Vector3(0.0f, 5.0f, 1.0f), 0.5f);
auto floor = AddStaticBoxEntity(AZ::Vector3::CreateZero(), AZ::Vector3(100.0f, 100.0f, 1.0f));
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::SetAngularDamping, 0.1f);
Physics::RigidBodyRequestBus::Event(sphereB->GetId(),
&Physics::RigidBodyRequests::SetAngularDamping, 0.2f);
float dampingA, dampingB;
Physics::RigidBodyRequestBus::EventResult(dampingA, sphereA->GetId(),
&Physics::RigidBodyRequests::GetAngularDamping);
Physics::RigidBodyRequestBus::EventResult(dampingB, sphereB->GetId(),
&Physics::RigidBodyRequests::GetAngularDamping);
EXPECT_NEAR(dampingA, 0.1f, 1e-3f);
EXPECT_NEAR(dampingB, 0.2f, 1e-3f);
AZ::Vector3 impulse(10.0f, 0.0f, 0.0f);
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::ApplyLinearImpulse, impulse);
Physics::RigidBodyRequestBus::Event(sphereB->GetId(),
&Physics::RigidBodyRequests::ApplyLinearImpulse, impulse);
UpdateDefaultWorld(10);
auto angularVelocityA = AZ::Vector3::CreateZero();
auto angularVelocityB = AZ::Vector3::CreateZero();
for (int timestep = 0; timestep < 10; timestep++)
{
Physics::RigidBodyRequestBus::EventResult(angularVelocityA, sphereA->GetId(),
&Physics::RigidBodyRequests::GetAngularVelocity);
Physics::RigidBodyRequestBus::EventResult(angularVelocityB, sphereB->GetId(),
&Physics::RigidBodyRequests::GetAngularVelocity);
EXPECT_GT(static_cast<float>(angularVelocityA.GetLength()), static_cast<float>(angularVelocityB.GetLength()));
UpdateDefaultWorld(1);
}
delete floor;
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, SetAngularDampingNegative_DynamicSphere_NegativeValueRejected)
{
ErrorHandler errorHandler("Negative angular damping value");
auto sphere = AddSphereEntity(AZ::Vector3::CreateZero(), 0.5f);
float damping = 0.0f, initialDamping = 0.0f;
Physics::RigidBodyRequestBus::EventResult(initialDamping, sphere->GetId(),
&Physics::RigidBodyRequests::GetAngularDamping);
// a negative damping value should be rejected and the damping should remain at its previous value
Physics::RigidBodyRequestBus::Event(sphere->GetId(),
&Physics::RigidBodyRequests::SetAngularDamping, -0.1f);
Physics::RigidBodyRequestBus::EventResult(damping, sphere->GetId(),
&Physics::RigidBodyRequests::GetAngularDamping);
EXPECT_NEAR(damping, initialDamping, 1e-3f);
EXPECT_TRUE(errorHandler.GetWarningCount() > 0);
delete sphere;
}
TEST_F(PhysicsComponentBusTest, AddImpulse_DynamicSphere_AffectsTrajectory)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f);
AZ::Vector3 impulse(10.0f, 0.0f, 0.0f);
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::ApplyLinearImpulse, impulse);
for (int i = 1; i < 10; i++)
{
float xPreviousA = GetPositionElement(sphereA, 0);
float xPreviousB = GetPositionElement(sphereB, 0);
UpdateDefaultWorld(10);
EXPECT_GT(GetPositionElement(sphereA, 0), xPreviousA);
EXPECT_NEAR(GetPositionElement(sphereB, 0), xPreviousB, 1e-3f);
}
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, SetLinearVelocity_DynamicSphere_AffectsTrajectory)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f);
AZ::Vector3 velocity(10.0f, 0.0f, 0.0f);
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::SetLinearVelocity, velocity);
for (int i = 1; i < 10; i++)
{
float xPreviousA = GetPositionElement(sphereA, 0);
float xPreviousB = GetPositionElement(sphereB, 0);
UpdateDefaultWorld(10);
EXPECT_GT(GetPositionElement(sphereA, 0), xPreviousA);
EXPECT_NEAR(GetPositionElement(sphereB, 0), xPreviousB, 1e-3f);
}
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, AddImpulseAtWorldPoint_DynamicSphere_AffectsTrajectoryAndRotation)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f);
AZ::Vector3 impulse(10.0f, 0.0f, 0.0f);
AZ::Vector3 worldPoint(0.0f, -5.0f, 0.25f);
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::ApplyLinearImpulseAtWorldPoint, impulse, worldPoint);
AZ::Vector3 angularVelocityA = AZ::Vector3::CreateZero();
AZ::Vector3 angularVelocityB = AZ::Vector3::CreateZero();
for (int i = 1; i < 10; i++)
{
float xPreviousA = GetPositionElement(sphereA, 0);
float xPreviousB = GetPositionElement(sphereB, 0);
UpdateDefaultWorld(10);
EXPECT_GT(GetPositionElement(sphereA, 0), xPreviousA);
EXPECT_NEAR(GetPositionElement(sphereB, 0), xPreviousB, 1e-3f);
Physics::RigidBodyRequestBus::EventResult(angularVelocityA, sphereA->GetId(),
&Physics::RigidBodyRequests::GetAngularVelocity);
Physics::RigidBodyRequestBus::EventResult(angularVelocityB, sphereB->GetId(),
&Physics::RigidBodyRequests::GetAngularVelocity);
EXPECT_FALSE(angularVelocityA.IsClose(AZ::Vector3::CreateZero()));
EXPECT_NEAR(static_cast<float>(angularVelocityA.GetX()), 0.0f, 1e-3f);
EXPECT_NEAR(static_cast<float>(angularVelocityA.GetZ()), 0.0f, 1e-3f);
EXPECT_TRUE(angularVelocityB.IsClose(AZ::Vector3::CreateZero()));
}
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, AddAngularImpulse_DynamicSphere_AffectsRotation)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f);
AZ::Vector3 angularImpulse(0.0f, 10.0f, 0.0f);
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::ApplyAngularImpulse, angularImpulse);
for (int i = 1; i < 10; i++)
{
float xPreviousA = GetPositionElement(sphereA, 0);
float xPreviousB = GetPositionElement(sphereB, 0);
UpdateDefaultWorld(10);
EXPECT_NEAR(GetPositionElement(sphereA, 0), xPreviousA, 1e-3f);
EXPECT_NEAR(GetPositionElement(sphereB, 0), xPreviousB, 1e-3f);
AZ::Vector3 angularVelocityA = AZ::Vector3::CreateZero();
AZ::Vector3 angularVelocityB = AZ::Vector3::CreateZero();
Physics::RigidBodyRequestBus::EventResult(angularVelocityA, sphereA->GetId(),
&Physics::RigidBodyRequests::GetAngularVelocity);
Physics::RigidBodyRequestBus::EventResult(angularVelocityB, sphereB->GetId(),
&Physics::RigidBodyRequests::GetAngularVelocity);
EXPECT_FALSE(angularVelocityA.IsClose(AZ::Vector3::CreateZero()));
EXPECT_NEAR(static_cast<float>(angularVelocityA.GetX()), 0.0f, 1e-3f);
EXPECT_NEAR(static_cast<float>(angularVelocityA.GetZ()), 0.0f, 1e-3f);
EXPECT_TRUE(angularVelocityB.IsClose(AZ::Vector3::CreateZero()));
}
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, SetAngularVelocity_DynamicSphere_AffectsRotation)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 0.0f), 0.5f);
auto sphereB = AddSphereEntity(AZ::Vector3(0.0f, 5.0f, 0.0f), 0.5f);
AZ::Vector3 angularVelocity(0.0f, 10.0f, 0.0f);
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::SetAngularVelocity, angularVelocity);
for (int i = 1; i < 10; i++)
{
float xPreviousA = GetPositionElement(sphereA, 0);
float xPreviousB = GetPositionElement(sphereB, 0);
UpdateDefaultWorld(10);
EXPECT_NEAR(GetPositionElement(sphereA, 0), xPreviousA, 1e-3f);
EXPECT_NEAR(GetPositionElement(sphereB, 0), xPreviousB, 1e-3f);
AZ::Vector3 angularVelocityA = AZ::Vector3::CreateZero();
AZ::Vector3 angularVelocityB = AZ::Vector3::CreateZero();
Physics::RigidBodyRequestBus::EventResult(angularVelocityA, sphereA->GetId(),
&Physics::RigidBodyRequests::GetAngularVelocity);
Physics::RigidBodyRequestBus::EventResult(angularVelocityB, sphereB->GetId(),
&Physics::RigidBodyRequests::GetAngularVelocity);
EXPECT_FALSE(angularVelocityA.IsClose(AZ::Vector3::CreateZero()));
EXPECT_NEAR(static_cast<float>(angularVelocityA.GetX()), 0.0f, 1e-3f);
EXPECT_NEAR(static_cast<float>(angularVelocityA.GetZ()), 0.0f, 1e-3f);
EXPECT_TRUE(angularVelocityB.IsClose(AZ::Vector3::CreateZero()));
}
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, GetLinearVelocity_FallingSphere_VelocityIncreasesOverTime)
{
auto sphere = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 0.0f), 0.5f);
Physics::RigidBodyRequestBus::Event(sphere->GetId(),
&Physics::RigidBodyRequests::SetLinearDamping, 0.0f);
float previousSpeed = 0.0f;
for (int timestep = 0; timestep < 60; timestep++)
{
UpdateDefaultWorld(1);
AZ::Vector3 velocity;
Physics::RigidBodyRequestBus::EventResult(velocity, sphere->GetId(),
&Physics::RigidBodyRequests::GetLinearVelocity);
float speed = velocity.GetLength();
EXPECT_GT(speed, previousSpeed);
previousSpeed = speed;
}
delete sphere;
}
TEST_F(PhysicsComponentBusTest, SetSleepThreshold_RollingSpheres_LowerThresholdSphereTravelsFurther)
{
auto sphereA = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 1.0f), 0.5f);
auto sphereB = AddSphereEntity(AZ::Vector3(0.0f, 5.0f, 1.0f), 0.5f);
auto floor = AddStaticBoxEntity(AZ::Vector3::CreateZero(), AZ::Vector3(100.0f, 100.0f, 1.0f));
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::SetAngularDamping, 0.75f);
Physics::RigidBodyRequestBus::Event(sphereB->GetId(),
&Physics::RigidBodyRequests::SetAngularDamping, 0.75f);
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::SetSleepThreshold, 1.0f);
Physics::RigidBodyRequestBus::Event(sphereB->GetId(),
&Physics::RigidBodyRequests::SetSleepThreshold, 0.5f);
float sleepThresholdA, sleepThresholdB;
Physics::RigidBodyRequestBus::EventResult(sleepThresholdA, sphereA->GetId(),
&Physics::RigidBodyRequests::GetSleepThreshold);
Physics::RigidBodyRequestBus::EventResult(sleepThresholdB, sphereB->GetId(),
&Physics::RigidBodyRequests::GetSleepThreshold);
EXPECT_NEAR(sleepThresholdA, 1.0f, 1e-3f);
EXPECT_NEAR(sleepThresholdB, 0.5f, 1e-3f);
AZ::Vector3 impulse(0.0f, 0.1f, 0.0f);
Physics::RigidBodyRequestBus::Event(sphereA->GetId(),
&Physics::RigidBodyRequests::ApplyAngularImpulse, impulse);
Physics::RigidBodyRequestBus::Event(sphereB->GetId(),
&Physics::RigidBodyRequests::ApplyAngularImpulse, impulse);
UpdateDefaultWorld(300);
EXPECT_GT(GetPositionElement(sphereB, 0), GetPositionElement(sphereA, 0));
delete floor;
delete sphereA;
delete sphereB;
}
TEST_F(PhysicsComponentBusTest, SetSleepThresholdNegative_DynamicSphere_NegativeValueRejected)
{
ErrorHandler errorHandler("Negative sleep threshold value");
auto sphere = AddSphereEntity(AZ::Vector3(0.0f, -5.0f, 1.0f), 0.5f);
float threshold = 0.0f, initialThreshold = 0.0f;
Physics::RigidBodyRequestBus::EventResult(initialThreshold, sphere->GetId(),
&Physics::RigidBodyRequests::GetSleepThreshold);
Physics::RigidBodyRequestBus::Event(sphere->GetId(),
&Physics::RigidBodyRequests::SetSleepThreshold, -0.5f);
Physics::RigidBodyRequestBus::EventResult(threshold, sphere->GetId(),
&Physics::RigidBodyRequests::GetSleepThreshold);
EXPECT_NEAR(threshold, initialThreshold, 1e-3f);
EXPECT_TRUE(errorHandler.GetWarningCount() > 0);
delete sphere;
}
TEST_F(PhysicsComponentBusTest, SetMass_Seesaw_TipsDownAtHeavierEnd)
{
auto floor = AddStaticBoxEntity(AZ::Vector3::CreateZero(), AZ::Vector3(100.0f, 100.0f, 1.0f));
auto pivot = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 0.7f), AZ::Vector3(0.4f, 1.0f, 0.4f));
auto seesaw = AddBoxEntity(AZ::Vector3(0.0f, 0.0f, 0.95f), AZ::Vector3(20.0f, 1.0f, 0.1f));
auto boxA = AddBoxEntity(AZ::Vector3(-9.0f, 0.0f, 1.5f), AZ::Vector3::CreateOne());
auto boxB = AddBoxEntity(AZ::Vector3(9.0f, 0.0f, 1.5f), AZ::Vector3::CreateOne());
Physics::RigidBodyRequestBus::Event(boxA->GetId(), &Physics::RigidBodyRequests::SetMass, 5.0f);
float mass = 0.0f;
Physics::RigidBodyRequestBus::EventResult(mass, boxA->GetId(),
&Physics::RigidBodyRequests::GetMass);
EXPECT_NEAR(mass, 5.0f, 1e-3f);
UpdateDefaultWorld(30);
EXPECT_GT(1.5f, GetPositionElement(boxA, 2));
EXPECT_LT(1.5f, GetPositionElement(boxB, 2));
Physics::RigidBodyRequestBus::Event(boxB->GetId(), &Physics::RigidBodyRequests::SetMass, 20.0f);
Physics::RigidBodyRequestBus::EventResult(mass, boxB->GetId(),
&Physics::RigidBodyRequests::GetMass);
EXPECT_NEAR(mass, 20.0f, 1e-3f);
UpdateDefaultWorld(60);
EXPECT_LT(1.5f, GetPositionElement(boxA, 2));
EXPECT_GT(1.5f, GetPositionElement(boxB, 2));
delete floor;
delete pivot;
delete seesaw;
delete boxA;
delete boxB;
}
TEST_F(PhysicsComponentBusTest, GetAabb_Sphere_ValidExtents)
{
AZ::Vector3 spherePosition(2.0f, -3.0f, 1.0f);
auto sphere = AddSphereEntity(spherePosition, 0.5f);
AZ::Aabb sphereAabb;
Physics::RigidBodyRequestBus::EventResult(sphereAabb, sphere->GetId(),
&Physics::RigidBodyRequests::GetAabb);
EXPECT_TRUE(sphereAabb.GetMin().IsClose(spherePosition - 0.5f * AZ::Vector3::CreateOne()));
EXPECT_TRUE(sphereAabb.GetMax().IsClose(spherePosition + 0.5f * AZ::Vector3::CreateOne()));
// rotate the sphere and check the bounding box is still correct
AZ::Quaternion quat = AZ::Quaternion::CreateRotationZ(0.25f * AZ::Constants::Pi);
AZ::TransformBus::Event(sphere->GetId(), &AZ::TransformInterface::SetWorldTM,
AZ::Transform::CreateFromQuaternionAndTranslation(quat, spherePosition));
sphere->Deactivate();
sphere->Activate();
Physics::RigidBodyRequestBus::EventResult(sphereAabb, sphere->GetId(),
&Physics::RigidBodyRequests::GetAabb);
EXPECT_TRUE(sphereAabb.GetMin().IsClose(spherePosition - 0.5f * AZ::Vector3::CreateOne()));
EXPECT_TRUE(sphereAabb.GetMax().IsClose(spherePosition + 0.5f * AZ::Vector3::CreateOne()));
delete sphere;
}
TEST_F(PhysicsComponentBusTest, GetAabb_Box_ValidExtents)
{
AZ::Vector3 boxPosition(2.0f, -3.0f, 1.0f);
AZ::Vector3 boxDimensions(3.0f, 4.0f, 5.0f);
auto box = AddBoxEntity(boxPosition, boxDimensions);
AZ::Aabb boxAabb;
Physics::RigidBodyRequestBus::EventResult(boxAabb, box->GetId(),
&Physics::RigidBodyRequests::GetAabb);
EXPECT_TRUE(boxAabb.GetMin().IsClose(boxPosition - 0.5f * boxDimensions));
EXPECT_TRUE(boxAabb.GetMax().IsClose(boxPosition + 0.5f * boxDimensions));
// rotate the box and check the bounding box is still correct
AZ::Quaternion quat = AZ::Quaternion::CreateRotationZ(0.25f * AZ::Constants::Pi);
AZ::TransformBus::Event(box->GetId(), &AZ::TransformInterface::SetWorldTM,
AZ::Transform::CreateFromQuaternionAndTranslation(quat, boxPosition));
box->Deactivate();
box->Activate();
Physics::RigidBodyRequestBus::EventResult(boxAabb, box->GetId(),
&Physics::RigidBodyRequests::GetAabb);
AZ::Vector3 expectedRotatedDimensions(3.5f * sqrtf(2.0f), 3.5f * sqrtf(2.0f), 5.0f);
EXPECT_TRUE(boxAabb.GetMin().IsClose(boxPosition - 0.5f * expectedRotatedDimensions));
EXPECT_TRUE(boxAabb.GetMax().IsClose(boxPosition + 0.5f * expectedRotatedDimensions));
delete box;
}
TEST_F(PhysicsComponentBusTest, GetAabb_Capsule_ValidExtents)
{
AZ::Vector3 capsulePosition(1.0f, -3.0f, 5.0f);
float capsuleHeight = 2.0f;
float capsuleRadius = 0.3f;
auto capsule = AddCapsuleEntity(capsulePosition, capsuleHeight, capsuleRadius);
AZ::Aabb capsuleAabb;
Physics::RigidBodyRequestBus::EventResult(capsuleAabb, capsule->GetId(),
&Physics::RigidBodyRequests::GetAabb);
AZ::Vector3 expectedCapsuleHalfExtents(capsuleRadius, capsuleRadius, 0.5f * capsuleHeight);
EXPECT_TRUE(capsuleAabb.GetMin().IsClose(capsulePosition - expectedCapsuleHalfExtents));
EXPECT_TRUE(capsuleAabb.GetMax().IsClose(capsulePosition + expectedCapsuleHalfExtents));
// rotate the capsule and check the bounding box is still correct
AZ::Quaternion quat = AZ::Quaternion::CreateRotationY(0.25f * AZ::Constants::Pi);
AZ::TransformBus::Event(capsule->GetId(), &AZ::TransformInterface::SetWorldTM,
AZ::Transform::CreateFromQuaternionAndTranslation(quat, capsulePosition));
capsule->Deactivate();
capsule->Activate();
Physics::RigidBodyRequestBus::EventResult(capsuleAabb, capsule->GetId(),
&Physics::RigidBodyRequests::GetAabb);
float rotatedHalfHeight = 0.25f * sqrtf(2.0f) * capsuleHeight + (1.0f - 0.5f * sqrt(2.0f)) * capsuleRadius;
expectedCapsuleHalfExtents = AZ::Vector3(rotatedHalfHeight, capsuleRadius, rotatedHalfHeight);
EXPECT_TRUE(capsuleAabb.GetMin().IsClose(capsulePosition - expectedCapsuleHalfExtents));
EXPECT_TRUE(capsuleAabb.GetMax().IsClose(capsulePosition + expectedCapsuleHalfExtents));
delete capsule;
}
TEST_F(PhysicsComponentBusTest, ForceAwakeForceAsleep_DynamicSphere_SleepStateCorrect)
{
auto floor = AddStaticBoxEntity(AZ::Vector3::CreateZero(), AZ::Vector3(100.0f, 100.0f, 1.0f));
auto boxA = AddBoxEntity(AZ::Vector3(-5.0f, 0.0f, 1.0f), AZ::Vector3::CreateOne());
auto boxB = AddBoxEntity(AZ::Vector3(5.0f, 0.0f, 100.0f), AZ::Vector3::CreateOne());
UpdateDefaultWorld(60);
bool isAwakeA = false;
bool isAwakeB = false;
Physics::RigidBodyRequestBus::EventResult(isAwakeA, boxA->GetId(),
&Physics::RigidBodyRequests::IsAwake);
Physics::RigidBodyRequestBus::EventResult(isAwakeB, boxB->GetId(),
&Physics::RigidBodyRequests::IsAwake);
EXPECT_FALSE(isAwakeA);
EXPECT_TRUE(isAwakeB);
Physics::RigidBodyRequestBus::Event(boxA->GetId(), &Physics::RigidBodyRequests::ForceAwake);
Physics::RigidBodyRequestBus::Event(boxB->GetId(), &Physics::RigidBodyRequests::ForceAsleep);
UpdateDefaultWorld(1);
Physics::RigidBodyRequestBus::EventResult(isAwakeA, boxA->GetId(),
&Physics::RigidBodyRequests::IsAwake);
Physics::RigidBodyRequestBus::EventResult(isAwakeB, boxB->GetId(),
&Physics::RigidBodyRequests::IsAwake);
EXPECT_TRUE(isAwakeA);
EXPECT_FALSE(isAwakeB);
UpdateDefaultWorld(60);
Physics::RigidBodyRequestBus::EventResult(isAwakeA, boxA->GetId(),
&Physics::RigidBodyRequests::IsAwake);
Physics::RigidBodyRequestBus::EventResult(isAwakeB, boxB->GetId(),
&Physics::RigidBodyRequests::IsAwake);
EXPECT_FALSE(isAwakeA);
EXPECT_FALSE(isAwakeB);
delete boxA;
delete boxB;
delete floor;
}
TEST_F(PhysicsComponentBusTest, DisableEnablePhysics_DynamicSphere)
{
using namespace AzFramework;
auto sphere = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 0.0f), 0.5f);
Physics::RigidBodyRequestBus::Event(sphere->GetId(), &Physics::RigidBodyRequests::SetLinearDamping, 0.0f);
AZ::Vector3 velocity;
float previousSpeed = 0.0f;
for (int timestep = 0; timestep < 30; timestep++)
{
UpdateDefaultWorld(1);
Physics::RigidBodyRequestBus::EventResult(velocity, sphere->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity);
previousSpeed = velocity.GetLength();
}
// Disable physics
Physics::RigidBodyRequestBus::Event(sphere->GetId(), &Physics::RigidBodyRequests::DisablePhysics);
// Check speed is not changing
for (int timestep = 0; timestep < 60; timestep++)
{
UpdateDefaultWorld(1);
Physics::RigidBodyRequestBus::EventResult(velocity, sphere->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity);
float speed = velocity.GetLength();
EXPECT_FLOAT_EQ(speed, previousSpeed);
previousSpeed = speed;
}
// Check physics is disabled
bool physicsEnabled = true;
Physics::RigidBodyRequestBus::EventResult(physicsEnabled, sphere->GetId(), &Physics::RigidBodyRequests::IsPhysicsEnabled);
EXPECT_FALSE(physicsEnabled);
// Enable physics
Physics::RigidBodyRequestBus::Event(sphere->GetId(), &Physics::RigidBodyRequests::EnablePhysics);
// Check speed is increasing
for (int timestep = 0; timestep < 60; timestep++)
{
UpdateDefaultWorld(1);
Physics::RigidBodyRequestBus::EventResult(velocity, sphere->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity);
float speed = velocity.GetLength();
EXPECT_GT(speed, previousSpeed);
previousSpeed = speed;
}
delete sphere;
}
TEST_F(PhysicsComponentBusTest, Shape_Box_GetAabbIsCorrect)
{
Physics::ColliderConfiguration colliderConfig;
Physics::BoxShapeConfiguration shapeConfiguration;
shapeConfiguration.m_dimensions = AZ::Vector3(20.f, 20.f, 20.f);
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
const AZ::Aabb localAabb = shape->GetAabbLocal();
EXPECT_TRUE(localAabb.GetMin().IsClose(-shapeConfiguration.m_dimensions / 2.f)
&& localAabb.GetMax().IsClose(shapeConfiguration.m_dimensions / 2.f));
AZ::Vector3 worldOffset = AZ::Vector3(0, 0, 40.f);
AZ::Transform worldTransform = AZ::Transform::Identity();
worldTransform.SetTranslation(worldOffset);
const AZ::Aabb worldAabb = shape->GetAabb(worldTransform);
EXPECT_TRUE(worldAabb.GetMin().IsClose((-shapeConfiguration.m_dimensions / 2.f) + worldOffset)
&& worldAabb.GetMax().IsClose((shapeConfiguration.m_dimensions / 2.f) + worldOffset));
}
TEST_F(PhysicsComponentBusTest, Shape_Sphere_GetAabbIsCorrect)
{
const float radius = 20.f;
Physics::ColliderConfiguration colliderConfig;
Physics::SphereShapeConfiguration shapeConfiguration;
shapeConfiguration.m_radius = radius;
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
const AZ::Aabb localAabb = shape->GetAabbLocal();
EXPECT_TRUE(localAabb.GetMin().IsClose(AZ::Vector3(-radius, -radius, -radius))
&& localAabb.GetMax().IsClose(AZ::Vector3(radius, radius, radius)));
AZ::Vector3 worldOffset = AZ::Vector3(0, 0, 40.f);
AZ::Transform worldTransform = AZ::Transform::Identity();
worldTransform.SetTranslation(worldOffset);
const AZ::Aabb worldAabb = shape->GetAabb(worldTransform);
EXPECT_TRUE(worldAabb.GetMin().IsClose(AZ::Vector3(-radius, -radius, -radius) + worldOffset)
&& worldAabb.GetMax().IsClose(AZ::Vector3(radius, radius, radius) + worldOffset));
}
TEST_F(PhysicsComponentBusTest, Shape_Capsule_GetAabbIsCorrect)
{
const float radius = 20.f;
const float height = 80.f;
Physics::ColliderConfiguration colliderConfig;
Physics::CapsuleShapeConfiguration shapeConfiguration;
shapeConfiguration.m_radius = radius;
shapeConfiguration.m_height = height;
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
const AZ::Aabb localAabb = shape->GetAabbLocal();
EXPECT_TRUE(localAabb.GetMin().IsClose(AZ::Vector3(-radius, -radius, -height / 2.f))
&& localAabb.GetMax().IsClose(AZ::Vector3(radius, radius, height / 2.f)));
AZ::Vector3 worldOffset = AZ::Vector3(0, 0, 40.f);
AZ::Transform worldTransform = AZ::Transform::Identity();
worldTransform.SetTranslation(worldOffset);
const AZ::Aabb worldAabb = shape->GetAabb(worldTransform);
EXPECT_TRUE(worldAabb.GetMin().IsClose(AZ::Vector3(-radius, -radius, -height / 2.f) + worldOffset)
&& worldAabb.GetMax().IsClose(AZ::Vector3(radius, radius, height / 2.f) + worldOffset));
}
TEST_F(PhysicsComponentBusTest, WorldBodyBus_RigidBodyColliders_AABBAreCorrect)
{
// Create 3 colliders, one of each type and check that the AABB of their body is the expected
AZStd::unique_ptr<AZ::Entity> box = AZStd::unique_ptr<AZ::Entity>(AddBoxEntity(AZ::Vector3(0, 0, 0), AZ::Vector3(32, 32, 32)));
AZ::Aabb boxAABB;
Physics::WorldBodyRequestBus::EventResult(boxAABB, box->GetId(), &Physics::WorldBodyRequests::GetAabb);
EXPECT_TRUE(boxAABB.GetMin().IsClose(AZ::Vector3(-16, -16, -16)) && boxAABB.GetMax().IsClose(AZ::Vector3(16, 16, 16)));
AZStd::unique_ptr<AZ::Entity> sphere = AZStd::unique_ptr<AZ::Entity>(AddSphereEntity(AZ::Vector3(-100, 0, 0), 16));
AZ::Aabb sphereAABB;
Physics::WorldBodyRequestBus::EventResult(sphereAABB, sphere->GetId(), &Physics::WorldBodyRequests::GetAabb);
EXPECT_TRUE(sphereAABB.GetMin().IsClose(AZ::Vector3(-16 -100, -16, -16)) && sphereAABB.GetMax().IsClose(AZ::Vector3(16 -100, 16, 16)));
AZStd::unique_ptr<AZ::Entity> capsule = AZStd::unique_ptr<AZ::Entity>(AddCapsuleEntity(AZ::Vector3(100, 0, 0), 128, 16));
AZ::Aabb capsuleAABB;
Physics::WorldBodyRequestBus::EventResult(capsuleAABB, capsule->GetId(), &Physics::WorldBodyRequests::GetAabb);
EXPECT_TRUE(capsuleAABB.GetMin().IsClose(AZ::Vector3(-16 +100, -16, -64)) && capsuleAABB.GetMax().IsClose(AZ::Vector3(16 +100, 16, 64)));
}
TEST_F(PhysicsComponentBusTest, WorldBodyBus_StaticRigidBodyColliders_AABBAreCorrect)
{
// Create 3 colliders, one of each type and check that the AABB of their body is the expected
AZStd::unique_ptr<AZ::Entity> box = AZStd::unique_ptr<AZ::Entity>(AddStaticBoxEntity(AZ::Vector3(0, 0, 0), AZ::Vector3(32, 32, 32)));
AZ::Aabb boxAABB;
Physics::WorldBodyRequestBus::EventResult(boxAABB, box->GetId(), &Physics::WorldBodyRequests::GetAabb);
EXPECT_TRUE(boxAABB.GetMin().IsClose(AZ::Vector3(-16, -16, -16)) && boxAABB.GetMax().IsClose(AZ::Vector3(16, 16, 16)));
AZStd::unique_ptr<AZ::Entity> sphere = AZStd::unique_ptr<AZ::Entity>(AddStaticSphereEntity(AZ::Vector3(-100, 0, 0), 16));
AZ::Aabb sphereAABB;
Physics::WorldBodyRequestBus::EventResult(sphereAABB, sphere->GetId(), &Physics::WorldBodyRequests::GetAabb);
EXPECT_TRUE(sphereAABB.GetMin().IsClose(AZ::Vector3(-16 -100, -16, -16)) && sphereAABB.GetMax().IsClose(AZ::Vector3(16 -100, 16, 16)));
AZStd::unique_ptr<AZ::Entity> capsule = AZStd::unique_ptr<AZ::Entity>(AddStaticCapsuleEntity(AZ::Vector3(100, 0, 0), 128, 16));
AZ::Aabb capsuleAABB;
Physics::WorldBodyRequestBus::EventResult(capsuleAABB, capsule->GetId(), &Physics::WorldBodyRequests::GetAabb);
EXPECT_TRUE(capsuleAABB.GetMin().IsClose(AZ::Vector3(-16 +100, -16, -64)) && capsuleAABB.GetMax().IsClose(AZ::Vector3(16 +100, 16, 64)));
}
using CreateEntityFunc = AZStd::function<AZStd::unique_ptr<AZ::Entity>(const AZ::Vector3&)>;
void CheckDisableEnablePhysics(AZStd::vector<CreateEntityFunc> entityCreations)
{
// Fake Pointer for filling result to make sure that m_body has changed
Physics::WorldBody* fakeBody = (Physics::WorldBody*)(0x1234);
int i = 0;
for (CreateEntityFunc entityCreation : entityCreations)
{
AZ::Vector3 entityPos = AZ::Vector3(128.f * i, 0, 0);
AZStd::unique_ptr<AZ::Entity> entity = entityCreation(entityPos);
Physics::RayCastHit hit;
Physics::RayCastRequest request;
request.m_start = entityPos + AZ::Vector3(0, 0, 100);
request.m_direction = AZ::Vector3(0, 0, -1);
request.m_distance = 200.f;
Physics::WorldBodyRequestBus::Event(entity->GetId(), &Physics::WorldBodyRequests::DisablePhysics);
bool enabled = true;
Physics::WorldBodyRequestBus::EventResult(enabled, entity->GetId(), &Physics::WorldBodyRequests::IsPhysicsEnabled);
EXPECT_FALSE(enabled);
hit.m_body = fakeBody;
Physics::WorldRequestBus::BroadcastResult(hit, &Physics::WorldRequests::RayCast, request);
EXPECT_FALSE(hit);
Physics::WorldBodyRequestBus::Event(entity->GetId(), &Physics::WorldBodyRequests::EnablePhysics);
enabled = false;
Physics::WorldBodyRequestBus::EventResult(enabled, entity->GetId(), &Physics::WorldBodyRequests::IsPhysicsEnabled);
EXPECT_TRUE(enabled);
hit.m_body = nullptr;
Physics::WorldRequestBus::BroadcastResult(hit, &Physics::WorldRequests::RayCast, request);
EXPECT_TRUE(hit && hit.m_body->GetEntityId() == entity->GetId());
++i;
}
}
TEST_F(PhysicsComponentBusTest, WorldBodyBus_EnableDisablePhysics_StaticRigidBody)
{
AZStd::vector<CreateEntityFunc> entityCreations =
{
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddStaticBoxEntity(position, AZ::Vector3(32, 32, 32))); },
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddStaticSphereEntity(position, 16)); },
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddStaticCapsuleEntity(position, 16, 16)); }
};
CheckDisableEnablePhysics(entityCreations);
}
TEST_F(PhysicsComponentBusTest, WorldBodyBus_EnableDisablePhysics_RigidBody)
{
AZStd::vector<CreateEntityFunc> entityCreations =
{
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddBoxEntity(position, AZ::Vector3(32, 32, 32))); },
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddSphereEntity(position, 16)); },
[this](const AZ::Vector3& position) { return AZStd::unique_ptr<AZ::Entity>(AddCapsuleEntity(position, 16, 16)); }
};
CheckDisableEnablePhysics(entityCreations);
}
TEST_F(PhysicsComponentBusTest, WorldBodyRayCast_CastAgainstStaticBox_ReturnsHit)
{
AZStd::unique_ptr<AZ::Entity> staticBoxEntity(AddStaticBoxEntity(AZ::Vector3(0.0f), AZ::Vector3(10.f, 10.f, 10.f)));
RayCastRequest request;
request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
Physics::RayCastHit hit;
Physics::WorldBodyRequestBus::EventResult(hit, staticBoxEntity->GetId(), &Physics::WorldBodyRequests::RayCast, request);
EXPECT_TRUE(hit);
bool hitIncludeSphereEntity = (hit.m_body->GetEntityId() == staticBoxEntity->GetId());
EXPECT_TRUE(hitIncludeSphereEntity);
}
using RayCastFunc = AZStd::function<Physics::RayCastHit(AZ::EntityId, const Physics::RayCastRequest&)>;
class PhysicsRigidBodyRayBusTest
: public Physics::GenericPhysicsInterfaceTest
, public ::testing::WithParamInterface<RayCastFunc>
{
};
TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstNothing_ReturnsNoHit)
{
RayCastRequest request;
request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
auto rayCastFunction = GetParam();
RayCastHit hit = rayCastFunction(AZ::EntityId(), request);
EXPECT_FALSE(hit);
}
TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstSphere_ReturnsHit)
{
AZStd::unique_ptr<AZ::Entity> sphereEntity(AddSphereEntity(AZ::Vector3(0.0f), 10.0f));
RayCastRequest request;
request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
auto rayCastFunction = GetParam();
RayCastHit hit = rayCastFunction(sphereEntity->GetId(), request);
EXPECT_TRUE(hit);
bool hitsIncludeSphereEntity = (hit.m_body->GetEntityId() == sphereEntity->GetId());
EXPECT_TRUE(hitsIncludeSphereEntity);
}
TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstBoxEntityWithLocalOffset_ReturnsHit)
{
const AZ::Vector3 boxExtent = AZ::Vector3(10.0f, 10.0f, 10.0f);
const AZ::Vector3 box1Offset(0.0f, 0.0f, 30.0f);
const AZ::Vector3 box2Offset(0.0f, 0.0f, -30.0f);
MultiShapeConfig config;
config.m_position = AZ::Vector3(0.0f, 100.0f, 20.0f);
config.m_shapes.AddBox(boxExtent, box1Offset);
config.m_shapes.AddBox(boxExtent, box2Offset);
auto shapeWithTwoBoxes = AddMultiShapeEntity(config);
RayCastRequest request;
request.m_start = AZ::Vector3(-100.0f, 100.0f, 50.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
auto rayCastFunction = GetParam();
RayCastHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludeEntity = (result.m_body->GetEntityId() == shapeWithTwoBoxes->GetId());
EXPECT_TRUE(hitIncludeEntity);
}
TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstBoxEntityWithMultipleShapesLocalOffset_ReturnsHits)
{
// Entity at (0, 100, 20) with two box childs with offsets +30 and -30 in Z.
// Child boxes world position centers are at (0, 100, 50) and (0, 100, -10)
// 4 rays tests that should retrieves the correct boxes
const AZ::Vector3 boxExtent = AZ::Vector3(10.0f, 10.0f, 10.0f);
const AZ::Vector3 box1Offset(0.0f, 0.0f, 30.0f);
const AZ::Vector3 box2Offset(0.0f, 0.0f, -30.0f);
MultiShapeConfig config;
config.m_position = AZ::Vector3(0.0f, 100.0f, 20.0f);
config.m_shapes.AddBox(boxExtent, box1Offset);
config.m_shapes.AddBox(boxExtent, box2Offset);
AZStd::unique_ptr<AZ::Entity> shapeWithTwoBoxes(AddMultiShapeEntity(config));
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> shapes;
PhysX::ColliderComponentRequestBus::EventResult(shapes, shapeWithTwoBoxes->GetId(), &PhysX::ColliderComponentRequests::GetShapes);
// Upper box part z=50 (-x to +x)
{
RayCastRequest request;
request.m_start = AZ::Vector3(-100.0f, 100.0f, 50.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
auto rayCastFunction = GetParam();
RayCastHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludesEntity = (result.m_body->GetEntityId() == shapeWithTwoBoxes->GetId());
EXPECT_TRUE(hitIncludesEntity);
bool hitIncludesShape = (result.m_shape == shapes[0].get());
EXPECT_TRUE(hitIncludesShape);
}
// Lower box part z=-10 (-x to +x)
{
RayCastRequest request;
request.m_start = AZ::Vector3(-100.0f, 100.0f, -10.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
auto rayCastFunction = GetParam();
RayCastHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludesEntity = (result.m_body->GetEntityId() == shapeWithTwoBoxes->GetId());
EXPECT_TRUE(hitIncludesEntity);
bool hitIncludesShape = (result.m_shape == shapes[1].get());
EXPECT_TRUE(hitIncludesShape);
}
// Trace Vertically from top, it should retrieve the upper box shape
{
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 100.0f, 80.0f);
request.m_direction = AZ::Vector3(0.0f, 0.0f, -1.0f);
request.m_distance = 200.0f;
auto rayCastFunction = GetParam();
RayCastHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludesEntity = (result.m_body->GetEntityId() == shapeWithTwoBoxes->GetId());
EXPECT_TRUE(hitIncludesEntity);
bool hitIncludesShape = (result.m_shape == shapes[0].get());
EXPECT_TRUE(hitIncludesShape);
}
// Trace Vertically from bottom, it should retrieve the lower box shape
{
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 100.0f, -80.0f);
request.m_direction = AZ::Vector3(0.0f, 0.0f, 1.0f);
request.m_distance = 200.0f;
auto rayCastFunction = GetParam();
RayCastHit result = rayCastFunction(shapeWithTwoBoxes->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludesEntity = (result.m_body->GetEntityId() == shapeWithTwoBoxes->GetId());
EXPECT_TRUE(hitIncludesEntity);
bool hitIncludesShape = (result.m_shape == shapes[1].get());
EXPECT_TRUE(hitIncludesShape);
}
}
TEST_P(PhysicsRigidBodyRayBusTest, ComponentRayCast_CastAgainstBoxEntityLocalOffsetAndRotation_ReturnsHits)
{
// Entity at (0,0,0) rotated by 90 degrees and child box offset (0,100,0).
// World position of the child should be (-100, 0, 0).
// This tests raycasts from (0, 0, 0) to (-200, 0 ,0) checking that collides with the box
const AZ::Vector3 boxExtent = AZ::Vector3(10.0f, 10.0f, 10.0f);
const AZ::Vector3 boxOffset(0.0f, 100.0f, 0.0f);
MultiShapeConfig config;
config.m_position = AZ::Vector3(0.0f, 0.0f, 0.0f);
config.m_rotation = AZ::Vector3(0, 0, AZ::Constants::HalfPi);
config.m_shapes.AddBox(boxExtent, boxOffset);
AZStd::unique_ptr<AZ::Entity> shapeWithOneBox(AddMultiShapeEntity(config));
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> shapes;
PhysX::ColliderComponentRequestBus::EventResult(shapes, shapeWithOneBox->GetId(), &PhysX::ColliderComponentRequests::GetShapes);
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(-1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
auto rayCastFunction = GetParam();
RayCastHit result = rayCastFunction(shapeWithOneBox->GetId(), request);
EXPECT_TRUE(result);
bool hitIncludesEntity = (result.m_body->GetEntityId() == shapeWithOneBox->GetId());
EXPECT_TRUE(hitIncludesEntity);
bool hitIncludesShape = (result.m_shape == shapes[0].get());
EXPECT_TRUE(hitIncludesShape);
}
static const RayCastFunc RigidBodyRaycastEBusCall = [](AZ::EntityId entityId, const Physics::RayCastRequest& request)
{
Physics::RayCastHit ret;
Physics::RigidBodyRequestBus::EventResult(ret, entityId, &Physics::RigidBodyRequests::RayCast, request);
return ret;
};
static const RayCastFunc WorldBodyRaycastEBusCall = [](AZ::EntityId entityId, const Physics::RayCastRequest& request)
{
Physics::RayCastHit ret;
Physics::WorldBodyRequestBus::EventResult(ret, entityId, &Physics::WorldBodyRequests::RayCast, request);
return ret;
};
INSTANTIATE_TEST_CASE_P(, PhysicsRigidBodyRayBusTest,
::testing::Values(RigidBodyRaycastEBusCall, WorldBodyRaycastEBusCall),
// Provide nice names for the tests runs
[](const testing::TestParamInfo<PhysicsRigidBodyRayBusTest::ParamType>& info)
{
const char* name = "";
switch (info.index)
{
case 0:
name = "RigidBodyRequestBus";
break;
case 1:
name = "WorldBodyRequestBus";
break;
}
return name;
});
} // namespace Physics
@@ -0,0 +1,919 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/Shape.h>
#include <AzFramework/Physics/RigidBodyBus.h>
#include <AzFramework/Physics/World.h>
#include <PhysX/ColliderComponentBus.h>
namespace Physics
{
static auto GetEntityInRayCastHitCallBack = [](AZ::EntityId entityId)
{
return [entityId](const RayCastHit& hit)
{
return hit.m_body->GetEntityId() == entityId;
};
};
TEST_F(GenericPhysicsInterfaceTest, World_CreateNewWorld_ReturnsNewWorld)
{
EXPECT_TRUE(CreateTestWorld() != nullptr);
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_CastAgainstNothing_ReturnsNoHits)
{
RayCastRequest request;
request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
RayCastHit hit;
WorldRequestBus::BroadcastResult(hit, &WorldRequests::RayCast, request);
EXPECT_FALSE(hit);
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_CastAgainstSphere_ReturnsHits)
{
auto sphereEntity = AddSphereEntity(AZ::Vector3(0.0f), 10.0f);
RayCastRequest request;
request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
RayCastHit hit;
WorldRequestBus::BroadcastResult(hit, &WorldRequests::RayCast, request);
EXPECT_TRUE(hit);
bool hitsIncludeSphereEntity = (hit.m_body->GetEntityId() == sphereEntity->GetId());
EXPECT_TRUE(hitsIncludeSphereEntity);
delete sphereEntity;
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_CastAgainstSphere_ReturnsCorrectShapeAndMaterial)
{
auto sphereEntity = AZStd::shared_ptr<AZ::Entity>(AddSphereEntity(AZ::Vector3(0.0f), 10.0f));
RayCastRequest request;
request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
RayCastHit result;
WorldRequestBus::BroadcastResult(result, &WorldRequests::RayCast, request);
ASSERT_TRUE(result);
RigidBody* rigidBody;
Physics::RigidBodyRequestBus::EventResult(rigidBody, sphereEntity->GetId(), &RigidBodyRequestBus::Events::GetRigidBody);
ASSERT_NE(rigidBody->GetShape(0), nullptr);
ASSERT_NE(result.m_material, nullptr);
ASSERT_EQ(result.m_shape, rigidBody->GetShape(0).get());
ASSERT_EQ(result.m_material, rigidBody->GetShape(0).get()->GetMaterial().get());
const AZStd::string& typeName = result.m_material->GetSurfaceTypeName();
ASSERT_EQ(typeName, AZStd::string("Default"));
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_CastAgainstStaticObject_ReturnsHits)
{
auto boxEntity = AZStd::shared_ptr<AZ::Entity>(AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(10.0f, 10.0f, 10.0f)));
RayCastRequest request;
request.m_start = AZ::Vector3(-100.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_distance = 200.0f;
RayCastHit result;
WorldRequestBus::BroadcastResult(result, &WorldRequests::RayCast, request);
EXPECT_TRUE(result);
bool hitsIncludeEntity = (result.m_body->GetEntityId() == boxEntity->GetId());
EXPECT_TRUE(hitsIncludeEntity);
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_CastAgainstFilteredSpheres_ReturnsHits)
{
auto entity1 = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 10.0f), 10.0f, CollisionLayer(0));
auto entity2 = AddCapsuleEntity(AZ::Vector3(0.0f, 0.0f, 20.0f), 10.0f, 2.0f, CollisionLayer(1));
auto entity3 = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 30.0f), AZ::Vector3(20.0f, 20.0f, 20.0f), CollisionLayer(2));
CollisionGroup group = CollisionGroup::All;
group.SetLayer(CollisionLayer(0), true);
group.SetLayer(CollisionLayer(1), false);
group.SetLayer(CollisionLayer(2), true);
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(0.0f, 0.0f, 1.0f);
request.m_distance = 200.0f;
request.m_collisionGroup = group;
AZStd::vector<RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::RayCastMultiple, request);
ASSERT_TRUE(hits.size() == 2);
EXPECT_TRUE(hits[1].m_body->GetEntityId() == entity1->GetId());
EXPECT_TRUE(hits[0].m_body->GetEntityId() == entity3->GetId());
delete entity1;
delete entity2;
delete entity3;
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_AgainstStaticOnly_ReturnsStaticBox)
{
auto dynamicSphere = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 10.0f), 10.0f);
auto staticBox = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 30.0f), AZ::Vector3(20.0f, 20.0f, 20.0f));
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(0.0f, 0.0f, 1.0f);
request.m_queryType = Physics::QueryType::Static;
AZStd::vector<RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::RayCastMultiple, request);
ASSERT_EQ(hits.size(), 1);
ASSERT_EQ(hits[0].m_body->GetEntityId(), staticBox->GetId());
delete dynamicSphere;
delete staticBox;
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_AgainstDynamicOnly_ReturnsDynamicSphere)
{
auto dynamicSphere = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 10.0f), 10.0f);
auto staticBox = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 30.0f), AZ::Vector3(20.0f, 20.0f, 20.0f));
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(0.0f, 0.0f, 1.0f);
request.m_queryType = Physics::QueryType::Dynamic;
AZStd::vector<RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::RayCastMultiple, request);
ASSERT_EQ(hits.size(), 1);
ASSERT_EQ(hits[0].m_body->GetEntityId(), dynamicSphere->GetId());
delete dynamicSphere;
delete staticBox;
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_AgainstStaticAndDynamic_ReturnsBothObjects)
{
auto dynamicSphere = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 10.0f), 10.0f);
auto staticBox = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 30.0f), AZ::Vector3(20.0f, 20.0f, 20.0f));
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(0.0f, 0.0f, 1.0f);
request.m_queryType = Physics::QueryType::StaticAndDynamic;
AZStd::vector<RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::RayCastMultiple, request);
ASSERT_EQ(hits.size(), 2);
ASSERT_EQ(hits[0].m_body->GetEntityId(), staticBox->GetId());
ASSERT_EQ(hits[1].m_body->GetEntityId(), dynamicSphere->GetId());
delete dynamicSphere;
delete staticBox;
}
TEST_F(GenericPhysicsInterfaceTest, RayCast_AgainstMultipleTouchAndBlockHits_ReturnsClosestBlockAndTouches)
{
auto dynamicSphere = AddSphereEntity(AZ::Vector3(20.0f, 0.0f, 0.0f), 10.0f);
auto staticBox = AddStaticBoxEntity(AZ::Vector3(40.0f, 0.0f, 0.0f), AZ::Vector3(5.0f, 5.0f, 5.0f));
auto blockingSphere = AddSphereEntity(AZ::Vector3(60.0f, 0.0f, 0.0f), 5.0f);
auto blockingBox = AddStaticBoxEntity(AZ::Vector3(80.0f, 0.0f, 0.0f), AZ::Vector3(5.0f, 5.0f, 5.0f));
auto farSphere = AddSphereEntity(AZ::Vector3(120.0f, 0.0f, 0.0f), 10.0f);
RayCastRequest request;
request.m_start = AZ::Vector3(0.0f, 0.0f, 0.0f);
request.m_direction = AZ::Vector3(1.0f, 0.0f, 0.0f);
request.m_queryType = Physics::QueryType::StaticAndDynamic;
request.m_filterCallback = [&](const Physics::WorldBody* body, [[maybe_unused]] const Physics::Shape* shape)
{
if (body->GetEntityId() == blockingBox->GetId() || body->GetEntityId() == blockingSphere->GetId())
{
return QueryHitType::Block;
}
return QueryHitType::Touch;
};
AZStd::vector<RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::RayCastMultiple, request);
ASSERT_EQ(hits.size(), 3);
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(dynamicSphere->GetId())));
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(staticBox->GetId())));
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(blockingSphere->GetId())));
delete dynamicSphere;
delete staticBox;
delete blockingSphere;
delete blockingBox;
delete farSphere;
}
TEST_F(GenericPhysicsInterfaceTest, ShapeCast_CastAgainstNothing_ReturnsNoHits)
{
Physics::RayCastHit hit;
WorldRequestBus::BroadcastResult(hit, &WorldRequests::SphereCast,
1.0f,
AZ::Transform::CreateTranslation(AZ::Vector3(-20.0f, 0.0f, 0.0f)),
AZ::Vector3(1.0f, 0.0f, 0.0f),
20.0f, Physics::QueryType::StaticAndDynamic,
Physics::CollisionGroup::All,
nullptr
);
EXPECT_FALSE(hit);
}
TEST_F(GenericPhysicsInterfaceTest, ShapeCast_CastAgainstSphere_ReturnsHits)
{
auto sphereEntity = AddSphereEntity(AZ::Vector3(0.0f), 10.0f);
Physics::RayCastHit hit;
WorldRequestBus::BroadcastResult(hit, &WorldRequests::SphereCast,
1.0f,
AZ::Transform::CreateTranslation(AZ::Vector3(-20.0f, 0.0f, 0.0f)),
AZ::Vector3(1.0f, 0.0f, 0.0f),
20.0f, Physics::QueryType::StaticAndDynamic,
Physics::CollisionGroup::All,
nullptr
);
EXPECT_TRUE(hit);
EXPECT_EQ(hit.m_body->GetEntityId(), sphereEntity->GetId());
// clear up scene
delete sphereEntity;
}
TEST_F(GenericPhysicsInterfaceTest, ShapeCast_SphereCastAgainstStaticObject_ReturnsHits)
{
auto boxEntity = AZStd::shared_ptr<AZ::Entity>(AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(1.0f, 1.0f, 1.0f)));
Physics::RayCastHit hit;
WorldRequestBus::BroadcastResult(hit, &WorldRequests::SphereCast,
1.5f,
AZ::Transform::CreateTranslation(AZ::Vector3(-20.0f, 0.0f, 0.0f)),
AZ::Vector3(1.0f, 0.0f, 0.0f),
20.0f, Physics::QueryType::StaticAndDynamic,
Physics::CollisionGroup::All,
nullptr
);
EXPECT_TRUE(hit);
EXPECT_EQ(hit.m_body->GetEntityId(), boxEntity->GetId());
}
TEST_F(GenericPhysicsInterfaceTest, ShapeCast_SphereCastAgainstFilteredObjects_ReturnsHits)
{
auto entity1 = AddSphereEntity(AZ::Vector3(0.0f, 0.0f, 10.0f), 10.0f, CollisionLayer(0));
auto entity2 = AddCapsuleEntity(AZ::Vector3(0.0f, 0.0f, 20.0f), 10.0f, 2.0f, CollisionLayer(1));
auto entity3 = AddStaticBoxEntity(AZ::Vector3(0.0f, 0.0f, 30.0f), AZ::Vector3(20.0f, 20.0f, 20.0f), CollisionLayer(2));
CollisionGroup group = CollisionGroup::All;
group.SetLayer(CollisionLayer(0), true);
group.SetLayer(CollisionLayer(1), false);
group.SetLayer(CollisionLayer(2), true);
AZStd::vector<Physics::RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::SphereCastMultiple,
1.5f,
AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 0.0f, 0.0f)),
AZ::Vector3(0.0f, 0.0f, 1.0f),
200.0f, Physics::QueryType::StaticAndDynamic,
group,
nullptr
);
ASSERT_TRUE(hits.size() == 2);
EXPECT_TRUE(hits[1].m_body->GetEntityId() == entity1->GetId());
EXPECT_TRUE(hits[0].m_body->GetEntityId() == entity3->GetId());
delete entity1;
delete entity2;
delete entity3;
}
TEST_F(GenericPhysicsInterfaceTest, ShapeCast_AgainstMultipleTouchAndBlockHits_ReturnsClosestBlockAndTouches)
{
auto dynamicSphere = AddSphereEntity(AZ::Vector3(20.0f, 0.0f, 0.0f), 10.0f);
auto staticBox = AddStaticBoxEntity(AZ::Vector3(40.0f, 0.0f, 0.0f), AZ::Vector3(5.0f, 5.0f, 5.0f));
auto blockingSphere = AddSphereEntity(AZ::Vector3(60.0f, 0.0f, 0.0f), 5.0f);
auto blockingBox = AddStaticBoxEntity(AZ::Vector3(80.0f, 0.0f, 0.0f), AZ::Vector3(5.0f, 5.0f, 5.0f));
auto farSphere = AddSphereEntity(AZ::Vector3(120.0f, 0.0f, 0.0f), 10.0f);
AZStd::vector<Physics::RayCastHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::SphereCastMultiple,
1.5f,
AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 0.0f, 0.0f)),
AZ::Vector3(1.0f, 0.0f, 0.0f),
200.0f, Physics::QueryType::StaticAndDynamic,
Physics::CollisionGroup::All,
[&](const Physics::WorldBody* body, [[maybe_unused]] const Physics::Shape* shape)
{
if (body->GetEntityId() == blockingBox->GetId() || body->GetEntityId() == blockingSphere->GetId())
{
return QueryHitType::Block;
}
return QueryHitType::Touch;
}
);
ASSERT_EQ(hits.size(), 3);
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(dynamicSphere->GetId())));
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(staticBox->GetId())));
ASSERT_EQ(1, AZStd::count_if(hits.begin(), hits.end(), GetEntityInRayCastHitCallBack(blockingSphere->GetId())));
delete dynamicSphere;
delete staticBox;
delete blockingSphere;
delete blockingBox;
delete farSphere;
}
TEST_F(GenericPhysicsInterfaceTest, Overlap_OverlapMultipleObjects_ReturnsHits)
{
AZStd::shared_ptr<AZ::Entity> sphereEntity(AddSphereEntity(AZ::Vector3(10.0f, 0.0f, 0.0f), 3.0f));
AZStd::shared_ptr<AZ::Entity> boxEntity(AddBoxEntity(AZ::Vector3(7.0f, 4.0f, 0.0f), AZ::Vector3(1.0f)));
AZStd::shared_ptr<AZ::Entity> capsuleEntity(AddCapsuleEntity(AZ::Vector3(15.0f, 0.0f, 0.0f), 3.0f, 1.0f));
BoxShapeConfiguration overlapShape;
overlapShape.m_dimensions = AZ::Vector3(3.0f);
OverlapRequest request;
request.m_pose = AZ::Transform::CreateTranslation(AZ::Vector3(13.0f, 0.0f, 0.0f));
request.m_shapeConfiguration = &overlapShape;
AZStd::vector<OverlapHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::Overlap, request);
EXPECT_EQ(hits.size(), 2);
// boxEntity shouldn't be included in the result
EXPECT_FALSE(AZStd::any_of(hits.begin(), hits.end(),
[idToFind = boxEntity->GetId()](const OverlapHit& hit) { return hit.m_body->GetEntityId() == idToFind; }));
}
TEST_F(GenericPhysicsInterfaceTest, Overlap_OverlapMultipleObjectsUseFriendlyFunctions_ReturnsHits)
{
AZStd::shared_ptr<AZ::Entity> sphereEntity(AddSphereEntity(AZ::Vector3(10.0f, 0.0f, 0.0f), 3.0f));
AZStd::shared_ptr<AZ::Entity> boxEntity(AddBoxEntity(AZ::Vector3(7.0f, 4.0f, 0.0f), AZ::Vector3(1.0f)));
AZStd::shared_ptr<AZ::Entity> capsuleEntity(AddCapsuleEntity(AZ::Vector3(15.0f, 0.0f, 0.0f), 3.0f, 1.0f));
AZStd::shared_ptr<World> defaultWorld;
DefaultWorldBus::BroadcastResult(defaultWorld, &DefaultWorldRequests::GetDefaultWorld);
{
AZStd::vector<OverlapHit> hits = defaultWorld->OverlapBox(AZ::Vector3(3.0f), AZ::Transform::CreateTranslation(AZ::Vector3(13.0f, 0.0f, 0.0f)));
EXPECT_EQ(hits.size(), 2);
// boxEntity shouldn't be included in the result
EXPECT_FALSE(AZStd::any_of(hits.begin(), hits.end(),
[idToFind = boxEntity->GetId()](const OverlapHit& hit) { return hit.m_body->GetEntityId() == idToFind; }));
}
{
AZStd::vector<OverlapHit> hits = defaultWorld->OverlapSphere(3.0f, AZ::Transform::CreateTranslation(AZ::Vector3(13.0f, 0.0f, 0.0f)));
EXPECT_EQ(hits.size(), 2);
// boxEntity shouldn't be included in the result
EXPECT_FALSE(AZStd::any_of(hits.begin(), hits.end(),
[idToFind = boxEntity->GetId()](const OverlapHit& hit) { return hit.m_body->GetEntityId() == idToFind; }));
}
}
TEST_F(GenericPhysicsInterfaceTest, Overlap_OverlapMultipleObjectsUseFriendlyFunctionsCustomFiltering_ReturnsHits)
{
AZStd::shared_ptr<AZ::Entity> sphereEntity(AddSphereEntity(AZ::Vector3(10.0f, 0.0f, 0.0f), 3.0f));
AZStd::shared_ptr<AZ::Entity> boxEntity(AddBoxEntity(AZ::Vector3(7.0f, 4.0f, 0.0f), AZ::Vector3(1.0f)));
AZStd::shared_ptr<AZ::Entity> capsuleEntity(AddCapsuleEntity(AZ::Vector3(15.0f, 0.0f, 0.0f), 3.0f, 1.0f));
AZStd::shared_ptr<World> defaultWorld;
DefaultWorldBus::BroadcastResult(defaultWorld, &DefaultWorldRequests::GetDefaultWorld);
// Here we do an overlap test that covers all objects in the scene
// However we provide a custom filtering function that filters out a specific entity
{
AZ::EntityId entityIdToFilterOut = capsuleEntity->GetId();
AZStd::vector<Physics::OverlapHit> hits = defaultWorld->OverlapCapsule(100.0f, 30.0f, AZ::Transform::CreateTranslation(AZ::Vector3(13.0f, 0.0f, 0.0f)),
[entityIdToFilterOut](const Physics::WorldBody* body, [[maybe_unused]] const Physics::Shape* shape)
{
return body->GetEntityId() != entityIdToFilterOut;
});
EXPECT_EQ(hits.size(), 2);
EXPECT_FALSE(AZStd::any_of(hits.begin(), hits.end(),
[entityIdToFilterOut](const OverlapHit& hit) { return hit.m_body->GetEntityId() == entityIdToFilterOut; }));
}
}
TEST_F(GenericPhysicsInterfaceTest, Overlap_OverlapMultipleObjects_ReturnsFilteredHits)
{
AZStd::shared_ptr<AZ::Entity> sphereEntity(AddSphereEntity(AZ::Vector3(10.0f, 0.0f, 0.0f), 3.0f, CollisionLayer(0)));
AZStd::shared_ptr<AZ::Entity> boxEntity(AddStaticBoxEntity(AZ::Vector3(12.0f, 0.0f, 0.0f), AZ::Vector3(1.0f), CollisionLayer(1)));
AZStd::shared_ptr<AZ::Entity> capsuleEntity(AddCapsuleEntity(AZ::Vector3(14.0f, 0.0f, 0.0f), 3.0f, 1.0f, CollisionLayer(2)));
BoxShapeConfiguration overlapShape;
overlapShape.m_dimensions = AZ::Vector3(1.0f);
OverlapRequest request;
request.m_pose = AZ::Transform::CreateTranslation(AZ::Vector3(13.0f, 0.0f, 0.0f));
request.m_shapeConfiguration = &overlapShape;
request.m_collisionGroup = CollisionGroup::All;
request.m_collisionGroup.SetLayer(CollisionLayer(0), false); // Filter out the sphere
request.m_collisionGroup.SetLayer(CollisionLayer(1), true);
request.m_collisionGroup.SetLayer(CollisionLayer(2), true);
AZStd::vector<OverlapHit> hits;
WorldRequestBus::BroadcastResult(hits, &WorldRequests::Overlap, request);
EXPECT_EQ(hits.size(), 2);
EXPECT_FALSE(AZStd::any_of(hits.begin(), hits.end(),
[sphereEntity](const OverlapHit& hit)
{
// Make sure the sphere was not included
return hit.m_body->GetEntityId() == sphereEntity->GetId();
}));
}
TEST_F(GenericPhysicsInterfaceTest, Gravity_DynamicBody_BodyFalls)
{
auto world = CreateTestWorld();
auto rigidBody = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 100.0f));
UpdateWorld(world.get(), 1.0f / 60.f, 60);
// expect velocity to be -gt and distance fallen to be 1/2gt^2, but allow quite a lot of tolerance
// due to potential differences in back end integration schemes etc.
EXPECT_NEAR(rigidBody->GetLinearVelocity().GetZ(), -10.0f, 0.5f);
EXPECT_NEAR(rigidBody->GetTransform().GetTranslation().GetZ(), 95.0f, 0.5f);
EXPECT_NEAR(rigidBody->GetCenterOfMassWorld().GetZ(), 95.0f, 0.5f);
EXPECT_NEAR(rigidBody->GetPosition().GetZ(), 95.0f, 0.5f);
}
TEST_F(GenericPhysicsInterfaceTest, World_SplitSimulation_BodyFallsTheSameInBothWorlds)
{
auto worldA = CreateTestWorld();
auto worldB = CreateTestWorld();
AZ::Vector3 initialPosition(0.0f, 0.0f, 100.0f);
auto rigidBodyA = AddUnitBoxToWorld(worldA.get(), initialPosition);
auto rigidBodyB = AddUnitBoxToWorld(worldB.get(), initialPosition);
Physics::WorldConfiguration worldConfiguration;
float deltaTime = worldConfiguration.m_fixedTimeStep;
AZ::u32 numSteps = 60;
UpdateWorld(worldA.get(), deltaTime, numSteps);
UpdateWorldSplitSim(worldB.get(), deltaTime, numSteps);
// expect velocity to be -gt and distance fallen to be 1/2gt^2, but allow quite a lot of tolerance
// due to potential differences in back end integration schemes etc.
EXPECT_NEAR(rigidBodyA->GetLinearVelocity().GetZ(), -10.0f, 0.5f);
EXPECT_NEAR(rigidBodyA->GetTransform().GetTranslation().GetZ(), 95.0f, 0.5f);
EXPECT_NEAR(rigidBodyA->GetCenterOfMassWorld().GetZ(), 95.0f, 0.5f);
EXPECT_NEAR(rigidBodyA->GetPosition().GetZ(), 95.0f, 0.5f);
// Verify simulation results are the same
EXPECT_TRUE(rigidBodyA->GetLinearVelocity().IsClose(rigidBodyB->GetLinearVelocity()));
EXPECT_TRUE(rigidBodyA->GetTransform().GetTranslation().IsClose(rigidBodyB->GetTransform().GetTranslation()));
EXPECT_TRUE(rigidBodyA->GetCenterOfMassWorld().IsClose(rigidBodyB->GetCenterOfMassWorld()));
EXPECT_TRUE(rigidBodyA->GetPosition().IsClose(rigidBodyB->GetPosition()));
}
TEST_F(GenericPhysicsInterfaceTest, IncreaseMass_StaggeredTowerOfBoxes_TowerOverbalances)
{
auto world = CreateTestWorld();
// make a tower of boxes which is staggered but should still balance if all the blocks are the same mass
auto boxA = AddStaticUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 0.5f));
auto boxB = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.3f, 0.0f, 1.5f));
auto boxC = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.6f, 0.0f, 2.5f));
// check that the tower balances
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
EXPECT_NEAR(2.5f, boxC->GetPosition().GetZ(), 0.01f);
// increasing the mass of the top block in the tower should overbalance it
boxC->SetMass(5.0f);
EXPECT_NEAR(1.0f, boxB->GetMass(), 0.01f);
EXPECT_NEAR(1.0f, boxB->GetInverseMass(), 0.01f);
EXPECT_NEAR(5.0f, boxC->GetMass(), 0.01f);
EXPECT_NEAR(0.2f, boxC->GetInverseMass(), 0.01f);
boxB->ForceAwake();
boxC->ForceAwake();
UpdateWorld(world.get(), 1.0f / 60.0f, 300);
EXPECT_GT(0.0f, static_cast<float>(boxC->GetPosition().GetZ()));
}
TEST_F(GenericPhysicsInterfaceTest, GetCenterOfMass_FallingBody_CenterOfMassCorrectDuringFall)
{
auto world = CreateTestWorld();
auto boxStatic = AddStaticUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 0.0f));
auto boxDynamic = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 2.0f));
auto tolerance = 1e-3f;
EXPECT_TRUE(boxDynamic->GetCenterOfMassWorld().IsClose(AZ::Vector3(0.0f, 0.0f, 2.0f), tolerance));
EXPECT_TRUE(boxDynamic->GetCenterOfMassLocal().IsClose(AZ::Vector3(0.0f, 0.0f, 0.0f), tolerance));
UpdateWorld(world.get(), 1.0f / 60.0f, 300);
EXPECT_NEAR(static_cast<float>(boxDynamic->GetCenterOfMassWorld().GetZ()), 1.0f, 1e-3f);
EXPECT_TRUE(boxDynamic->GetCenterOfMassLocal().IsClose(AZ::Vector3(0.0f, 0.0f, 0.0f), tolerance));
}
TEST_F(GenericPhysicsInterfaceTest, SetLinearVelocity_DynamicBox_AffectsTrajectory)
{
auto world = CreateTestWorld();
auto boxA = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, -5.0f, 10.0f));
auto boxB = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 5.0f, 10.0f));
boxA->SetLinearVelocity(AZ::Vector3(10.0f, 0.0f, 0.0f));
for (int i = 1; i < 10; i++)
{
float xPreviousA = boxA->GetPosition().GetX();
float xPreviousB = boxB->GetPosition().GetX();
UpdateWorld(world.get(), 1.0f / 60.0f, 10);
EXPECT_GT(static_cast<float>(boxA->GetPosition().GetX()), xPreviousA);
EXPECT_NEAR(boxB->GetPosition().GetX(), xPreviousB, 1e-3f);
}
}
TEST_F(GenericPhysicsInterfaceTest, ApplyLinearImpulse_DynamicBox_AffectsTrajectory)
{
auto world = CreateTestWorld();
auto boxA = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 100.0f));
auto boxB = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 10.0f, 100.0f));
boxA->ApplyLinearImpulse(AZ::Vector3(10.0f, 0.0f, 0.0f));
for (int i = 1; i < 10; i++)
{
float xPreviousA = boxA->GetPosition().GetX();
float xPreviousB = boxB->GetPosition().GetX();
UpdateWorld(world.get(), 1.0f / 60.0f, 10);
EXPECT_GT(static_cast<float>(boxA->GetPosition().GetX()), xPreviousA);
EXPECT_NEAR(boxB->GetPosition().GetX(), xPreviousB, 1e-3f);
}
}
// allow a more generous tolerance on tests involving objects in contact, since the way physics engines normally
// handle multiple contacts between objects can lead to slight imbalances in contact forces
static constexpr float ContactTestTolerance = 0.01f;
TEST_F(GenericPhysicsInterfaceTest, GetAngularVelocity_DynamicCapsuleOnSlope_GainsAngularVelocity)
{
auto world = CreateTestWorld();
AZ::Transform slopeTransform = AZ::Transform::CreateRotationY(0.1f);
auto slope = AddStaticFloorToWorld(world.get(), slopeTransform);
auto capsule = AddCapsuleToWorld(world.get(), slopeTransform.TransformPoint(AZ::Vector3::CreateAxisZ()));
// the capsule should roll down the slope, picking up angular velocity parallel to the Y axis
float angularVelocityMagnitude = capsule->GetAngularVelocity().GetLength();
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
angularVelocityMagnitude = capsule->GetAngularVelocity().GetLength();
for (int i = 0; i < 60; i++)
{
world->Update(1.0f / 60.0f);
auto angularVelocity = capsule->GetAngularVelocity();
EXPECT_TRUE(angularVelocity.IsPerpendicular(AZ::Vector3::CreateAxisX(), ContactTestTolerance));
EXPECT_TRUE(angularVelocity.IsPerpendicular(AZ::Vector3::CreateAxisZ(), ContactTestTolerance));
EXPECT_TRUE(capsule->GetAngularVelocity().GetLength() > angularVelocityMagnitude);
angularVelocityMagnitude = angularVelocity.GetLength();
}
}
TEST_F(GenericPhysicsInterfaceTest, SetAngularVelocity_DynamicCapsule_StartsRolling)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
auto capsule = AddCapsuleToWorld(world.get(), AZ::Vector3::CreateAxisZ());
// capsule should remain stationary
for (int i = 0; i < 60; i++)
{
world->Update(1.0f / 60.0f);
EXPECT_TRUE(capsule->GetPosition().IsClose(AZ::Vector3::CreateAxisZ(), ContactTestTolerance));
EXPECT_TRUE(capsule->GetLinearVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
EXPECT_TRUE(capsule->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
}
// apply an angular velocity and it should start rolling
auto angularVelocity = AZ::Vector3::CreateAxisY(10.0f);
capsule->SetAngularVelocity(angularVelocity);
EXPECT_TRUE(capsule->GetAngularVelocity().IsClose(angularVelocity));
for (int i = 0; i < 60; i++)
{
float xPrevious = capsule->GetPosition().GetX();
world->Update(1.0f / 60.0f);
EXPECT_TRUE(capsule->GetPosition().GetX() > xPrevious);
}
}
TEST_F(GenericPhysicsInterfaceTest, GetLinearVelocityAtWorldPoint_FallingRotatingCapsule_EdgeVelocitiesCorrect)
{
auto world = CreateTestWorld();
// create dynamic capsule and start it falling and rotating
auto capsule = AddCapsuleToWorld(world.get(), AZ::Vector3::CreateAxisZ());
float angularVelocityMagnitude = 1.0f;
capsule->SetAngularVelocity(AZ::Vector3::CreateAxisY(angularVelocityMagnitude));
capsule->SetAngularDamping(0.0f);
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
// check the velocities at some points on the rim of the capsule are as expected
for (int i = 0; i < 60; i++)
{
world->Update(1.0f / 60.0f);
auto position = capsule->GetPosition();
float fallingSpeed = capsule->GetLinearVelocity().GetZ();
float radius = 0.5f;
AZ::Vector3 z = AZ::Vector3::CreateAxisZ(radius);
AZ::Vector3 x = AZ::Vector3::CreateAxisX(radius);
auto v1 = capsule->GetLinearVelocityAtWorldPoint(position - z);
auto v2 = capsule->GetLinearVelocityAtWorldPoint(position - x);
auto v3 = capsule->GetLinearVelocityAtWorldPoint(position + x);
EXPECT_TRUE(v1.IsClose(AZ::Vector3(-radius * angularVelocityMagnitude, 0.0f, fallingSpeed)));
EXPECT_TRUE(v2.IsClose(AZ::Vector3(0.0f, 0.0f, fallingSpeed + radius * angularVelocityMagnitude)));
EXPECT_TRUE(v3.IsClose(AZ::Vector3(0.0f, 0.0f, fallingSpeed - radius * angularVelocityMagnitude)));
}
}
TEST_F(GenericPhysicsInterfaceTest, GetPosition_RollingCapsule_OrientationCorrect)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
// create dynamic capsule and start it rolling
auto capsule = AddCapsuleToWorld(world.get(), AZ::Vector3::CreateAxisZ());
capsule->SetLinearVelocity(AZ::Vector3::CreateAxisX(5.0f));
capsule->SetAngularVelocity(AZ::Vector3::CreateAxisY(10.0f));
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
// check the capsule orientation evolves as expected
for (int i = 0; i < 60; i++)
{
auto orientationPrevious = capsule->GetOrientation();
float xPrevious = capsule->GetPosition().GetX();
world->Update(1.0f / 60.0f);
float angle = 2.0f * (capsule->GetPosition().GetX() - xPrevious);
EXPECT_TRUE(capsule->GetOrientation().IsClose(orientationPrevious * AZ::Quaternion::CreateRotationY(angle)));
}
}
TEST_F(GenericPhysicsInterfaceTest, OffCenterImpulse_DynamicCapsule_StartsRotating)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
AZ::Vector3 posA(0.0f, -5.0f, 1.0f);
AZ::Vector3 posB(0.0f, 0.0f, 1.0f);
AZ::Vector3 posC(0.0f, 5.0f, 1.0f);
auto capsuleA = AddCapsuleToWorld(world.get(), posA);
auto capsuleB = AddCapsuleToWorld(world.get(), posB);
auto capsuleC = AddCapsuleToWorld(world.get(), posC);
// all the capsules should be stationary initially
for (int i = 0; i < 10; i++)
{
world->Update(1.0f / 60.0f);
EXPECT_TRUE(capsuleA->GetPosition().IsClose(posA));
EXPECT_TRUE(capsuleA->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
EXPECT_TRUE(capsuleB->GetPosition().IsClose(posB));
EXPECT_TRUE(capsuleB->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
EXPECT_TRUE(capsuleC->GetPosition().IsClose(posC));
EXPECT_TRUE(capsuleC->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
}
// apply off-center impulses to capsule A and C, and an impulse through the center of B
AZ::Vector3 impulse(0.0f, 0.0f, 10.0f);
capsuleA->ApplyLinearImpulseAtWorldPoint(impulse, posA + AZ::Vector3::CreateAxisX(0.5f));
capsuleB->ApplyLinearImpulseAtWorldPoint(impulse, posB);
capsuleC->ApplyLinearImpulseAtWorldPoint(impulse, posC + AZ::Vector3::CreateAxisX(-0.5f));
// A and C should be rotating in opposite directions, B should still have 0 angular velocity
for (int i = 0; i < 30; i++)
{
world->Update(1.0f / 60.0f);
EXPECT_TRUE(capsuleA->GetAngularVelocity().GetY() < 0.0f);
EXPECT_TRUE(capsuleB->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero(), ContactTestTolerance));
EXPECT_TRUE(capsuleC->GetAngularVelocity().GetY() > 0.0f);
}
}
TEST_F(GenericPhysicsInterfaceTest, ApplyAngularImpulse_DynamicSphere_StartsRotating)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
AZStd::shared_ptr<RigidBody> spheres[3];
for (int i = 0; i < 3; i++)
{
spheres[i] = AddSphereToWorld(world.get(), AZ::Vector3(0.0f, -5.0f + 5.0f * i, 1.0f));
}
// all the spheres should start stationary
UpdateWorld(world.get(), 1.0f / 60.0f, 10);
for (int i = 0; i < 3; i++)
{
EXPECT_TRUE(spheres[i]->GetAngularVelocity().IsClose(AZ::Vector3::CreateZero()));
}
// apply angular impulses and they should gain angular velocity parallel to the impulse direction
AZ::Vector3 impulses[3] = { AZ::Vector3(2.0f, 4.0f, 0.0f), AZ::Vector3(-3.0f, 1.0f, 0.0f),
AZ::Vector3(-2.0f, 3.0f, 0.0f) };
for (int i = 0; i < 3; i++)
{
spheres[i]->ApplyAngularImpulse(impulses[i]);
}
UpdateWorld(world.get(), 1.0f / 60.0f, 10);
for (int i = 0; i < 3; i++)
{
auto angVel = spheres[i]->GetAngularVelocity();
EXPECT_TRUE(angVel.GetProjected(impulses[i]).IsClose(angVel, 0.1f));
}
}
TEST_F(GenericPhysicsInterfaceTest, StartAsleep_FallingBox_DoesNotFall)
{
auto world = CreateTestWorld();
// Box should start asleep
RigidBodyConfiguration config;
config.m_startAsleep = true;
// Create rigid body
AZStd::shared_ptr<RigidBody> box;
SystemRequestBus::BroadcastResult(box, &SystemRequests::CreateRigidBody, config);
world->AddBody(*box);
UpdateWorld(world.get(), 1.0f / 60.0f, 100);
// Check the box is still at 0 and hasn't dropped
EXPECT_NEAR(0.0f, box->GetPosition().GetZ(), 0.01f);
}
TEST_F(GenericPhysicsInterfaceTest, ForceAsleep_FallingBox_BecomesStationary)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
auto box = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 10.0f));
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
EXPECT_TRUE(box->IsAwake());
auto pos = box->GetPosition();
box->ForceAsleep();
EXPECT_FALSE(box->IsAwake());
UpdateWorld(world.get(), 1.0f / 60.0f, 30);
EXPECT_FALSE(box->IsAwake());
// the box should be asleep so it shouldn't have moved
EXPECT_TRUE(box->GetPosition().IsClose(pos));
}
TEST_F(GenericPhysicsInterfaceTest, ForceAwake_SleepingBox_SleepStateCorrect)
{
auto world = CreateTestWorld();
auto floor = AddStaticFloorToWorld(world.get());
auto box = AddUnitBoxToWorld(world.get(), AZ::Vector3(0.0f, 0.0f, 1.0f));
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
EXPECT_FALSE(box->IsAwake());
box->ForceAwake();
EXPECT_TRUE(box->IsAwake());
UpdateWorld(world.get(), 1.0f / 60.0f, 60);
// the box should have gone back to sleep
EXPECT_FALSE(box->IsAwake());
}
TEST_F(GenericPhysicsInterfaceTest, GetAabb_Box_ValidExtents)
{
auto world = CreateTestWorld();
AZ::Vector3 posBox(0.0f, 0.0f, 0.0f);
auto box = AddUnitBoxToWorld(world.get(), posBox);
EXPECT_TRUE(box->GetAabb().GetMin().IsClose(posBox - 0.5f * AZ::Vector3::CreateOne()));
EXPECT_TRUE(box->GetAabb().GetMax().IsClose(posBox + 0.5f * AZ::Vector3::CreateOne()));
// rotate the box and check the bounding box is still correct
AZ::Quaternion quat = AZ::Quaternion::CreateRotationZ(0.25f * AZ::Constants::Pi);
box->SetTransform(AZ::Transform::CreateFromQuaternionAndTranslation(quat, posBox));
AZ::Vector3 boxExtent(sqrtf(0.5f), sqrtf(0.5f), 0.5f);
EXPECT_TRUE(box->GetAabb().GetMin().IsClose(posBox - boxExtent));
EXPECT_TRUE(box->GetAabb().GetMax().IsClose(posBox + boxExtent));
}
TEST_F(GenericPhysicsInterfaceTest, GetAabb_Sphere_ValidExtents)
{
auto world = CreateTestWorld();
AZ::Vector3 posSphere(0.0f, 0.0f, 0.0f);
auto sphere = AddSphereToWorld(world.get(), posSphere);
EXPECT_TRUE(sphere->GetAabb().GetMin().IsClose(posSphere - 0.5f * AZ::Vector3::CreateOne()));
EXPECT_TRUE(sphere->GetAabb().GetMax().IsClose(posSphere + 0.5f * AZ::Vector3::CreateOne()));
// rotate the sphere and check the bounding box is still correct
AZ::Quaternion quat = AZ::Quaternion::CreateRotationZ(0.25f * AZ::Constants::Pi);
sphere->SetTransform(AZ::Transform::CreateFromQuaternionAndTranslation(quat, posSphere));
EXPECT_TRUE(sphere->GetAabb().GetMin().IsClose(posSphere - 0.5f * AZ::Vector3::CreateOne()));
EXPECT_TRUE(sphere->GetAabb().GetMax().IsClose(posSphere + 0.5f * AZ::Vector3::CreateOne()));
}
TEST_F(GenericPhysicsInterfaceTest, GetAabb_Capsule_ValidExtents)
{
auto world = CreateTestWorld();
AZ::Vector3 posCapsule(0.0f, 0.0f, 0.0f);
auto capsule = AddCapsuleToWorld(world.get(), posCapsule);
EXPECT_TRUE(capsule->GetAabb().GetMin().IsClose(posCapsule - AZ::Vector3(0.5f, 1.0f, 0.5f)));
EXPECT_TRUE(capsule->GetAabb().GetMax().IsClose(posCapsule + AZ::Vector3(0.5f, 1.0f, 0.5f)));
// rotate the bodies and check the bounding boxes are still correct
AZ::Quaternion quat = AZ::Quaternion::CreateRotationZ(0.25f * AZ::Constants::Pi);
capsule->SetTransform(AZ::Transform::CreateFromQuaternionAndTranslation(quat, posCapsule));
AZ::Vector3 capsuleExtent(0.5f + sqrt(0.125f), 0.5f + sqrt(0.125f), 0.5f);
EXPECT_TRUE(capsule->GetAabb().GetMin().IsClose(posCapsule - capsuleExtent));
EXPECT_TRUE(capsule->GetAabb().GetMax().IsClose(posCapsule + capsuleExtent));
}
TEST_F(GenericPhysicsInterfaceTest, Materials_BoxesSharingDefaultMaterial_JumpingSameHeight)
{
auto world = CreateTestWorld();
auto boxA = AddStaticFloorToWorld(world.get());
auto boxB = AddUnitBoxToWorld(world.get(), AZ::Vector3(1.0f, 0.0f, 10.0f));
auto boxC = AddUnitBoxToWorld(world.get(), AZ::Vector3(-1.0f, 0.0f, 10.0f));
auto material = boxC->GetShape(0)->GetMaterial();
material->SetRestitution(1.0f);
UpdateWorld(world.get(), 1.0f / 60.0f, 150);
// boxB and boxC should have the same material (default)
// so they should both bounce high
EXPECT_NEAR(boxB->GetPosition().GetZ(), boxC->GetPosition().GetZ(), 0.5f);
}
TEST_F(GenericPhysicsInterfaceTest, World_GetNativePtrByWorldName_ReturnsNativePtr)
{
void* validNativePtr = nullptr;
WorldRequestBus::EventResult(validNativePtr, Physics::DefaultPhysicsWorldId, &WorldRequests::GetNativePointer);
EXPECT_TRUE(validNativePtr != nullptr);
void* invalidNativePtr = nullptr;
WorldRequestBus::EventResult(invalidNativePtr, AZ_CRC("Bad World Name"), &WorldRequests::GetNativePointer);
EXPECT_TRUE(invalidNativePtr == nullptr);
}
TEST_F(GenericPhysicsInterfaceTest, Collider_ColliderTag_IsSetFromConfiguration)
{
const AZStd::string colliderTagName = "ColliderTestTag";
Physics::ColliderConfiguration colliderConfig;
colliderConfig.m_tag = colliderTagName;
Physics::SphereShapeConfiguration shapeConfig;
AZStd::shared_ptr<Physics::Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfig);
EXPECT_EQ(shape->GetTag(), AZ::Crc32(colliderTagName));
}
} // namespace Physics
+168
View File
@@ -0,0 +1,168 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzTest/AzTest.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Math/Vector3.h>
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/RigidBody.h>
#include <AzFramework/Physics/SystemBus.h>
#include <AzFramework/Physics/World.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
#include <AzFramework/Physics/Collision/CollisionLayers.h>
#include <AzFramework/Physics/Configuration/SystemConfiguration.h>
namespace Physics
{
class GenericPhysicsFixture
: protected Physics::DefaultWorldBus::Handler
{
public:
// Helper functions for setting up test worlds using API only
// These can be implemented here as they should not require any gem specific functions
AZStd::shared_ptr<Physics::World> CreateTestWorld();
void DestroyTestScene();
void SetUpInternal();
void TearDownInternal();
// Helper functions for setting up entities used in tests
// These need to be implemented in the gem as they may require gem specific components etc.
AZ::Entity* AddSphereEntity(const AZ::Vector3& position, const float radius, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
AZ::Entity* AddBoxEntity(const AZ::Vector3& position, const AZ::Vector3& dimensions, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
AZ::Entity* AddCapsuleEntity(const AZ::Vector3& position, const float height, const float radius, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
AZ::Entity* AddStaticSphereEntity(const AZ::Vector3& position, const float radius, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
AZ::Entity* AddStaticBoxEntity(const AZ::Vector3& position, const AZ::Vector3& dimensions, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
AZ::Entity* AddStaticCapsuleEntity(const AZ::Vector3& position, const float height, const float radius, const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default);
// Helper function for creating multishape entity
struct MultiShapeConfig
{
AZ::Vector3 m_position; // Position of entity
AZ::Vector3 m_rotation = AZ::Vector3(0.f, 0.f, 0.f); // Euler rotation of entity in radians
AzPhysics::CollisionLayer m_layer = AzPhysics::CollisionLayer::Default; // Collision layer
struct ShapeList
{
struct ShapeData
{
struct Box
{
AZ::Vector3 m_extent;
};
struct Sphere
{
float m_radius;
};
struct Capsule
{
float m_height;
float m_radius;
};
AZ::Vector3 m_offset;
AZStd::variant<AZStd::monostate, Box, Sphere, Capsule> m_data;
};
void AddBox(AZ::Vector3 extent, AZ::Vector3 offset)
{
ShapeData box;
ShapeData::Box boxData{ extent };
box.m_data = boxData;
box.m_offset = offset;
m_shapesData.push_back(box);
}
void AddSphere(float radius, AZ::Vector3 offset)
{
ShapeData sphere;
ShapeData::Sphere sphereData{ radius };
sphere.m_data = sphereData;
sphere.m_offset = offset;
m_shapesData.push_back(sphere);
}
void AddCapsule(float height, float radius, AZ::Vector3 offset)
{
ShapeData capsule;
ShapeData::Capsule capsuleData{ height, radius };
capsule.m_data = capsuleData;
capsule.m_offset = offset;
m_shapesData.push_back(capsule);
}
AZStd::vector<ShapeData> m_shapesData;
};
ShapeList m_shapes;
};
AZStd::unique_ptr<AZ::Entity> AddMultiShapeEntity(const MultiShapeConfig& config);
protected:
// DefaultWorldBus
AZStd::shared_ptr<World> GetDefaultWorld();
AzPhysics::Scene* m_defaultScene = nullptr;
AzPhysics::SceneHandle m_testSceneHandle = AzPhysics::InvalidSceneHandle;
};
/// Class to contain tests which any implementation of the AzFramework::Physics API should pass
/// Each gem which implements the common physics API can run the generic API tests by:
/// - including this header file and the appropriate .inl files.
/// - deriving from AZ::Test::ITestEnvironment and extending the environment functions to set up the gem system component etc.
/// - implementing the helper functions required for the tests using gem specific components etc.
/// - adding a AZ_UNIT_TEST_HOOK with the derived environment class
class GenericPhysicsInterfaceTest
: protected GenericPhysicsFixture
, public testing::Test
{
public:
void SetUp() override
{
SetUpInternal();
}
void TearDown() override
{
TearDownInternal();
DestroyTestScene(); //cleanup any physics scene if created.
}
};
class PhysicsComponentBusTest
: public GenericPhysicsInterfaceTest
{
void SetUp() override
{
GenericPhysicsInterfaceTest::SetUp();
}
void TearDown() override
{
GenericPhysicsInterfaceTest::TearDown();
}
};
// helper functions
AZStd::shared_ptr<RigidBodyStatic> AddStaticFloorToWorld(World* world, const AZ::Transform& transform = AZ::Transform::CreateIdentity());
AZStd::shared_ptr<RigidBodyStatic> AddStaticUnitBoxToWorld(World* world, const AZ::Vector3& position);
AZStd::shared_ptr<RigidBody> AddUnitBoxToWorld(World* world, const AZ::Vector3& position);
AZStd::shared_ptr<RigidBody> AddSphereToWorld(World* world, const AZ::Vector3& position);
AZStd::shared_ptr<RigidBody> AddCapsuleToWorld(World* world, const AZ::Vector3& position);
void UpdateScene(AzPhysics::Scene* scene, float timeStep, AZ::u32 numSteps);
float GetPositionElement(AZ::Entity* entity, int element);
} // namespace Physics
@@ -0,0 +1,149 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "PhysicsTests.h"
#include <AzFramework/Physics/World.h>
#include <AzFramework/Physics/RigidBody.h>
#include <AzFramework/Physics/Shape.h>
#include <AzFramework/Physics/SystemBus.h>
#include <AzCore/Component/TransformBus.h>
namespace Physics
{
// helper functions
AZStd::shared_ptr<World> GenericPhysicsFixture::CreateTestWorld()
{
if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
{
AzPhysics::SceneConfiguration sceneConfiguration = physicsSystem->GetDefaultSceneConfiguration();
sceneConfiguration.m_legacyId = AZ_CRC_CE("testWorld");
sceneConfiguration.m_legacyConfiguration.m_gravity = AZ::Vector3(0.0f, 0.0f, -10.0f);
m_testSceneHandle = physicsSystem->AddScene(sceneConfiguration);
m_defaultScene = physicsSystem->GetScene(m_testSceneHandle);
return m_defaultScene->GetLegacyWorld();
}
return nullptr;
}
void GenericPhysicsFixture::DestroyTestScene()
{
//clean up the test scene
if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
{
physicsSystem->RemoveScene(m_testSceneHandle);
}
}
AZStd::shared_ptr<RigidBodyStatic> AddStaticFloorToWorld(World* world, const AZ::Transform& transform)
{
WorldBodyConfiguration rigidBodySettings;
AZStd::shared_ptr<RigidBodyStatic> floor;
SystemRequestBus::BroadcastResult(floor, &SystemRequests::CreateStaticRigidBody, rigidBodySettings);
Physics::ColliderConfiguration colliderConfig;
Physics::BoxShapeConfiguration shapeConfiguration(AZ::Vector3(20.0f, 20.0f, 1.0f));
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
floor->AddShape(shape);
world->AddBody(*floor);
floor->SetTransform(transform);
return floor;
}
AZStd::shared_ptr<RigidBodyStatic> AddStaticUnitBoxToWorld(World* world, const AZ::Vector3& position)
{
WorldBodyConfiguration rigidBodySettings;
rigidBodySettings.m_position = position;
AZStd::shared_ptr<RigidBodyStatic> box;
SystemRequestBus::BroadcastResult(box, &SystemRequests::CreateStaticRigidBody, rigidBodySettings);
Physics::ColliderConfiguration colliderConfig;
Physics::BoxShapeConfiguration shapeConfiguration;
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
box->AddShape(shape);
world->AddBody(*box);
return box;
}
AZStd::shared_ptr<RigidBody> AddUnitBoxToWorld(World* world, const AZ::Vector3& position)
{
RigidBodyConfiguration rigidBodySettings;
rigidBodySettings.m_linearDamping = 0.0f;
AZStd::shared_ptr<RigidBody> box;
SystemRequestBus::BroadcastResult(box, &SystemRequests::CreateRigidBody, rigidBodySettings);
Physics::ColliderConfiguration colliderConfig;
Physics::BoxShapeConfiguration shapeConfiguration;
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
box->AddShape(shape);
world->AddBody(*box.get());
box->SetTransform(AZ::Transform::CreateTranslation(position));
return box;
}
AZStd::shared_ptr<RigidBody> AddSphereToWorld(World* world, const AZ::Vector3& position)
{
RigidBodyConfiguration rigidBodySettings;
rigidBodySettings.m_linearDamping = 0.0f;
AZStd::shared_ptr<RigidBody> sphere;
SystemRequestBus::BroadcastResult(sphere, &SystemRequests::CreateRigidBody, rigidBodySettings);
Physics::ColliderConfiguration colliderConfig;
Physics::SphereShapeConfiguration shapeConfiguration;
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfiguration);
sphere->AddShape(shape);
world->AddBody(*sphere.get());
sphere->SetTransform(AZ::Transform::CreateTranslation(position));
return sphere;
}
AZStd::shared_ptr<RigidBody> AddCapsuleToWorld(World* world, const AZ::Vector3& position)
{
RigidBodyConfiguration rigidBodySettings;
AZStd::shared_ptr<RigidBody> capsule = nullptr;
SystemRequestBus::BroadcastResult(capsule, &SystemRequests::CreateRigidBody, rigidBodySettings);
Physics::ColliderConfiguration colliderConfig;
colliderConfig.m_rotation = AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi);
Physics::CapsuleShapeConfiguration shapeConfig(2.0f, 0.5f);
AZStd::shared_ptr<Shape> shape;
SystemRequestBus::BroadcastResult(shape, &SystemRequests::CreateShape, colliderConfig, shapeConfig);
capsule->AddShape(shape);
world->AddBody(*capsule.get());
capsule->SetTransform(AZ::Transform::CreateTranslation(position));
return capsule;
}
void UpdateScene(AzPhysics::Scene* scene, float timeStep, AZ::u32 numSteps)
{
for (AZ::u32 i = 0; i < numSteps; i++)
{
scene->StartSimulation(timeStep);
scene->FinishSimulation();
}
}
float GetPositionElement(AZ::Entity* entity, int element)
{
AZ::Transform transform = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(transform, entity->GetId(), &AZ::TransformInterface::GetWorldTM);
return transform.GetTranslation().GetElement(element);
}
} // namespace Physics
@@ -0,0 +1,17 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
PhysicsTests.h
PhysicsTests.inl
PhysicsGenericInterfaceTests.inl
PhysicsComponentBusTests.inl
)
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN 0
#define AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST 0
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFrameworkTests_Traits_Android.h>
@@ -0,0 +1,15 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
AzFrameworkTests_Traits_Platform.h
AzFrameworkTests_Traits_Android.h
)
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN 0
#define AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST 1
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFrameworkTests_Traits_Linux.h>
@@ -0,0 +1,15 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
AzFrameworkTests_Traits_Platform.h
AzFrameworkTests_Traits_Linux.h
)
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN 0
#define AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST 1
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFrameworkTests_Traits_Mac.h>
@@ -0,0 +1,15 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
AzFrameworkTests_Traits_Platform.h
AzFrameworkTests_Traits_Mac.h
)
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFrameworkTests_Traits_Windows.h>
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN 1
#define AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST 1
@@ -0,0 +1,15 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
AzFrameworkTests_Traits_Platform.h
AzFrameworkTests_Traits_Windows.h
)
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFrameworkTests_Traits_iOS.h>
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN 0
#define AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST 0
@@ -0,0 +1,15 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
AzFrameworkTests_Traits_Platform.h
AzFrameworkTests_Traits_iOS.h
)
+143
View File
@@ -0,0 +1,143 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Platform/PlatformDefaults.h>
class PlatformHelperTest
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
};
TEST_F(PlatformHelperTest, SinglePlatformFlags_PlatformId_Valid)
{
AzFramework::PlatformFlags platform = AzFramework::PlatformFlags::Platform_PC;
auto platforms = AzFramework::PlatformHelper::GetPlatforms(platform);
EXPECT_EQ(platforms.size(), 1);
EXPECT_EQ(platforms[0], "pc");
}
TEST_F(PlatformHelperTest, MultiplePlatformFlags_PlatformId_Valid)
{
AzFramework::PlatformFlags platformFlags = AzFramework::PlatformFlags::Platform_PC | AzFramework::PlatformFlags::Platform_ES3;
auto platforms = AzFramework::PlatformHelper::GetPlatforms(platformFlags);
EXPECT_EQ(platforms.size(), 2);
EXPECT_EQ(platforms[0], "pc");
EXPECT_EQ(platforms[1], "es3");
}
TEST_F(PlatformHelperTest, SpecialAllFlag_PlatformId_Valid)
{
AzFramework::PlatformFlags platformFlags = AzFramework::PlatformFlags::Platform_ALL;
auto platforms = AzFramework::PlatformHelper::GetPlatformsInterpreted(platformFlags);
EXPECT_EQ(platforms.size(), AzFramework::NumPlatforms);
EXPECT_THAT(platforms, testing::UnorderedElementsAre("pc", "es3", "ios", "osx_gl", "xenia", "provo", "salem", "jasper", "server"));
}
TEST_F(PlatformHelperTest, SpecialAllClientFlag_PlatformId_Valid)
{
AzFramework::PlatformFlags platformFlags = AzFramework::PlatformFlags::Platform_ALL_CLIENT;
auto platforms = AzFramework::PlatformHelper::GetPlatformsInterpreted(platformFlags);
EXPECT_EQ(platforms.size(), AzFramework::NumClientPlatforms);
EXPECT_THAT(platforms, testing::UnorderedElementsAre("pc", "es3", "ios", "osx_gl", "xenia", "provo", "salem", "jasper"));
}
TEST_F(PlatformHelperTest, InvalidPlatformFlags_PlatformId_Empty)
{
AZ::u32 platformFlags = 1 << 20; // Currently we do not have this bit set indicating a valid platform.
auto platforms = AzFramework::PlatformHelper::GetPlatforms(static_cast<AzFramework::PlatformFlags>(platformFlags));
EXPECT_EQ(platforms.size(), 0);
}
TEST_F(PlatformHelperTest, GetPlatformName_Valid_OK)
{
AZStd::string platformName = AzFramework::PlatformHelper::GetPlatformName(AzFramework::PlatformId::PC);
EXPECT_EQ(platformName, AzFramework::PlatformPC);
}
TEST_F(PlatformHelperTest, GetPlatformName_Invalid_OK)
{
AZStd::string platformName = AzFramework::PlatformHelper::GetPlatformName(static_cast<AzFramework::PlatformId>(-1));
EXPECT_TRUE(platformName.compare("invalid") == 0);
}
TEST_F(PlatformHelperTest, GetPlatformIndexByName_Valid_OK)
{
AZ::u32 platformIndex = AzFramework::PlatformHelper::GetPlatformIndexFromName(AzFramework::PlatformPC);
EXPECT_EQ(platformIndex, AzFramework::PlatformId::PC);
}
TEST_F(PlatformHelperTest, GetPlatformIndexByName_Invalid_OK)
{
AZ::u32 platformIndex = AzFramework::PlatformHelper::GetPlatformIndexFromName("dummy");
EXPECT_EQ(platformIndex, AzFramework::PlatformId::Invalid);
}
TEST_F(PlatformHelperTest, GetServerPlatformIndexByName_Valid_OK)
{
AZ::u32 platformIndex = AzFramework::PlatformHelper::GetPlatformIndexFromName(AzFramework::PlatformServer);
EXPECT_EQ(platformIndex, AzFramework::PlatformId::SERVER);
}
TEST_F(PlatformHelperTest, GetPlatformIdByName_Valid_OK)
{
AzFramework::PlatformId platformId = AzFramework::PlatformHelper::GetPlatformIdFromName(AzFramework::PlatformPC);
EXPECT_EQ(platformId, AzFramework::PlatformId::PC);
}
TEST_F(PlatformHelperTest, GetPlatformIdName_Invalid_OK)
{
AzFramework::PlatformId platformId = AzFramework::PlatformHelper::GetPlatformIdFromName("dummy");
EXPECT_EQ(platformId, AzFramework::PlatformId::Invalid);
}
TEST_F(PlatformHelperTest, AppendPlatformCodeNames_ByValidName_OK)
{
AZStd::fixed_vector<AZStd::string_view, AzFramework::MaxPlatformCodeNames> platformCodes;
AzFramework::PlatformHelper::AppendPlatformCodeNames(platformCodes, AzFramework::PlatformPC);
ASSERT_EQ(2, platformCodes.size());
AZStd::string windows = platformCodes[0];
AZStd::string linux = platformCodes[1];
EXPECT_STRCASEEQ(AzFramework::PlatformCodeNameWindows, windows.c_str());
EXPECT_STRCASEEQ(AzFramework::PlatformCodeNameLinux, linux.c_str());
}
TEST_F(PlatformHelperTest, AppendPlatformCodeNames_ByInvalidName_OK)
{
AZStd::fixed_vector<AZStd::string_view, AzFramework::MaxPlatformCodeNames> platformCodes;
AZ_TEST_START_TRACE_SUPPRESSION;
AzFramework::PlatformHelper::AppendPlatformCodeNames(platformCodes, "dummy");
AZ_TEST_STOP_TRACE_SUPPRESSION(2);
EXPECT_TRUE(platformCodes.empty());
}
TEST_F(PlatformHelperTest, AppendPlatformCodeNames_ByValidId_OK)
{
AZStd::fixed_vector<AZStd::string_view, AzFramework::MaxPlatformCodeNames> platformCodes;
AzFramework::PlatformHelper::AppendPlatformCodeNames(platformCodes, AzFramework::PlatformId::PC);
ASSERT_EQ(2, platformCodes.size());
AZStd::string windows = platformCodes[0];
AZStd::string linux = platformCodes[1];
EXPECT_STRCASEEQ(AzFramework::PlatformCodeNameWindows, windows.c_str());
EXPECT_STRCASEEQ(AzFramework::PlatformCodeNameLinux, linux.c_str());
}
TEST_F(PlatformHelperTest, AppendPlatformCodeNames_ByInvalidId_OK)
{
AZStd::fixed_vector<AZStd::string_view, AzFramework::MaxPlatformCodeNames> platformCodes;
AZ_TEST_START_TRACE_SUPPRESSION;
AzFramework::PlatformHelper::AppendPlatformCodeNames(platformCodes, AzFramework::PlatformId::Invalid);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
EXPECT_TRUE(platformCodes.empty());
}
@@ -0,0 +1,50 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Memory/AllocatorManager.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <iostream>
void OutputArgs(const AzFramework::CommandLine& commandLine)
{
const AzFramework::CommandLine::ParamMap& switchList = commandLine.GetSwitchList();
std::cout << "Switch List:" << std::endl;
for (auto& switchPair : switchList)
{
// We strip white space from all of our switch names, so "flush" names will start arguments
std::cout << switchPair.first.c_str() << std::endl;
for (auto& switchValue : switchPair.second)
{
// Auto space every switch value by one space
std::cout << " " << switchValue.c_str() << std::endl;
}
}
std::cout << "End Switch List:" << std::endl;
}
int main(int argc, char** argv)
{
AZ::AllocatorInstance<AZ::OSAllocator>::Create();
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
{
AzFramework::CommandLine commandLine;
commandLine.Parse(argc, argv);
OutputArgs(commandLine);
}
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
AZ::AllocatorInstance<AZ::OSAllocator>::Destroy();
return 0;
}
@@ -0,0 +1,254 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <AzToolsFramework/Process/ProcessWatcher.h>
#include <AzToolsFramework/Process/ProcessCommunicator.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzFramework/StringFunc/StringFunc.h>
using namespace AzFramework;
namespace UnitTest
{
class ProcessLaunchParseTests
: public AllocatorsFixture
{
public:
using ParsedArgMap = AZStd::unordered_map<AZStd::string, AZStd::vector<AZStd::string>>;
static ParsedArgMap ParseParameters(const AZStd::string& processOutput);
};
ProcessLaunchParseTests::ParsedArgMap ProcessLaunchParseTests::ParseParameters(const AZStd::string& processOutput)
{
ParsedArgMap parsedArgs;
AZStd::string currentSwitch;
bool inSwitches{ false };
AZStd::vector<AZStd::string> parsedLines;
AzFramework::StringFunc::Tokenize(processOutput.c_str(), parsedLines, "\r\n");
for (const AZStd::string& thisLine : parsedLines)
{
if (thisLine == "Switch List:")
{
inSwitches = true;
continue;
}
else if (thisLine == "End Switch List:")
{
inSwitches = false;
continue;
}
if (thisLine.empty())
{
continue;
}
if(inSwitches)
{
if (thisLine[0] != ' ')
{
currentSwitch = thisLine;
}
else
{
parsedArgs[currentSwitch].push_back(thisLine.substr(1));
}
}
}
return parsedArgs;
}
TEST_F(ProcessLaunchParseTests, ProcessLauncher_LaunchBasicProcess_Success)
{
AzToolsFramework::ProcessOutput processOutput;
AzToolsFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest";
processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath();
processLaunchInfo.m_showWindow = false;
bool launchReturn = AzToolsFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzToolsFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput);
EXPECT_EQ(launchReturn, true);
EXPECT_EQ(processOutput.outputResult.empty(), false);
}
TEST_F(ProcessLaunchParseTests, ProcessLauncher_BasicParameter_Success)
{
ProcessLaunchParseTests::ParsedArgMap argMap;
AzToolsFramework::ProcessOutput processOutput;
AzToolsFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest -param1 param1val -param2=param2val";
processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath();
processLaunchInfo.m_showWindow = false;
bool launchReturn = AzToolsFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzToolsFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput);
EXPECT_EQ(launchReturn, true);
argMap = ProcessLaunchParseTests::ParseParameters(processOutput.outputResult);
auto param1itr = argMap.find("param1");
EXPECT_NE(param1itr, argMap.end());
AZStd::vector<AZStd::string> param1{ param1itr->second };
EXPECT_EQ(param1.size(), 1);
EXPECT_EQ(param1[0], "param1val");
auto param2itr = argMap.find("param2");
EXPECT_NE(param2itr, argMap.end());
AZStd::vector<AZStd::string> param2{ param2itr->second };
EXPECT_EQ(param2.size(), 1);
EXPECT_EQ(param2[0], "param2val");
}
#if AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS
TEST_F(ProcessLaunchParseTests, DISABLED_ProcessLauncher_StringsWithCommas_Success)
#else
TEST_F(ProcessLaunchParseTests, ProcessLauncher_StringsWithCommas_Success)
#endif // AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS
{
ProcessLaunchParseTests::ParsedArgMap argMap;
AzToolsFramework::ProcessOutput processOutput;
AzToolsFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER R"(ProcessLaunchTest -param1 "\"param,1val\"" -param2="\"param2v,al\"")";
processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath();
processLaunchInfo.m_showWindow = false;
bool launchReturn = AzToolsFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzToolsFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput);
EXPECT_EQ(launchReturn, true);
argMap = ProcessLaunchParseTests::ParseParameters(processOutput.outputResult);
auto param1itr = argMap.find("param1");
EXPECT_NE(param1itr, argMap.end());
AZStd::vector<AZStd::string> param1{ param1itr->second };
EXPECT_EQ(param1.size(), 1);
EXPECT_EQ(param1[0], "param,1val");
auto param2itr = argMap.find("param2");
EXPECT_NE(param2itr, argMap.end());
AZStd::vector<AZStd::string> param2{ param2itr->second };
EXPECT_EQ(param2.size(), 1);
EXPECT_EQ(param2[0], "param2v,al");
}
#if AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS
TEST_F(ProcessLaunchParseTests, DISABLED_ProcessLauncher_StringsWithSpaces_Success)
#else
TEST_F(ProcessLaunchParseTests, ProcessLauncher_StringsWithSpaces_Success)
#endif // AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS
{
ProcessLaunchParseTests::ParsedArgMap argMap;
AzToolsFramework::ProcessOutput processOutput;
AzToolsFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER R"(ProcessLaunchTest -param1 "\"param 1val\"" -param2="\"param2v al\"")";
processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath();
processLaunchInfo.m_showWindow = false;
bool launchReturn = AzToolsFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzToolsFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput);
EXPECT_EQ(launchReturn, true);
argMap = ProcessLaunchParseTests::ParseParameters(processOutput.outputResult);
auto param1itr = argMap.find("param1");
EXPECT_NE(param1itr, argMap.end());
AZStd::vector<AZStd::string> param1{ param1itr->second };
EXPECT_EQ(param1.size(), 1);
EXPECT_EQ(param1[0], "param 1val");
auto param2itr = argMap.find("param2");
EXPECT_NE(param2itr, argMap.end());
AZStd::vector<AZStd::string> param2{ param2itr->second };
EXPECT_EQ(param2.size(), 1);
EXPECT_EQ(param2[0], "param2v al");
}
#if AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS
TEST_F(ProcessLaunchParseTests, DISABLED_ProcessLauncher_StringsWithSpacesAndComma_Success)
#else
TEST_F(ProcessLaunchParseTests, ProcessLauncher_StringsWithSpacesAndComma_Success)
#endif // AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS
{
ProcessLaunchParseTests::ParsedArgMap argMap;
AzToolsFramework::ProcessOutput processOutput;
AzToolsFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER R"(ProcessLaunchTest -param1 "\"par,am 1val\"" -param2="\"param,2v al\"")";
processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath();
processLaunchInfo.m_showWindow = false;
bool launchReturn = AzToolsFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzToolsFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput);
EXPECT_EQ(launchReturn, true);
argMap = ProcessLaunchParseTests::ParseParameters(processOutput.outputResult);
auto param1itr = argMap.find("param1");
EXPECT_NE(param1itr, argMap.end());
AZStd::vector<AZStd::string> param1{ param1itr->second };
EXPECT_EQ(param1.size(), 1);
EXPECT_EQ(param1[0], "par,am 1val");
auto param2itr = argMap.find("param2");
EXPECT_NE(param2itr, argMap.end());
AZStd::vector<AZStd::string> param2{ param2itr->second };
EXPECT_EQ(param2.size(), 1);
EXPECT_EQ(param2[0], "param,2v al");
}
TEST_F(ProcessLaunchParseTests, ProcessLauncher_CommaStringNoQuotes_Success)
{
ProcessLaunchParseTests::ParsedArgMap argMap;
AzToolsFramework::ProcessOutput processOutput;
AzToolsFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest -param1 param,1val -param2=param2v,al";
processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath();
processLaunchInfo.m_showWindow = false;
bool launchReturn = AzToolsFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzToolsFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput);
EXPECT_EQ(launchReturn, true);
argMap = ProcessLaunchParseTests::ParseParameters(processOutput.outputResult);
auto param1itr = argMap.find("param1");
EXPECT_NE(param1itr, argMap.end());
AZStd::vector<AZStd::string> param1{ param1itr->second };
EXPECT_EQ(param1.size(), 2);
EXPECT_EQ(param1[0], "param");
EXPECT_EQ(param1[1], "1val");
auto param2itr = argMap.find("param2");
EXPECT_NE(param2itr, argMap.end());
AZStd::vector<AZStd::string> param2{ param2itr->second };
EXPECT_EQ(param2.size(), 2);
EXPECT_EQ(param2[0], "param2v");
EXPECT_EQ(param2[1], "al");
}
} // namespace UnitTest
@@ -0,0 +1,133 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#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()));
}
}
}
+354
View File
@@ -0,0 +1,354 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/UnitTest/TestTypes.h>
AZ_PUSH_DISABLE_WARNING(, "-Wdelete-non-virtual-dtor")
#include <FrameworkApplicationFixture.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/Component.h>
#include <AzFramework/Scene/SceneSystemComponent.h>
#include <AzFramework/Scene/Scene.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzCore/Jobs/JobManagerComponent.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/Slice/SliceAssetHandler.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/Slice/SliceSystemComponent.h>
using namespace AzFramework;
// Test component that allows code to be injected into activate / deactivate for testing.
namespace SceneUnitTest
{
class TestComponent;
class TestComponentConfig : public AZ::ComponentConfig
{
public:
AZ_RTTI(TestComponentConfig, "{DCD12D72-3BFE-43A9-9679-66B745814CAF}", ComponentConfig);
typedef void(*ActivateFunction)(TestComponent* component);
ActivateFunction m_activateFunction = nullptr;
typedef void(*DeactivateFunction)(TestComponent* component);
DeactivateFunction m_deactivateFunction = nullptr;
};
static const AZ::TypeId TestComponentTypeId = "{DC096267-4815-47D1-BA23-A1CDF0D72D9D}";
class TestComponent : public AZ::Component
{
public:
AZ_COMPONENT(TestComponent, TestComponentTypeId);
static void Reflect(AZ::ReflectContext*) {};
void Activate() override
{
if (m_config.m_activateFunction)
{
m_config.m_activateFunction(this);
}
}
void Deactivate() override
{
if (m_config.m_deactivateFunction)
{
m_config.m_deactivateFunction(this);
}
}
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override
{
if (auto config = azrtti_cast<const TestComponentConfig*>(baseConfig))
{
m_config = *config;
return true;
}
return false;
}
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override
{
if (auto outConfig = azrtti_cast<TestComponentConfig*>(outBaseConfig))
{
*outConfig = m_config;
return true;
}
return false;
}
TestComponentConfig m_config;
};
// Fixture that creates a bare-bones app with only the system components necesary.
class SceneTest
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
void SetUp() override
{
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
m_prevFileIO = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FileIOBase::SetInstance(&m_fileIO);
m_app.RegisterComponentDescriptor(SceneSystemComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(AZ::SliceSystemComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(AZ::AssetManagerComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(AZ::JobManagerComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(AZ::StreamerComponent::CreateDescriptor());
AZ::ComponentApplication::Descriptor desc;
desc.m_enableDrilling = false; // the unit test framework already adds a driller
m_systemEntity = m_app.Create(desc);
m_systemEntity->Init();
m_systemEntity->CreateComponent<SceneSystemComponent>();
// Asset / slice system components needed by entity contexts
m_systemEntity->CreateComponent<AZ::SliceSystemComponent>();
m_systemEntity->CreateComponent<AZ::AssetManagerComponent>();
m_systemEntity->CreateComponent<AZ::JobManagerComponent>();
m_systemEntity->CreateComponent<AZ::StreamerComponent>();
m_systemEntity->Activate();
}
void TearDown() override
{
m_app.Destroy();
AZ::IO::FileIOBase::SetInstance(m_prevFileIO);
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
}
AZ::IO::LocalFileIO m_fileIO;
AZ::IO::FileIOBase* m_prevFileIO;
AZ::ComponentApplication m_app;
AZ::Entity* m_systemEntity = nullptr;
};
TEST_F(SceneTest, CreateScene)
{
Scene* scene = nullptr;
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
// A scene should be able to be created with a given name.
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, "TestScene");
EXPECT_TRUE(createSceneOutcome.IsSuccess()) << "Unable to create a scene.";
// The scene pointer returned should be valid
scene = createSceneOutcome.GetValue();
EXPECT_TRUE(scene != nullptr) << "Scene creation reported success, but no scene actually was actually returned.";
// Attempting to create another scene with the same name should fail.
createSceneOutcome = AZ::Failure<AZStd::string>("");
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, "TestScene");
EXPECT_TRUE(!createSceneOutcome.IsSuccess()) << "Should not be able to create two scenes with the same name.";
}
TEST_F(SceneTest, GetScene)
{
Scene* createdScene = nullptr;
Scene* retrievedScene = nullptr;
Scene* nullScene = nullptr;
const static AZStd::string_view s_sceneName = "TestScene";
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, s_sceneName);
createdScene = createSceneOutcome.GetValue();
// Should be able to get a scene by name, and it should match the scene that was created.
AzFramework::SceneSystemRequestBus::BroadcastResult(retrievedScene, &AzFramework::SceneSystemRequestBus::Events::GetScene, s_sceneName);
EXPECT_TRUE(retrievedScene != nullptr) << "Attempting to get scene by name resulted in nullptr.";
EXPECT_TRUE(retrievedScene == createdScene) << "Retrieved scene does not match created scene.";
// An invalid name should return a null scene.
AzFramework::SceneSystemRequestBus::BroadcastResult(nullScene, &AzFramework::SceneSystemRequestBus::Events::GetScene, "non-existant scene");
EXPECT_TRUE(nullScene == nullptr) << "Should not be able to retrieve a scene that wasn't created.";
}
TEST_F(SceneTest, RemoveScene)
{
Scene* createdScene = nullptr;
const static AZStd::string_view s_sceneName = "TestScene";
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, s_sceneName);
createdScene = createSceneOutcome.GetValue();
bool success = false;
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::RemoveScene, s_sceneName);
EXPECT_TRUE(success) << "Failed to remove the scene that was just created.";
success = true;
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::RemoveScene, "non-existant scene");
EXPECT_FALSE(success) << "Remove scene returned success for a non-existant scene.";
}
TEST_F(SceneTest, GetAllScenes)
{
constexpr size_t NumScenes = 5;
Scene* scenes[NumScenes] = { nullptr };
for (size_t i = 0; i < NumScenes; ++i)
{
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
AZStd::string sceneName = AZStd::string::format("scene %zu", i);
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, sceneName);
scenes[i] = createSceneOutcome.GetValue();
}
AZStd::vector<Scene*> retrievedScenes;
AzFramework::SceneSystemRequestBus::BroadcastResult(retrievedScenes, &AzFramework::SceneSystemRequestBus::Events::GetAllScenes);
EXPECT_EQ(NumScenes, retrievedScenes.size()) << "GetAllScenes() returned a different number of scenes than those created.";
for (size_t i = 0; i < NumScenes; ++i)
{
EXPECT_EQ(scenes[i], retrievedScenes.at(i)) << "GetAllScenes() returned scenes in a different order than they were created.";
}
}
TEST_F(SceneTest, EntityContextSceneMapping)
{
AZStd::unique_ptr<SliceEntityOwnershipService> m_entityOwnershipService =
AZStd::make_unique<AzFramework::SliceEntityOwnershipService>(AZ::Uuid::CreateNull(), m_app.GetSerializeContext());
// Create the entity context, entity, and component
EntityContext* testEntityContext = new EntityContext(AZ::Uuid::CreateRandom(), AZStd::move(m_entityOwnershipService));
testEntityContext->InitContext();
EntityContextId testEntityContextId = testEntityContext->GetContextId();
AZ::Entity* testEntity = testEntityContext->CreateEntity("TestEntity");
TestComponent* testComponent = testEntity->CreateComponent<TestComponent>();
// Try to activate an entity and get the scene before a scene has been set. This should fail.
TestComponentConfig failConfig;
failConfig.m_activateFunction = [](TestComponent* component)
{
(void)component;
Scene* scene = nullptr;
EntityContextId entityContextId = EntityContextId::CreateNull();
AzFramework::EntityIdContextQueryBus::BroadcastResult(entityContextId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
// A null scene should be returned since a scene has not been set for this entity context.
AzFramework::SceneSystemRequestBus::BroadcastResult(scene, &AzFramework::SceneSystemRequestBus::Events::GetSceneFromEntityContextId, entityContextId);
EXPECT_TRUE(scene == nullptr) << "Found a scene when one shouldn't exist.";
};
testComponent->SetConfiguration(failConfig);
testComponent->Activate();
testComponent->Deactivate();
// Create the scene
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, "TestScene");
Scene* scene = createSceneOutcome.GetValue();
// Map the Entity context to the scene
bool success = false;
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::SetSceneForEntityContextId, testEntityContextId, scene);
EXPECT_TRUE(success) << "Unable to associate an entity context with a scene.";
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::SetSceneForEntityContextId, testEntityContextId, scene);
EXPECT_FALSE(success) << "Attempting to map an entity context to a scene that's already mapped, this should not work.";
// Now it should be possible to get the scene from the entity context within an Entity's Activate()
TestComponentConfig successConfig;
successConfig.m_activateFunction = [](TestComponent* component)
{
(void)component;
Scene* scene = nullptr;
EntityContextId entityContextId = EntityContextId::CreateNull();
AzFramework::EntityIdContextQueryBus::BroadcastResult(entityContextId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
// A scene should be returned since a scene has been set for this entity context.
AzFramework::SceneSystemRequestBus::BroadcastResult(scene, &AzFramework::SceneSystemRequestBus::Events::GetSceneFromEntityContextId, entityContextId);
EXPECT_TRUE(scene != nullptr) << "Could not find a scene for the entity context.";
};
testComponent->SetConfiguration(successConfig);
testComponent->Activate();
testComponent->Deactivate();
// Now remove the entity context / scene association and make sure things fail again.
success = false;
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::RemoveSceneForEntityContextId, testEntityContextId, nullptr);
EXPECT_FALSE(success) << "Should not be able to remove an entity context from a scene it's not associated with.";
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::RemoveSceneForEntityContextId, testEntityContextId, scene);
EXPECT_TRUE(success) << "Was not able to remove an entity context from a scene it's associated with.";
testComponent->SetConfiguration(failConfig);
testComponent->Activate();
testComponent->Deactivate();
delete testEntityContext; // This should also clean up owned entities / components.
}
// Test classes for use in the SceneSystem test. These can't be defined in the test itself due to some functions created by AZ_RTTI not having a body which breaks VS2015.
class Foo1
{
public:
AZ_RTTI(Foo1, "{9A6AA770-E2EA-4C5E-952A-341802E2DE58}");
};
class Foo2
{
public:
AZ_RTTI(Foo2, "{916A2DB4-9C30-4B90-837E-2BC9855B474B}");
};
TEST_F(SceneTest, SceneSystem)
{
// Create the scene
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, "TestScene");
AzFramework::Scene* scene = createSceneOutcome.GetValue();
// Set a class on the Scene
Foo1* foo1a = new Foo1();
EXPECT_TRUE(scene->SetSubsystem(foo1a));
// Get that class back from the Scene
EXPECT_EQ(foo1a, scene->GetSubsystem<Foo1>());
// Try to set the same class type twice, this should fail.
Foo1* foo1b = new Foo1();
EXPECT_FALSE(scene->SetSubsystem(foo1b));
delete foo1b;
// Try to un-set a class that was never set, this should fail.
EXPECT_FALSE(scene->UnsetSubsystem<Foo2>());
// Unset the class that was previously set
EXPECT_TRUE(scene->UnsetSubsystem<Foo1>());
// Make sure that the previsouly set class was really removed.
EXPECT_EQ(nullptr, scene->GetSubsystem<Foo1>());
}
} // UnitTest
AZ_POP_DISABLE_WARNING
+243
View File
@@ -0,0 +1,243 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#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 <AzCore/UserSettings/UserSettingsComponent.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();
}
class ScriptComponentTest
: public ::testing::Test
{
static int mySubValue;
static int myReloadValue;
public:
void run()
{
{
ComponentApplication app;
ComponentApplication::Descriptor appDesc;
appDesc.m_memoryBlocksByteSize = 100 * 1024 * 1024;
//appDesc.m_recordsMode = AllocationRecords::RECORD_FULL;
//appDesc.m_stackRecordLevels = 20;
Entity* systemEntity = 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();
// 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);
ScriptComponent::CreateDescriptor(); // descriptor is deleted by app
ScriptContext* scriptContext = nullptr;
EBUS_EVENT_RESULT(scriptContext, ScriptSystemRequestBus, GetContext, DefaultScriptContextId);
BehaviorContext* behaviorContext = nullptr;
EBUS_EVENT_RESULT(behaviorContext, AZ::ComponentApplicationBus, GetBehaviorContext);
// make sure script instances don't can read only share data, but don't modify the source table
{
Data::Asset<ScriptAsset> scriptAsset = Data::AssetManager::Instance().CreateAsset<ScriptAsset>(Uuid::CreateRandom());
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;";
scriptAsset.Get()->m_scriptBuffer.insert(scriptAsset.Get()->m_scriptBuffer.begin(), script.begin(), script.end());
EBUS_EVENT(Data::AssetManagerBus, OnAssetReady, scriptAsset);
app.Tick();
app.TickSystem();
Entity* entity1 = aznew Entity();
entity1->CreateComponent<ScriptComponent>()->SetScript(scriptAsset);
entity1->Init();
entity1->Activate();
Entity* entity2 = aznew Entity();
entity2->CreateComponent<ScriptComponent>()->SetScript(scriptAsset);
entity2->Init();
entity2->Activate();
behaviorContext->Property("globalMySubValue", BehaviorValueProperty(&mySubValue));
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;
scriptAsset.Reset();
}
// Test script reload
{
behaviorContext->Property("myReloadValue", BehaviorValueProperty(&myReloadValue));
Data::Asset<ScriptAsset> scriptAsset1 = Data::AssetManager::Instance().CreateAsset<ScriptAsset>(Uuid::CreateRandom());
AZStd::string script1 ="local testReload = {}\
function testReload:OnActivate()\
myReloadValue = 1\
end\
function testReload:OnDeactivate()\
myReloadValue = 0\
end\
return testReload;";
scriptAsset1.Get()->m_scriptBuffer.insert(scriptAsset1.Get()->m_scriptBuffer.begin(), script1.begin(), script1.end());
EBUS_EVENT(Data::AssetManagerBus, OnAssetReady, scriptAsset1);
app.Tick();
app.TickSystem(); // flush assets etc.
Entity* 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);
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);
scriptAsset2.Get()->m_scriptBuffer.insert(scriptAsset2.Get()->m_scriptBuffer.begin(), script2.begin(), script2.end());
// 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.
app.Tick();
app.TickSystem();
// test value with the reloaded value
EXPECT_EQ(5, myReloadValue);
delete entity;
scriptAsset1.Reset();
scriptAsset2.Reset();
}
app.Destroy();
//////////////////////////////////////////////////////////////////////////
}
}
};
TEST_F(ScriptComponentTest, ScriptComponentTestExecution)
{
run();
}
int ScriptComponentTest::mySubValue = 0;
int ScriptComponentTest::myReloadValue = 0;
} // namespace UnitTest
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+83
View File
@@ -0,0 +1,83 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "Utils.h"
#include <AzCore/IO/Path/Path.h>
#include <AzCore/StringFunc/StringFunc.h>
UnitTest::ScopedTemporaryDirectory::ScopedTemporaryDirectory()
{
constexpr int MaxAttempts = 255;
#if !AZ_TRAIT_USE_POSIX_TEMP_FOLDER
const auto userTempFolder = std::filesystem::temp_directory_path();
#else
AZ::IO::Path userTempFolder("/tmp");
#endif
for (int i = 0; i < MaxAttempts; ++i)
{
auto randomFolder = AZ::Uuid::CreateRandom().ToString<AZStd::string>(false, false);
AZStd::string testPath;
#if !AZ_TRAIT_USE_POSIX_TEMP_FOLDER
auto path = userTempFolder / ("UnitTest-" + randomFolder).c_str();
testPath = path.string().c_str();
#else
userTempFolder /= ("UnitTest-" + randomFolder).c_str();
testPath = userTempFolder.c_str();
#endif
if (!AZ::IO::SystemFile::Exists(testPath.c_str()))
{
#if !AZ_TRAIT_USE_POSIX_TEMP_FOLDER
m_path = path;
m_tempDirectory = m_path.string().c_str();
#else
m_tempDirectory = testPath;
#endif
m_directoryExists = AZ::IO::LocalFileIO::GetInstance()->CreatePath(m_tempDirectory.c_str());
break;
}
}
AZ_Error("ScopedTemporaryDirectory", !m_tempDirectory.empty(), "Failed to create unique temporary directory after attempting %d random folder names", MaxAttempts);
}
UnitTest::ScopedTemporaryDirectory::~ScopedTemporaryDirectory()
{
if (m_directoryExists)
{
AZ::IO::LocalFileIO::GetInstance()->DestroyPath(m_tempDirectory.c_str());
}
}
bool UnitTest::ScopedTemporaryDirectory::IsValid() const
{
return m_directoryExists;
}
const char* UnitTest::ScopedTemporaryDirectory::GetDirectory() const
{
return m_tempDirectory.c_str();
}
#if !AZ_TRAIT_USE_POSIX_TEMP_FOLDER
const std::filesystem::path& UnitTest::ScopedTemporaryDirectory::GetPath() const
{
return m_path;
}
std::filesystem::path UnitTest::ScopedTemporaryDirectory::operator/(const std::filesystem::path& rhs) const
{
return m_path / rhs;
}
#endif // !AZ_TRAIT_USE_POSIX_TEMP_FOLDER
+48
View File
@@ -0,0 +1,48 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/IO/LocalFileIO.h>
#if !AZ_TRAIT_USE_POSIX_TEMP_FOLDER
#include <filesystem>
#endif
namespace UnitTest
{
// Creates a randomly named folder inside the user's temporary directory.
// The folder and all contents will be destroyed when the object goes out of scope
struct ScopedTemporaryDirectory
{
ScopedTemporaryDirectory();
~ScopedTemporaryDirectory();
bool IsValid() const;
const char* GetDirectory() const;
#if !AZ_TRAIT_USE_POSIX_TEMP_FOLDER
const std::filesystem::path& GetPath() const;
std::filesystem::path operator/(const std::filesystem::path& rhs) const;
#endif
AZ_DISABLE_COPY_MOVE(ScopedTemporaryDirectory);
private:
bool m_directoryExists{ false };
AZStd::string m_tempDirectory;
#if !AZ_TRAIT_USE_POSIX_TEMP_FOLDER
std::filesystem::path m_path;
#endif
};
}
@@ -0,0 +1,15 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Utils/Utils.h
Utils/Utils.cpp
)
@@ -0,0 +1,54 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
../AzCore/Tests/Main.cpp
ArchiveCompressionTests.cpp
ArchiveTests.cpp
BehaviorEntityTests.cpp
BinToTextEncode.cpp
ComponentAddRemove.cpp
ComponentAdapterTests.cpp
EntityContext.cpp
EntityTestbed.h
FileFunc.cpp
FileIO.cpp
FileTagTests.cpp
FrameworkApplicationFixture.h
GenAppDescriptors.cpp
GenericComponentWrapperTest.cpp
InstanceDataHierarchy.cpp
NetBinding.cpp
NetworkContext.cpp
OctreePerformanceTests.cpp
OctreeTests.cpp
Slices.cpp
Script.cpp
AssetCatalog.cpp
AssetProcessorConnection.cpp
NetBindingSystemImplTest.cpp
NetBindingMocks.h
NativeWindow.cpp
TransformComponent.cpp
GridMocks.h
InterestManagerComponentTests.cpp
SQLiteConnectionTests.cpp
ProcessLaunchParseTests.cpp
Application.cpp
PlatformHelper.cpp
Scene.cpp
EntityOwnershipService/EntityOwnershipServiceTestFixture.h
EntityOwnershipService/EntityOwnershipServiceTestFixture.cpp
EntityOwnershipService/SliceEditorEntityOwnershipTests.cpp
EntityOwnershipService/SliceEntityOwnershipTests.cpp
CameraState.cpp
InputTests.cpp
)
@@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
ProcessLaunchMain.cpp
)