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.
777 lines
32 KiB
C++
777 lines
32 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 "RCcontrollerUnitTests.h"
|
|
#include <AzTest/AzTest.h>
|
|
#include <QCoreApplication>
|
|
|
|
#if defined(AZ_PLATFORM_LINUX)
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
using namespace AssetProcessor;
|
|
using namespace AzFramework::AssetSystem;
|
|
|
|
class MockRCJob
|
|
: public RCJob
|
|
{
|
|
public:
|
|
MockRCJob(QObject* parent = nullptr)
|
|
:RCJob(parent)
|
|
{
|
|
}
|
|
|
|
void DoWork(AssetBuilderSDK::ProcessJobResponse& /*result*/, BuilderParams& builderParams, AssetUtilities::QuitListener& /*listener*/) override
|
|
{
|
|
m_DoWorkCalled = true;
|
|
m_capturedParams = builderParams;
|
|
}
|
|
public:
|
|
bool m_DoWorkCalled = false;
|
|
BuilderParams m_capturedParams;
|
|
};
|
|
|
|
RCcontrollerUnitTests::RCcontrollerUnitTests()
|
|
: m_rcController(1, 4)
|
|
{
|
|
}
|
|
|
|
void RCcontrollerUnitTests::Reset()
|
|
{
|
|
m_rcController.m_RCJobListModel.m_jobs.clear();
|
|
m_rcController.m_RCJobListModel.m_jobs.clear();
|
|
m_rcController.m_RCJobListModel.m_jobsInFlight.clear();
|
|
m_rcController.m_RCJobListModel.m_jobsInQueueLookup.clear();
|
|
|
|
m_rcController.m_pendingCriticalJobsPerPlatform.clear();
|
|
m_rcController.m_jobsCountPerPlatform.clear();
|
|
|
|
// Doing this to refresh the SortModel
|
|
m_rcController.m_RCQueueSortModel.AttachToModel(nullptr);
|
|
m_rcController.m_RCQueueSortModel.AttachToModel(&m_rcController.m_RCJobListModel);
|
|
m_rcController.m_RCQueueSortModel.m_currentJobRunKeyToJobEntries.clear();
|
|
m_rcController.m_RCQueueSortModel.m_currentlyConnectedPlatforms.clear();
|
|
}
|
|
|
|
void RCcontrollerUnitTests::StartTest()
|
|
{
|
|
PrepareRCController();
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
RunRCControllerTests();
|
|
}
|
|
|
|
void RCcontrollerUnitTests::PrepareRCController()
|
|
{
|
|
// Create 6 jobs
|
|
using namespace AssetProcessor;
|
|
RCJobListModel* rcJobListModel = m_rcController.GetQueueModel();
|
|
|
|
AssetProcessor::JobDetails jobDetails;
|
|
jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile0.txt";
|
|
jobDetails.m_jobEntry.m_watchFolderPath = QCoreApplication::applicationDirPath();
|
|
jobDetails.m_jobEntry.m_databaseSourceName = "someFile0.txt";
|
|
jobDetails.m_jobEntry.m_platformInfo = { "pc", { "desktop", "renderer" } };
|
|
jobDetails.m_jobEntry.m_jobKey = "Text files";
|
|
|
|
RCJob* job0 = new RCJob(rcJobListModel);
|
|
job0->Init(jobDetails);
|
|
rcJobListModel->addNewJob(job0);
|
|
|
|
RCJob* job1 = new RCJob(rcJobListModel);
|
|
jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile1.txt";
|
|
jobDetails.m_jobEntry.m_platformInfo = { "pc", { "desktop", "renderer" } };
|
|
jobDetails.m_jobEntry.m_jobKey = "Text files";
|
|
job1->Init(jobDetails);
|
|
rcJobListModel->addNewJob(job1);
|
|
|
|
RCJob* job2 = new RCJob(rcJobListModel);
|
|
jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile2.txt";
|
|
jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
|
|
jobDetails.m_jobEntry.m_jobKey = "Text files";
|
|
job2->Init(jobDetails);
|
|
rcJobListModel->addNewJob(job2);
|
|
|
|
RCJob* job3 = new RCJob(rcJobListModel);
|
|
jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile3.txt";
|
|
jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
|
|
jobDetails.m_jobEntry.m_jobKey = "Text files";
|
|
job3->Init(jobDetails);
|
|
rcJobListModel->addNewJob(job3);
|
|
|
|
RCJob* job4 = new RCJob(rcJobListModel);
|
|
jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile4.txt";
|
|
jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
|
|
jobDetails.m_jobEntry.m_jobKey = "Text files";
|
|
job4->Init(jobDetails);
|
|
rcJobListModel->addNewJob(job4);
|
|
|
|
RCJob* job5 = new RCJob(rcJobListModel);
|
|
jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "someFile5.txt";
|
|
jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
|
|
jobDetails.m_jobEntry.m_jobKey = "Text files";
|
|
job5->Init(jobDetails);
|
|
rcJobListModel->addNewJob(job5);
|
|
|
|
// Complete 1 job
|
|
RCJob* rcJob = job0;
|
|
rcJobListModel->markAsProcessing(rcJob);
|
|
rcJob->SetState(RCJob::completed);
|
|
rcJobListModel->markAsCompleted(rcJob);
|
|
|
|
// Put 1 job in processing state
|
|
rcJob = job1;
|
|
rcJobListModel->markAsProcessing(rcJob);
|
|
}
|
|
|
|
void RCcontrollerUnitTests::RunRCControllerTests()
|
|
{
|
|
int jobsInQueueCount = 0;
|
|
QString platformInQueueCount;
|
|
bool gotJobsInQueueCall = false;
|
|
|
|
QObject::connect(&m_rcController, &RCController::JobsInQueuePerPlatform, this, [&](QString platformName, int newCount)
|
|
{
|
|
gotJobsInQueueCall = true;
|
|
platformInQueueCount = platformName;
|
|
jobsInQueueCount = newCount;
|
|
});
|
|
|
|
RCJobListModel* rcJobListModel = m_rcController.GetQueueModel();
|
|
int returnedCount = rcJobListModel->rowCount(QModelIndex());
|
|
int expectedCount = 5; // finished ones should be removed, so it shouldn't show up
|
|
|
|
if (returnedCount != expectedCount)
|
|
{
|
|
Q_EMIT UnitTestFailed("RCJobListModel has " + QString(returnedCount) + " elements, which is invalid. Expected " + QString(expectedCount));
|
|
return;
|
|
}
|
|
|
|
QModelIndex rcJobIndex;
|
|
int rcJobJobIndex;
|
|
QString rcJobCommand;
|
|
QString rcJobState;
|
|
|
|
for (int i = 0; i < expectedCount; i++)
|
|
{
|
|
rcJobIndex = rcJobListModel->index(i, 0, QModelIndex());
|
|
|
|
if (!rcJobIndex.isValid())
|
|
{
|
|
Q_EMIT UnitTestFailed("ModelIndex for row " + QString(i) + " is invalid.");
|
|
return;
|
|
}
|
|
|
|
if (rcJobIndex.row() >= expectedCount)
|
|
{
|
|
Q_EMIT UnitTestFailed("ModelIndex for row " + QString(i) + " is invalid (outside expected range).");
|
|
return;
|
|
}
|
|
|
|
rcJobJobIndex = rcJobListModel->data(rcJobIndex, RCJobListModel::jobIndexRole).toInt();
|
|
rcJobCommand = rcJobListModel->data(rcJobIndex, RCJobListModel::displayNameRole).toString();
|
|
rcJobState = rcJobListModel->data(rcJobIndex, RCJobListModel::stateRole).toString();
|
|
}
|
|
|
|
// ----------------- test the Compile Group functionality
|
|
// add some jobs to play with:
|
|
|
|
QStringList tempJobNames;
|
|
|
|
// Note that while this is an OS-SPECIFIC path, this test does not actually invoke the file system
|
|
// or file operators, so is purely doing in-memory testing. So the path does not actually matter and the
|
|
// test should function on other operating systems too.
|
|
|
|
// test - exact match
|
|
tempJobNames << "c:/somerandomfolder/dev/blah/test.dds";
|
|
tempJobNames << "c:/somerandomfolder/dev/blah/test.cre"; // must not match
|
|
|
|
// test - NO MATCH
|
|
tempJobNames << "c:/somerandomfolder/dev/wap/wap.wap";
|
|
|
|
// test - Multiple match, ignoring extensions
|
|
tempJobNames << "c:/somerandomfolder/dev/abc/123.456";
|
|
tempJobNames << "c:/somerandomfolder/dev/abc/123.567";
|
|
tempJobNames << "c:/somerandomfolder/dev/def/123.456"; // must not match
|
|
tempJobNames << "c:/somerandomfolder/dev/def/123.567"; // must not match
|
|
|
|
// test - Multiple match, wide search, ignoring extensions and postfixes like underscore
|
|
tempJobNames << "c:/somerandomfolder/dev/aaa/bbb/123.456";
|
|
tempJobNames << "c:/somerandomfolder/dev/aaa/bbb/123.567";
|
|
tempJobNames << "c:/somerandomfolder/dev/aaa/bbb/123.890";
|
|
tempJobNames << "c:/somerandomfolder/dev/aaa/ccc/123.567"; // must not match!
|
|
tempJobNames << "c:/somerandomfolder/dev/aaa/ccc/456.567"; // must not match
|
|
|
|
// test - compile group fails the moment any file in it fails
|
|
tempJobNames << "c:/somerandomfolder/mmmnnnoo/123.456";
|
|
tempJobNames << "c:/somerandomfolder/mmmnnnoo/123.567";
|
|
|
|
// test - compile group by uuid is always an exact match. Its the same code path
|
|
// as the others in terms of success/failure.
|
|
tempJobNames << "c:/somerandomfolder/pqr/123.456";
|
|
|
|
// test - compile group for an exact ID succeeds when that exact ID is called.
|
|
|
|
QList<RCJob*> createdJobs;
|
|
|
|
|
|
|
|
for (QString name : tempJobNames)
|
|
{
|
|
AZ::Uuid uuidOfSource = AZ::Uuid::CreateName(name.toUtf8().constData());
|
|
RCJob* job = new RCJob(rcJobListModel);
|
|
AssetProcessor::JobDetails jobDetails;
|
|
jobDetails.m_jobEntry.m_watchFolderPath = "c:/somerandomfolder/dev";
|
|
jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = name;
|
|
jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
|
|
jobDetails.m_jobEntry.m_jobKey = "Compile Stuff";
|
|
jobDetails.m_jobEntry.m_sourceFileUUID = uuidOfSource;
|
|
job->Init(jobDetails);
|
|
rcJobListModel->addNewJob(job);
|
|
createdJobs.push_back(job);
|
|
}
|
|
|
|
// double them up for "android" to make sure that platform is respected
|
|
for (QString name : tempJobNames)
|
|
{
|
|
AZ::Uuid uuidOfSource = AZ::Uuid::CreateName(name.toUtf8().constData());
|
|
RCJob* job0 = new RCJob(rcJobListModel);
|
|
AssetProcessor::JobDetails jobDetails;
|
|
jobDetails.m_jobEntry.m_databaseSourceName = jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = name;
|
|
jobDetails.m_jobEntry.m_platformInfo = { "android" ,{ "mobile", "renderer" } };
|
|
jobDetails.m_jobEntry.m_jobKey = "Compile Other Stuff";
|
|
jobDetails.m_jobEntry.m_sourceFileUUID = uuidOfSource;
|
|
job0->Init(jobDetails);
|
|
rcJobListModel->addNewJob(job0);
|
|
}
|
|
|
|
bool gotCreated = false;
|
|
bool gotCompleted = false;
|
|
NetworkRequestID gotGroupID;
|
|
AssetStatus gotStatus = AssetStatus_Unknown;
|
|
QObject::connect(&m_rcController, &RCController::CompileGroupCreated, this, [&](NetworkRequestID groupID, AssetStatus status)
|
|
{
|
|
gotCreated = true;
|
|
gotGroupID = groupID;
|
|
gotStatus = status;
|
|
});
|
|
|
|
QObject::connect(&m_rcController, &RCController::CompileGroupFinished, this, [&](NetworkRequestID groupID, AssetStatus status)
|
|
{
|
|
gotCompleted = true;
|
|
gotGroupID = groupID;
|
|
gotStatus = status;
|
|
});
|
|
|
|
|
|
// EXACT MATCH TEST (with prefixes and such)
|
|
NetworkRequestID requestID(1, 1234);
|
|
m_rcController.OnRequestCompileGroup(requestID, "pc", "@assets@/blah/test.dds", AZ::Data::AssetId());
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
// this should have matched exactly one item, and when we finish that item, it should terminate:
|
|
UNIT_TEST_EXPECT_TRUE(gotCreated);
|
|
UNIT_TEST_EXPECT_FALSE(gotCompleted);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Queued);
|
|
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
|
|
// FINISH that job, we expect the finished message:
|
|
|
|
rcJobListModel->markAsProcessing(createdJobs[0]);
|
|
createdJobs[0]->SetState(RCJob::completed);
|
|
m_rcController.FinishJob(createdJobs[0]);
|
|
m_rcController.OnJobComplete(createdJobs[0]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
|
|
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_FALSE(gotCreated);
|
|
UNIT_TEST_EXPECT_TRUE(gotCompleted);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Compiled);
|
|
|
|
// ----------------------- NO MATCH TEST -----------------
|
|
// give it a name that for sure does not match:
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
m_rcController.OnRequestCompileGroup(requestID, "pc", "bibbidybobbidy.boo", AZ::Data::AssetId());
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotCreated);
|
|
UNIT_TEST_EXPECT_FALSE(gotCompleted);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Unknown);
|
|
|
|
// ----------------------- NO MATCH TEST (invalid platform) -----------------
|
|
// give it a name that for sure does not match due to platform
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
m_rcController.OnRequestCompileGroup(requestID, "aaaaaa", "blah/test.cre", AZ::Data::AssetId());
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotCreated);
|
|
UNIT_TEST_EXPECT_FALSE(gotCompleted);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Unknown);
|
|
|
|
// -------------------------------- test - Multiple match, ignoring extensions
|
|
// Give it something that should match 3 and 4 but not 5 and not 6
|
|
|
|
// in this test, we create a group with two assets in it
|
|
// so that when the one finishes, it shouldn't complete the group, until the other also finishes
|
|
// because compile groups are only finished when all assets in them are complete (or any have failed)
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
m_rcController.OnRequestCompileGroup(requestID, "pc", "abc/123.nnn", AZ::Data::AssetId());
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotCreated);
|
|
UNIT_TEST_EXPECT_FALSE(gotCompleted);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Queued);
|
|
|
|
// complete one of them. It should still be a busy group
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
rcJobListModel->markAsProcessing(createdJobs[3]);
|
|
createdJobs[3]->SetState(RCJob::completed);
|
|
m_rcController.FinishJob(createdJobs[3]);
|
|
m_rcController.OnJobComplete(createdJobs[3]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
|
|
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
// despite us finishing the one job, its still an open compile group with remaining work.
|
|
UNIT_TEST_EXPECT_FALSE(gotCreated);
|
|
UNIT_TEST_EXPECT_FALSE(gotCompleted);
|
|
|
|
// finish the other
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
rcJobListModel->markAsProcessing(createdJobs[4]);
|
|
createdJobs[4]->SetState(RCJob::completed);
|
|
m_rcController.FinishJob(createdJobs[4]);
|
|
m_rcController.OnJobComplete(createdJobs[4]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
|
|
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotCompleted);
|
|
UNIT_TEST_EXPECT_FALSE(gotCreated);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Compiled);
|
|
|
|
// test - Multiple match, wide search, ignoring extensions and postfixes like underscore:
|
|
// we expect 7, 8, and 9 to match
|
|
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
m_rcController.OnRequestCompileGroup(requestID, "pc", "aaa/bbb/123_45.abc", AZ::Data::AssetId());
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotCreated);
|
|
UNIT_TEST_EXPECT_FALSE(gotCompleted);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Queued);
|
|
|
|
// complete two of them. It should still be a busy group!
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
|
|
rcJobListModel->markAsProcessing(createdJobs[7]);
|
|
createdJobs[7]->SetState(RCJob::completed);
|
|
m_rcController.FinishJob(createdJobs[7]);
|
|
m_rcController.OnJobComplete(createdJobs[7]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
|
|
|
|
rcJobListModel->markAsProcessing(createdJobs[8]);
|
|
createdJobs[8]->SetState(RCJob::completed);
|
|
m_rcController.FinishJob(createdJobs[8]);
|
|
m_rcController.OnJobComplete(createdJobs[8]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
|
|
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_FALSE(gotCreated);
|
|
UNIT_TEST_EXPECT_FALSE(gotCompleted);
|
|
|
|
// finish the final one
|
|
rcJobListModel->markAsProcessing(createdJobs[9]);
|
|
createdJobs[9]->SetState(RCJob::completed);
|
|
m_rcController.FinishJob(createdJobs[9]);
|
|
m_rcController.OnJobComplete(createdJobs[9]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
|
|
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotCompleted);
|
|
UNIT_TEST_EXPECT_FALSE(gotCreated);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Compiled);
|
|
|
|
// ---------------- TEST : Ensure that a group fails when any member of it fails.
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
m_rcController.OnRequestCompileGroup(requestID, "pc", "mmmnnnoo/123.ZZZ", AZ::Data::AssetId()); // should match exactly 2 elements
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotCreated);
|
|
UNIT_TEST_EXPECT_FALSE(gotCompleted);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Queued);
|
|
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
|
|
rcJobListModel->markAsProcessing(createdJobs[12]);
|
|
createdJobs[12]->SetState(RCJob::failed);
|
|
m_rcController.FinishJob(createdJobs[12]);
|
|
m_rcController.OnJobComplete(createdJobs[12]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Failed);
|
|
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
// this should have failed it immediately.
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotCompleted);
|
|
UNIT_TEST_EXPECT_FALSE(gotCreated);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Failed);
|
|
|
|
/// --- TEST ------------- compile group but with UUID instead of file name.
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
AZ::Data::AssetId sourceDataID(createdJobs[14]->GetJobEntry().m_sourceFileUUID);
|
|
m_rcController.OnRequestCompileGroup(requestID, "pc", "", sourceDataID); // should match exactly 1 element.
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotCreated);
|
|
UNIT_TEST_EXPECT_FALSE(gotCompleted);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Queued);
|
|
|
|
gotCreated = false;
|
|
gotCompleted = false;
|
|
|
|
rcJobListModel->markAsProcessing(createdJobs[14]);
|
|
createdJobs[14]->SetState(RCJob::completed);
|
|
m_rcController.FinishJob(createdJobs[14]);
|
|
m_rcController.OnJobComplete(createdJobs[14]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
|
|
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotCompleted);
|
|
UNIT_TEST_EXPECT_TRUE(gotGroupID == requestID);
|
|
UNIT_TEST_EXPECT_TRUE(gotStatus == AssetStatus_Compiled);
|
|
|
|
/// --- TEST ------------- RC controller should not accept duplicate jobs.
|
|
|
|
AZ::Uuid sourceId = AZ::Uuid("{2206A6E0-FDBC-45DE-B6FE-C2FC63020BD5}");
|
|
JobDetails details;
|
|
details.m_jobEntry = JobEntry("d:/test", "test1.txt", "test1.txt", AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "pc" , {"desktop", "renderer"} }, "Test Job", 1234, 1, sourceId);
|
|
gotJobsInQueueCall = false;
|
|
int priorJobs = jobsInQueueCount;
|
|
m_rcController.JobSubmitted(details);
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotJobsInQueueCall);
|
|
UNIT_TEST_EXPECT_TRUE(jobsInQueueCount == priorJobs + 1);
|
|
priorJobs = jobsInQueueCount;
|
|
gotJobsInQueueCall = false;
|
|
|
|
// submit same job, different run key
|
|
details.m_jobEntry = JobEntry("d:/test", "/test1.txt", "test1.txt", AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "pc" ,{ "desktop", "renderer" } }, "Test Job", 1234, 2, sourceId);
|
|
m_rcController.JobSubmitted(details);
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
UNIT_TEST_EXPECT_FALSE(gotJobsInQueueCall);
|
|
|
|
// submit same job but different platform:
|
|
details.m_jobEntry = JobEntry("d:/test", "test1.txt", "test1.txt", AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "android" ,{ "mobile", "renderer" } }, "Test Job", 1234, 3, sourceId);
|
|
m_rcController.JobSubmitted(details);
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
|
|
UNIT_TEST_EXPECT_TRUE(gotJobsInQueueCall);
|
|
UNIT_TEST_EXPECT_TRUE(jobsInQueueCount == priorJobs);
|
|
|
|
|
|
////--------------- RCJob Test with critical locking TRUE
|
|
QTemporaryDir dir;
|
|
QDir tempPath(dir.path());
|
|
// test task generation while a file is in still in use
|
|
QString fileInUsePath = AssetUtilities::NormalizeFilePath(tempPath.absoluteFilePath("subfolder4/needsLock.tiff"));
|
|
|
|
UNIT_TEST_EXPECT_TRUE(UnitTestUtils::CreateDummyFile(fileInUsePath, "xxx"));
|
|
|
|
QFile lockFileTest(fileInUsePath);
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
// on windows, its enough to just open the file:
|
|
lockFileTest.open(QFile::ReadOnly);
|
|
#elif defined(AZ_PLATFORM_LINUX)
|
|
int handleOfLock = open(fileInUsePath.toUtf8().constData(), O_RDONLY | O_EXCL | O_NONBLOCK);
|
|
UNIT_TEST_EXPECT_TRUE(handleOfLock != -1);
|
|
#else
|
|
int handleOfLock = open(fileInUsePath.toUtf8().constData(), O_RDONLY | O_EXLOCK | O_NONBLOCK);
|
|
UNIT_TEST_EXPECT_TRUE(handleOfLock != -1);
|
|
#endif
|
|
|
|
AZ::Uuid uuidOfSource = AZ::Uuid("{D013122E-CF2C-4534-A87D-F82570FBC2CD}");
|
|
MockRCJob rcJob;
|
|
ScanFolderInfo scanFolderInfo("samplepath", "sampledisplayname", "samplekey", false, false);
|
|
AssetProcessor::JobDetails jobDetailsToInitWith;
|
|
jobDetailsToInitWith.m_jobEntry.m_watchFolderPath = tempPath.absoluteFilePath("subfolder4");
|
|
jobDetailsToInitWith.m_jobEntry.m_databaseSourceName = jobDetailsToInitWith.m_jobEntry.m_pathRelativeToWatchFolder = "needsLock.tiff";
|
|
jobDetailsToInitWith.m_jobEntry.m_platformInfo = { "pc", { "tools", "editor"} };
|
|
jobDetailsToInitWith.m_jobEntry.m_jobKey = "Text files";
|
|
jobDetailsToInitWith.m_jobEntry.m_sourceFileUUID = uuidOfSource;
|
|
jobDetailsToInitWith.m_scanFolder = &scanFolderInfo;
|
|
rcJob.Init(jobDetailsToInitWith);
|
|
|
|
bool beginWork = false;
|
|
QObject::connect(&rcJob, &RCJob::BeginWork, this, [&beginWork]()
|
|
{
|
|
beginWork = true;
|
|
}
|
|
);
|
|
bool jobFinished = false;
|
|
QObject::connect(&rcJob, &RCJob::JobFinished, this, [&jobFinished](AssetBuilderSDK::ProcessJobResponse /*result*/)
|
|
{
|
|
jobFinished = true;
|
|
}
|
|
);
|
|
rcJob.SetCheckExclusiveLock(true);
|
|
rcJob.Start();
|
|
|
|
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
// on windows, opening a file for reading locks it
|
|
// but on other platforms, this is not the case.
|
|
// we only expect work to begin when we can gain an exclusive lock on this file.
|
|
UNIT_TEST_EXPECT_FALSE(UnitTestUtils::BlockUntil(beginWork, 5000));
|
|
|
|
// Once we release the file, it should process normally
|
|
lockFileTest.close();
|
|
#else
|
|
close(handleOfLock);
|
|
#endif
|
|
|
|
//Once we release the lock we should see jobStarted and jobFinished
|
|
UNIT_TEST_EXPECT_TRUE(UnitTestUtils::BlockUntil(jobFinished, 5000));
|
|
UNIT_TEST_EXPECT_TRUE(beginWork);
|
|
UNIT_TEST_EXPECT_TRUE(rcJob.m_DoWorkCalled);
|
|
|
|
// make sure the source UUID made its way all the way from create jobs to process jobs.
|
|
UNIT_TEST_EXPECT_TRUE(rcJob.m_capturedParams.m_processJobRequest.m_sourceFileUUID == uuidOfSource);
|
|
////-----------------------UNIT TEST Order Job Dependency
|
|
|
|
QString fileA = AssetUtilities::NormalizeFilePath(tempPath.absoluteFilePath("FileA.txt"));
|
|
QString fileB = AssetUtilities::NormalizeFilePath(tempPath.absoluteFilePath("FileB.txt"));
|
|
QString fileC = AssetUtilities::NormalizeFilePath(tempPath.absoluteFilePath("FileC.txt"));
|
|
QString fileD = AssetUtilities::NormalizeFilePath(tempPath.absoluteFilePath("FileD.txt"));
|
|
|
|
UNIT_TEST_EXPECT_TRUE(UnitTestUtils::CreateDummyFile(fileA, "xxx"));
|
|
UNIT_TEST_EXPECT_TRUE(UnitTestUtils::CreateDummyFile(fileB, "xxx"));
|
|
UNIT_TEST_EXPECT_TRUE(UnitTestUtils::CreateDummyFile(fileC, "xxx"));
|
|
UNIT_TEST_EXPECT_TRUE(UnitTestUtils::CreateDummyFile(fileD, "xxx"));
|
|
|
|
Reset();
|
|
AZ::Uuid builderUuid = AZ::Uuid::CreateRandom();
|
|
m_assetBuilderDesc.m_name = "Job Dependency UnitTest";
|
|
m_assetBuilderDesc.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
|
|
m_assetBuilderDesc.m_busId = builderUuid;
|
|
m_assetBuilderDesc.m_processJobFunction = []
|
|
([[maybe_unused]] const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
|
|
{
|
|
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
|
|
};
|
|
|
|
m_rcController.SetDispatchPaused(true);
|
|
|
|
// Job B has an order job dependency on Job A
|
|
|
|
// Setting up JobA
|
|
MockRCJob* jobA = new MockRCJob(&m_rcController.m_RCJobListModel);
|
|
JobDetails jobdetailsA;
|
|
jobdetailsA.m_scanFolder = &scanFolderInfo;
|
|
jobdetailsA.m_assetBuilderDesc = m_assetBuilderDesc;
|
|
jobdetailsA.m_jobEntry.m_databaseSourceName = jobdetailsA.m_jobEntry.m_pathRelativeToWatchFolder = "fileA.txt";
|
|
jobdetailsA.m_jobEntry.m_watchFolderPath = scanFolderInfo.ScanPath();
|
|
jobdetailsA.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
|
|
jobdetailsA.m_jobEntry.m_jobKey = "TestJobA";
|
|
jobdetailsA.m_jobEntry.m_builderGuid = builderUuid;
|
|
|
|
jobA->Init(jobdetailsA);
|
|
m_rcController.m_RCQueueSortModel.AddJobIdEntry(jobA);
|
|
m_rcController.m_RCJobListModel.addNewJob(jobA);
|
|
|
|
bool beginWorkA = false;
|
|
QObject::connect(jobA, &RCJob::BeginWork, this, [this, &beginWorkA]()
|
|
{
|
|
beginWorkA = true;
|
|
}
|
|
);
|
|
|
|
bool jobFinishedA = false;
|
|
QObject::connect(jobA, &RCJob::JobFinished, this, [this, &jobFinishedA](AssetBuilderSDK::ProcessJobResponse /*result*/)
|
|
{
|
|
jobFinishedA = true;
|
|
}
|
|
);
|
|
|
|
// Setting up JobB
|
|
JobDetails jobdetailsB;
|
|
jobdetailsB.m_scanFolder = &scanFolderInfo;
|
|
jobdetailsA.m_assetBuilderDesc = m_assetBuilderDesc;
|
|
jobdetailsB.m_jobEntry.m_databaseSourceName = jobdetailsB.m_jobEntry.m_pathRelativeToWatchFolder = "fileB.txt";
|
|
jobdetailsB.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
|
|
jobdetailsB.m_jobEntry.m_watchFolderPath = scanFolderInfo.ScanPath();
|
|
jobdetailsB.m_jobEntry.m_jobKey = "TestJobB";
|
|
jobdetailsB.m_jobEntry.m_builderGuid = builderUuid;
|
|
|
|
jobdetailsB.m_critical = true; //make jobB critical so that it will be analyzed first even though we want JobA to run first
|
|
|
|
AssetBuilderSDK::SourceFileDependency sourceFileBDependency;
|
|
sourceFileBDependency.m_sourceFileDependencyPath = "fileB.txt";
|
|
|
|
AssetBuilderSDK::SourceFileDependency sourceFileADependency;
|
|
sourceFileADependency.m_sourceFileDependencyPath = "fileA.txt";
|
|
|
|
// Make job B has an order job dependency on Job A
|
|
AssetBuilderSDK::JobDependency jobDependencyA("TestJobA", "pc", AssetBuilderSDK::JobDependencyType::Order, sourceFileADependency);
|
|
jobdetailsB.m_jobDependencyList.push_back({ jobDependencyA });
|
|
|
|
//Setting JobB
|
|
MockRCJob* jobB = new MockRCJob(&m_rcController.m_RCJobListModel);
|
|
jobB->Init(jobdetailsB);
|
|
m_rcController.m_RCQueueSortModel.AddJobIdEntry(jobB);
|
|
m_rcController.m_RCJobListModel.addNewJob(jobB);
|
|
|
|
bool beginWorkB = false;
|
|
QMetaObject::Connection conn = QObject::connect(jobB, &RCJob::BeginWork, this, [this, &beginWorkB, &jobFinishedA]()
|
|
{
|
|
// JobA should finish first before JobB starts
|
|
UNIT_TEST_EXPECT_TRUE(jobFinishedA);
|
|
beginWorkB = true;
|
|
}
|
|
);
|
|
|
|
bool jobFinishedB = false;
|
|
QObject::connect(jobB, &RCJob::JobFinished, this, [this, &jobFinishedB](AssetBuilderSDK::ProcessJobResponse /*result*/)
|
|
{
|
|
jobFinishedB = true;
|
|
}
|
|
);
|
|
bool allJobsCompleted = false;
|
|
QObject::connect(&m_rcController, &RCController::BecameIdle, this, [&allJobsCompleted]()
|
|
{
|
|
allJobsCompleted = true;
|
|
}
|
|
);
|
|
|
|
JobEntry completedJob;
|
|
|
|
QObject::connect(&m_rcController, &RCController::FileCompiled, this, [&completedJob](JobEntry entry, AssetBuilderSDK::ProcessJobResponse response)
|
|
{
|
|
completedJob = entry;
|
|
});
|
|
|
|
QObject::connect(&m_rcController, &RCController::FileCancelled, this, [&completedJob](JobEntry entry)
|
|
{
|
|
completedJob = entry;
|
|
});
|
|
|
|
QObject::connect(&m_rcController, &RCController::FileFailed, this, [&completedJob](JobEntry entry)
|
|
{
|
|
completedJob = entry;
|
|
});
|
|
|
|
QObject::connect(&m_rcController, &RCController::ActiveJobsCountChanged, this, [this, &completedJob](unsigned int /*count*/)
|
|
{
|
|
m_rcController.OnAddedToCatalog(completedJob);
|
|
completedJob = {};
|
|
});
|
|
|
|
m_rcController.SetDispatchPaused(false);
|
|
|
|
m_rcController.DispatchJobs();
|
|
UNIT_TEST_EXPECT_TRUE(UnitTestUtils::BlockUntil(allJobsCompleted, 5000));
|
|
UNIT_TEST_EXPECT_TRUE(jobFinishedB);
|
|
|
|
// Now test the use case where we have a cyclic dependency,
|
|
// although the order in which these job will start is not defined but we can ensure that
|
|
// all the job finishes and RCController goes Idle
|
|
allJobsCompleted = false;
|
|
Reset();
|
|
m_rcController.SetDispatchPaused(true);
|
|
QObject::disconnect(conn);
|
|
|
|
//Setting up JobC
|
|
JobDetails jobdetailsC;
|
|
jobdetailsC.m_scanFolder = &scanFolderInfo;
|
|
jobdetailsC.m_assetBuilderDesc = m_assetBuilderDesc;
|
|
jobdetailsC.m_jobEntry.m_databaseSourceName = jobdetailsC.m_jobEntry.m_pathRelativeToWatchFolder = "fileC.txt";
|
|
jobdetailsC.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
|
|
jobdetailsC.m_jobEntry.m_watchFolderPath = scanFolderInfo.ScanPath();
|
|
jobdetailsC.m_jobEntry.m_jobKey = "TestJobC";
|
|
jobdetailsC.m_jobEntry.m_builderGuid = builderUuid;
|
|
|
|
AssetBuilderSDK::SourceFileDependency sourceFileCDependency;
|
|
sourceFileCDependency.m_sourceFileDependencyPath = "fileC.txt";
|
|
|
|
//Setting up Job D
|
|
JobDetails jobdetailsD;
|
|
jobdetailsD.m_scanFolder = &scanFolderInfo;
|
|
jobdetailsD.m_assetBuilderDesc = m_assetBuilderDesc;
|
|
jobdetailsD.m_jobEntry.m_databaseSourceName = jobdetailsD.m_jobEntry.m_pathRelativeToWatchFolder = "fileD.txt";
|
|
jobdetailsD.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
|
|
jobdetailsD.m_jobEntry.m_watchFolderPath = scanFolderInfo.ScanPath();
|
|
jobdetailsD.m_jobEntry.m_jobKey = "TestJobD";
|
|
jobdetailsD.m_jobEntry.m_builderGuid = builderUuid;
|
|
AssetBuilderSDK::SourceFileDependency sourceFileDDependency;
|
|
sourceFileDDependency.m_sourceFileDependencyPath = "fileD.txt";
|
|
|
|
//creating cyclic job order dependencies i.e JobC and JobD have order job dependency on each other
|
|
AssetBuilderSDK::JobDependency jobDependencyC("TestJobC", "pc", AssetBuilderSDK::JobDependencyType::Order, sourceFileCDependency);
|
|
AssetBuilderSDK::JobDependency jobDependencyD("TestJobD", "pc", AssetBuilderSDK::JobDependencyType::Order, sourceFileDDependency);
|
|
jobdetailsC.m_jobDependencyList.push_back({ jobDependencyD });
|
|
jobdetailsD.m_jobDependencyList.push_back({ jobDependencyC });
|
|
|
|
MockRCJob* jobD = new MockRCJob(&m_rcController.m_RCJobListModel);
|
|
MockRCJob* jobC = new MockRCJob(&m_rcController.m_RCJobListModel);
|
|
|
|
jobC->Init(jobdetailsC);
|
|
m_rcController.m_RCQueueSortModel.AddJobIdEntry(jobC);
|
|
m_rcController.m_RCJobListModel.addNewJob(jobC);
|
|
|
|
jobD->Init(jobdetailsD);
|
|
m_rcController.m_RCQueueSortModel.AddJobIdEntry(jobD);
|
|
m_rcController.m_RCJobListModel.addNewJob(jobD);
|
|
|
|
m_rcController.SetDispatchPaused(false);
|
|
m_rcController.DispatchJobs();
|
|
UNIT_TEST_EXPECT_TRUE(UnitTestUtils::BlockUntil(allJobsCompleted, 5000));
|
|
|
|
// Test case when source file is deleted before it started processing
|
|
{
|
|
int prevJobCount = rcJobListModel->itemCount();
|
|
MockRCJob rcJobAddAndDelete;
|
|
AssetProcessor::JobDetails jobDetailsToInitWithInsideScope;
|
|
jobDetailsToInitWithInsideScope.m_jobEntry.m_pathRelativeToWatchFolder = jobDetailsToInitWithInsideScope.m_jobEntry.m_databaseSourceName = "someFile0.txt";
|
|
jobDetailsToInitWithInsideScope.m_jobEntry.m_platformInfo = { "pc",{ "tools", "editor" } };
|
|
jobDetailsToInitWithInsideScope.m_jobEntry.m_jobKey = "Text files";
|
|
jobDetailsToInitWithInsideScope.m_jobEntry.m_sourceFileUUID = jobDetailsToInitWith.m_jobEntry.m_sourceFileUUID;
|
|
rcJobAddAndDelete.Init(jobDetailsToInitWithInsideScope);
|
|
rcJobListModel->addNewJob(&rcJobAddAndDelete);
|
|
// verify that job was added
|
|
UNIT_TEST_EXPECT_TRUE(rcJobListModel->itemCount() == prevJobCount + 1);
|
|
m_rcController.RemoveJobsBySource("someFile0.txt");
|
|
// verify that job was removed
|
|
UNIT_TEST_EXPECT_TRUE(rcJobListModel->itemCount() == prevJobCount);
|
|
}
|
|
Q_EMIT UnitTestPassed();
|
|
}
|
|
|
|
#if !AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
|
REGISTER_UNIT_TEST(RCcontrollerUnitTests)
|
|
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
|
|