Files
o3de/Code/Framework/AzFramework/Tests/ArchiveTests.cpp
T
lumberyard-employee-dm e638f27572 Fixed PathView MakeRelativeTo and Append functions path segment comparisons (#3628)
* 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>
2021-09-10 10:02:17 -05:00

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>>
{
};
}