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

191 lines
8.8 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "CrySystem_precompiled.h"
#include <AzTest/AzTest.h>
#include <AzCore/UnitTest/UnitTest.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/IO/SystemFile.h> // for max path decl
#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 <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Archive/ArchiveFileIO.h>
#include <AzFramework/Archive/Archive.h>
#include <AzFramework/Archive/INestedArchive.h>
#include <ILevelSystem.h>
namespace CryPakUnitTests
{
#if defined(AZ_PLATFORM_WINDOWS)
// Note: none of the below is really a unit test, its all basic feature tests
// for critical functionality
class Integ_CryPakUnitTests
: public ::testing::Test
{
protected:
bool IsPackValid(const char* path)
{
AZ::IO::IArchive* pak = gEnv->pCryPak;
if (!pak)
{
return false;
}
if (!pak->OpenPack(path, AZ::IO::IArchive::FLAGS_PATH_REAL))
{
return false;
}
pak->ClosePack(path);
return true;
}
};
TEST_F(Integ_CryPakUnitTests, TestCryPakArchiveContainingLevels)
{
AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance();
ASSERT_NE(nullptr, fileIo);
constexpr const char* testPakPath = "@usercache@/archivecontainerlevel.pak";
char resolvedArchivePath[AZ_MAX_PATH_LEN] = { 0 };
EXPECT_TRUE(fileIo->ResolvePath(testPakPath, resolvedArchivePath, AZ_MAX_PATH_LEN));
AZ::IO::IArchive* pak = gEnv->pCryPak;
ASSERT_NE(nullptr, pak);
// delete test files in case they already exist
pak->ClosePack(testPakPath);
fileIo->Remove(testPakPath);
ILevelSystem* levelSystem = gEnv->pSystem->GetILevelSystem();
EXPECT_NE(nullptr, levelSystem);
// ------------ Create an archive with a dummy level in it ------------
AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = pak->OpenArchive(testPakPath, nullptr, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
EXPECT_NE(nullptr, pArchive);
const char levelInfoFile[] = "levelInfo.xml";
AZStd::string relativeLevelPakPath = AZStd::string::format("levels/dummy/%s", ILevelSystem::LevelPakName);
AZStd::string relativeLevelInfoPath = AZStd::string::format("levels/dummy/%s", levelInfoFile);
EXPECT_EQ(0, pArchive->UpdateFile(relativeLevelPakPath.c_str(), const_cast<char*>("test"), 4, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST));
EXPECT_EQ(0, pArchive->UpdateFile(relativeLevelInfoPath.c_str(), const_cast<char*>("test"), 4, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST));
pArchive.reset();
EXPECT_TRUE(IsPackValid(testPakPath));
AZStd::fixed_string<AZ::IO::IArchive::MaxPath> fullLevelPakPath;
bool addLevel = true;
EXPECT_TRUE(pak->OpenPack("@assets@", resolvedArchivePath, AZ::IO::IArchive::FLAGS_LEVEL_PAK_INSIDE_PAK, nullptr, &fullLevelPakPath, addLevel));
ILevelInfo* levelInfo = nullptr;
// Since the archive was open, we should be able to find the level "dummy"
levelInfo = levelSystem->GetLevelInfo("dummy");
EXPECT_NE(nullptr, levelInfo);
EXPECT_TRUE(pak->ClosePack(resolvedArchivePath));
// After closing the archive we should not be able to find the level "dummy"
levelInfo = levelSystem->GetLevelInfo("dummy");
EXPECT_EQ(nullptr, levelInfo);
}
TEST_F(Integ_CryPakUnitTests, TestCryPakModTime)
{
AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance();
ASSERT_NE(nullptr, fileIo);
AZ::IO::IArchive* pak = gEnv->pCryPak;
// repeat the following test multiple times, since timing (seconds) can affect it and it involves time!
for (int iteration = 0; iteration < 10; ++iteration)
{
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds{ 100 });
// helper paths and strings
AZStd::string gameFolder = fileIo->GetAlias("@usercache@");
AZStd::string testFile = "unittest.bin";
AZStd::string testFilePath = gameFolder + "\\" + testFile;
AZStd::string testPak = "unittest.pak";
AZStd::string testPakPath = gameFolder + "\\" + testPak;
AZStd::string zipCmd = "-zip=" + testPakPath;
// delete test files in case they already exist
fileIo->Remove(testFilePath.c_str());
pak->ClosePack(testPakPath);
fileIo->Remove(testPakPath.c_str());
// create a test file
char data[] = "unittest";
FILE* f = nullptr;
azfopen(&f, testFilePath.c_str(), "wb");
EXPECT_TRUE(f != nullptr); // file successfully opened for writing
EXPECT_TRUE(fwrite(data, sizeof(char), sizeof(data), f) == sizeof(data)); // file written to successfully
EXPECT_TRUE(fclose(f) == 0); // file closed successfully
AZ::IO::HandleType fDisk = pak->FOpen(testFilePath.c_str(), "rb");
EXPECT_TRUE(fDisk > 0); // opened file on disk successfully
uint64_t modTimeDisk = pak->GetModificationTime(fDisk); // high res mod time extracted from file on disk
EXPECT_TRUE(pak->FClose(fDisk) == 0); // file closed successfully
// create a low res copy of disk file's mod time
uint64_t absDiff, maxDiff = 20000000ul;
uint16_t dosDate, dosTime;
FILETIME ft;
LARGE_INTEGER lt;
ft.dwHighDateTime = modTimeDisk >> 32;
ft.dwLowDateTime = modTimeDisk & 0xFFFFFFFF;
EXPECT_TRUE(FileTimeToDosDateTime(&ft, &dosDate, &dosTime) != FALSE); // converted to DOSTIME successfully
ft.dwHighDateTime = 0;
ft.dwLowDateTime = 0;
EXPECT_TRUE(DosDateTimeToFileTime(dosDate, dosTime, &ft) != FALSE); // converted to FILETIME successfully
lt.HighPart = ft.dwHighDateTime;
lt.LowPart = ft.dwLowDateTime;
uint64_t modTimeDiskLowRes = lt.QuadPart;
absDiff = modTimeDiskLowRes >= modTimeDisk ? modTimeDiskLowRes - modTimeDisk : modTimeDisk - modTimeDiskLowRes;
EXPECT_LE(absDiff, maxDiff); // FILETIME (high res) and DOSTIME (low res) should be at most 2 seconds apart
gEnv->pResourceCompilerHelper->CallResourceCompiler(testFilePath.c_str(), zipCmd.c_str());
EXPECT_EQ(AZ::IO::ResultCode::Success, fileIo->Remove(testFilePath.c_str())); // test file on disk deleted successfully
EXPECT_TRUE(pak->OpenPack(testPakPath)); // opened pak successfully
AZ::IO::HandleType fPak = pak->FOpen(testFilePath.c_str(), "rb");
EXPECT_GT(fPak, 0); // file (in pak) opened correctly
uint64_t modTimePak = pak->GetModificationTime(fPak); // low res mod time extracted from file in pak
EXPECT_EQ(0, pak->FClose(fPak)); // file closed successfully
EXPECT_TRUE(pak->ClosePack(testPakPath)); // closed pak successfully
EXPECT_EQ(AZ::IO::ResultCode::Success, fileIo->Remove(testPakPath.c_str())); // test pak file deleted successfully
absDiff = modTimePak >= modTimeDisk ? modTimePak - modTimeDisk : modTimeDisk - modTimePak;
// compare mod times. They are allowed to be up to 2 seconds apart but no more
EXPECT_LE(absDiff, maxDiff); // FILETIME (disk) and DOSTIME (pak) should be at most 2 seconds apart
// note: Do not directly compare the disk time and pack time, the resolution drops the last digit off in some cases in pak
// it only has a 2 second resolution. you may compare to make sure that the pak time is WITHIN 2 seconds (as above) but not equal.
// we depend on the fact that crypak is rounding up, instead of down
EXPECT_GE(modTimePak, modTimeDisk);
}
}
#endif
}