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/Tools/AssetProcessor/native/tests/resourcecompiler/RCJobTest.cpp

291 lines
16 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 "RCJobTest.h"
#include <native/tests/AssetProcessorTest.h>
#include <native/resourcecompiler/rcjob.h>
namespace UnitTests
{
using namespace testing;
using ::testing::NiceMock;
using namespace AssetProcessor;
using namespace AssetBuilderSDK;
class MockDiskSpaceResponder : public DiskSpaceInfoBus::Handler
{
public:
MOCK_METHOD3(CheckSufficientDiskSpace, bool(const QString&, qint64, bool));
};
class IgnoreNotifyTracker : public ProcessingJobInfoBus::Handler
{
public:
// Will notify other systems which old product is just about to get removed from the cache
// before we copy the new product instead along.
void BeginCacheFileUpdate(const char* productPath) override
{
m_capturedStartPaths.push_back(productPath);
}
// Will notify other systems which product we are trying to copy in the cache
// along with status of whether that copy succeeded or failed.
void EndCacheFileUpdate(const char* productPath, bool /*queueAgainForProcessing*/) override
{
m_capturedStopPaths.push_back(productPath);
}
AZStd::vector<AZStd::string> m_capturedStartPaths;
AZStd::vector<AZStd::string> m_capturedStopPaths;
};
class RCJobTest : public AssetProcessorTest
{
public:
void SetUp() override
{
AssetProcessorTest::SetUp();
m_data.reset(new StaticData());
m_data->tempDirPath = QDir(m_data->m_tempDir.path());
m_data->m_absolutePathToTempInputFolder = m_data->tempDirPath.absoluteFilePath("InputFolder").toUtf8().constData();
// note that the case of OutputFolder is intentionally upper/lower case becuase
// while files inside the output folder should be lowercased, the path to there should not be lowercased by RCJob.
m_data->m_absolutePathToTempOutputFolder = m_data->tempDirPath.absoluteFilePath("OutputFolder").toUtf8().constData();
m_data->tempDirPath.mkpath(QString::fromUtf8(m_data->m_absolutePathToTempInputFolder.c_str()));
m_data->m_diskSpaceResponder.BusConnect();
m_data->m_notifyTracker.BusConnect();
// this can be overridden in each test but if you don't override it, then this fixture will do it.
ON_CALL(m_data->m_diskSpaceResponder, CheckSufficientDiskSpace(_, _, _))
.WillByDefault(Return(true));
}
void TearDown() override
{
m_data->m_diskSpaceResponder.BusDisconnect();
m_data->m_notifyTracker.BusDisconnect();
m_data.reset();
AssetProcessorTest::TearDown();
}
protected:
struct StaticData
{
QTemporaryDir m_tempDir;
QDir tempDirPath;
AZStd::string m_absolutePathToTempInputFolder;
AZStd::string m_absolutePathToTempOutputFolder;
NiceMock<MockDiskSpaceResponder> m_diskSpaceResponder;
IgnoreNotifyTracker m_notifyTracker;
};
AZStd::unique_ptr<StaticData> m_data;
};
TEST_F(RCJobTest, CopyCompiledAssets_NoWorkToDo_Succeeds)
{
BuilderParams builderParams;
ProcessJobResponse response;
EXPECT_TRUE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 0);
}
TEST_F(RCJobTest, CopyCompiledAssets_InvalidOutputPath_FailsAndAsserts)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
response.m_outputProducts.push_back({ "file1.txt" }); // make sure that there is at least one product so that it doesn't early out.
// set only the input path, not the output path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
EXPECT_FALSE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 1);
}
TEST_F(RCJobTest, CopyCompiledAssets_InvalidInputPath_FailsAndAsserts)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
response.m_outputProducts.push_back({ "file1.txt" }); // make sure that there is at least one product so that it doesn't early out.
// set the input dir to be a broken invalid dir:
builderParams.m_processJobRequest.m_tempDirPath = AZ::Uuid::CreateRandom().ToString<AZStd::string>();
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
EXPECT_FALSE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 1);
}
TEST_F(RCJobTest, CopyCompiledAssets_TooLongPath_FailsButDoesNotAssert)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
// set only the output path, but not the input path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
// give it an overly long file name:
AZStd::string reallyLongFileName;
reallyLongFileName.resize(4096, 'x');
response.m_outputProducts.push_back({ reallyLongFileName.c_str() });
EXPECT_FALSE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 1);
}
TEST_F(RCJobTest, CopyCompiledAssets_OutOfDiskSpace_FailsButDoesNotAssert)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
// set only the output path, but not the input path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
response.m_resultCode = ProcessJobResult_Success;
response.m_outputProducts.push_back({ "file1.txt" }); // make sure that there is at least one product so that it doesn't early out.
UnitTestUtils::CreateDummyFile(QDir(m_data->m_absolutePathToTempInputFolder.c_str()).absoluteFilePath("file1.txt"), "output of file 1");
response.m_outputProducts.push_back({ "file2.txt" }); // make sure that there is at least one product so that it doesn't early out.
UnitTestUtils::CreateDummyFile(QDir(m_data->m_absolutePathToTempInputFolder.c_str()).absoluteFilePath("file2.txt"), "output of file 2");
// we exepct exactly one call to check for disk space, (not once for each file), and in this case, we'll return false.
EXPECT_CALL(m_data->m_diskSpaceResponder, CheckSufficientDiskSpace(_,_,_))
.Times(1)
.WillRepeatedly(Return(false));
EXPECT_FALSE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 1);
// no notifies should be hit since the operation should not have been attempted at all (disk space should be checked up front)
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStartPaths.size(), 0);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStopPaths.size(), 0);
// no cached files should have been copied at all.
QString expectedFinalOutputPath = QDir(QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str())).absoluteFilePath("file1.txt");
EXPECT_FALSE(QFile::exists(expectedFinalOutputPath));
expectedFinalOutputPath = QDir(QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str())).absoluteFilePath("file2.txt");
EXPECT_FALSE(QFile::exists(expectedFinalOutputPath));
}
// The RC Copy Compiled Assets routine is supposed to check up front for problem situations such as out of disk space
// or missing source files, before it tries to perform any operation. This test gives it one file which does work
// but one missing file also, and expects it to fail (without asserting) but without even trying to copy the files at all.
TEST_F(RCJobTest, CopyCompiledAssets_MissingInputFile_Fails_DoesNotAssert_DoesNotAlterCache)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
// set only the output path, but not the input path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
response.m_resultCode = ProcessJobResult_Success;
response.m_outputProducts.push_back({ "FiLe1.TxT" }); // make sure that there is at least one product so that it doesn't early out.
UnitTestUtils::CreateDummyFile(QDir(m_data->m_absolutePathToTempInputFolder.c_str()).absoluteFilePath("FiLe1.TxT"), "output of file 1");
response.m_outputProducts.push_back({ "FiLe2.txt" });
// note well that we create the first file but we don't acutally create the second one, so it is missing.
EXPECT_FALSE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 1);
// no notifies should be hit since the operation should not have been attempted at all.
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStartPaths.size(), 0);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStopPaths.size(), 0);
QString expectedFinalOutputPath = QDir(QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str())).absoluteFilePath("file1.txt");
EXPECT_FALSE(QFile::exists(expectedFinalOutputPath));
}
TEST_F(RCJobTest, CopyCompiledAssets_AbsolutePath_SucceedsAndNotifiesAboutCacheDelete)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
// set only the output path, but not the input path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
response.m_resultCode = ProcessJobResult_Success;
// make up a completely different random path to put an absolute file in:
QTemporaryDir extraDir;
QDir randomDir(extraDir.path());
randomDir.mkpath(extraDir.path());
QString absolutePathToCreate = randomDir.absoluteFilePath("someabsolutefile.txt");
UnitTestUtils::CreateDummyFile(absolutePathToCreate, "output of the file");
response.m_outputProducts.push_back({ absolutePathToCreate.toUtf8().constData() }); // absolute path to file not actually in the product scratch space folder.
// this should copy that file into the target path.
EXPECT_TRUE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStartPaths.size(), 1);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStopPaths.size(), 1);
// note that output files are automatically lowercased within the cache but the path to the cache folder itself is not lowered, just the output file.
// this is to make sure that game code never has to worry about the casing of output file paths, CRYPAK can just always lower the relpath and always know
// that even on case-sensitive platforms it won't cause trouble or a difference of behavior from non-case-sensitive ones.
QString expectedFinalOutputPath = QDir(QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str())).absoluteFilePath("someabsolutefile.txt");
ASSERT_STREQ(m_data->m_notifyTracker.m_capturedStartPaths[0].c_str(), m_data->m_notifyTracker.m_capturedStopPaths[0].c_str());
ASSERT_STREQ(m_data->m_notifyTracker.m_capturedStartPaths[0].c_str(), expectedFinalOutputPath.toUtf8().constData());
EXPECT_TRUE(QFile::exists(expectedFinalOutputPath));
}
TEST_F(RCJobTest, CopyCompiledAssets_RelativePath_SucceedsAndNotifiesAboutCacheDelete)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
// set only the output path, but not the input path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
response.m_resultCode = ProcessJobResult_Success;
response.m_outputProducts.push_back({ "FiLe1.TxT" }); // make sure that there is at least one product so that it doesn't early out.
UnitTestUtils::CreateDummyFile(QDir(m_data->m_absolutePathToTempInputFolder.c_str()).absoluteFilePath("FiLe1.TxT"), "output of file 1");
EXPECT_TRUE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStartPaths.size(), 1);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStopPaths.size(), 1);
// note that output files are automatically lowercased within the cache but the path to the cache folder itself is not lowered, just the output file.
// this is to make sure that game code never has to worry about the casing of output file paths, CRYPAK can just always lower the relpath and always know
// that even on case-sensitive platforms it won't cause trouble or a difference of behavior from non-case-sensitive ones.
QString expectedFinalOutputPath = QDir(QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str())).absoluteFilePath("file1.txt");
ASSERT_STREQ(m_data->m_notifyTracker.m_capturedStartPaths[0].c_str(), m_data->m_notifyTracker.m_capturedStopPaths[0].c_str());
ASSERT_STREQ(m_data->m_notifyTracker.m_capturedStartPaths[0].c_str(), expectedFinalOutputPath.toUtf8().constData());
EXPECT_TRUE(QFile::exists(expectedFinalOutputPath));
// Start and end paths should, however, be normalized even if the input is not.
QString normalizedStartPath = QString::fromUtf8(m_data->m_notifyTracker.m_capturedStartPaths[0].c_str());
normalizedStartPath = AssetUtilities::NormalizeFilePath(normalizedStartPath);
EXPECT_STREQ(normalizedStartPath.toUtf8().constData(), m_data->m_notifyTracker.m_capturedStartPaths[0].c_str());
QString normalizedStopPath = QString::fromUtf8(m_data->m_notifyTracker.m_capturedStopPaths[0].c_str());
normalizedStopPath = AssetUtilities::NormalizeFilePath(normalizedStopPath);
EXPECT_STREQ(normalizedStopPath.toUtf8().constData(), m_data->m_notifyTracker.m_capturedStopPaths[0].c_str());
}
} // end namespace UnitTests