@@ -279,14 +279,11 @@ namespace AZ
|
||||
return SystemFile::Exists(resolvedPath);
|
||||
}
|
||||
|
||||
void LocalFileIO::CheckInvalidWrite(const char* path)
|
||||
void LocalFileIO::CheckInvalidWrite([[maybe_unused]] const char* path)
|
||||
{
|
||||
(void)path;
|
||||
|
||||
#if defined(AZ_ENABLE_TRACING)
|
||||
const char* assetsAlias = GetAlias("@assets@");
|
||||
|
||||
if (((path) && (assetsAlias) && (azstrnicmp(path, assetsAlias, strlen(assetsAlias)) == 0)))
|
||||
if (path && assetsAlias && AZ::IO::PathView(path).IsRelativeTo(assetsAlias))
|
||||
{
|
||||
AZ_Error("FileIO", false, "You may not alter data inside the asset cache. Please check the call stack and consider writing into the source asset folder instead.\n"
|
||||
"Attempted write location: %s", path);
|
||||
|
||||
@@ -52,3 +52,63 @@ ly_add_source_properties(
|
||||
PROPERTY COMPILE_DEFINITIONS
|
||||
VALUES TOUCHBENDING_LAYER_BIT=${LY_TOUCHBENDING_LAYER_BIT}
|
||||
)
|
||||
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
|
||||
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Tests/Platform/${PAL_PLATFORM_NAME})
|
||||
|
||||
ly_add_target(
|
||||
NAME AzFrameworkTestShared STATIC
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
Tests/framework_shared_tests_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PUBLIC
|
||||
Tests
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzCore
|
||||
AZ::AzFramework
|
||||
)
|
||||
|
||||
if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
|
||||
ly_add_target(
|
||||
NAME ProcessLaunchTest EXECUTABLE
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
Tests/process_launch_test_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
Tests
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzCore
|
||||
AZ::AzFramework
|
||||
)
|
||||
|
||||
ly_add_target(
|
||||
NAME AzFramework.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
Tests/frameworktests_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
Tests
|
||||
${pal_dir}
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzFramework
|
||||
AZ::AzTest
|
||||
AZ::AzTestShared
|
||||
AZ::AzFrameworkTestShared
|
||||
RUNTIME_DEPENDENCIES
|
||||
AZ::ProcessLaunchTest
|
||||
)
|
||||
ly_add_googletest(
|
||||
NAME AZ::AzFramework.Tests
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
endif()
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "FrameworkApplicationFixture.h"
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
#include <AzTest/Utils.h>
|
||||
|
||||
class ApplicationTest
|
||||
: public UnitTest::FrameworkApplicationFixture
|
||||
{
|
||||
protected:
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
FrameworkApplicationFixture::SetUp();
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder, m_tempDirectory.GetDirectory());
|
||||
}
|
||||
if (auto fileIoBase = AZ::IO::FileIOBase::GetInstance(); fileIoBase != nullptr)
|
||||
{
|
||||
fileIoBase->SetAlias("@assets@", 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;
|
||||
AZ::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;
|
||||
AZ::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());
|
||||
AZ::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());
|
||||
AZ::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;
|
||||
AZ::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;
|
||||
AZ::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,462 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.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
|
||||
{
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_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 = "@usercache@/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 = "@usercache@/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 = "@usercache@/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 = "@usercache@/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,642 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "FrameworkApplicationFixture.h"
|
||||
|
||||
#include <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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
TEST_F(APConnectionTest, DISABLED_TestAddRemoveCallbacks)
|
||||
#else
|
||||
TEST_F(APConnectionTest, TestAddRemoveCallbacks)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
TEST_F(APConnectionTest, DISABLED_TestAddRemoveCallbacks_RemoveDuringCallback_DoesNotCrash)
|
||||
#else
|
||||
TEST_F(APConnectionTest, TestAddRemoveCallbacks_RemoveDuringCallback_DoesNotCrash)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
TEST_F(APConnectionTest, DISABLED_TestAddRemoveCallbacks_AddDuringCallback_DoesNotCrash)
|
||||
#else
|
||||
TEST_F(APConnectionTest, TestAddRemoveCallbacks_AddDuringCallback_DoesNotCrash)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
TEST_F(APConnectionTest, DISABLED_TestConnection)
|
||||
#else
|
||||
TEST_F(APConnectionTest, TestConnection)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
TEST_F(APConnectionTest, DISABLED_TestReconnect)
|
||||
#else
|
||||
TEST_F(APConnectionTest, TestReconnect)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
{
|
||||
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,299 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "FrameworkApplicationFixture.h"
|
||||
#include <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);
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
|
||||
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
#include <AzFramework/Viewport/CameraInput.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class CameraInputFixture : public AllocatorsTestFixture
|
||||
{
|
||||
public:
|
||||
AzFramework::Camera m_camera;
|
||||
AzFramework::Camera m_targetCamera;
|
||||
AZStd::shared_ptr<AzFramework::CameraSystem> m_cameraSystem;
|
||||
|
||||
bool HandleEventAndUpdate(const AzFramework::InputEvent& event)
|
||||
{
|
||||
constexpr float deltaTime = 0.01666f; // 60fps
|
||||
const bool consumed = m_cameraSystem->HandleEvents(event);
|
||||
m_camera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime);
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AllocatorsTestFixture::SetUp();
|
||||
|
||||
m_cameraSystem = AZStd::make_shared<AzFramework::CameraSystem>();
|
||||
|
||||
m_translateCameraInputChannels.m_leftChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_A");
|
||||
m_translateCameraInputChannels.m_rightChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_D");
|
||||
m_translateCameraInputChannels.m_forwardChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_W");
|
||||
m_translateCameraInputChannels.m_backwardChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_S");
|
||||
m_translateCameraInputChannels.m_upChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_E");
|
||||
m_translateCameraInputChannels.m_downChannelId = AzFramework::InputChannelId("keyboard_key_alphanumeric_Q");
|
||||
m_translateCameraInputChannels.m_boostChannelId = AzFramework::InputChannelId("keyboard_key_modifier_shift_l");
|
||||
|
||||
m_firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Right);
|
||||
m_firstPersonTranslateCamera =
|
||||
AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::LookTranslation, m_translateCameraInputChannels);
|
||||
|
||||
auto orbitCamera =
|
||||
AZStd::make_shared<AzFramework::OrbitCameraInput>(AzFramework::InputChannelId("keyboard_key_modifier_alt_l"));
|
||||
auto orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Left);
|
||||
auto orbitTranslateCamera =
|
||||
AZStd::make_shared<AzFramework::TranslateCameraInput>(AzFramework::OrbitTranslation, m_translateCameraInputChannels);
|
||||
|
||||
orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
|
||||
orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
|
||||
|
||||
m_cameraSystem->m_cameras.AddCamera(m_firstPersonRotateCamera);
|
||||
m_cameraSystem->m_cameras.AddCamera(m_firstPersonTranslateCamera);
|
||||
m_cameraSystem->m_cameras.AddCamera(orbitCamera);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_firstPersonRotateCamera.reset();
|
||||
m_firstPersonTranslateCamera.reset();
|
||||
|
||||
m_cameraSystem->m_cameras.Clear();
|
||||
m_cameraSystem.reset();
|
||||
|
||||
AllocatorsTestFixture::TearDown();
|
||||
}
|
||||
|
||||
AzFramework::TranslateCameraInputChannels m_translateCameraInputChannels;
|
||||
AZStd::shared_ptr<AzFramework::RotateCameraInput> m_firstPersonRotateCamera;
|
||||
AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_firstPersonTranslateCamera;
|
||||
};
|
||||
|
||||
TEST_F(CameraInputFixture, Begin_and_end_OrbitCameraInput_consumes_correct_events)
|
||||
{
|
||||
// begin orbit camera
|
||||
const bool consumed1 = HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceKeyboard::Key::ModifierAltL,
|
||||
AzFramework::InputChannel::State::Began });
|
||||
// begin listening for orbit rotate (click detector) - event is not consumed
|
||||
const bool consumed2 = HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began });
|
||||
// begin orbit rotate (mouse has moved sufficient distance to initiate)
|
||||
const bool consumed3 = HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ 5 });
|
||||
// end orbit (mouse up) - event is not consumed
|
||||
const bool consumed4 = HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended });
|
||||
|
||||
const auto allConsumed = AZStd::vector<bool>{ consumed1, consumed2, consumed3, consumed4 };
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
EXPECT_THAT(allConsumed, ElementsAre(true, false, true, false));
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, Begin_CameraInput_notifies_ActivationBeganFn_for_TranslateCameraInput)
|
||||
{
|
||||
bool activationBegan = false;
|
||||
m_firstPersonTranslateCamera->SetActivationBeganFn(
|
||||
[&activationBegan]
|
||||
{
|
||||
activationBegan = true;
|
||||
});
|
||||
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ m_translateCameraInputChannels.m_forwardChannelId, AzFramework::InputChannel::State::Began });
|
||||
|
||||
EXPECT_TRUE(activationBegan);
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, Begin_CameraInput_notifies_ActivationBeganFn_after_delta_for_RotateCameraInput)
|
||||
{
|
||||
bool activationBegan = false;
|
||||
m_firstPersonRotateCamera->SetActivationBeganFn(
|
||||
[&activationBegan]
|
||||
{
|
||||
activationBegan = true;
|
||||
});
|
||||
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ 20 }); // must move input device
|
||||
|
||||
EXPECT_TRUE(activationBegan);
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, Begin_CameraInput_does_not_notify_ActivationBeganFn_with_no_delta_for_RotateCameraInput)
|
||||
{
|
||||
bool activationBegan = false;
|
||||
m_firstPersonRotateCamera->SetActivationBeganFn(
|
||||
[&activationBegan]
|
||||
{
|
||||
activationBegan = true;
|
||||
});
|
||||
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began });
|
||||
|
||||
EXPECT_FALSE(activationBegan);
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, End_CameraInput_notifies_ActivationEndFn_after_delta_for_RotateCameraInput)
|
||||
{
|
||||
bool activationEnded = false;
|
||||
m_firstPersonRotateCamera->SetActivationEndedFn(
|
||||
[&activationEnded]
|
||||
{
|
||||
activationEnded = true;
|
||||
});
|
||||
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ 20 });
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Ended });
|
||||
|
||||
EXPECT_TRUE(activationEnded);
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, End_CameraInput_does_not_notify_ActivationBeganFn_or_ActivationBeganFn_with_no_delta_for_RotateCameraInput)
|
||||
{
|
||||
bool activationBegan = false;
|
||||
m_firstPersonRotateCamera->SetActivationBeganFn(
|
||||
[&activationBegan]
|
||||
{
|
||||
activationBegan = true;
|
||||
});
|
||||
|
||||
bool activationEnded = false;
|
||||
m_firstPersonRotateCamera->SetActivationEndedFn(
|
||||
[&activationEnded]
|
||||
{
|
||||
activationEnded = true;
|
||||
});
|
||||
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Ended });
|
||||
|
||||
EXPECT_FALSE(activationBegan);
|
||||
EXPECT_FALSE(activationEnded);
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, End_CameraInput_notifies_ActivationBeganFn_or_ActivationEndFn_with_TranslateCamera)
|
||||
{
|
||||
bool activationBegan = false;
|
||||
m_firstPersonTranslateCamera->SetActivationBeganFn(
|
||||
[&activationBegan]
|
||||
{
|
||||
activationBegan = true;
|
||||
});
|
||||
|
||||
bool activationEnded = false;
|
||||
m_firstPersonTranslateCamera->SetActivationEndedFn(
|
||||
[&activationEnded]
|
||||
{
|
||||
activationEnded = true;
|
||||
});
|
||||
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ m_translateCameraInputChannels.m_forwardChannelId, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ m_translateCameraInputChannels.m_forwardChannelId, AzFramework::InputChannel::State::Ended });
|
||||
|
||||
EXPECT_TRUE(activationBegan);
|
||||
EXPECT_TRUE(activationEnded);
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, End_activation_called_for_CameraInput_if_active_when_cameras_are_cleared)
|
||||
{
|
||||
bool activationEnded = false;
|
||||
m_firstPersonTranslateCamera->SetActivationEndedFn(
|
||||
[&activationEnded]
|
||||
{
|
||||
activationEnded = true;
|
||||
});
|
||||
|
||||
HandleEventAndUpdate(
|
||||
AzFramework::DiscreteInputEvent{ m_translateCameraInputChannels.m_forwardChannelId, AzFramework::InputChannel::State::Began });
|
||||
|
||||
m_cameraSystem->m_cameras.Clear();
|
||||
|
||||
EXPECT_TRUE(activationEnded);
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/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,154 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Viewport/ClickDetector.h>
|
||||
#include <AzFramework/Viewport/ScreenGeometry.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
std::ostream& operator<<(std::ostream& os, const ClickDetector::ClickOutcome clickOutcome)
|
||||
{
|
||||
switch (clickOutcome)
|
||||
{
|
||||
case ClickDetector::ClickOutcome::Click:
|
||||
os << "ClickOutcome::Click";
|
||||
break;
|
||||
case ClickDetector::ClickOutcome::Move:
|
||||
os << "ClickOutcome::Move";
|
||||
break;
|
||||
case ClickDetector::ClickOutcome::Release:
|
||||
os << "ClickOutcome::Release";
|
||||
break;
|
||||
case ClickDetector::ClickOutcome::Nil:
|
||||
os << "ClickOutcome::Nil";
|
||||
break;
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
using AzFramework::ClickDetector;
|
||||
using AzFramework::ScreenVector;
|
||||
|
||||
class ClickDetectorFixture : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
ClickDetector m_clickDetector;
|
||||
};
|
||||
|
||||
TEST_F(ClickDetectorFixture, ClickIsDetectedWithNoMouseMovementOnMouseUp)
|
||||
{
|
||||
using ::testing::Eq;
|
||||
|
||||
const ClickDetector::ClickOutcome initialDownOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
|
||||
const ClickDetector::ClickOutcome initialUpOutcome = m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(0, 0));
|
||||
|
||||
EXPECT_THAT(initialDownOutcome, Eq(ClickDetector::ClickOutcome::Nil));
|
||||
EXPECT_THAT(initialUpOutcome, Eq(ClickDetector::ClickOutcome::Click));
|
||||
}
|
||||
|
||||
TEST_F(ClickDetectorFixture, MoveIsDetectedWithMouseMovementAfterMouseDown)
|
||||
{
|
||||
using ::testing::Eq;
|
||||
|
||||
const ClickDetector::ClickOutcome initialDownOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
|
||||
const ClickDetector::ClickOutcome initialMoveOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Nil, ScreenVector(10, 10));
|
||||
|
||||
EXPECT_THAT(initialDownOutcome, Eq(ClickDetector::ClickOutcome::Nil));
|
||||
EXPECT_THAT(initialMoveOutcome, Eq(ClickDetector::ClickOutcome::Move));
|
||||
}
|
||||
|
||||
TEST_F(ClickDetectorFixture, ReleaseIsDetectedAfterMouseMovementOnMouseUp)
|
||||
{
|
||||
using ::testing::Eq;
|
||||
|
||||
const ClickDetector::ClickOutcome initialDownOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
|
||||
// move
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Nil, ScreenVector(10, 10));
|
||||
const ClickDetector::ClickOutcome initialUpOutcome = m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(0, 0));
|
||||
|
||||
EXPECT_THAT(initialDownOutcome, Eq(ClickDetector::ClickOutcome::Nil));
|
||||
EXPECT_THAT(initialUpOutcome, Eq(ClickDetector::ClickOutcome::Release));
|
||||
}
|
||||
|
||||
TEST_F(ClickDetectorFixture, MoveIsReturnedOnlyAfterFirstMouseMove)
|
||||
{
|
||||
using ::testing::Eq;
|
||||
|
||||
const ClickDetector::ClickOutcome initialDownOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
|
||||
const ClickDetector::ClickOutcome initialMoveOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Nil, ScreenVector(10, 10));
|
||||
const ClickDetector::ClickOutcome secondaryMoveOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Nil, ScreenVector(10, 10));
|
||||
|
||||
EXPECT_THAT(initialDownOutcome, Eq(ClickDetector::ClickOutcome::Nil));
|
||||
EXPECT_THAT(initialMoveOutcome, Eq(ClickDetector::ClickOutcome::Move));
|
||||
EXPECT_THAT(secondaryMoveOutcome, Eq(ClickDetector::ClickOutcome::Nil));
|
||||
}
|
||||
|
||||
TEST_F(ClickDetectorFixture, ClickIsNotRegisteredAfterDoubleClick)
|
||||
{
|
||||
using ::testing::Eq;
|
||||
|
||||
const ClickDetector::ClickOutcome initialDownOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
|
||||
const ClickDetector::ClickOutcome initialUpOutcome = m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(0, 0));
|
||||
const ClickDetector::ClickOutcome secondaryDownOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
|
||||
const ClickDetector::ClickOutcome secondaryUpOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(0, 0));
|
||||
|
||||
EXPECT_THAT(initialDownOutcome, Eq(ClickDetector::ClickOutcome::Nil));
|
||||
EXPECT_THAT(initialUpOutcome, Eq(ClickDetector::ClickOutcome::Click));
|
||||
EXPECT_THAT(secondaryDownOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // double click
|
||||
EXPECT_THAT(secondaryUpOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // click not registered
|
||||
}
|
||||
|
||||
TEST_F(ClickDetectorFixture, ClickIsNotRegisteredAfterIgnoredDoubleClick)
|
||||
{
|
||||
using ::testing::Eq;
|
||||
|
||||
const ClickDetector::ClickOutcome initialDownOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
|
||||
const ClickDetector::ClickOutcome initialUpOutcome = m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(0, 0));
|
||||
const ClickDetector::ClickOutcome secondaryDownOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Nil, ScreenVector(0, 0));
|
||||
const ClickDetector::ClickOutcome secondaryUpOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(0, 0));
|
||||
|
||||
EXPECT_THAT(initialDownOutcome, Eq(ClickDetector::ClickOutcome::Nil));
|
||||
EXPECT_THAT(initialUpOutcome, Eq(ClickDetector::ClickOutcome::Click));
|
||||
EXPECT_THAT(secondaryDownOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // ignored double click
|
||||
EXPECT_THAT(secondaryUpOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // click not registered
|
||||
}
|
||||
|
||||
// if the click detector registers a mouse down event, but then all intermediate calls are ignored
|
||||
// (another system may start intercepting events and swallowing them) then when we do receive a mouse
|
||||
// up event we should ensure we take into account the current delta - if the delta is large, then the
|
||||
// outcome will be release
|
||||
TEST_F(ClickDetectorFixture, ClickIsNotRegisteredAfterIgnoringMouseMovesBeforeMouseUpWithLargeDelta)
|
||||
{
|
||||
using ::testing::Eq;
|
||||
|
||||
const ClickDetector::ClickOutcome downOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
|
||||
const ClickDetector::ClickOutcome upOutcome =
|
||||
m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(50, 50));
|
||||
|
||||
EXPECT_THAT(downOutcome, Eq(ClickDetector::ClickOutcome::Nil));
|
||||
EXPECT_THAT(upOutcome, Eq(ClickDetector::ClickOutcome::Release));
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Viewport/CursorState.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
using AzFramework::CursorState;
|
||||
using AzFramework::ScreenVector;
|
||||
using AzFramework::ScreenPoint;
|
||||
|
||||
class CursorStateFixture : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
CursorState m_cursorState;
|
||||
};
|
||||
|
||||
TEST_F(CursorStateFixture, CursorStateHasZeroDeltaInitially)
|
||||
{
|
||||
using ::testing::Eq;
|
||||
EXPECT_THAT(m_cursorState.CursorDelta(), Eq(ScreenVector(0, 0)));
|
||||
}
|
||||
|
||||
TEST_F(CursorStateFixture, CursorStateReturnsZeroDeltaAfterSingleMoveAndUpdate)
|
||||
{
|
||||
using ::testing::Eq;
|
||||
|
||||
m_cursorState.SetCurrentPosition(ScreenPoint(10, 10));
|
||||
m_cursorState.Update();
|
||||
|
||||
EXPECT_THAT(m_cursorState.CursorDelta(), Eq(ScreenVector(0, 0)));
|
||||
}
|
||||
|
||||
TEST_F(CursorStateFixture, CursorStateReturnsDeltaAfterSecondMoveAndUpdate)
|
||||
{
|
||||
using ::testing::Eq;
|
||||
|
||||
m_cursorState.SetCurrentPosition(ScreenPoint(10, 10));
|
||||
m_cursorState.Update();
|
||||
m_cursorState.SetCurrentPosition(ScreenPoint(15, 22));
|
||||
|
||||
EXPECT_THAT(m_cursorState.CursorDelta(), Eq(ScreenVector(5, 12)));
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Math/Uuid.h>
|
||||
#include <AzCore/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,940 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/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
|
||||
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/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::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,81 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/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 FileIOBaseRAII
|
||||
{
|
||||
public:
|
||||
FileIOBaseRAII(AZ::IO::FileIOBase& fileIO)
|
||||
: m_prevFileIO(AZ::IO::FileIOBase::GetInstance())
|
||||
{
|
||||
AZ::IO::FileIOBase::SetInstance(&fileIO);
|
||||
}
|
||||
|
||||
~FileIOBaseRAII()
|
||||
{
|
||||
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;
|
||||
FileIOBaseRAII restoreFileIOScope(fileIO);
|
||||
run();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/UnitTest/UnitTest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesManager.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class MockSpawnableEntitiesInterface;
|
||||
using NiceSpawnableEntitiesInterfaceMock = ::testing::NiceMock<MockSpawnableEntitiesInterface>;
|
||||
|
||||
class MockSpawnableEntitiesInterface : public SpawnableEntitiesDefinition
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(MockSpawnableEntitiesInterface, "{2A20FF73-C445-4F32-ABB9-5CF0A5778404}", SpawnableEntitiesDefinition);
|
||||
|
||||
MockSpawnableEntitiesInterface()
|
||||
{
|
||||
AZ::Interface<SpawnableEntitiesDefinition>::Register(this);
|
||||
}
|
||||
|
||||
virtual ~MockSpawnableEntitiesInterface()
|
||||
{
|
||||
AZ::Interface<SpawnableEntitiesDefinition>::Unregister(this);
|
||||
}
|
||||
|
||||
MOCK_METHOD2(SpawnAllEntities, void(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
SpawnEntities,
|
||||
void(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD2(DespawnAllEntities, void(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
ReloadSpawnable,
|
||||
void(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
ListEntities, void(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
ListIndicesAndEntities,
|
||||
void(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
ClaimEntities,
|
||||
void(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(Barrier, void(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD1(CreateTicket, AZStd::pair<EntitySpawnTicket::Id, void*>(AZ::Data::Asset<Spawnable>&& spawnable));
|
||||
MOCK_METHOD1(DestroyTicket, void(void* ticket));
|
||||
|
||||
/** Installs some default result values for the above functions.
|
||||
* Note that you can always override these in scope of your test by adding additional ON_CALL / EXPECT_CALL
|
||||
* statements in the body of your test or after calling this function, and yours will take precedence.
|
||||
**/
|
||||
static void InstallDefaultReturns(NiceSpawnableEntitiesInterfaceMock& target)
|
||||
{
|
||||
using namespace ::testing;
|
||||
|
||||
// The ID and pointer are completely arbitrary, they just need to both be non-zero to look like a valid ticket.
|
||||
constexpr EntitySpawnTicket::Id ticketId(1);
|
||||
static int ticketPayload = 0;
|
||||
ON_CALL(target, CreateTicket(_)).WillByDefault(
|
||||
Return(AZStd::make_pair<AzFramework::EntitySpawnTicket::Id, void*>(ticketId, &ticketPayload)));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace AzFramework
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <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
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/Name/NameDictionary.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;
|
||||
}
|
||||
|
||||
if (!AZ::NameDictionary::IsReady())
|
||||
{
|
||||
AZ::NameDictionary::Create();
|
||||
}
|
||||
m_octreeSystemComponent = new AzFramework::OctreeSystemComponent;
|
||||
m_visScene = m_octreeSystemComponent->CreateVisibilityScene(AZ::Name("OctreeBenchmarkVisibilityScene"));
|
||||
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
|
||||
{
|
||||
m_octreeSystemComponent->DestroyVisibilityScene(m_visScene);
|
||||
delete m_octreeSystemComponent;
|
||||
AZ::NameDictionary::Destroy();
|
||||
|
||||
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)
|
||||
{
|
||||
for (uint32_t i = 0; i < entryCount; ++i)
|
||||
{
|
||||
m_visScene->InsertOrUpdateEntry(m_dataArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveEntries(uint32_t entryCount)
|
||||
{
|
||||
for (uint32_t i = 0; i < entryCount; ++i)
|
||||
{
|
||||
m_visScene->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 = nullptr;
|
||||
AzFramework::IVisibilityScene* m_visScene = nullptr;
|
||||
};
|
||||
|
||||
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_visScene->Enumerate(queryData.aabb, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.aabb, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.aabb, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.aabb, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.sphere, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.sphere, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.sphere, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.sphere, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.frustum, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.frustum, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.frustum, [](const AzFramework::IVisibilityScene::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_visScene->Enumerate(queryData.frustum, [](const AzFramework::IVisibilityScene::NodeData&) {});
|
||||
}
|
||||
}
|
||||
RemoveEntries(EntryCount);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/Console/Console.h>
|
||||
#include <AzCore/Name/NameDictionary.h>
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzFramework/Visibility/OctreeSystemComponent.h>
|
||||
#include <random>
|
||||
|
||||
using namespace AzFramework;
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class OctreeTests
|
||||
: public AllocatorsFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
// Create the SystemAllocator if not available
|
||||
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
|
||||
{
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
|
||||
m_ownsSystemAllocator = true;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if (!AZ::NameDictionary::IsReady())
|
||||
{
|
||||
AZ::NameDictionary::Create();
|
||||
}
|
||||
m_octreeSystemComponent = new OctreeSystemComponent;
|
||||
IVisibilityScene* visScene = m_octreeSystemComponent->CreateVisibilityScene(AZ::Name("OctreeUnitTestScene"));
|
||||
m_octreeScene = azdynamic_cast<OctreeScene*>(visScene);
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
m_octreeSystemComponent->DestroyVisibilityScene(m_octreeScene);
|
||||
delete m_octreeSystemComponent;
|
||||
m_octreeSystemComponent = nullptr;
|
||||
|
||||
AZ::NameDictionary::Destroy();
|
||||
|
||||
AZ::Interface<AZ::IConsole>::Unregister(m_console);
|
||||
delete m_console;
|
||||
m_console = nullptr;
|
||||
|
||||
// Destroy system allocator only if it was created by this environment
|
||||
if (m_ownsSystemAllocator)
|
||||
{
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
|
||||
m_ownsSystemAllocator = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool m_ownsSystemAllocator = false;
|
||||
OctreeSystemComponent* m_octreeSystemComponent = nullptr;
|
||||
OctreeScene* m_octreeScene = nullptr;
|
||||
uint32_t m_savedMaxEntries = 0;
|
||||
uint32_t m_savedMinEntries = 0;
|
||||
float m_savedBounds = 0.0f;
|
||||
AZ::Console* m_console;
|
||||
};
|
||||
|
||||
void ValidateEntryCountEqualsExpectedCount(const IVisibilityScene* visScene, uint32_t expectedEntryCount)
|
||||
{
|
||||
// InsertOrUpdateEntry assumes that updating an existing entry won't change the count
|
||||
// so it doesn't modify the counter used by GetEntryCount.
|
||||
// If an entry is removed from the octree as an unintended side effect of updating an existing entry,
|
||||
// GetEntryCount can't be relied upon to report the actual entry count.
|
||||
// So manually count the entries when using the entry count for validation.
|
||||
uint32_t manualEntryCount = 0;
|
||||
visScene->EnumerateNoCull([&manualEntryCount](const AzFramework::IVisibilityScene::NodeData& nodeData) { manualEntryCount += nodeData.m_entries.size(); });
|
||||
|
||||
EXPECT_EQ(manualEntryCount, expectedEntryCount);
|
||||
EXPECT_EQ(visScene->GetEntryCount(), expectedEntryCount);
|
||||
}
|
||||
|
||||
TEST_F(OctreeTests, InsertDeleteSingleEntry)
|
||||
{
|
||||
AzFramework::VisibilityEntry visEntry;
|
||||
visEntry.m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3::CreateZero(), AZ::Vector3::CreateOne());
|
||||
|
||||
m_octreeScene->InsertOrUpdateEntry(visEntry);
|
||||
EXPECT_TRUE(visEntry.m_internalNode != nullptr);
|
||||
EXPECT_TRUE(visEntry.m_internalNodeIndex == 0);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 1);
|
||||
|
||||
m_octreeScene->RemoveEntry(visEntry);
|
||||
EXPECT_TRUE(visEntry.m_internalNode == nullptr);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 0);
|
||||
|
||||
EXPECT_TRUE(true); //TEST
|
||||
}
|
||||
|
||||
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_octreeScene->InsertOrUpdateEntry(visEntry[0]);
|
||||
EXPECT_TRUE(visEntry[0].m_internalNode != nullptr);
|
||||
EXPECT_TRUE(visEntry[0].m_internalNodeIndex == 0);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 1);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1);
|
||||
|
||||
m_octreeScene->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);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 2);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1 + m_octreeScene->GetChildNodeCount());
|
||||
|
||||
m_octreeScene->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);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 3);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1 + (2 * m_octreeScene->GetChildNodeCount()));
|
||||
|
||||
m_octreeScene->RemoveEntry(visEntry[2]);
|
||||
EXPECT_TRUE(visEntry[2].m_internalNode == nullptr);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 2);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1 + m_octreeScene->GetChildNodeCount());
|
||||
|
||||
m_octreeScene->RemoveEntry(visEntry[1]);
|
||||
EXPECT_TRUE(visEntry[1].m_internalNode == nullptr);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 1);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1);
|
||||
|
||||
m_octreeScene->RemoveEntry(visEntry[0]);
|
||||
EXPECT_TRUE(visEntry[0].m_internalNode == nullptr);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 0);
|
||||
}
|
||||
|
||||
TEST_F(OctreeTests, UpdateSingleEntry)
|
||||
{
|
||||
AzFramework::VisibilityEntry visEntry;
|
||||
visEntry.m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3::CreateZero(), AZ::Vector3::CreateOne());
|
||||
|
||||
m_octreeScene->InsertOrUpdateEntry(visEntry);
|
||||
EXPECT_TRUE(visEntry.m_internalNode != nullptr);
|
||||
EXPECT_TRUE(visEntry.m_internalNodeIndex == 0);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 1);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1);
|
||||
|
||||
visEntry.m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f));
|
||||
m_octreeScene->InsertOrUpdateEntry(visEntry);
|
||||
EXPECT_TRUE(visEntry.m_internalNode != nullptr);
|
||||
EXPECT_TRUE(visEntry.m_internalNodeIndex == 0);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 1);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1);
|
||||
|
||||
m_octreeScene->RemoveEntry(visEntry);
|
||||
EXPECT_TRUE(visEntry.m_internalNode == nullptr);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 0);
|
||||
EXPECT_TRUE(m_octreeScene->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_octreeScene->InsertOrUpdateEntry(visEntry[0]);
|
||||
EXPECT_TRUE(visEntry[0].m_internalNode != nullptr);
|
||||
EXPECT_TRUE(visEntry[0].m_internalNodeIndex == 0);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 1);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1);
|
||||
|
||||
m_octreeScene->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);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 2);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1 + m_octreeScene->GetChildNodeCount());
|
||||
|
||||
m_octreeScene->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);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 3);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1 + (2 * m_octreeScene->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_octreeScene->InsertOrUpdateEntry(visEntry[0]);
|
||||
m_octreeScene->InsertOrUpdateEntry(visEntry[1]);
|
||||
m_octreeScene->InsertOrUpdateEntry(visEntry[2]);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 3);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1 + (2 * m_octreeScene->GetChildNodeCount()));
|
||||
|
||||
m_octreeScene->RemoveEntry(visEntry[2]);
|
||||
EXPECT_TRUE(visEntry[2].m_internalNode == nullptr);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 2);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1 + m_octreeScene->GetChildNodeCount());
|
||||
|
||||
m_octreeScene->RemoveEntry(visEntry[1]);
|
||||
EXPECT_TRUE(visEntry[1].m_internalNode == nullptr);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 1);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1);
|
||||
|
||||
m_octreeScene->RemoveEntry(visEntry[0]);
|
||||
EXPECT_TRUE(visEntry[0].m_internalNode == nullptr);
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, 0);
|
||||
EXPECT_TRUE(m_octreeScene->GetNodeCount() == 1);
|
||||
}
|
||||
|
||||
void AppendEntries(AZStd::vector<VisibilityEntry*>& gatheredEntries, const AzFramework::IVisibilityScene::NodeData& nodeData)
|
||||
{
|
||||
gatheredEntries.insert(gatheredEntries.end(), nodeData.m_entries.begin(), nodeData.m_entries.end());
|
||||
}
|
||||
|
||||
template <typename BoundType>
|
||||
void EnumerateSingleEntryHelper(IVisibilityScene* visScene, const BoundType& bounds)
|
||||
{
|
||||
AzFramework::VisibilityEntry visEntry;
|
||||
visEntry.m_boundingVolume = AZ::Aabb::CreateFromMinMax(AZ::Vector3::CreateZero(), AZ::Vector3::CreateOne());
|
||||
|
||||
AZStd::vector<VisibilityEntry*> gatheredEntries;
|
||||
visScene->Enumerate(bounds, [&gatheredEntries](const AzFramework::IVisibilityScene::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
|
||||
EXPECT_TRUE(gatheredEntries.empty());
|
||||
|
||||
visScene->InsertOrUpdateEntry(visEntry);
|
||||
visScene->Enumerate(bounds, [&gatheredEntries](const AzFramework::IVisibilityScene::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));
|
||||
visScene->InsertOrUpdateEntry(visEntry);
|
||||
gatheredEntries.clear();
|
||||
visScene->Enumerate(bounds, [&gatheredEntries](const AzFramework::IVisibilityScene::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
|
||||
EXPECT_TRUE(gatheredEntries.size() == 1);
|
||||
EXPECT_TRUE(gatheredEntries[0] == &visEntry);
|
||||
|
||||
visScene->RemoveEntry(visEntry);
|
||||
gatheredEntries.clear();
|
||||
visScene->Enumerate(bounds, [&gatheredEntries](const AzFramework::IVisibilityScene::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
|
||||
EXPECT_TRUE(gatheredEntries.empty());
|
||||
}
|
||||
|
||||
TEST_F(OctreeTests, EnumerateSphereSingleEntry)
|
||||
{
|
||||
AZ::Sphere bounds = AZ::Sphere::CreateUnitSphere();
|
||||
EnumerateSingleEntryHelper(m_octreeScene, bounds);
|
||||
}
|
||||
|
||||
TEST_F(OctreeTests, EnumerateAabbSingleEntry)
|
||||
{
|
||||
AZ::Aabb bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-1.0f), AZ::Vector3(1.0f));
|
||||
EnumerateSingleEntryHelper(m_octreeScene, 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_octreeScene, 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(IVisibilityScene* visScene, 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));
|
||||
|
||||
visScene->InsertOrUpdateEntry(visEntry[0]);
|
||||
visScene->InsertOrUpdateEntry(visEntry[1]);
|
||||
visScene->InsertOrUpdateEntry(visEntry[2]);
|
||||
|
||||
gatheredEntries.clear();
|
||||
visScene->Enumerate(bound1, [&gatheredEntries](const AzFramework::IVisibilityScene::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
|
||||
EXPECT_TRUE(gatheredEntries.size() == 3);
|
||||
|
||||
gatheredEntries.clear();
|
||||
visScene->Enumerate(bound2, [&gatheredEntries](const AzFramework::IVisibilityScene::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
|
||||
EXPECT_TRUE(gatheredEntries.size() == 1);
|
||||
EXPECT_TRUE(gatheredEntries[0] == &(visEntry[0]));
|
||||
|
||||
gatheredEntries.clear();
|
||||
visScene->Enumerate(bound3, [&gatheredEntries](const AzFramework::IVisibilityScene::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));
|
||||
visScene->InsertOrUpdateEntry(visEntry[0]);
|
||||
visScene->InsertOrUpdateEntry(visEntry[1]);
|
||||
visScene->InsertOrUpdateEntry(visEntry[2]);
|
||||
|
||||
gatheredEntries.clear();
|
||||
visScene->Enumerate(bound1, [&gatheredEntries](const AzFramework::IVisibilityScene::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
|
||||
EXPECT_TRUE(gatheredEntries.size() == 3);
|
||||
|
||||
gatheredEntries.clear();
|
||||
visScene->Enumerate(bound2, [&gatheredEntries](const AzFramework::IVisibilityScene::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
|
||||
EXPECT_TRUE(gatheredEntries.size() == 1);
|
||||
EXPECT_TRUE(gatheredEntries[0] == &(visEntry[1]));
|
||||
|
||||
gatheredEntries.clear();
|
||||
visScene->Enumerate(bound3, [&gatheredEntries](const AzFramework::IVisibilityScene::NodeData& nodeData) { AppendEntries(gatheredEntries, nodeData); });
|
||||
EXPECT_TRUE(gatheredEntries.size() == 1);
|
||||
EXPECT_TRUE(gatheredEntries[0] == &(visEntry[0]));
|
||||
|
||||
visScene->RemoveEntry(visEntry[0]);
|
||||
visScene->RemoveEntry(visEntry[1]);
|
||||
visScene->RemoveEntry(visEntry[2]);
|
||||
gatheredEntries.clear();
|
||||
visScene->Enumerate(bound1, [&gatheredEntries](const AzFramework::IVisibilityScene::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_octreeScene, 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_octreeScene, 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_octreeScene, bound1, bound2, bound3);
|
||||
}
|
||||
|
||||
TEST_F(OctreeTests, InsertOrUpdateEntry_OverFillRootNodeWithLargeEntries_EntriesAreNotLost)
|
||||
{
|
||||
// Validate that the octree works if you exceed the max entry count with large entries,
|
||||
// which will overfill the root node since they can't be distributed to child nodes
|
||||
|
||||
// Get the max extents and entries-per-node for the octree
|
||||
AZ::IConsole* console = AZ::Interface<AZ::IConsole>::Get();
|
||||
EXPECT_TRUE(console);
|
||||
|
||||
float maxExtents = 0.0f;
|
||||
AZ::GetValueResult getCvarResult = console->GetCvarValue("bg_octreeMaxWorldExtents", maxExtents);
|
||||
EXPECT_EQ(getCvarResult, AZ::GetValueResult::Success);
|
||||
|
||||
uint32_t maxEntriesPerNode = 0;
|
||||
getCvarResult = console->GetCvarValue("bg_octreeNodeMaxEntries", maxEntriesPerNode);
|
||||
EXPECT_EQ(getCvarResult, AZ::GetValueResult::Success);
|
||||
|
||||
// Create root entries that would exceed the size of the root node
|
||||
AZ::Aabb exceedMaxExtents = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-maxExtents - 1.0f), AZ::Vector3(maxExtents + 1.0f));
|
||||
uint32_t exceedMaxEntriesPerNode = maxEntriesPerNode + 1;
|
||||
|
||||
AzFramework::VisibilityEntry visEntry;
|
||||
visEntry.m_boundingVolume = exceedMaxExtents;
|
||||
AZStd::vector<AzFramework::VisibilityEntry> visEntries(exceedMaxEntriesPerNode, visEntry);
|
||||
|
||||
// Insert them all into the scene
|
||||
for (AzFramework::VisibilityEntry& entry : visEntries)
|
||||
{
|
||||
m_octreeScene->InsertOrUpdateEntry(entry);
|
||||
}
|
||||
|
||||
// Expect all the entries to be in the scene
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, visEntries.size());
|
||||
|
||||
// Update them, without making any actual changes
|
||||
for (AzFramework::VisibilityEntry& entry : visEntries)
|
||||
{
|
||||
m_octreeScene->InsertOrUpdateEntry(entry);
|
||||
}
|
||||
|
||||
// Expect all the entries to be in the scene
|
||||
ValidateEntryCountEqualsExpectedCount(m_octreeScene, visEntries.size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN 0
|
||||
#define AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST 0
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzFrameworkTests_Traits_Android.h>
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
AzFrameworkTests_Traits_Platform.h
|
||||
AzFrameworkTests_Traits_Android.h
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN 0
|
||||
#define AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST 1
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzFrameworkTests_Traits_Linux.h>
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
AzFrameworkTests_Traits_Platform.h
|
||||
AzFrameworkTests_Traits_Linux.h
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN 0
|
||||
#define AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST 1
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzFrameworkTests_Traits_Mac.h>
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
AzFrameworkTests_Traits_Platform.h
|
||||
AzFrameworkTests_Traits_Mac.h
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzFrameworkTests_Traits_Windows.h>
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN 1
|
||||
#define AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST 1
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
AzFrameworkTests_Traits_Platform.h
|
||||
AzFrameworkTests_Traits_Windows.h
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzFrameworkTests_Traits_iOS.h>
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN 0
|
||||
#define AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST 0
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
AzFrameworkTests_Traits_Platform.h
|
||||
AzFrameworkTests_Traits_iOS.h
|
||||
)
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <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_ANDROID;
|
||||
auto platforms = AzFramework::PlatformHelper::GetPlatforms(platformFlags);
|
||||
EXPECT_EQ(platforms.size(), 2);
|
||||
EXPECT_EQ(platforms[0], "pc");
|
||||
EXPECT_EQ(platforms[1], "android");
|
||||
}
|
||||
|
||||
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", "android", "ios", "mac", "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", "android", "ios", "mac", "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,44 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Memory/AllocatorManager.h>
|
||||
#include <AzFramework/CommandLine/CommandLine.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void OutputArgs(const AzFramework::CommandLine& commandLine)
|
||||
{
|
||||
std::cout << "Switch List:" << std::endl;
|
||||
for (auto& switchPair : commandLine)
|
||||
{
|
||||
// We strip white space from all of our switch names, so "flush" names will start arguments
|
||||
if (!switchPair.m_option.empty())
|
||||
{
|
||||
std::cout << switchPair.m_option.c_str() << std::endl;
|
||||
}
|
||||
std::cout << " " << switchPair.m_value.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,249 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/CommandLine/CommandLine.h>
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzFramework/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)
|
||||
{
|
||||
AzFramework::ProcessOutput processOutput;
|
||||
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
|
||||
processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest";
|
||||
processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath();
|
||||
processLaunchInfo.m_showWindow = false;
|
||||
bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput);
|
||||
|
||||
EXPECT_EQ(launchReturn, true);
|
||||
EXPECT_EQ(processOutput.outputResult.empty(), false);
|
||||
}
|
||||
|
||||
TEST_F(ProcessLaunchParseTests, ProcessLauncher_BasicParameter_Success)
|
||||
{
|
||||
ProcessLaunchParseTests::ParsedArgMap argMap;
|
||||
AzFramework::ProcessOutput processOutput;
|
||||
AzFramework::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 = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::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;
|
||||
AzFramework::ProcessOutput processOutput;
|
||||
AzFramework::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 = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::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;
|
||||
AzFramework::ProcessOutput processOutput;
|
||||
AzFramework::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 = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::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;
|
||||
AzFramework::ProcessOutput processOutput;
|
||||
AzFramework::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 = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::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;
|
||||
AzFramework::ProcessOutput processOutput;
|
||||
AzFramework::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 = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::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,320 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
|
||||
AZ_PUSH_DISABLE_WARNING(, "-Wdelete-non-virtual-dtor")
|
||||
|
||||
#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();
|
||||
|
||||
m_sceneSystem = AzFramework::SceneSystemInterface::Get();
|
||||
}
|
||||
|
||||
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;
|
||||
AzFramework::ISceneSystem* m_sceneSystem = nullptr;
|
||||
|
||||
};
|
||||
|
||||
TEST_F(SceneTest, CreateScene)
|
||||
{
|
||||
// A scene should be able to be created with a given name.
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene("TestScene");
|
||||
EXPECT_TRUE(createSceneOutcome.IsSuccess()) << "Unable to create a scene.";
|
||||
|
||||
// The scene pointer returned should be valid
|
||||
AZStd::shared_ptr<Scene> scene = createSceneOutcome.TakeValue();
|
||||
EXPECT_NE(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 = m_sceneSystem->CreateScene("TestScene");
|
||||
EXPECT_TRUE(!createSceneOutcome.IsSuccess()) << "Should not be able to create two scenes with the same name.";
|
||||
|
||||
}
|
||||
|
||||
TEST_F(SceneTest, GetScene)
|
||||
{
|
||||
constexpr AZStd::string_view sceneName = "TestScene";
|
||||
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene(sceneName);
|
||||
AZStd::shared_ptr<Scene> createdScene = createSceneOutcome.TakeValue();
|
||||
|
||||
// Should be able to get a scene by name, and it should match the scene that was created.
|
||||
AZStd::shared_ptr<Scene> retrievedScene = m_sceneSystem->GetScene(sceneName);
|
||||
EXPECT_NE(retrievedScene, nullptr) << "Attempting to get scene by name resulted in nullptr.";
|
||||
EXPECT_EQ(retrievedScene, createdScene) << "Retrieved scene does not match created scene.";
|
||||
|
||||
// An invalid name should return a null scene.
|
||||
AZStd::shared_ptr<Scene> nullScene = m_sceneSystem->GetScene("non-existant scene");
|
||||
EXPECT_EQ(nullScene, nullptr) << "Should not be able to retrieve a scene that wasn't created.";
|
||||
}
|
||||
|
||||
TEST_F(SceneTest, RemoveScene)
|
||||
{
|
||||
constexpr AZStd::string_view sceneName = "TestScene";
|
||||
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene(sceneName);
|
||||
bool success = m_sceneSystem->RemoveScene(sceneName);
|
||||
EXPECT_TRUE(success) << "Failed to remove the scene that was just created.";
|
||||
|
||||
success = m_sceneSystem->RemoveScene("non-existant scene");
|
||||
EXPECT_FALSE(success) << "Remove scene returned success for a non-existant scene.";
|
||||
}
|
||||
|
||||
TEST_F(SceneTest, IterateActiveScenes)
|
||||
{
|
||||
constexpr size_t NumScenes = 5;
|
||||
|
||||
AZStd::shared_ptr<Scene> scenes[NumScenes] = {nullptr};
|
||||
|
||||
for (size_t i = 0; i < NumScenes; ++i)
|
||||
{
|
||||
AZStd::string sceneName = AZStd::string::format("scene %zu", i);
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene(sceneName);
|
||||
scenes[i] = createSceneOutcome.TakeValue();
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
m_sceneSystem->IterateActiveScenes([&index, &scenes](const AZStd::shared_ptr<Scene>& scene)
|
||||
{
|
||||
EXPECT_EQ(scenes[index++], scene);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SceneTest, IterateZombieScenes)
|
||||
{
|
||||
constexpr size_t NumScenes = 5;
|
||||
|
||||
AZStd::shared_ptr<Scene> scenes[NumScenes] = {nullptr};
|
||||
|
||||
// Create zombies.
|
||||
for (size_t i = 0; i < NumScenes; ++i)
|
||||
{
|
||||
AZStd::string sceneName = AZStd::string::format("scene %zu", i);
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene(sceneName);
|
||||
scenes[i] = createSceneOutcome.TakeValue();
|
||||
m_sceneSystem->RemoveScene(sceneName);
|
||||
}
|
||||
|
||||
// Check to make sure there are no more active scenes.
|
||||
size_t index = 0;
|
||||
m_sceneSystem->IterateActiveScenes([&index, &scenes](const AZStd::shared_ptr<Scene>&)
|
||||
{
|
||||
index++;
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ(0, index);
|
||||
|
||||
// Check that the scenes are still returned as zombies.
|
||||
index = 0;
|
||||
m_sceneSystem->IterateZombieScenes([&index, &scenes](Scene& scene)
|
||||
{
|
||||
EXPECT_EQ(scenes[index++].get(), &scene);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Check that all scenes are removed when there are no more handles.
|
||||
for (size_t i = 0; i < NumScenes; ++i)
|
||||
{
|
||||
scenes[i].reset();
|
||||
}
|
||||
index = 0;
|
||||
m_sceneSystem->IterateZombieScenes([&index, &scenes](Scene&) {
|
||||
index++;
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ(0, index);
|
||||
}
|
||||
|
||||
// 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<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene("TestScene");
|
||||
EXPECT_TRUE(createSceneOutcome.IsSuccess());
|
||||
AZStd::shared_ptr<Scene> scene = createSceneOutcome.TakeValue();
|
||||
|
||||
// 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->FindSubsystem<Foo1*>());
|
||||
|
||||
// Try to set the same class type twice, this should fail.
|
||||
Foo1* foo1b = new Foo1();
|
||||
EXPECT_FALSE(scene->SetSubsystem(foo1b));
|
||||
delete foo1b;
|
||||
|
||||
// Add a child scene
|
||||
createSceneOutcome = m_sceneSystem->CreateSceneWithParent("ChildScene", scene);
|
||||
EXPECT_TRUE(createSceneOutcome.IsSuccess());
|
||||
AZStd::shared_ptr<Scene> childScene = createSceneOutcome.TakeValue();
|
||||
|
||||
// Get class back from parent scene.
|
||||
EXPECT_EQ(foo1a, *childScene->FindSubsystem<Foo1*>());
|
||||
|
||||
// Find overloaded version of class on child scene.
|
||||
Foo1* foo1c = new Foo1();
|
||||
EXPECT_TRUE(childScene->SetSubsystem(foo1c));
|
||||
EXPECT_EQ(foo1c, *childScene->FindSubsystem<Foo1*>());
|
||||
|
||||
// Unset system on child scene, using alternative unset function.
|
||||
EXPECT_TRUE(childScene->UnsetSubsystem(foo1c));
|
||||
delete foo1c;
|
||||
|
||||
// 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>());
|
||||
delete foo1a;
|
||||
|
||||
// Make sure that the previously set class was really removed.
|
||||
EXPECT_EQ(nullptr, scene->FindSubsystem<Foo1*>());
|
||||
}
|
||||
} // UnitTest
|
||||
|
||||
AZ_POP_DISABLE_WARNING
|
||||
@@ -0,0 +1,957 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
#include <AzFramework/Application/Application.h>
|
||||
#include <AzFramework/Spawnable/SpawnableAssetHandler.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesManager.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class TestApplication : public AzFramework::Application
|
||||
{
|
||||
public:
|
||||
// ComponentApplication
|
||||
void SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations) override
|
||||
{
|
||||
Application::SetSettingsRegistrySpecializations(specializations);
|
||||
specializations.Append("test");
|
||||
specializations.Append("spawnable");
|
||||
}
|
||||
};
|
||||
|
||||
// Test component that has a reference to a different entity for use in validating per-instance entity id fixups.
|
||||
class ComponentWithEntityReference : public AZ::Component
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(ComponentWithEntityReference, "{CF5FDE59-86E5-40B6-9272-BBC1C4AFD061}");
|
||||
|
||||
void Activate() override
|
||||
{
|
||||
}
|
||||
|
||||
void Deactivate() override
|
||||
{
|
||||
}
|
||||
|
||||
static void Reflect(AZ::ReflectContext* reflection)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
|
||||
{
|
||||
serializeContext->Class<ComponentWithEntityReference, AZ::Component>()
|
||||
->Field("EntityReference", &ComponentWithEntityReference::m_entityReference)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
AZ::EntityId m_entityReference;
|
||||
};
|
||||
|
||||
class SpawnableEntitiesManagerTest : public AllocatorsFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
AllocatorsFixture::SetUp();
|
||||
|
||||
m_application = new TestApplication();
|
||||
AZ::ComponentApplication::Descriptor descriptor;
|
||||
m_application->Start(descriptor);
|
||||
m_application->RegisterComponentDescriptor(ComponentWithEntityReference::CreateDescriptor());
|
||||
|
||||
// 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_spawnable = aznew AzFramework::Spawnable(
|
||||
AZ::Data::AssetId::CreateString("{EB2E8A2B-F253-4A90-BBF4-55F2EED786B8}:0"), AZ::Data::AssetData::AssetStatus::Ready);
|
||||
m_spawnableAsset = new AZ::Data::Asset<AzFramework::Spawnable>(m_spawnable, AZ::Data::AssetLoadBehavior::Default);
|
||||
m_ticket = new AzFramework::EntitySpawnTicket(*m_spawnableAsset);
|
||||
|
||||
auto managerInterface = AzFramework::SpawnableEntitiesInterface::Get();
|
||||
m_manager = azrtti_cast<AzFramework::SpawnableEntitiesManager*>(managerInterface);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
delete m_ticket;
|
||||
m_ticket = nullptr;
|
||||
// One more tick on the spawnable entities manager in order to delete the ticket fully.
|
||||
while (m_manager->ProcessQueue(
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High |
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular) !=
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueueStatus::NoCommandsLeft)
|
||||
;
|
||||
|
||||
delete m_spawnableAsset;
|
||||
m_spawnableAsset = nullptr;
|
||||
// This will also delete m_spawnable.
|
||||
|
||||
delete m_application;
|
||||
m_application = nullptr;
|
||||
|
||||
AllocatorsFixture::TearDown();
|
||||
}
|
||||
|
||||
void FillSpawnable(size_t numElements)
|
||||
{
|
||||
AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities();
|
||||
entities.clear();
|
||||
entities.reserve(numElements);
|
||||
for (size_t i=0; i<numElements; ++i)
|
||||
{
|
||||
entities.push_back(AZStd::make_unique<AZ::Entity>());
|
||||
}
|
||||
}
|
||||
|
||||
void CreateRecursiveHierarchy()
|
||||
{
|
||||
AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities();
|
||||
size_t numElements = entities.size();
|
||||
AZ::EntityId parent;
|
||||
for (size_t i=0; i<numElements; ++i)
|
||||
{
|
||||
AZStd::unique_ptr<AZ::Entity>& entity = entities[i];
|
||||
auto component = entity->CreateComponent<AzFramework::TransformComponent>();
|
||||
if (i > 0)
|
||||
{
|
||||
component->SetParent(parent);
|
||||
}
|
||||
parent = entity->GetId();
|
||||
}
|
||||
}
|
||||
|
||||
void CreateSingleParent()
|
||||
{
|
||||
AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities();
|
||||
size_t numElements = entities.size();
|
||||
if (numElements > 0)
|
||||
{
|
||||
AZ::EntityId parent = entities[0]->GetId();
|
||||
for (size_t i = 0; i < numElements; ++i)
|
||||
{
|
||||
AZStd::unique_ptr<AZ::Entity>& entity = entities[i];
|
||||
auto component = entity->CreateComponent<AzFramework::TransformComponent>();
|
||||
if (i > 0)
|
||||
{
|
||||
component->SetParent(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class EntityReferenceScheme
|
||||
{
|
||||
AllReferenceFirst,
|
||||
AllReferenceLast,
|
||||
AllReferenceThemselves,
|
||||
AllReferenceNextCircular,
|
||||
AllReferencePreviousCircular
|
||||
};
|
||||
|
||||
void CreateEntityReferences(EntityReferenceScheme refScheme)
|
||||
{
|
||||
AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities();
|
||||
size_t numElements = entities.size();
|
||||
for (size_t i = 0; i < numElements; ++i)
|
||||
{
|
||||
AZStd::unique_ptr<AZ::Entity>& entity = entities[i];
|
||||
auto component = entity->CreateComponent<ComponentWithEntityReference>();
|
||||
switch (refScheme)
|
||||
{
|
||||
case EntityReferenceScheme::AllReferenceFirst :
|
||||
component->m_entityReference = entities[0]->GetId();
|
||||
break;
|
||||
case EntityReferenceScheme::AllReferenceLast:
|
||||
component->m_entityReference = entities[numElements - 1]->GetId();
|
||||
break;
|
||||
case EntityReferenceScheme::AllReferenceThemselves:
|
||||
component->m_entityReference = entities[i]->GetId();
|
||||
break;
|
||||
case EntityReferenceScheme::AllReferenceNextCircular:
|
||||
component->m_entityReference = entities[(i + 1) % numElements]->GetId();
|
||||
break;
|
||||
case EntityReferenceScheme::AllReferencePreviousCircular:
|
||||
component->m_entityReference = entities[(i + numElements - 1) % numElements]->GetId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the entity references are pointing to the correct other entities within the same spawn batch.
|
||||
// A "spawn batch" is the set of entities produced for each SpawnAllEntities command.
|
||||
void ValidateEntityReferences(
|
||||
EntityReferenceScheme refScheme, size_t entitiesPerBatch, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
size_t numElements = entities.size();
|
||||
|
||||
for (size_t i = 0; i < numElements; ++i)
|
||||
{
|
||||
// Calculate the element offset that's the start of each batch of entities spawned.
|
||||
size_t curSpawnBatch = i / entitiesPerBatch;
|
||||
size_t curBatchOffset = curSpawnBatch * entitiesPerBatch;
|
||||
size_t curBatchIndex = i - curBatchOffset;
|
||||
|
||||
const AZ::Entity* const entity = *(entities.begin() + i);
|
||||
|
||||
auto component = entity->FindComponent<ComponentWithEntityReference>();
|
||||
ASSERT_NE(nullptr, component);
|
||||
AZ::EntityId comparisonId;
|
||||
// Ids should be local to a batch, so each of these will be compared within a batch of entities, not globally across
|
||||
// the entire set.
|
||||
switch (refScheme)
|
||||
{
|
||||
case EntityReferenceScheme::AllReferenceFirst:
|
||||
// Compare against the first entity in each batch
|
||||
comparisonId = (*(entities.begin() + curBatchOffset))->GetId();
|
||||
break;
|
||||
case EntityReferenceScheme::AllReferenceLast:
|
||||
// Compare against the last entity in each batch
|
||||
comparisonId = (*(entities.begin() + curBatchOffset + (entitiesPerBatch - 1)))->GetId();
|
||||
break;
|
||||
case EntityReferenceScheme::AllReferenceThemselves:
|
||||
// Compare against itself
|
||||
comparisonId = entity->GetId();
|
||||
break;
|
||||
case EntityReferenceScheme::AllReferenceNextCircular:
|
||||
// Compare against the next entity in each batch, looping around so that the last entity in the batch should refer
|
||||
// to the first entity in the batch.
|
||||
comparisonId = (*(entities.begin() + curBatchOffset + ((curBatchIndex + 1) % entitiesPerBatch)))->GetId();
|
||||
break;
|
||||
case EntityReferenceScheme::AllReferencePreviousCircular:
|
||||
// Compare against the previous entity in each batch, looping around so that the first entity in the batch should refer
|
||||
// to the last entity in the batch.
|
||||
comparisonId = (*(entities.begin() + curBatchOffset + ((curBatchIndex + numElements - 1) % entitiesPerBatch)))->GetId();
|
||||
break;
|
||||
}
|
||||
EXPECT_EQ(comparisonId, component->m_entityReference);
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
AZ::Data::Asset<AzFramework::Spawnable>* m_spawnableAsset { nullptr };
|
||||
AzFramework::SpawnableEntitiesManager* m_manager { nullptr };
|
||||
AzFramework::EntitySpawnTicket* m_ticket { nullptr };
|
||||
AzFramework::Spawnable* m_spawnable { nullptr };
|
||||
TestApplication* m_application { nullptr };
|
||||
};
|
||||
|
||||
//
|
||||
// SpawnAllEntitities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_Call_AllEntitiesSpawned)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback =
|
||||
[&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(NumEntities, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_SetParentOnSpawnedEntities_LineageIsPreserved)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
CreateRecursiveHierarchy();
|
||||
|
||||
auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
AZ::EntityId parentId;
|
||||
bool isFirst = true;
|
||||
for (const AZ::Entity* entity : entities)
|
||||
{
|
||||
if (!isFirst)
|
||||
{
|
||||
auto transform = entity->GetTransform();
|
||||
ASSERT_NE(nullptr, transform);
|
||||
EXPECT_EQ(parentId, transform->GetParentId());
|
||||
}
|
||||
else
|
||||
{
|
||||
isFirst = false;
|
||||
}
|
||||
parentId = entity->GetId();
|
||||
}
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllEntitiesReferenceOtherEntities_EntityIdsAreMappedCorrectly)
|
||||
{
|
||||
// This tests that entity id references get mapped correctly in a SpawnAllEntities call whether they're forward referencing
|
||||
// in the list, backwards referencing, or self-referencing. The circular tests are to ensure the implementation works regardless
|
||||
// of entity ordering.
|
||||
for (EntityReferenceScheme refScheme : {
|
||||
EntityReferenceScheme::AllReferenceFirst, EntityReferenceScheme::AllReferenceLast,
|
||||
EntityReferenceScheme::AllReferenceThemselves, EntityReferenceScheme::AllReferenceNextCircular,
|
||||
EntityReferenceScheme::AllReferencePreviousCircular })
|
||||
{
|
||||
constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
CreateEntityReferences(refScheme);
|
||||
|
||||
auto callback = [this, refScheme, NumEntities]
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
ValidateEntityReferences(refScheme, NumEntities, entities);
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllEntitiesReferenceOtherEntities_EntityIdsOnlyReferWithinASingleCall)
|
||||
{
|
||||
// This tests that entity id references get mapped correctly with multiple SpawnAllEntities calls. Each call should only map
|
||||
// the entities to other entities within the same call, regardless of forward or backward mapping.
|
||||
// For example, suppose entities 1, 2, and 3 refer to 4. In the first SpawnAllEntities call, entities 1-3 will refer to 4.
|
||||
// In the second SpawnAllEntities call, entities 1-3 will refer to the second 4, not the previously-spawned 4.
|
||||
for (EntityReferenceScheme refScheme :
|
||||
{ EntityReferenceScheme::AllReferenceFirst, EntityReferenceScheme::AllReferenceLast,
|
||||
EntityReferenceScheme::AllReferenceThemselves, EntityReferenceScheme::AllReferenceNextCircular,
|
||||
EntityReferenceScheme::AllReferencePreviousCircular
|
||||
})
|
||||
{
|
||||
// Make sure we start with a fresh ticket each time, or else each iteration through this loop would continue to build up
|
||||
// more and more entities.
|
||||
delete m_ticket;
|
||||
m_ticket = new AzFramework::EntitySpawnTicket(*m_spawnableAsset);
|
||||
|
||||
constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
CreateEntityReferences(refScheme);
|
||||
|
||||
auto callback = [this, refScheme, NumEntities]
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
ValidateEntityReferences(refScheme, NumEntities, entities);
|
||||
};
|
||||
|
||||
// Spawn twice.
|
||||
constexpr size_t NumSpawnAllCalls = 2;
|
||||
for (int spawns = 0; spawns < NumSpawnAllCalls; spawns++)
|
||||
{
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
}
|
||||
|
||||
m_manager->ListEntities(*m_ticket, callback);
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->SpawnAllEntities(ticket);
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// SpawnEntities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_Call_AllEntitiesSpawned)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(NumEntities, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_SpawnTheSameEntity_AllEntitiesSpawned)
|
||||
{
|
||||
static constexpr size_t NumEntities = 1;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 0 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback =
|
||||
[&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(NumEntities * 2, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_MultipleSpawns_AllEntitiesSpawned)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback =
|
||||
[&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, indices, optionalArgs);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(NumEntities * 2, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_ReferencesAreRemappedForNewBatch_AllPointToLatestParent)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
CreateSingleParent();
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 1, 2, 3 };
|
||||
AZStd::vector<AZ::EntityId> parents;
|
||||
|
||||
auto callback = [&parents](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
AZ::EntityId parent = (*entities.begin())->GetId();
|
||||
parents.push_back(parent);
|
||||
auto it = entities.begin();
|
||||
++it; // Skip the first as that is the parent.
|
||||
for (; it != entities.end(); ++it)
|
||||
{
|
||||
AZ::TransformInterface* transform = (*it)->GetTransform();
|
||||
ASSERT_NE(nullptr, transform);
|
||||
ASSERT_EQ(parent, transform->GetParentId());
|
||||
}
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
optionalArgs.m_referencePreviouslySpawnedEntities = false;
|
||||
m_manager->SpawnEntities(*m_ticket, indices, optionalArgs);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_NE(parents[0], parents[1]);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_ReferencesAreRemappedForContinuedBatch_AllPointToLatestParent)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
CreateSingleParent();
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 1, 2, 3 };
|
||||
AZStd::vector<AZ::EntityId> parents;
|
||||
|
||||
auto callback =
|
||||
[&parents](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
AZ::EntityId parent = (*entities.begin())->GetId();
|
||||
parents.push_back(parent);
|
||||
auto it = entities.begin();
|
||||
++it; // Skip the first as that is the parent.
|
||||
for (; it!=entities.end(); ++it)
|
||||
{
|
||||
AZ::TransformInterface* transform = (*it)->GetTransform();
|
||||
ASSERT_NE(nullptr, transform);
|
||||
ASSERT_EQ(parent, transform->GetParentId());
|
||||
}
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
optionalArgs.m_referencePreviouslySpawnedEntities = true;
|
||||
m_manager->SpawnEntities(*m_ticket, indices, optionalArgs);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_NE(parents[0], parents[1]);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_ReferencesAreRemappedAcrossBatches_AllPointToLatestParent)
|
||||
{
|
||||
FillSpawnable(4);
|
||||
CreateSingleParent();
|
||||
|
||||
// Spawn a regular batch but with two parents and store the id of the last entity. This will the parent for the next batch.
|
||||
AZ::EntityId parent;
|
||||
auto getParent = [&parent](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
ASSERT_NE(entities.begin(), entities.end());
|
||||
parent = (*AZStd::prev(entities.end()))->GetId();
|
||||
};
|
||||
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgsFirstBatch;
|
||||
optionalArgsFirstBatch.m_completionCallback = AZStd::move(getParent);
|
||||
optionalArgsFirstBatch.m_referencePreviouslySpawnedEntities = true;
|
||||
m_manager->SpawnEntities(*m_ticket, {0, 1, 2, 3, 0}, AZStd::move(optionalArgsFirstBatch));
|
||||
|
||||
// Next, spawn all the entities that have a reference to the parent that was just stored.
|
||||
auto parentCheck = [&parent](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
for (auto& it : entities)
|
||||
{
|
||||
AZ::TransformInterface* transform = it->GetTransform();
|
||||
ASSERT_NE(nullptr, transform);
|
||||
ASSERT_EQ(parent, transform->GetParentId());
|
||||
}
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgsSecondBatch;
|
||||
optionalArgsSecondBatch.m_completionCallback = AZStd::move(parentCheck);
|
||||
optionalArgsSecondBatch.m_referencePreviouslySpawnedEntities = true;
|
||||
m_manager->SpawnEntities(*m_ticket, {1, 2, 3}, AZStd::move(optionalArgsSecondBatch));
|
||||
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllEntitiesReferenceOtherEntities_ForwardReferencesWorkInSingleCall)
|
||||
{
|
||||
constexpr EntityReferenceScheme refScheme = EntityReferenceScheme::AllReferenceNextCircular;
|
||||
constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
CreateEntityReferences(refScheme);
|
||||
|
||||
auto callback =
|
||||
[this, refScheme, NumEntities](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
ValidateEntityReferences(refScheme, NumEntities, entities);
|
||||
};
|
||||
|
||||
// Verify that by default, entities that refer to other entities that haven't been spawned yet have the correct references
|
||||
// when the spawning all occurs in the same call
|
||||
m_manager->SpawnEntities(*m_ticket, { 0, 1, 2, 3 });
|
||||
m_manager->ListEntities(*m_ticket, callback);
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllEntitiesReferenceOtherEntities_ForwardReferencesWorkAcrossCalls)
|
||||
{
|
||||
constexpr EntityReferenceScheme refScheme = EntityReferenceScheme::AllReferenceNextCircular;
|
||||
constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
CreateEntityReferences(refScheme);
|
||||
|
||||
auto callback =
|
||||
[this, refScheme, NumEntities](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
ValidateEntityReferences(refScheme, NumEntities, entities);
|
||||
};
|
||||
|
||||
// Verify that by default, entities that refer to other entities that haven't been spawned yet have the correct references
|
||||
// even when the spawning is across multiple calls
|
||||
m_manager->SpawnEntities(*m_ticket, { 0 });
|
||||
m_manager->SpawnEntities(*m_ticket, { 1 });
|
||||
m_manager->SpawnEntities(*m_ticket, { 2 });
|
||||
m_manager->SpawnEntities(*m_ticket, { 3 });
|
||||
m_manager->ListEntities(*m_ticket, callback);
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllEntitiesReferenceOtherEntities_ReferencesPointToFirstOrLatest)
|
||||
{
|
||||
// With SpawnEntities, entity references should either refer to the first entity that *will* be spawned, or the last entity
|
||||
// that *has* been spawned. This test will create entities 0 1 2 3 that all refer to entity 3, and it will create two batches
|
||||
// of those. In the first batch, they'll forward-reference. In the second batch, they should backward-reference, except for
|
||||
// the second entity 3, which will now refer to itself as the last one that's been spawned.
|
||||
constexpr EntityReferenceScheme refScheme = EntityReferenceScheme::AllReferenceLast;
|
||||
constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
CreateEntityReferences(refScheme);
|
||||
|
||||
auto callback =
|
||||
[this, refScheme, NumEntities](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
size_t numElements = entities.size();
|
||||
|
||||
for (size_t i = 0; i < numElements; ++i)
|
||||
{
|
||||
const AZ::Entity* const entity = *(entities.begin() + i);
|
||||
|
||||
auto component = entity->FindComponent<ComponentWithEntityReference>();
|
||||
ASSERT_NE(nullptr, component);
|
||||
AZ::EntityId comparisonId;
|
||||
if (i < (numElements - 1))
|
||||
{
|
||||
// There are two batches of NumEntities elements. Every entity should either forward-reference or backward-reference
|
||||
// to the last entity of the first batch, except for the very last entity of the second batch, which should reference
|
||||
// itself.
|
||||
comparisonId = (*(entities.begin() + (NumEntities- 1)))->GetId();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The very last entity of the second batch should reference itself because it's now the latest instance of that
|
||||
// entity to be spawned.
|
||||
comparisonId = entity->GetId();
|
||||
}
|
||||
|
||||
EXPECT_EQ(comparisonId, component->m_entityReference);
|
||||
}
|
||||
};
|
||||
|
||||
// Create 2 batches of forward references. In the first batch, entities 0 1 2 will point forward to 3. In the second batch,
|
||||
// entities 0 1 2 will point *backward* to the first 3, and the second entity 3 will point to itself.
|
||||
m_manager->SpawnEntities(*m_ticket, { 0, 1, 2, 3 });
|
||||
m_manager->SpawnEntities(*m_ticket, { 0, 1, 2, 3 });
|
||||
m_manager->ListEntities(*m_ticket, callback);
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllEntitiesReferenceOtherEntities_MultipleSpawnsInSameCallReferenceCorrectly)
|
||||
{
|
||||
// With SpawnEntities, entity references should either refer to the first entity that *will* be spawned, or the last entity
|
||||
// that *has* been spawned. This test will create entities 0 1 2 3 that all refer to entity 3, and it will create three sets
|
||||
// of those in the same call, with the following results:
|
||||
// - The first 0 1 2 will forward-reference to the first 3
|
||||
// - The first 3 will reference itself
|
||||
// - The second 0 1 2 will backwards-reference to the first 3
|
||||
// - The second 3 will reference itself
|
||||
// - The third 0 1 2 will backwards-reference to the second 3
|
||||
// - The third 3 will reference itself
|
||||
constexpr EntityReferenceScheme refScheme = EntityReferenceScheme::AllReferenceLast;
|
||||
constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
CreateEntityReferences(refScheme);
|
||||
|
||||
auto callback =
|
||||
[this, refScheme, NumEntities](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
size_t numElements = entities.size();
|
||||
|
||||
for (size_t i = 0; i < numElements; ++i)
|
||||
{
|
||||
const AZ::Entity* const entity = *(entities.begin() + i);
|
||||
|
||||
auto component = entity->FindComponent<ComponentWithEntityReference>();
|
||||
ASSERT_NE(nullptr, component);
|
||||
AZ::EntityId comparisonId;
|
||||
|
||||
if (i < ((NumEntities * 2) - 1))
|
||||
{
|
||||
// The first 7 entities (0 1 2 3 0 1 2) will all refer to the 4th one (1st '3').
|
||||
comparisonId = (*(entities.begin() + (NumEntities - 1)))->GetId();
|
||||
}
|
||||
else if (i < (numElements - 1))
|
||||
{
|
||||
// The next 4 entities (3 0 1 2) will all refer to the 8th one (2nd '3').
|
||||
comparisonId = (*(entities.begin() + ((NumEntities * 2) - 1)))->GetId();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The very last entity (3) will reference itself (3rd '3').
|
||||
comparisonId = entity->GetId();
|
||||
}
|
||||
|
||||
EXPECT_EQ(comparisonId, component->m_entityReference);
|
||||
}
|
||||
};
|
||||
|
||||
// Create the 3 batches of entities 0, 1, 2, 3. The entity references should work as described at the top of the test.
|
||||
m_manager->SpawnEntities(*m_ticket, { 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3 });
|
||||
m_manager->ListEntities(*m_ticket, callback);
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllEntitiesReferenceOtherEntities_OptionalFlagClearsReferenceMap)
|
||||
{
|
||||
constexpr EntityReferenceScheme refScheme = EntityReferenceScheme::AllReferenceLast;
|
||||
constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
CreateEntityReferences(refScheme);
|
||||
|
||||
auto callback =
|
||||
[this, refScheme, NumEntities](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
ValidateEntityReferences(refScheme, NumEntities, entities);
|
||||
};
|
||||
|
||||
// By setting the "referencePreviouslySpawnedEntities" flag to false, the map will get cleared on each call, so in both batches
|
||||
// the entities will forward-reference to the last entity in the batch. If the flag were true, entities 0 1 2 in the second
|
||||
// batch would refer backwards to the first entity 3.
|
||||
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgsSecondBatch;
|
||||
optionalArgsSecondBatch.m_completionCallback = AZStd::move(callback);
|
||||
optionalArgsSecondBatch.m_referencePreviouslySpawnedEntities = false;
|
||||
|
||||
m_manager->SpawnEntities(*m_ticket, { 0, 1, 2, 3 }, optionalArgsSecondBatch);
|
||||
m_manager->SpawnEntities(*m_ticket, { 0, 1, 2, 3 }, AZStd::move(optionalArgsSecondBatch));
|
||||
m_manager->ListEntities(*m_ticket, callback);
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->SpawnEntities(ticket, {/* Deliberate empty list of indices. */});
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// DespawnAllEntities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, DespawnAllEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->DespawnAllEntities(ticket);
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ReloadSpawnable
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ReloadSpawnable_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->ReloadSpawnable(ticket, *m_spawnableAsset);
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ListEntitities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ListEntities_Call_AllEntitiesAreReported)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
bool allValidEntityIds = true;
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&allValidEntityIds, &spawnedEntitiesCount]
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
for (auto&& entity : entities)
|
||||
{
|
||||
allValidEntityIds = entity->GetId().IsValid() && allValidEntityIds;
|
||||
}
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
|
||||
m_manager->SpawnAllEntities(*m_ticket);
|
||||
m_manager->ListEntities(*m_ticket, AZStd::move(callback));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_TRUE(allValidEntityIds);
|
||||
EXPECT_EQ(NumEntities, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ListEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView) {};
|
||||
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->ListEntities(ticket, AZStd::move(callback));
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ListIndicesAndEntities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ListIndicesAndEntities_Call_AllEntitiesAreReportedAndIncrementByOne)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
bool allValidEntityIds = true;
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&allValidEntityIds, &spawnedEntitiesCount]
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstIndexEntityContainerView entities)
|
||||
{
|
||||
for (auto&& indexEntityPair : entities)
|
||||
{
|
||||
// Since all entities are spawned a single time, the indices should be 0..NumEntities.
|
||||
if (indexEntityPair.GetIndex() == spawnedEntitiesCount)
|
||||
{
|
||||
spawnedEntitiesCount++;
|
||||
}
|
||||
allValidEntityIds = indexEntityPair.GetEntity()->GetId().IsValid() && allValidEntityIds;
|
||||
}
|
||||
};
|
||||
|
||||
m_manager->SpawnAllEntities(*m_ticket);
|
||||
m_manager->ListIndicesAndEntities(*m_ticket, AZStd::move(callback));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_TRUE(allValidEntityIds);
|
||||
EXPECT_EQ(NumEntities, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ListIndicesAndEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstIndexEntityContainerView) {};
|
||||
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->ListIndicesAndEntities(ticket, AZStd::move(callback));
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ClaimEntities
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, ClaimEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView) {};
|
||||
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->ClaimEntities(ticket, AZStd::move(callback));
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Barrier
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, Barrier_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
auto callback = [](AzFramework::EntitySpawnTicket::Id) {};
|
||||
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset);
|
||||
m_manager->Barrier(ticket, AZStd::move(callback));
|
||||
}
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Misc. - Priority tests
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, Priority_HighBeforeDefault_HigherPriorityCallHappensBeforeDefaultPriorityEvenWhenQueuedLater)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AzFramework::EntitySpawnTicket highPriorityTicket(*m_spawnableAsset);
|
||||
|
||||
size_t callCounter = 1;
|
||||
size_t highPriorityCallId = 0;
|
||||
size_t defaultPriorityCallId = 0;
|
||||
auto highCallback = [&callCounter, &highPriorityCallId]
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView)
|
||||
{
|
||||
highPriorityCallId = callCounter++;
|
||||
};
|
||||
auto defaultCallback = [&callCounter, &defaultPriorityCallId]
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView)
|
||||
{
|
||||
defaultPriorityCallId = callCounter++;
|
||||
};
|
||||
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(defaultCallback);
|
||||
optionalArgs.m_priority = AzFramework::SpawnablePriority_Default;
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs highPriortyOptionalArgs;
|
||||
highPriortyOptionalArgs.m_completionCallback = AZStd::move(highCallback);
|
||||
highPriortyOptionalArgs.m_priority = AzFramework::SpawnablePriority_High;
|
||||
m_manager->SpawnAllEntities(highPriorityTicket, AZStd::move(highPriortyOptionalArgs));
|
||||
|
||||
m_manager->ProcessQueue(
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High |
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_LT(highPriorityCallId, defaultPriorityCallId);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, Priority_SameTicket_DefaultPriorityCallHappensBeforeHighPriority)
|
||||
{
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
size_t callCounter = 1;
|
||||
size_t highPriorityCallId = 0;
|
||||
size_t defaultPriorityCallId = 0;
|
||||
auto highCallback =
|
||||
[&callCounter, &highPriorityCallId](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView)
|
||||
{
|
||||
highPriorityCallId = callCounter++;
|
||||
};
|
||||
auto defaultCallback =
|
||||
[&callCounter, &defaultPriorityCallId](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView)
|
||||
{
|
||||
defaultPriorityCallId = callCounter++;
|
||||
};
|
||||
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(defaultCallback);
|
||||
optionalArgs.m_priority = AzFramework::SpawnablePriority_Default;
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs highPriortyOptionalArgs;
|
||||
highPriortyOptionalArgs.m_completionCallback = AZStd::move(highCallback);
|
||||
highPriortyOptionalArgs.m_priority = AzFramework::SpawnablePriority_High;
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(highPriortyOptionalArgs));
|
||||
|
||||
m_manager->ProcessQueue(
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High |
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
// Run a second time as the high priority task will be pending at this point.
|
||||
m_manager->ProcessQueue(
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High |
|
||||
AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_LT(defaultPriorityCallId, highPriorityCallId);
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "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::fixed_string<512>>(false, false);
|
||||
AZ::IO::FixedMaxPath 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::SystemFile::CreateDir(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::SystemFile::DeleteDir(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
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/Math/Uuid.h>
|
||||
#include <AzCore/IO/Path/Path.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 };
|
||||
AZ::IO::FixedMaxPath m_tempDirectory;
|
||||
#if !AZ_TRAIT_USE_POSIX_TEMP_FOLDER
|
||||
std::filesystem::path m_path;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Mocks/MockSpawnableEntitiesInterface.h
|
||||
Utils/Utils.h
|
||||
Utils/Utils.cpp
|
||||
FrameworkApplicationFixture.h
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
../../AzCore/Tests/Main.cpp
|
||||
Spawnable/SpawnableEntitiesManagerTests.cpp
|
||||
ArchiveCompressionTests.cpp
|
||||
ArchiveTests.cpp
|
||||
BehaviorEntityTests.cpp
|
||||
BinToTextEncode.cpp
|
||||
CameraInputTests.cpp
|
||||
ClickDetectorTests.cpp
|
||||
CursorStateTests.cpp
|
||||
EntityContext.cpp
|
||||
FileIO.cpp
|
||||
FileTagTests.cpp
|
||||
GenAppDescriptors.cpp
|
||||
OctreePerformanceTests.cpp
|
||||
OctreeTests.cpp
|
||||
AssetCatalog.cpp
|
||||
AssetProcessorConnection.cpp
|
||||
NativeWindow.cpp
|
||||
ProcessLaunchParseTests.cpp
|
||||
Application.cpp
|
||||
PlatformHelper.cpp
|
||||
Scene.cpp
|
||||
CameraState.cpp
|
||||
InputTests.cpp
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
ProcessLaunchMain.cpp
|
||||
)
|
||||
Reference in New Issue
Block a user