e638f27572
* Fixed PathView `MakeRelativeTo` and `Append` functions path segment comparisons when using the Windows path separator of '\' The PathSegment comparisons were case-sensitive in both those functions and now use `Internal::ComparePathSegments` function to perform the appropriate case comparison based on the path separator value of the Path class Reverted the LocalFileIO::CheckInvalidWrite function back to not lowercasing the assets alias and input path before invoking `PathView::IsRelativeTo` Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Simplified the LocalFileIO::ConvertToAliasBuffer logic Fix for the ArchiveTest `IResourceList_Add_AbsolutePath_RemovesAndReplacesWithAlias` and `TestArchiveViaFileIO` test Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Added a PathIterable structure stores a non-heap container of normalized path segments of an input path. Moved the PathParser logic to a PathParser.inl file Removed dependency of the PathView::IsRelativeTo logic on FixedMaxPath There is no longer a 1024 character limit when determining if a path is relative to a base Added a GetNormalPathParts and AppendNormalPathParts to function and removed LexicallyNormalInplace to share the logic for creating a normalized path between IsRelativeTo and LexicallyNormal Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Clang PathIterable.inl build fix Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Fixed Normalize and Relative Path functions initialize the result paths With the correct path separator for the paths being transformed Ported over the Custom Path Root Separator logic to the PathParser.inl Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated he Shader Preprocessor include path gather. It now uses AZ::IO::Path for the path operations and checks if the path exist before adding it to the list of include paths. Finally the set logic has been removed for a simpler find_if check to see if the include path already since in the project include paths This fixes the Asset Processing issues with shader includes due to the Path.inl changes Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Fixed tail recursion call to AppendNormalPathParts to supply a PathView with the same path separator as the parent call Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Adding reference qualifier overloads to the Path class Native function Removed the conversion operators from the Path class for converting to a string_type&/const string_type& Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
1059 lines
50 KiB
C++
1059 lines
50 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <AzTest/AzTest.h>
|
|
#include <AzCore/Console/IConsole.h>
|
|
#include <AzCore/UnitTest/TestTypes.h>
|
|
#include <AzCore/UnitTest/UnitTest.h>
|
|
|
|
#include <AzCore/IO/SystemFile.h> // for max path decl
|
|
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
|
#include <AzCore/std/parallel/thread.h>
|
|
#include <AzCore/std/parallel/semaphore.h>
|
|
#include <AzCore/std/functional.h> // for function<> in the find files callback.
|
|
#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/ArchiveVars.h>
|
|
#include <AzFramework/Archive/INestedArchive.h>
|
|
|
|
namespace UnitTest
|
|
{
|
|
class ArchiveTestFixture
|
|
: public ScopedAllocatorSetupFixture
|
|
{
|
|
public:
|
|
ArchiveTestFixture()
|
|
: m_application{ AZStd::make_unique<AzFramework::Application>() }
|
|
{
|
|
}
|
|
|
|
void SetUp() override
|
|
{
|
|
AZ::ComponentApplication::Descriptor descriptor;
|
|
descriptor.m_stackRecordLevels = 30;
|
|
|
|
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(descriptor);
|
|
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
|
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
|
|
// in the unit tests.
|
|
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
|
|
}
|
|
|
|
void TearDown() override
|
|
{
|
|
m_application->Stop();
|
|
}
|
|
|
|
protected:
|
|
bool IsPackValid(const char* path)
|
|
{
|
|
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
|
|
if (!archive)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return archive->OpenPack(path, AZ::IO::IArchive::FLAGS_PATH_REAL) && archive->ClosePack(path);
|
|
}
|
|
|
|
template <class Function>
|
|
void RunConcurrentUnitTest(AZ::u32 numIterations, AZ::u32 numThreads, Function testFunction)
|
|
{
|
|
AZStd::atomic_int successCount{};
|
|
constexpr size_t maxTestThreads = 16;
|
|
|
|
for (AZ::u32 testIteration = 0; testIteration < numIterations; ++testIteration)
|
|
{
|
|
AZStd::fixed_vector<AZStd::thread, maxTestThreads> testThreads;
|
|
successCount = 0;
|
|
|
|
for (AZ::u32 threadIdx = 0; threadIdx < numThreads; ++threadIdx)
|
|
{
|
|
auto threadFunctor = [&testFunction, &successCount]()
|
|
{
|
|
// Add some variability to thread timing by yielding each thread
|
|
AZStd::this_thread::yield();
|
|
|
|
if (testFunction())
|
|
{
|
|
++successCount;
|
|
}
|
|
};
|
|
testThreads.emplace_back(threadFunctor);
|
|
}
|
|
|
|
for (AZStd::thread& testThread : testThreads)
|
|
{
|
|
testThread.join();
|
|
}
|
|
|
|
EXPECT_EQ(numThreads, successCount);
|
|
}
|
|
}
|
|
|
|
void TestFGetCachedFileData(const char* testFilePath, size_t dataLen, const char* testData)
|
|
{
|
|
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
|
|
ASSERT_NE(nullptr, archive);
|
|
|
|
constexpr uint32_t numThreadedIterations = 1;
|
|
constexpr uint32_t numTestThreads = 5;
|
|
|
|
{
|
|
// Canary tests first
|
|
AZ::IO::HandleType fileHandle = archive->FOpen(testFilePath, "rb", 0);
|
|
|
|
ASSERT_NE(AZ::IO::InvalidHandle, fileHandle);
|
|
|
|
size_t fileSize = 0;
|
|
char* pFileBuffer = (char*)archive->FGetCachedFileData(fileHandle, fileSize);
|
|
ASSERT_NE(nullptr, pFileBuffer);
|
|
EXPECT_EQ(dataLen, fileSize);
|
|
EXPECT_EQ(0, memcmp(pFileBuffer, testData, dataLen));
|
|
|
|
// 2nd call to FGetCachedFileData, same file handle
|
|
fileSize = 0;
|
|
char* pFileBuffer2 = (char*)archive->FGetCachedFileData(fileHandle, fileSize);
|
|
EXPECT_NE(nullptr, pFileBuffer2);
|
|
EXPECT_EQ(pFileBuffer, pFileBuffer2);
|
|
EXPECT_EQ(dataLen, fileSize);
|
|
|
|
// open already open file and call FGetCachedFileData
|
|
fileSize = 0;
|
|
{
|
|
AZ::IO::HandleType fileHandle2 = archive->FOpen(testFilePath, "rb", 0);
|
|
char* pFileBuffer3 = (char*)archive->FGetCachedFileData(fileHandle2, fileSize);
|
|
ASSERT_NE(nullptr,pFileBuffer3);
|
|
EXPECT_EQ(dataLen, fileSize);
|
|
EXPECT_EQ(0, memcmp(pFileBuffer3, testData, dataLen));
|
|
archive->FClose(fileHandle2);
|
|
}
|
|
|
|
// Multithreaded test #1 reading from the same file handle in parallel
|
|
auto parallelArchiveFileReadFunc = [archive, fileHandle, pFileBuffer, dataLen, testFilePath]()
|
|
{
|
|
size_t fileSize = 0;
|
|
auto pFileBufferThread = reinterpret_cast<const char*>(archive->FGetCachedFileData(fileHandle, fileSize));
|
|
if (pFileBufferThread == nullptr)
|
|
{
|
|
EXPECT_NE(nullptr, pFileBufferThread) << "FGetCachedFileData returned nullptr for file " << testFilePath;
|
|
return false;
|
|
}
|
|
if (pFileBuffer != pFileBufferThread)
|
|
{
|
|
EXPECT_EQ(pFileBufferThread, pFileBuffer) << "Read file data for file " << testFilePath << "Does not match expected file data";
|
|
return false;
|
|
}
|
|
if (fileSize != dataLen)
|
|
{
|
|
EXPECT_EQ(dataLen, fileSize) << "Read filesize does not match expected filesize for file " << testFilePath;
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
RunConcurrentUnitTest(numThreadedIterations, numTestThreads, parallelArchiveFileReadFunc);
|
|
|
|
archive->FClose(fileHandle);
|
|
}
|
|
|
|
// Multithreaded Test #2 reading from the same file concurrently
|
|
auto concurrentArchiveFileReadFunc = [archive, testFilePath, dataLen, testData]()
|
|
{
|
|
AZ::IO::HandleType threadFileHandle = archive->FOpen(testFilePath, "rb", 0);
|
|
|
|
if (threadFileHandle == AZ::IO::InvalidHandle)
|
|
{
|
|
EXPECT_NE(AZ::IO::InvalidHandle, threadFileHandle) << "Failed to open file handle " << testFilePath;
|
|
return false;
|
|
}
|
|
size_t fileSize = 0;
|
|
auto pFileBufferThread = reinterpret_cast<const char*>(archive->FGetCachedFileData(threadFileHandle, fileSize));
|
|
if (pFileBufferThread == nullptr)
|
|
{
|
|
EXPECT_NE(nullptr, pFileBufferThread) << "FGetCachedFileData returned nullptr for file " << testFilePath;
|
|
return false;
|
|
}
|
|
if (fileSize != dataLen)
|
|
{
|
|
EXPECT_EQ(dataLen, fileSize) << "Read filesize does not match expected filesize for file " << testFilePath;
|
|
return false;
|
|
}
|
|
if (memcmp(pFileBufferThread, testData, dataLen) != 0)
|
|
{
|
|
ADD_FAILURE() << "Read file data for file " << testFilePath << "Does not match expected file data";
|
|
}
|
|
archive->FClose(threadFileHandle);
|
|
return true;
|
|
};
|
|
RunConcurrentUnitTest(numThreadedIterations, numTestThreads, concurrentArchiveFileReadFunc);
|
|
}
|
|
|
|
AZStd::unique_ptr<AzFramework::Application> m_application;
|
|
};
|
|
|
|
struct CVarIntValueScope
|
|
{
|
|
CVarIntValueScope(AZ::IConsole& console, const char* cvarName)
|
|
: m_console{ console }
|
|
, m_cvarName{ cvarName }
|
|
{
|
|
// Store current CVar value
|
|
m_valueStored = m_cvarName != nullptr && m_console.GetCvarValue(cvarName, m_oldValue) == AZ::GetValueResult::Success;
|
|
}
|
|
~CVarIntValueScope()
|
|
{
|
|
// Restore the old value if it was successfully stored
|
|
if (m_valueStored)
|
|
{
|
|
m_console.PerformCommand(m_cvarName, { AZ::CVarFixedString::format("%d", m_oldValue) });
|
|
}
|
|
}
|
|
AZ::IConsole& m_console;
|
|
const char* m_cvarName{};
|
|
int32_t m_oldValue{};
|
|
bool m_valueStored{};
|
|
};
|
|
|
|
TEST_F(ArchiveTestFixture, TestArchiveFGetCachedFileData_PakFile)
|
|
{
|
|
// Test setup - from Archive
|
|
constexpr const char* fileInArchiveFile = "levels\\mylevel\\levelinfo.xml";
|
|
constexpr AZStd::string_view dataString = "HELLO WORLD"; // other unit tests make sure writing and reading is working, so don't test that here
|
|
|
|
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
|
|
ASSERT_NE(nullptr, archive);
|
|
|
|
AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance();
|
|
ASSERT_NE(nullptr, fileIo);
|
|
|
|
auto console = AZ::Interface<AZ::IConsole>::Get();
|
|
ASSERT_NE(nullptr, console);
|
|
|
|
{
|
|
AZStd::string testArchivePath_withSubfolders = "@usercache@/immediate.pak";
|
|
AZStd::string testArchivePath_withMountPoint = "@usercache@/levels/test/flatarchive.pak";
|
|
|
|
// delete test files in case they already exist
|
|
archive->ClosePack(testArchivePath_withSubfolders.c_str());
|
|
fileIo->Remove(testArchivePath_withSubfolders.c_str());
|
|
fileIo->Remove(testArchivePath_withMountPoint.c_str());
|
|
fileIo->CreatePath("@usercache@/levels/test");
|
|
|
|
// setup test archive and file
|
|
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath_withSubfolders.c_str(), nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
|
|
EXPECT_NE(nullptr, pArchive);
|
|
EXPECT_EQ(0, pArchive->UpdateFile(fileInArchiveFile, dataString.data(), dataString.size(), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST));
|
|
pArchive.reset();
|
|
EXPECT_TRUE(IsPackValid(testArchivePath_withSubfolders.c_str()));
|
|
|
|
EXPECT_TRUE(archive->OpenPack("@assets@", testArchivePath_withSubfolders.c_str()));
|
|
|
|
EXPECT_TRUE(archive->IsFileExist(fileInArchiveFile));
|
|
}
|
|
|
|
// Prevent Archive file searches from using the OS filesystem
|
|
// Also enable extra verbosity in the AZ::IO::Archive code
|
|
CVarIntValueScope previousLocationPriority{ *console, "sys_pakPriority" };
|
|
CVarIntValueScope oldArchiveVerbosity{ *console, "az_archive_verbosity" };
|
|
console->PerformCommand("sys_PakPriority", { AZ::CVarFixedString::format("%d", aznumeric_cast<int>(AZ::IO::ArchiveLocationPriority::ePakPriorityPakOnly)) });
|
|
console->PerformCommand("az_archive_verbosity", { "1" });
|
|
|
|
// ---- Archive FGetCachedFileDataTests (these leverage Archive CachedFile mechanism for caching data ---
|
|
TestFGetCachedFileData(fileInArchiveFile, dataString.size(), dataString.data());
|
|
}
|
|
|
|
TEST_F(ArchiveTestFixture, TestArchiveOpenPacks_FindsMultiplePaks_Works)
|
|
{
|
|
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
|
|
ASSERT_NE(nullptr, archive);
|
|
|
|
AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance();
|
|
ASSERT_NE(nullptr, fileIo);
|
|
|
|
auto resetArchiveFile = [archive, fileIo](const AZStd::string& filePath)
|
|
{
|
|
archive->ClosePack(filePath.c_str());
|
|
fileIo->Remove(filePath.c_str());
|
|
|
|
auto pArchive = archive->OpenArchive(filePath.c_str(), nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
|
|
EXPECT_NE(nullptr, pArchive);
|
|
pArchive.reset();
|
|
archive->ClosePack(filePath.c_str());
|
|
};
|
|
|
|
AZStd::string testArchivePath_pakOne = "@usercache@/one.pak";
|
|
AZStd::string testArchivePath_pakTwo = "@usercache@/two.pak";
|
|
|
|
// reset test files in case they already exist
|
|
resetArchiveFile(testArchivePath_pakOne);
|
|
resetArchiveFile(testArchivePath_pakTwo);
|
|
|
|
// open and fetch the opened pak file using a *.pak
|
|
AZStd::vector<AZ::IO::FixedMaxPathString> fullPaths;
|
|
archive->OpenPacks("@usercache@/*.pak", AZ::IO::IArchive::EPathResolutionRules::FLAGS_PATH_REAL, &fullPaths);
|
|
EXPECT_TRUE(AZStd::any_of(fullPaths.cbegin(), fullPaths.cend(), [](auto& path) { return path.ends_with("one.pak"); }));
|
|
EXPECT_TRUE(AZStd::any_of(fullPaths.cbegin(), fullPaths.cend(), [](auto& path) { return path.ends_with("two.pak"); }));
|
|
}
|
|
|
|
TEST_F(ArchiveTestFixture, TestArchiveFGetCachedFileData_LooseFile)
|
|
{
|
|
// ------setup loose file FGetCachedFileData tests -------------------------
|
|
|
|
constexpr AZStd::string_view dataString = "HELLO WORLD";
|
|
|
|
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
|
|
ASSERT_NE(nullptr, archive);
|
|
const char* testRootPath = "@log@/unittesttemp";
|
|
const char* looseTestFilePath = "@log@/unittesttemp/realfileforunittest.txt";
|
|
AZ::IO::ArchiveFileIO cpfio(archive);
|
|
|
|
// remove existing
|
|
EXPECT_EQ(AZ::IO::ResultCode::Success, cpfio.DestroyPath(testRootPath));
|
|
|
|
// create test file
|
|
EXPECT_EQ(AZ::IO::ResultCode::Success, cpfio.CreatePath(testRootPath));
|
|
|
|
AZ::IO::HandleType normalFileHandle;
|
|
EXPECT_EQ(AZ::IO::ResultCode::Success, cpfio.Open(looseTestFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, normalFileHandle));
|
|
|
|
AZ::u64 bytesWritten = 0;
|
|
EXPECT_EQ(AZ::IO::ResultCode::Success, cpfio.Write(normalFileHandle, dataString.data(), dataString.size(), &bytesWritten));
|
|
EXPECT_EQ(dataString.size(), bytesWritten);
|
|
|
|
EXPECT_EQ(AZ::IO::ResultCode::Success, cpfio.Close(normalFileHandle));
|
|
EXPECT_TRUE(cpfio.Exists(looseTestFilePath));
|
|
|
|
TestFGetCachedFileData(looseTestFilePath, dataString.size(), dataString.data());
|
|
}
|
|
|
|
// a bug was found that causes problems reading data from packs if they are immediately mounted after writing.
|
|
// this unit test adjusts for that.
|
|
TEST_F(ArchiveTestFixture, TestArchivePackImmediateReading)
|
|
{
|
|
// the strategy is to create a archive file similar to how the level system does
|
|
// one which contains subfolders
|
|
// and a file inside that subfolder
|
|
// to be successful, it must be possible to write that pack, close it, then open it via Archive
|
|
// and be able to IMMEDIATELY
|
|
// * read the file in the subfolder
|
|
// * enumerate the folders (including that subfolder) even though they are 'virtual', not real folders on physical media
|
|
// * all of the above even though the mount point for the archive is @assets@ wheras the physical pack lives in @usercache@
|
|
// finally, we're going to repeat the above test but with files mounted with subfolders
|
|
// so for example, the pack will contain levelinfo.xml at the root of it
|
|
// but it will be mounted at a subfolder (levels/mylevel).
|
|
// this must cause FindNext and Open to work for levels/mylevel/levelinfo.xml.
|
|
|
|
constexpr const char* testArchivePath_withSubfolders = "@usercache@/immediate.pak";
|
|
constexpr const char* testArchivePath_withMountPoint = "@usercache@/levels/test/flatarchive.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);
|
|
|
|
auto console = AZ::Interface<AZ::IConsole>::Get();
|
|
ASSERT_NE(nullptr, console);
|
|
|
|
constexpr AZStd::string_view dataString = "HELLO WORLD"; // other unit tests make sure writing and reading is working, so don't test that here
|
|
|
|
|
|
// delete test files in case they already exist
|
|
archive->ClosePack(testArchivePath_withSubfolders);
|
|
archive->ClosePack(testArchivePath_withMountPoint);
|
|
fileIo->Remove(testArchivePath_withSubfolders);
|
|
fileIo->Remove(testArchivePath_withMountPoint);
|
|
fileIo->CreatePath("@usercache@/levels/test");
|
|
|
|
|
|
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath_withSubfolders, {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
|
|
EXPECT_NE(nullptr, pArchive);
|
|
EXPECT_EQ(0, pArchive->UpdateFile("levels\\mylevel\\levelinfo.xml", dataString.data(), dataString.size(), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST));
|
|
pArchive.reset();
|
|
EXPECT_TRUE(IsPackValid(testArchivePath_withSubfolders));
|
|
|
|
EXPECT_TRUE(archive->OpenPack("@assets@", testArchivePath_withSubfolders));
|
|
// ---- BARRAGE OF TESTS
|
|
EXPECT_TRUE(archive->IsFileExist("levels\\mylevel\\levelinfo.xml"));
|
|
EXPECT_TRUE(archive->IsFileExist("levels//mylevel//levelinfo.xml"));
|
|
|
|
bool found_mylevel_folder = false;
|
|
AZ::IO::ArchiveFileIterator handle = archive->FindFirst("levels\\*");
|
|
|
|
EXPECT_TRUE(static_cast<bool>(handle));
|
|
if (handle)
|
|
{
|
|
do
|
|
{
|
|
if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
|
|
{
|
|
if (azstricmp(handle.m_filename.data(), "mylevel") == 0)
|
|
{
|
|
found_mylevel_folder = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EXPECT_STRCASENE("levelinfo.xml", handle.m_filename.data()); // you may not find files inside the archive in this folder.
|
|
}
|
|
} while (handle = archive->FindNext(handle));
|
|
|
|
archive->FindClose(handle);
|
|
}
|
|
|
|
EXPECT_TRUE(found_mylevel_folder);
|
|
|
|
bool found_mylevel_file = false;
|
|
|
|
handle = archive->FindFirst("levels\\mylevel\\*");
|
|
|
|
EXPECT_TRUE(static_cast<bool>(handle));
|
|
if (handle)
|
|
{
|
|
do
|
|
{
|
|
if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) != AZ::IO::FileDesc::Attribute::Subdirectory)
|
|
{
|
|
if (azstricmp(handle.m_filename.data(), "levelinfo.xml") == 0)
|
|
{
|
|
found_mylevel_file = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EXPECT_STRCASENE("mylevel", handle.m_filename.data()); // you may not find the level subfolder here since we're in the subfolder already
|
|
EXPECT_STRCASENE("levels\\mylevel", handle.m_filename.data());
|
|
EXPECT_STRCASENE("levels//mylevel", handle.m_filename.data());
|
|
}
|
|
} while (handle = archive->FindNext(handle));
|
|
|
|
archive->FindClose(handle);
|
|
}
|
|
|
|
EXPECT_TRUE(found_mylevel_file);
|
|
|
|
// now test clean-up
|
|
archive->ClosePack(testArchivePath_withSubfolders);
|
|
fileIo->Remove(testArchivePath_withSubfolders);
|
|
|
|
EXPECT_FALSE(archive->IsFileExist("levels\\mylevel\\levelinfo.xml"));
|
|
EXPECT_FALSE(archive->IsFileExist("levels//mylevel//levelinfo.xml"));
|
|
|
|
// Once the archive has been deleted it should no longer be searched
|
|
CVarIntValueScope previousLocationPriority{ *console, "sys_pakPriority" };
|
|
console->PerformCommand("sys_PakPriority", { AZ::CVarFixedString::format("%d", aznumeric_cast<int>(AZ::IO::ArchiveLocationPriority::ePakPriorityPakOnly)) });
|
|
|
|
handle = archive->FindFirst("levels\\*");
|
|
EXPECT_FALSE(static_cast<bool>(handle));
|
|
}
|
|
TEST_F(ArchiveTestFixture, FilesInArchive_AreSearchable)
|
|
{
|
|
// ----------- SECOND TEST. File in levels/mylevel/ showing up as searchable.
|
|
// note that the actual file's folder has nothing to do with the mount point.
|
|
|
|
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);
|
|
|
|
constexpr AZStd::string_view dataString = "HELLO WORLD";
|
|
constexpr const char* testArchivePath_withMountPoint = "@usercache@/levels/test/flatarchive.pak";
|
|
bool found_mylevel_file{};
|
|
bool found_mylevel_folder{};
|
|
|
|
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath_withMountPoint, nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
|
|
EXPECT_NE(nullptr, pArchive);
|
|
EXPECT_EQ(0, pArchive->UpdateFile("levelinfo.xml", dataString.data(), dataString.size(), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST));
|
|
pArchive.reset();
|
|
EXPECT_TRUE(IsPackValid(testArchivePath_withMountPoint));
|
|
|
|
EXPECT_TRUE(archive->OpenPack("@assets@\\uniquename\\mylevel2", testArchivePath_withMountPoint));
|
|
|
|
// ---- BARRAGE OF TESTS
|
|
EXPECT_TRUE(archive->IsFileExist("uniquename\\mylevel2\\levelinfo.xml"));
|
|
EXPECT_TRUE(archive->IsFileExist("uniquename//mylevel2//levelinfo.xml"));
|
|
|
|
EXPECT_TRUE(!archive->IsFileExist("uniquename\\mylevel\\levelinfo.xml"));
|
|
EXPECT_TRUE(!archive->IsFileExist("uniquename//mylevel//levelinfo.xml"));
|
|
EXPECT_TRUE(!archive->IsFileExist("uniquename\\test\\levelinfo.xml"));
|
|
EXPECT_TRUE(!archive->IsFileExist("uniquename//test//levelinfo.xml"));
|
|
|
|
found_mylevel_folder = false;
|
|
AZ::IO::ArchiveFileIterator handle = archive->FindFirst("uniquename\\*");
|
|
|
|
EXPECT_TRUE(static_cast<bool>(handle));
|
|
if (handle)
|
|
{
|
|
do
|
|
{
|
|
if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
|
|
{
|
|
if (azstricmp(handle.m_filename.data(), "mylevel2") == 0)
|
|
{
|
|
found_mylevel_folder = true;
|
|
}
|
|
}
|
|
} while (handle = archive->FindNext(handle));
|
|
|
|
archive->FindClose(handle);
|
|
}
|
|
|
|
EXPECT_TRUE(found_mylevel_folder);
|
|
|
|
found_mylevel_file = false;
|
|
|
|
handle = archive->FindFirst("uniquename\\mylevel2\\*");
|
|
|
|
EXPECT_TRUE(static_cast<bool>(handle));
|
|
if (handle)
|
|
{
|
|
do
|
|
{
|
|
if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) != AZ::IO::FileDesc::Attribute::Subdirectory)
|
|
{
|
|
if (azstricmp(handle.m_filename.data(), "levelinfo.xml") == 0)
|
|
{
|
|
found_mylevel_file = true;
|
|
}
|
|
}
|
|
} while (handle = archive->FindNext(handle));
|
|
|
|
archive->FindClose(handle);
|
|
}
|
|
|
|
EXPECT_TRUE(found_mylevel_file);
|
|
|
|
archive->ClosePack(testArchivePath_withMountPoint);
|
|
|
|
// --- test to make sure that when you iterate only the first component is found, so bury it deep and ask for the root
|
|
EXPECT_TRUE(archive->OpenPack("@assets@\\uniquename\\mylevel2\\mylevel3\\mylevel4", testArchivePath_withMountPoint));
|
|
|
|
found_mylevel_folder = false;
|
|
handle = archive->FindFirst("uniquename\\*");
|
|
|
|
int numFound = 0;
|
|
EXPECT_TRUE(static_cast<bool>(handle));
|
|
if (handle)
|
|
{
|
|
do
|
|
{
|
|
if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
|
|
{
|
|
++numFound;
|
|
if (azstricmp(handle.m_filename.data(), "mylevel2") == 0)
|
|
{
|
|
found_mylevel_folder = true;
|
|
}
|
|
}
|
|
} while (handle = archive->FindNext(handle));
|
|
|
|
archive->FindClose(handle);
|
|
}
|
|
|
|
EXPECT_EQ(1, numFound);
|
|
EXPECT_TRUE(found_mylevel_folder);
|
|
|
|
numFound = 0;
|
|
found_mylevel_folder = false;
|
|
|
|
// now make sure no red herrings appear
|
|
// for example, if a file is mounted at "@assets@\\uniquename\\mylevel2\\mylevel3\\mylevel4"
|
|
// and the file "@assets@\\somethingelse" is requested it should not be found
|
|
// in addition if the file "@assets@\\uniquename\\mylevel3" is requested it should not be found
|
|
handle = archive->FindFirst("somethingelse\\*");
|
|
EXPECT_FALSE(static_cast<bool>(handle));
|
|
|
|
handle = archive->FindFirst("uniquename\\mylevel3*");
|
|
EXPECT_FALSE(static_cast<bool>(handle));
|
|
|
|
archive->ClosePack(testArchivePath_withMountPoint);
|
|
fileIo->Remove(testArchivePath_withMountPoint);
|
|
}
|
|
|
|
// test that ArchiveFileIO class works as expected
|
|
TEST_F(ArchiveTestFixture, TestArchiveViaFileIO)
|
|
{
|
|
// strategy:
|
|
// create a loose file and a packed file
|
|
// mount the pack
|
|
// make sure that the pack and loose file both appear when the PaKFileIO interface is used.
|
|
using namespace AZ::IO;
|
|
AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
|
|
AZ::IO::ArchiveFileIO cpfio(archive);
|
|
|
|
constexpr const char* genericArchiveFileName = "@usercache@/testarchiveio.pak";
|
|
ASSERT_NE(nullptr, archive);
|
|
const char* dataString = "HELLO WORLD"; // other unit tests make sure writing and reading is working, so don't test that here
|
|
size_t dataLen = strlen(dataString);
|
|
AZStd::vector<char> dataBuffer;
|
|
dataBuffer.resize(dataLen);
|
|
|
|
// delete test files in case they already exist
|
|
archive->ClosePack(genericArchiveFileName);
|
|
cpfio.Remove(genericArchiveFileName);
|
|
|
|
// create the asset alias directory
|
|
cpfio.CreatePath("@assets@");
|
|
|
|
// create generic file
|
|
|
|
HandleType normalFileHandle;
|
|
AZ::u64 bytesWritten = 0;
|
|
EXPECT_EQ(ResultCode::Success, cpfio.DestroyPath("@log@/unittesttemp"));
|
|
EXPECT_TRUE(!cpfio.Exists("@log@/unittesttemp"));
|
|
EXPECT_TRUE(!cpfio.IsDirectory("@log@/unittesttemp"));
|
|
EXPECT_EQ(ResultCode::Success, cpfio.CreatePath("@log@/unittesttemp"));
|
|
EXPECT_TRUE(cpfio.Exists("@log@/unittesttemp"));
|
|
EXPECT_TRUE(cpfio.IsDirectory("@log@/unittesttemp"));
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Open("@log@/unittesttemp/realfileforunittest.xml", OpenMode::ModeWrite | OpenMode::ModeBinary, normalFileHandle));
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Write(normalFileHandle, dataString, dataLen, &bytesWritten));
|
|
EXPECT_EQ(dataLen, bytesWritten);
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Close(normalFileHandle));
|
|
normalFileHandle = InvalidHandle;
|
|
EXPECT_TRUE(cpfio.Exists("@log@/unittesttemp/realfileforunittest.xml"));
|
|
|
|
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(genericArchiveFileName, nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
|
|
EXPECT_NE(nullptr, pArchive);
|
|
EXPECT_EQ(0, pArchive->UpdateFile("testfile.xml", dataString, aznumeric_cast<uint32_t>(dataLen), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST));
|
|
pArchive.reset();
|
|
EXPECT_TRUE(IsPackValid(genericArchiveFileName));
|
|
|
|
EXPECT_TRUE(archive->OpenPack("@assets@", genericArchiveFileName));
|
|
|
|
// ---- BARRAGE OF TESTS
|
|
EXPECT_TRUE(cpfio.Exists("testfile.xml"));
|
|
EXPECT_TRUE(cpfio.Exists("@assets@/testfile.xml")); // this should be hte same file
|
|
EXPECT_TRUE(!cpfio.Exists("@log@/testfile.xml"));
|
|
EXPECT_TRUE(!cpfio.Exists("@usercache@/testfile.xml"));
|
|
EXPECT_TRUE(cpfio.Exists("@log@/unittesttemp/realfileforunittest.xml"));
|
|
|
|
// --- Coverage test ----
|
|
AZ::u64 fileSize = 0;
|
|
AZ::u64 bytesRead = 0;
|
|
AZ::u64 currentOffset = 0;
|
|
char fileNameBuffer[AZ_MAX_PATH_LEN] = { 0 };
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Size("testfile.xml", fileSize));
|
|
EXPECT_EQ(dataLen, fileSize);
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Open("testfile.xml", OpenMode::ModeRead | OpenMode::ModeBinary, normalFileHandle));
|
|
EXPECT_NE(InvalidHandle, normalFileHandle);
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Size(normalFileHandle, fileSize));
|
|
EXPECT_EQ(dataLen, fileSize);
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Read(normalFileHandle, dataBuffer.data(), 2, true, &bytesRead));
|
|
EXPECT_EQ(2, bytesRead);
|
|
EXPECT_EQ('H', dataBuffer[0]);
|
|
EXPECT_EQ('E', dataBuffer[1]);
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Tell(normalFileHandle, currentOffset));
|
|
EXPECT_EQ(2, currentOffset);
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Seek(normalFileHandle, 2, SeekType::SeekFromCurrent));
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Tell(normalFileHandle, currentOffset));
|
|
EXPECT_EQ(4, currentOffset);
|
|
EXPECT_TRUE(!cpfio.Eof(normalFileHandle));
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Seek(normalFileHandle, 2, SeekType::SeekFromStart));
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Tell(normalFileHandle, currentOffset));
|
|
EXPECT_EQ(2, currentOffset);
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Seek(normalFileHandle, -2, SeekType::SeekFromEnd));
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Tell(normalFileHandle, currentOffset));
|
|
EXPECT_EQ(dataLen - 2, currentOffset);
|
|
EXPECT_NE(ResultCode::Success, cpfio.Read(normalFileHandle, dataBuffer.data(), 4, true, &bytesRead));
|
|
EXPECT_EQ(2, bytesRead);
|
|
EXPECT_TRUE(cpfio.Eof(normalFileHandle));
|
|
EXPECT_TRUE(cpfio.GetFilename(normalFileHandle, fileNameBuffer, AZ_MAX_PATH_LEN));
|
|
EXPECT_NE(AZStd::string_view::npos, AZStd::string_view(fileNameBuffer).find("testfile.xml"));
|
|
// just coverage-call this function:
|
|
EXPECT_EQ(archive->GetModificationTime(normalFileHandle), cpfio.ModificationTime(normalFileHandle));
|
|
EXPECT_EQ(archive->GetModificationTime(normalFileHandle), cpfio.ModificationTime("testfile.xml"));
|
|
EXPECT_NE(0, cpfio.ModificationTime("testfile.xml"));
|
|
EXPECT_NE(0, cpfio.ModificationTime("@log@/unittesttemp/realfileforunittest.xml"));
|
|
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Close(normalFileHandle));
|
|
|
|
EXPECT_TRUE(!cpfio.IsDirectory("testfile.xml"));
|
|
EXPECT_TRUE(cpfio.IsDirectory("@assets@"));
|
|
EXPECT_TRUE(cpfio.IsReadOnly("testfile.xml"));
|
|
EXPECT_TRUE(cpfio.IsReadOnly("@assets@/testfile.xml"));
|
|
EXPECT_TRUE(!cpfio.IsReadOnly("@log@/unittesttemp/realfileforunittest.xml"));
|
|
|
|
|
|
// copy file from inside archive:
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Copy("testfile.xml", "@log@/unittesttemp/copiedfile.xml"));
|
|
EXPECT_TRUE(cpfio.Exists("@log@/unittesttemp/copiedfile.xml"));
|
|
|
|
// make sure copy is ok
|
|
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Open("@log@/unittesttemp/copiedfile.xml", OpenMode::ModeRead | OpenMode::ModeBinary, normalFileHandle));
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Size(normalFileHandle, fileSize));
|
|
EXPECT_EQ(dataLen, fileSize);
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Read(normalFileHandle, dataBuffer.data(), dataLen + 10, false, &bytesRead)); // allowed to read less
|
|
EXPECT_EQ(dataLen, bytesRead);
|
|
EXPECT_EQ(0, memcmp(dataBuffer.data(), dataString, dataLen));
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Close(normalFileHandle));
|
|
|
|
// make sure file does not exist, since copy will NOT overwrite:
|
|
cpfio.Remove("@log@/unittesttemp/copiedfile2.xml");
|
|
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Rename("@log@/unittesttemp/copiedfile.xml", "@log@/unittesttemp/copiedfile2.xml"));
|
|
EXPECT_TRUE(!cpfio.Exists("@log@/unittesttemp/copiedfile.xml"));
|
|
EXPECT_TRUE(cpfio.Exists("@log@/unittesttemp/copiedfile2.xml"));
|
|
|
|
// find files test.
|
|
|
|
bool foundIt = false;
|
|
// note that this file exists only in the archive.
|
|
cpfio.FindFiles("@assets@", "*.xml", [&foundIt](const char* foundName)
|
|
{
|
|
// according to the contract stated in the FileIO.h file, we expect full paths. (Aliases are full paths)
|
|
if (azstricmp(foundName, "@assets@/testfile.xml") == 0)
|
|
{
|
|
foundIt = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
EXPECT_TRUE(foundIt);
|
|
|
|
|
|
// The following test is disabled because it will trigger an AZ_ERROR which will affect the outcome of this entire test
|
|
// EXPECT_NE(ResultCode::Success, cpfio.Remove("@assets@/testfile.xml")); // may not delete archive files
|
|
|
|
// make sure it works with and without alias:
|
|
EXPECT_TRUE(cpfio.Exists("@assets@/testfile.xml"));
|
|
EXPECT_TRUE(cpfio.Exists("testfile.xml"));
|
|
|
|
EXPECT_TRUE(cpfio.Exists("@log@/unittesttemp/realfileforunittest.xml"));
|
|
EXPECT_EQ(ResultCode::Success, cpfio.Remove("@log@/unittesttemp/realfileforunittest.xml"));
|
|
EXPECT_TRUE(!cpfio.Exists("@log@/unittesttemp/realfileforunittest.xml"));
|
|
|
|
// now test clean-up
|
|
archive->ClosePack(genericArchiveFileName);
|
|
cpfio.Remove(genericArchiveFileName);
|
|
|
|
EXPECT_EQ(ResultCode::Success, cpfio.DestroyPath("@log@/unittesttemp"));
|
|
EXPECT_TRUE(!cpfio.Exists("@log@/unittesttemp/realfileforunittest.xml"));
|
|
EXPECT_TRUE(!cpfio.Exists("@log@/unittesttemp"));
|
|
EXPECT_TRUE(!archive->IsFileExist("testfile.xml"));
|
|
}
|
|
|
|
TEST_F(ArchiveTestFixture, TestArchiveFolderAliases)
|
|
{
|
|
AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance();
|
|
ASSERT_NE(nullptr, fileIo);
|
|
|
|
// test whether aliasing works as expected. We'll create a archive in the cache, but we'll map it to a bunch of folders
|
|
constexpr const char* testArchivePath = "@usercache@/archivetest.pak";
|
|
|
|
char realNameBuf[AZ_MAX_PATH_LEN] = { 0 };
|
|
EXPECT_TRUE(fileIo->ResolvePath(testArchivePath, realNameBuf, AZ_MAX_PATH_LEN));
|
|
|
|
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);
|
|
fileIo->Remove(testArchivePath);
|
|
|
|
// ------------ BASIC TEST: Create and read Empty Archive ------------
|
|
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath, nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
|
|
EXPECT_NE(nullptr, pArchive);
|
|
|
|
EXPECT_EQ(0, pArchive->UpdateFile("foundit.dat", const_cast<char*>("test"), 4, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST));
|
|
|
|
pArchive.reset();
|
|
EXPECT_TRUE(IsPackValid(testArchivePath));
|
|
|
|
EXPECT_TRUE(archive->OpenPack("@usercache@", realNameBuf));
|
|
EXPECT_TRUE(archive->IsFileExist("@usercache@/foundit.dat"));
|
|
EXPECT_FALSE(archive->IsFileExist("@usercache@/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk));
|
|
EXPECT_FALSE(archive->IsFileExist("@usercache@/notfoundit.dat"));
|
|
EXPECT_TRUE(archive->ClosePack(realNameBuf));
|
|
|
|
// change its actual location:
|
|
EXPECT_TRUE(archive->OpenPack("@assets@", realNameBuf));
|
|
EXPECT_TRUE(archive->IsFileExist("@assets@/foundit.dat"));
|
|
EXPECT_FALSE(archive->IsFileExist("@usercache@/foundit.dat")); // do not find it in the previous location!
|
|
EXPECT_FALSE(archive->IsFileExist("@assets@/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk));
|
|
EXPECT_FALSE(archive->IsFileExist("@assets@/notfoundit.dat"));
|
|
EXPECT_TRUE(archive->ClosePack(realNameBuf));
|
|
|
|
// try sub-folders
|
|
EXPECT_TRUE(archive->OpenPack("@assets@/mystuff", realNameBuf));
|
|
EXPECT_TRUE(archive->IsFileExist("@assets@/mystuff/foundit.dat"));
|
|
EXPECT_FALSE(archive->IsFileExist("@assets@/foundit.dat")); // do not find it in the previous locations!
|
|
EXPECT_FALSE(archive->IsFileExist("@usercache@/foundit.dat")); // do not find it in the previous locations!
|
|
EXPECT_FALSE(archive->IsFileExist("@assets@/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk));
|
|
EXPECT_FALSE(archive->IsFileExist("@assets@/mystuff/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk));
|
|
EXPECT_FALSE(archive->IsFileExist("@assets@/notfoundit.dat")); // non-existent file
|
|
EXPECT_FALSE(archive->IsFileExist("@assets@/mystuff/notfoundit.dat")); // non-existent file
|
|
EXPECT_TRUE(archive->ClosePack(realNameBuf));
|
|
}
|
|
|
|
TEST_F(ArchiveTestFixture, IResourceList_Add_EmptyFileName_DoesNotCrash)
|
|
{
|
|
AZ::IO::IResourceList* reslist = AZ::Interface<AZ::IO::IArchive>::Get()->GetResourceList(AZ::IO::IArchive::RFOM_EngineStartup);
|
|
ASSERT_NE(nullptr, reslist);
|
|
|
|
reslist->Clear();
|
|
reslist->Add("");
|
|
EXPECT_STREQ(reslist->GetFirst(), "");
|
|
reslist->Clear();
|
|
}
|
|
|
|
TEST_F(ArchiveTestFixture, IResourceList_Add_RegularFileName_NormalizesAppropriately)
|
|
{
|
|
AZ::IO::IResourceList* reslist = AZ::Interface<AZ::IO::IArchive>::Get()->GetResourceList(AZ::IO::IArchive::RFOM_EngineStartup);
|
|
ASSERT_NE(nullptr, reslist);
|
|
|
|
reslist->Clear();
|
|
reslist->Add("blah\\blah/AbCDE");
|
|
|
|
// it normalizes the string, so the slashes flip and everything is lowercased.
|
|
EXPECT_STREQ(reslist->GetFirst(), "blah/blah/abcde");
|
|
reslist->Clear();
|
|
}
|
|
|
|
TEST_F(ArchiveTestFixture, IResourceList_Add_ReallyShortFileName_NormalizesAppropriately)
|
|
{
|
|
AZ::IO::IResourceList* reslist = AZ::Interface<AZ::IO::IArchive>::Get()->GetResourceList(AZ::IO::IArchive::RFOM_EngineStartup);
|
|
ASSERT_NE(nullptr, reslist);
|
|
|
|
reslist->Clear();
|
|
reslist->Add("A");
|
|
|
|
// it normalizes the string, so the slashes flip and everything is lowercased.
|
|
EXPECT_STREQ(reslist->GetFirst(), "a");
|
|
reslist->Clear();
|
|
}
|
|
|
|
TEST_F(ArchiveTestFixture, IResourceList_Add_AbsolutePath_RemovesAndReplacesWithAlias)
|
|
{
|
|
AZ::IO::IResourceList* reslist = AZ::Interface<AZ::IO::IArchive>::Get()->GetResourceList(AZ::IO::IArchive::RFOM_EngineStartup);
|
|
ASSERT_NE(nullptr, reslist);
|
|
|
|
AZ::IO::FileIOBase* ioBase = AZ::IO::FileIOBase::GetInstance();
|
|
ASSERT_NE(nullptr, ioBase);
|
|
|
|
const char *assetsPath = ioBase->GetAlias("@assets@");
|
|
ASSERT_NE(nullptr, assetsPath);
|
|
|
|
auto stringToAdd = AZ::IO::Path(assetsPath) / "textures" / "test.dds";
|
|
|
|
reslist->Clear();
|
|
reslist->Add(stringToAdd.Native());
|
|
|
|
// it normalizes the string, so the slashes flip and everything is lowercased.
|
|
AZ::IO::FixedMaxPath resolvedAddedPath;
|
|
AZ::IO::FixedMaxPath resolvedResourcePath;
|
|
EXPECT_TRUE(ioBase->ReplaceAlias(resolvedAddedPath, "@assets@/textures/test.dds"));
|
|
EXPECT_TRUE(ioBase->ReplaceAlias(resolvedResourcePath, reslist->GetFirst()));
|
|
EXPECT_EQ(resolvedAddedPath, resolvedResourcePath);
|
|
reslist->Clear();
|
|
}
|
|
|
|
|
|
class ArchiveUnitTestsWithAllocators
|
|
: public ScopedAllocatorSetupFixture
|
|
{
|
|
protected:
|
|
|
|
void SetUp() override
|
|
{
|
|
m_localFileIO = aznew AZ::IO::LocalFileIO();
|
|
AZ::IO::FileIOBase::SetDirectInstance(m_localFileIO);
|
|
m_localFileIO->SetAlias(m_firstAlias.c_str(), m_firstAliasPath.c_str());
|
|
m_localFileIO->SetAlias(m_secondAlias.c_str(), m_secondAliasPath.c_str());
|
|
}
|
|
|
|
void TearDown() override
|
|
{
|
|
AZ::IO::FileIOBase::SetDirectInstance(nullptr);
|
|
delete m_localFileIO;
|
|
m_localFileIO = nullptr;
|
|
}
|
|
|
|
AZ::IO::FileIOBase* m_localFileIO = nullptr;
|
|
AZStd::string m_firstAlias = "@devassets@";
|
|
AZStd::string m_firstAliasPath = "devassets_absolutepath";
|
|
AZStd::string m_secondAlias = "@assets@";
|
|
AZStd::string m_secondAliasPath = "assets_absolutepath";
|
|
};
|
|
|
|
// ConvertAbsolutePathToAliasedPath tests are built to verify existing behavior doesn't change.
|
|
// It's a legacy function and the actual intended behavior is unknown, so these are black box unit tests.
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_NullString_ReturnsSuccess)
|
|
{
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(nullptr);
|
|
EXPECT_TRUE(conversionResult);
|
|
EXPECT_TRUE(conversionResult->empty());
|
|
}
|
|
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_NoAliasInSource_ReturnsSource)
|
|
{
|
|
AZStd::string sourceString("NoAlias");
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(sourceString.c_str());
|
|
EXPECT_TRUE(conversionResult);
|
|
// ConvertAbsolutePathToAliasedPath returns sourceString if there is no alias in the source.
|
|
EXPECT_STREQ(sourceString.c_str(), conversionResult->c_str());
|
|
}
|
|
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_NullAliasToLookFor_ReturnsSource)
|
|
{
|
|
AZStd::string sourceString("NoAlias");
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(sourceString.c_str(), nullptr);
|
|
EXPECT_TRUE(conversionResult);
|
|
EXPECT_STREQ(sourceString.c_str(), conversionResult->c_str());
|
|
}
|
|
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_NullAliasToReplaceWith_ReturnsSource)
|
|
{
|
|
AZStd::string sourceString("NoAlias");
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(sourceString.c_str(), "@SomeAlias", nullptr);
|
|
EXPECT_TRUE(conversionResult);
|
|
EXPECT_STREQ(sourceString.c_str(), conversionResult->c_str());
|
|
}
|
|
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_NullAliases_ReturnsSource)
|
|
{
|
|
AZStd::string sourceString("NoAlias");
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(sourceString.c_str(), nullptr, nullptr);
|
|
EXPECT_TRUE(conversionResult);
|
|
EXPECT_STREQ(sourceString.c_str(), conversionResult->c_str());
|
|
}
|
|
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AbsPathInSource_ReturnsReplacedAlias)
|
|
{
|
|
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
|
|
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
|
|
|
|
const char* fullPath = AZ::IO::FileIOBase::GetDirectInstance()->GetAlias(m_firstAlias.c_str());
|
|
AZStd::string sourceString = AZStd::string::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "SomeStringWithAlias", fullPath);
|
|
AZStd::string expectedResult = AZStd::string::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
|
|
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
|
|
sourceString.c_str(),
|
|
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
|
|
m_secondAlias.c_str()); // replace it with SecondAlias
|
|
EXPECT_TRUE(conversionResult);
|
|
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
|
|
}
|
|
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AliasInSource_ReturnsReplacedAlias)
|
|
{
|
|
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
|
|
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
|
|
|
|
AZStd::string sourceString = AZStd::string::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "SomeStringWithAlias", m_firstAlias.c_str());
|
|
AZStd::string expectedResult = AZStd::string::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
|
|
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
|
|
sourceString.c_str(),
|
|
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
|
|
m_secondAlias.c_str()); // replace it with SecondAlias
|
|
EXPECT_TRUE(conversionResult);
|
|
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
|
|
}
|
|
|
|
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AbsPathInSource_DOSSlashInSource_ReturnsReplacedAlias)
|
|
{
|
|
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
|
|
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
|
|
|
|
const char* fullPath = AZ::IO::FileIOBase::GetDirectInstance()->GetAlias(m_firstAlias.c_str());
|
|
AZStd::string sourceString = AZStd::string::format("%s" AZ_WRONG_DATABASE_SEPARATOR_STRING "SomeStringWithAlias", fullPath);
|
|
AZStd::string expectedResult = AZStd::string::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
|
|
sourceString.c_str(),
|
|
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
|
|
m_secondAlias.c_str()); // replace it with SecondAlias
|
|
EXPECT_TRUE(conversionResult);
|
|
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
|
|
}
|
|
#endif // AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
|
|
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AbsPathInSource_UNIXSlashInSource_ReturnsReplacedAlias)
|
|
{
|
|
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
|
|
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
|
|
|
|
const char* fullPath = AZ::IO::FileIOBase::GetDirectInstance()->GetAlias(m_firstAlias.c_str());
|
|
AZStd::string sourceString = AZStd::string::format("%s" AZ_CORRECT_DATABASE_SEPARATOR_STRING "SomeStringWithAlias", fullPath);
|
|
AZStd::string expectedResult = AZStd::string::format("%s" AZ_CORRECT_DATABASE_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
|
|
sourceString.c_str(),
|
|
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
|
|
m_secondAlias.c_str()); // replace it with SecondAlias
|
|
EXPECT_TRUE(conversionResult);
|
|
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
|
|
}
|
|
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AliasInSource_DOSSlashInSource_ReturnsReplacedAlias)
|
|
{
|
|
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
|
|
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
|
|
|
|
AZStd::string sourceString = AZStd::string::format("%s" AZ_WRONG_DATABASE_SEPARATOR_STRING "SomeStringWithAlias", m_firstAlias.c_str());
|
|
AZStd::string expectedResult = AZStd::string::format("%s" AZ_WRONG_DATABASE_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
|
|
|
|
// sourceString is now (firstAlias)SomeStringWithAlias
|
|
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
|
|
sourceString.c_str(),
|
|
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
|
|
m_secondAlias.c_str()); // replace it with SecondAlias
|
|
EXPECT_TRUE(conversionResult);
|
|
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
|
|
}
|
|
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_AliasInSource_UNIXSlashInSource_ReturnsReplacedAlias)
|
|
{
|
|
// ConvertAbsolutePathToAliasedPath only replaces data if GetDirectInstance is valid.
|
|
EXPECT_TRUE(AZ::IO::FileIOBase::GetDirectInstance() != nullptr);
|
|
|
|
AZStd::string sourceString = AZStd::string::format("%s" AZ_CORRECT_DATABASE_SEPARATOR_STRING "SomeStringWithAlias", m_firstAlias.c_str());
|
|
AZStd::string expectedResult = AZStd::string::format("%s" AZ_CORRECT_DATABASE_SEPARATOR_STRING "somestringwithalias", m_secondAlias.c_str());
|
|
|
|
// sourceString is now (firstAlias)SomeStringWithAlias
|
|
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(
|
|
sourceString.c_str(),
|
|
m_firstAlias.c_str(), // find any instance of FirstAlias in sourceString
|
|
m_secondAlias.c_str()); // replace it with SecondAlias
|
|
EXPECT_TRUE(conversionResult);
|
|
EXPECT_STREQ(conversionResult->c_str(), expectedResult.c_str());
|
|
}
|
|
|
|
TEST_F(ArchiveUnitTestsWithAllocators, ConvertAbsolutePathToAliasedPath_SourceLongerThanMaxPath_ReturnsFailure)
|
|
{
|
|
const int longPathArraySize = AZ::IO::MaxPathLength + 2;
|
|
char longPath[longPathArraySize];
|
|
memset(longPath, 'a', sizeof(char) * longPathArraySize);
|
|
longPath[longPathArraySize - 1] = '\0';
|
|
AZ_TEST_START_TRACE_SUPPRESSION;
|
|
auto conversionResult = AZ::IO::ArchiveInternal::ConvertAbsolutePathToAliasedPath(longPath);
|
|
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
|
EXPECT_FALSE(conversionResult);
|
|
}
|
|
|
|
class ArchivePathCompareTestFixture
|
|
: public ScopedAllocatorSetupFixture
|
|
, public ::testing::WithParamInterface<AZStd::tuple<AZStd::string_view, AZStd::string_view>>
|
|
{
|
|
};
|
|
}
|