RCcontrollerUnitTests.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzTest/AzTest.h>
  9. #include <native/resourcecompiler/rccontroller.h>
  10. #include <native/tests/MockAssetDatabaseRequestsHandler.h>
  11. #include <native/unittests/RCcontrollerUnitTests.h>
  12. #include <native/unittests/UnitTestUtils.h>
  13. #include <QCoreApplication>
  14. #if defined(AZ_PLATFORM_LINUX)
  15. #include <sys/stat.h>
  16. #include <fcntl.h>
  17. #endif
  18. #include <tests/UnitTestUtilities.h>
  19. #include <tests/AssetProcessorTest.h>
  20. using namespace AssetProcessor;
  21. using namespace AzFramework::AssetSystem;
  22. namespace
  23. {
  24. constexpr NetworkRequestID RequestID(1, 1234);
  25. constexpr int MaxProcessingWaitTimeMs = 60 * 1000; // Wait up to 1 minute. Give a generous amount of time to allow for slow CPUs
  26. const ScanFolderInfo TestScanFolderInfo("c:/samplepath", "sampledisplayname", "samplekey", false, false);
  27. const AZ::Uuid BuilderUuid = AZ::Uuid::CreateRandom();
  28. constexpr int MinRCJobs = 1;
  29. constexpr int MaxRCJobs = 4;
  30. }
  31. class MockRCJob
  32. : public RCJob
  33. {
  34. public:
  35. MockRCJob(QObject* parent = nullptr)
  36. :RCJob(parent)
  37. {
  38. }
  39. void DoWork(AssetBuilderSDK::ProcessJobResponse& /*result*/, BuilderParams& builderParams, AssetUtilities::QuitListener& /*listener*/) override
  40. {
  41. m_DoWorkCalled = true;
  42. m_capturedParams = builderParams;
  43. }
  44. public:
  45. bool m_DoWorkCalled = false;
  46. BuilderParams m_capturedParams;
  47. };
  48. void RCcontrollerUnitTests::FinishJob(AssetProcessor::RCJob* rcJob)
  49. {
  50. m_rcController->FinishJob(rcJob);
  51. }
  52. void RCcontrollerUnitTests::PrepareRCJobListModelTest(int& numJobs)
  53. {
  54. // Create 6 jobs
  55. using namespace AssetProcessor;
  56. m_rcJobListModel = m_rcController->GetQueueModel();
  57. m_rcQueueSortModel = &m_rcController->m_RCQueueSortModel;
  58. AssetProcessor::JobDetails jobDetails;
  59. jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile0.txt");
  60. jobDetails.m_jobEntry.m_platformInfo = { "pc", { "desktop", "renderer" } };
  61. jobDetails.m_jobEntry.m_jobKey = "Text files";
  62. RCJob* job0 = new RCJob(m_rcJobListModel);
  63. job0->Init(jobDetails);
  64. m_rcJobListModel->addNewJob(job0);
  65. ++numJobs;
  66. RCJob* job1 = new RCJob(m_rcJobListModel);
  67. jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile1.txt");
  68. jobDetails.m_jobEntry.m_platformInfo = { "pc", { "desktop", "renderer" } };
  69. jobDetails.m_jobEntry.m_jobKey = "Text files";
  70. job1->Init(jobDetails);
  71. m_rcJobListModel->addNewJob(job1);
  72. ++numJobs;
  73. RCJob* job2 = new RCJob(m_rcJobListModel);
  74. jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile2.txt");
  75. jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
  76. jobDetails.m_jobEntry.m_jobKey = "Text files";
  77. job2->Init(jobDetails);
  78. m_rcJobListModel->addNewJob(job2);
  79. ++numJobs;
  80. RCJob* job3 = new RCJob(m_rcJobListModel);
  81. jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile3.txt");
  82. jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
  83. jobDetails.m_jobEntry.m_jobKey = "Text files";
  84. job3->Init(jobDetails);
  85. m_rcJobListModel->addNewJob(job3);
  86. ++numJobs;
  87. RCJob* job4 = new RCJob(m_rcJobListModel);
  88. jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile4.txt");
  89. jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
  90. jobDetails.m_jobEntry.m_jobKey = "Text files";
  91. job4->Init(jobDetails);
  92. m_rcJobListModel->addNewJob(job4);
  93. ++numJobs;
  94. RCJob* job5 = new RCJob(m_rcJobListModel);
  95. jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/someFile5.txt");
  96. jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
  97. jobDetails.m_jobEntry.m_jobKey = "Text files";
  98. job5->Init(jobDetails);
  99. m_rcJobListModel->addNewJob(job5);
  100. ++numJobs;
  101. // Complete 1 job
  102. RCJob* rcJob = job0;
  103. m_rcJobListModel->markAsProcessing(rcJob);
  104. rcJob->SetState(RCJob::completed);
  105. m_rcJobListModel->markAsCompleted(rcJob);
  106. // Put 1 job in processing state
  107. rcJob = job1;
  108. m_rcJobListModel->markAsProcessing(rcJob);
  109. QCoreApplication::processEvents(QEventLoop::AllEvents);
  110. }
  111. void RCcontrollerUnitTests::PrepareCompileGroupTests(const QStringList& tempJobNames, bool& gotCreated, bool& gotCompleted, AssetProcessor::NetworkRequestID& gotGroupID, AzFramework::AssetSystem::AssetStatus& gotStatus)
  112. {
  113. // Note that while this is an OS-SPECIFIC path, this test does not actually invoke the file system
  114. // or file operators, so is purely doing in-memory testing. So the path does not actually matter and the
  115. // test should function on other operating systems too.
  116. // Compile group for an exact ID succeeds when that exact ID is called.
  117. for (QString name : tempJobNames)
  118. {
  119. AZ::Uuid uuidOfSource = AZ::Uuid::CreateName(name.toUtf8().constData());
  120. RCJob* job = new RCJob(m_rcJobListModel);
  121. AssetProcessor::JobDetails jobDetails;
  122. jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/dev", name);
  123. jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
  124. jobDetails.m_jobEntry.m_jobKey = "Compile Stuff";
  125. jobDetails.m_jobEntry.m_sourceFileUUID = uuidOfSource;
  126. job->Init(jobDetails);
  127. m_rcJobListModel->addNewJob(job);
  128. m_createdJobs.push_back(job);
  129. }
  130. // double them up for "android" to make sure that platform is respected
  131. for (QString name : tempJobNames)
  132. {
  133. AZ::Uuid uuidOfSource = AZ::Uuid::CreateName(name.toUtf8().constData());
  134. RCJob* job0 = new RCJob(m_rcJobListModel);
  135. AssetProcessor::JobDetails jobDetails;
  136. jobDetails.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference("c:/somerandomfolder/dev", name);
  137. jobDetails.m_jobEntry.m_platformInfo = { "android" ,{ "mobile", "renderer" } };
  138. jobDetails.m_jobEntry.m_jobKey = "Compile Other Stuff";
  139. jobDetails.m_jobEntry.m_sourceFileUUID = uuidOfSource;
  140. job0->Init(jobDetails);
  141. m_rcJobListModel->addNewJob(job0);
  142. }
  143. ConnectCompileGroupSignalsAndSlots(gotCreated, gotCompleted, gotGroupID, gotStatus);
  144. }
  145. void RCcontrollerUnitTests::Reset()
  146. {
  147. m_rcController->m_RCJobListModel.m_jobs.clear();
  148. m_rcController->m_RCJobListModel.m_jobsInFlight.clear();
  149. m_rcController->m_RCJobListModel.m_jobsInQueueLookup.clear();
  150. m_rcController->m_pendingCriticalJobsPerPlatform.clear();
  151. m_rcController->m_jobsCountPerPlatform.clear();
  152. // Doing this to refresh the SortModel
  153. m_rcController->m_RCQueueSortModel.AttachToModel(nullptr);
  154. m_rcController->m_RCQueueSortModel.AttachToModel(&m_rcController->m_RCJobListModel);
  155. m_rcController->m_RCQueueSortModel.m_currentJobRunKeyToJobEntries.clear();
  156. m_rcController->m_RCQueueSortModel.m_currentlyConnectedPlatforms.clear();
  157. }
  158. void RCcontrollerUnitTests::ConnectCompileGroupSignalsAndSlots(bool& gotCreated, bool& gotCompleted, NetworkRequestID& gotGroupID, AssetStatus& gotStatus)
  159. {
  160. QObject::connect(m_rcController.get(), &RCController::CompileGroupCreated, this, [&](NetworkRequestID groupID, AssetStatus status)
  161. {
  162. gotCreated = true;
  163. gotGroupID = groupID;
  164. gotStatus = status;
  165. });
  166. QObject::connect(m_rcController.get(), &RCController::CompileGroupFinished, this, [&](NetworkRequestID groupID, AssetStatus status)
  167. {
  168. gotCompleted = true;
  169. gotGroupID = groupID;
  170. gotStatus = status;
  171. });
  172. }
  173. void RCcontrollerUnitTests::ConnectJobSignalsAndSlots(bool& allJobsCompleted, JobEntry& completedJob)
  174. {
  175. QObject::connect(m_rcController.get(), &RCController::FileCompiled, this, [&](JobEntry entry, [[maybe_unused]] AssetBuilderSDK::ProcessJobResponse response)
  176. {
  177. completedJob = entry;
  178. });
  179. QObject::connect(m_rcController.get(), &RCController::FileCancelled, this, [&](JobEntry entry)
  180. {
  181. completedJob = entry;
  182. });
  183. QObject::connect(m_rcController.get(), &RCController::FileFailed, this, [&](JobEntry entry)
  184. {
  185. completedJob = entry;
  186. });
  187. QObject::connect(m_rcController.get(), &RCController::ActiveJobsCountChanged, this, [&](unsigned int /*count*/)
  188. {
  189. m_rcController->OnAddedToCatalog(completedJob);
  190. completedJob = {};
  191. });
  192. QObject::connect(m_rcController.get(), &RCController::BecameIdle, this, [&]()
  193. {
  194. allJobsCompleted = true;
  195. }
  196. );
  197. }
  198. void RCcontrollerUnitTests::SetUp()
  199. {
  200. UnitTest::AssetProcessorUnitTestBase::SetUp();
  201. m_rcController = AZStd::make_unique<AssetProcessor::RCController>(MinRCJobs, MaxRCJobs);
  202. QDir assetRootPath(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
  203. m_appManager->m_platformConfig->AddScanFolder(TestScanFolderInfo);
  204. m_appManager->m_platformConfig->AddScanFolder(
  205. AssetProcessor::ScanFolderInfo{ "c:/somerandomfolder", "scanfolder", "scanfolder", true, true, {}, 0, 1 });
  206. m_appManager->m_platformConfig->AddScanFolder(
  207. AssetProcessor::ScanFolderInfo{ "d:/test", "scanfolder2", "scanfolder2", true, true, {}, 0, 2 });
  208. m_appManager->m_platformConfig->AddScanFolder(
  209. AssetProcessor::ScanFolderInfo{ assetRootPath.absoluteFilePath("subfolder4"), "subfolder4", "subfolder4", false, true, {}, 0, 3 });
  210. using namespace AssetProcessor;
  211. m_rcJobListModel = m_rcController->GetQueueModel();
  212. m_rcQueueSortModel = &m_rcController->m_RCQueueSortModel;
  213. }
  214. void RCcontrollerUnitTests::TearDown()
  215. {
  216. m_rcJobListModel = nullptr;
  217. m_rcQueueSortModel = nullptr;
  218. m_rcController.reset();
  219. UnitTest::AssetProcessorUnitTestBase::TearDown();
  220. }
  221. TEST_F(RCcontrollerUnitTests, TestRCJobListModel_AddJobEntries_Succeeds)
  222. {
  223. int numJobs = 0;
  224. PrepareRCJobListModelTest(numJobs);
  225. int returnedCount = m_rcJobListModel->rowCount(QModelIndex());
  226. int expectedCount = numJobs - 1; // Finished jobs should be removed, so they shouldn't show up
  227. ASSERT_EQ(returnedCount, expectedCount) << AZStd::string::format("RCJobListModel has %d elements, which is invalid. Expected %d", returnedCount, expectedCount).c_str();
  228. QModelIndex rcJobIndex;
  229. QString rcJobCommand;
  230. QString rcJobState;
  231. for (int i = 0; i < expectedCount; i++)
  232. {
  233. rcJobIndex = m_rcJobListModel->index(i, 0, QModelIndex());
  234. ASSERT_TRUE(rcJobIndex.isValid()) << AZStd::string::format("ModelIndex for row %d is invalid.", i).c_str();
  235. ASSERT_LT(rcJobIndex.row(), expectedCount) << AZStd::string::format("ModelIndex for row %d is invalid (outside expected range).", i).c_str();
  236. rcJobCommand = m_rcJobListModel->data(rcJobIndex, RCJobListModel::displayNameRole).toString();
  237. rcJobState = m_rcJobListModel->data(rcJobIndex, RCJobListModel::stateRole).toString();
  238. }
  239. }
  240. TEST_F(RCcontrollerUnitTests, TestCompileGroup_RequestExactMatchCompileGroup_Succeeds)
  241. {
  242. bool gotCreated = false;
  243. bool gotCompleted = false;
  244. NetworkRequestID gotGroupID;
  245. AssetStatus gotStatus = AssetStatus_Unknown;
  246. QStringList tempJobNames;
  247. tempJobNames << "c:/somerandomfolder/dev/blah/test.dds";
  248. tempJobNames << "c:/somerandomfolder/dev/blah/test.cre"; // must not match
  249. PrepareCompileGroupTests(tempJobNames, gotCreated, gotCompleted, gotGroupID, gotStatus);
  250. m_rcController->OnRequestCompileGroup(RequestID, "pc", "@products@/blah/test.dds", AZ::Data::AssetId());
  251. QCoreApplication::processEvents(QEventLoop::AllEvents);
  252. // this should have matched exactly one item, and when we finish that item, it should terminate:
  253. EXPECT_TRUE(gotCreated);
  254. EXPECT_FALSE(gotCompleted);
  255. EXPECT_EQ(gotGroupID, RequestID);
  256. EXPECT_EQ(gotStatus, AssetStatus_Queued);
  257. gotCreated = false;
  258. gotCompleted = false;
  259. // FINISH that job, we expect the finished message:
  260. m_rcJobListModel->markAsProcessing(m_createdJobs[0]);
  261. m_createdJobs[0]->SetState(RCJob::completed);
  262. FinishJob(m_createdJobs[0]);
  263. m_rcController->OnJobComplete(m_createdJobs[0]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
  264. QCoreApplication::processEvents(QEventLoop::AllEvents);
  265. EXPECT_FALSE(gotCreated);
  266. EXPECT_TRUE(gotCompleted);
  267. EXPECT_EQ(gotGroupID, RequestID);
  268. EXPECT_EQ(gotStatus, AssetStatus_Compiled);
  269. }
  270. TEST_F(RCcontrollerUnitTests, TestCompileGroup_RequestNoMatchCompileGroup_Succeeds)
  271. {
  272. bool gotCreated = false;
  273. bool gotCompleted = false;
  274. NetworkRequestID gotGroupID;
  275. AssetStatus gotStatus = AssetStatus_Unknown;
  276. QStringList tempJobNames;
  277. tempJobNames << "c:/somerandomfolder/dev/wap/wap.wap";
  278. PrepareCompileGroupTests(tempJobNames, gotCreated, gotCompleted, gotGroupID, gotStatus);
  279. // give it a name that for sure does not match:
  280. m_rcController->OnRequestCompileGroup(RequestID, "pc", "bibbidybobbidy.boo", AZ::Data::AssetId());
  281. QCoreApplication::processEvents(QEventLoop::AllEvents);
  282. EXPECT_TRUE(gotCreated);
  283. EXPECT_FALSE(gotCompleted);
  284. EXPECT_EQ(gotGroupID, RequestID);
  285. EXPECT_EQ(gotStatus, AssetStatus_Unknown);
  286. }
  287. TEST_F(RCcontrollerUnitTests, TestCompileGroup_RequestCompileGroupWithInvalidPlatform_Succeeds)
  288. {
  289. bool gotCreated = false;
  290. bool gotCompleted = false;
  291. NetworkRequestID gotGroupID;
  292. AssetStatus gotStatus = AssetStatus_Unknown;
  293. QStringList tempJobNames;
  294. tempJobNames << "c:/somerandomfolder/dev/blah/test.cre"; // must not match
  295. PrepareCompileGroupTests(tempJobNames, gotCreated, gotCompleted, gotGroupID, gotStatus);
  296. // give it a name that for sure does not match due to platform.
  297. m_rcController->OnRequestCompileGroup(RequestID, "aaaaaa", "blah/test.cre", AZ::Data::AssetId());
  298. QCoreApplication::processEvents(QEventLoop::AllEvents);
  299. EXPECT_TRUE(gotCreated);
  300. EXPECT_FALSE(gotCompleted);
  301. EXPECT_EQ(gotGroupID, RequestID);
  302. EXPECT_EQ(gotStatus, AssetStatus_Unknown);
  303. }
  304. TEST_F(RCcontrollerUnitTests, TestCompileGroup_FinishEachAssetsInGroup_Succeeds)
  305. {
  306. // in this test, we create a group with two assets in it
  307. // so that when the one finishes, it shouldn't complete the group, until the other also finishes
  308. // because compile groups are only finished when all assets in them are complete (or any have failed)
  309. bool gotCreated = false;
  310. bool gotCompleted = false;
  311. NetworkRequestID gotGroupID;
  312. AssetStatus gotStatus = AssetStatus_Unknown;
  313. QStringList tempJobNames;
  314. tempJobNames << "c:/somerandomfolder/dev/abc/123.456";
  315. tempJobNames << "c:/somerandomfolder/dev/abc/123.567";
  316. tempJobNames << "c:/somerandomfolder/dev/def/123.456"; // must not match
  317. tempJobNames << "c:/somerandomfolder/dev/def/123.567"; // must not match
  318. PrepareCompileGroupTests(tempJobNames, gotCreated, gotCompleted, gotGroupID, gotStatus);
  319. m_rcController->OnRequestCompileGroup(RequestID, "pc", "abc/123.nnn", AZ::Data::AssetId());
  320. QCoreApplication::processEvents(QEventLoop::AllEvents);
  321. EXPECT_TRUE(gotCreated);
  322. EXPECT_FALSE(gotCompleted);
  323. EXPECT_EQ(gotGroupID, RequestID);
  324. EXPECT_EQ(gotStatus, AssetStatus_Queued);
  325. // complete one of them. It should still be a busy group.
  326. int IndexOfJobToComplete = 0;
  327. gotCreated = false;
  328. gotCompleted = false;
  329. m_rcJobListModel->markAsProcessing(m_createdJobs[IndexOfJobToComplete]);
  330. m_createdJobs[IndexOfJobToComplete]->SetState(RCJob::completed);
  331. FinishJob(m_createdJobs[IndexOfJobToComplete]);
  332. m_rcController->OnJobComplete(m_createdJobs[IndexOfJobToComplete]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
  333. QCoreApplication::processEvents(QEventLoop::AllEvents);
  334. // despite us finishing the one job, its still an open compile group with remaining work.
  335. EXPECT_FALSE(gotCreated);
  336. EXPECT_FALSE(gotCompleted);
  337. // finish the other
  338. ++IndexOfJobToComplete;
  339. EXPECT_LT(IndexOfJobToComplete, m_createdJobs.size());
  340. gotCreated = false;
  341. gotCompleted = false;
  342. m_rcJobListModel->markAsProcessing(m_createdJobs[IndexOfJobToComplete]);
  343. m_createdJobs[IndexOfJobToComplete]->SetState(RCJob::completed);
  344. FinishJob(m_createdJobs[IndexOfJobToComplete]);
  345. m_rcController->OnJobComplete(m_createdJobs[IndexOfJobToComplete]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
  346. QCoreApplication::processEvents(QEventLoop::AllEvents);
  347. EXPECT_TRUE(gotCompleted);
  348. EXPECT_FALSE(gotCreated);
  349. EXPECT_EQ(gotGroupID, RequestID);
  350. EXPECT_EQ(gotStatus, AssetStatus_Compiled);
  351. }
  352. TEST_F(RCcontrollerUnitTests, TestCompileGroup_RequestWideSearchCompileGroup_Succeeds)
  353. {
  354. bool gotCreated = false;
  355. bool gotCompleted = false;
  356. NetworkRequestID gotGroupID;
  357. AssetStatus gotStatus = AssetStatus_Unknown;
  358. QStringList tempJobNames;
  359. tempJobNames << "c:/somerandomfolder/dev/aaa/bbb/123.456";
  360. tempJobNames << "c:/somerandomfolder/dev/aaa/bbb/123.567";
  361. tempJobNames << "c:/somerandomfolder/dev/aaa/bbb/123.890";
  362. tempJobNames << "c:/somerandomfolder/dev/aaa/ccc/123.567"; // must not match!
  363. tempJobNames << "c:/somerandomfolder/dev/aaa/ccc/456.567"; // must not match
  364. PrepareCompileGroupTests(tempJobNames, gotCreated, gotCompleted, gotGroupID, gotStatus);
  365. m_rcController->OnRequestCompileGroup(RequestID, "pc", "aaa/bbb/123_45.abc", AZ::Data::AssetId());
  366. QCoreApplication::processEvents(QEventLoop::AllEvents);
  367. EXPECT_TRUE(gotCreated);
  368. EXPECT_FALSE(gotCompleted);
  369. EXPECT_EQ(gotGroupID, RequestID);
  370. EXPECT_EQ(gotStatus, AssetStatus_Queued);
  371. // complete two of them. It should still be a busy group!
  372. int IndexOfJobToComplete = 0;
  373. gotCreated = false;
  374. gotCompleted = false;
  375. m_rcJobListModel->markAsProcessing(m_createdJobs[IndexOfJobToComplete]);
  376. m_createdJobs[IndexOfJobToComplete]->SetState(RCJob::completed);
  377. FinishJob(m_createdJobs[IndexOfJobToComplete]);
  378. m_rcController->OnJobComplete(m_createdJobs[IndexOfJobToComplete]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
  379. ++IndexOfJobToComplete;
  380. EXPECT_LT(IndexOfJobToComplete, m_createdJobs.size());
  381. m_rcJobListModel->markAsProcessing(m_createdJobs[IndexOfJobToComplete]);
  382. m_createdJobs[IndexOfJobToComplete]->SetState(RCJob::completed);
  383. FinishJob(m_createdJobs[IndexOfJobToComplete]);
  384. m_rcController->OnJobComplete(m_createdJobs[IndexOfJobToComplete]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
  385. QCoreApplication::processEvents(QEventLoop::AllEvents);
  386. EXPECT_FALSE(gotCreated);
  387. EXPECT_FALSE(gotCompleted);
  388. // finish the final one
  389. ++IndexOfJobToComplete;
  390. EXPECT_LT(IndexOfJobToComplete, m_createdJobs.size());
  391. m_rcJobListModel->markAsProcessing(m_createdJobs[IndexOfJobToComplete]);
  392. m_createdJobs[IndexOfJobToComplete]->SetState(RCJob::completed);
  393. FinishJob(m_createdJobs[IndexOfJobToComplete]);
  394. m_rcController->OnJobComplete(m_createdJobs[IndexOfJobToComplete]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
  395. QCoreApplication::processEvents(QEventLoop::AllEvents);
  396. EXPECT_TRUE(gotCompleted);
  397. EXPECT_FALSE(gotCreated);
  398. EXPECT_EQ(gotGroupID, RequestID);
  399. EXPECT_EQ(gotStatus, AssetStatus_Compiled);
  400. }
  401. TEST_F(RCcontrollerUnitTests, TestCompileGroup_GroupMemberFails_GroupFails)
  402. {
  403. // Ensure that a group fails when any member of it fails.
  404. bool gotCreated = false;
  405. bool gotCompleted = false;
  406. NetworkRequestID gotGroupID;
  407. AssetStatus gotStatus = AssetStatus_Unknown;
  408. QStringList tempJobNames;
  409. tempJobNames << "c:/somerandomfolder/mmmnnnoo/123.456";
  410. tempJobNames << "c:/somerandomfolder/mmmnnnoo/123.567";
  411. PrepareCompileGroupTests(tempJobNames, gotCreated, gotCompleted, gotGroupID, gotStatus);
  412. m_rcController->OnRequestCompileGroup(RequestID, "pc", "mmmnnnoo/123.ZZZ", AZ::Data::AssetId()); // should match exactly 2 elements
  413. QCoreApplication::processEvents(QEventLoop::AllEvents);
  414. EXPECT_TRUE(gotCreated);
  415. EXPECT_FALSE(gotCompleted);
  416. EXPECT_EQ(gotGroupID, RequestID);
  417. EXPECT_EQ(gotStatus, AssetStatus_Queued);
  418. gotCreated = false;
  419. gotCompleted = false;
  420. int IndexOfJobToFail = 0;
  421. m_rcJobListModel->markAsProcessing(m_createdJobs[IndexOfJobToFail]);
  422. m_createdJobs[IndexOfJobToFail]->SetState(RCJob::failed);
  423. FinishJob(m_createdJobs[IndexOfJobToFail]);
  424. m_rcController->OnJobComplete(m_createdJobs[IndexOfJobToFail]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Failed);
  425. QCoreApplication::processEvents(QEventLoop::AllEvents);
  426. // this should have failed it immediately.
  427. EXPECT_TRUE(gotCompleted);
  428. EXPECT_FALSE(gotCreated);
  429. EXPECT_EQ(gotGroupID, RequestID);
  430. EXPECT_EQ(gotStatus, AssetStatus_Failed);
  431. }
  432. TEST_F(RCcontrollerUnitTests, TestCompileGroup_RequestCompileGroupWithUuid_Succeeds)
  433. {
  434. // compile group but with UUID instead of file name.
  435. bool gotCreated = false;
  436. bool gotCompleted = false;
  437. NetworkRequestID gotGroupID;
  438. AssetStatus gotStatus = AssetStatus_Unknown;
  439. QStringList tempJobNames;
  440. tempJobNames << "c:/somerandomfolder/pqr/123.456";
  441. PrepareCompileGroupTests(tempJobNames, gotCreated, gotCompleted, gotGroupID, gotStatus);
  442. int IndexOfJobToRequest = 0;
  443. AZ::Data::AssetId sourceDataID(m_createdJobs[IndexOfJobToRequest]->GetJobEntry().m_sourceFileUUID);
  444. m_rcController->OnRequestCompileGroup(RequestID, "pc", "", sourceDataID); // should match exactly 1 element.
  445. QCoreApplication::processEvents(QEventLoop::AllEvents);
  446. EXPECT_TRUE(gotCreated);
  447. EXPECT_FALSE(gotCompleted);
  448. EXPECT_EQ(gotGroupID, RequestID);
  449. EXPECT_EQ(gotStatus, AssetStatus_Queued);
  450. gotCreated = false;
  451. gotCompleted = false;
  452. m_rcJobListModel->markAsProcessing(m_createdJobs[IndexOfJobToRequest]);
  453. m_createdJobs[IndexOfJobToRequest]->SetState(RCJob::completed);
  454. FinishJob(m_createdJobs[IndexOfJobToRequest]);
  455. m_rcController->OnJobComplete(m_createdJobs[IndexOfJobToRequest]->GetJobEntry(), AzToolsFramework::AssetSystem::JobStatus::Completed);
  456. QCoreApplication::processEvents(QEventLoop::AllEvents);
  457. EXPECT_TRUE(gotCompleted);
  458. EXPECT_EQ(gotGroupID, RequestID);
  459. EXPECT_EQ(gotStatus, AssetStatus_Compiled);
  460. }
  461. TEST_F(RCcontrollerUnitTests, TestRCController_FeedDuplicateJobs_NotAccept)
  462. {
  463. bool gotJobsInQueueCall = false;
  464. QString platformInQueueCount;
  465. int jobsInQueueCount = 0;
  466. QObject::connect(m_rcController.get(), &RCController::JobsInQueuePerPlatform, this, [&gotJobsInQueueCall, &platformInQueueCount, &jobsInQueueCount](QString platformName, int newCount)
  467. {
  468. gotJobsInQueueCall = true;
  469. platformInQueueCount = platformName;
  470. jobsInQueueCount = newCount;
  471. });
  472. AZ::Uuid sourceId = AZ::Uuid("{2206A6E0-FDBC-45DE-B6FE-C2FC63020BD5}");
  473. JobDetails details;
  474. details.m_jobEntry = JobEntry(AssetProcessor::SourceAssetReference("d:/test", "test1.txt"), AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "pc" , {"desktop", "renderer"} }, "Test Job", 1234, 1, sourceId);
  475. gotJobsInQueueCall = false;
  476. int priorJobs = jobsInQueueCount;
  477. m_rcController->JobSubmitted(details);
  478. QCoreApplication::processEvents(QEventLoop::AllEvents);
  479. EXPECT_TRUE(gotJobsInQueueCall);
  480. EXPECT_EQ(jobsInQueueCount, priorJobs + 1);
  481. priorJobs = jobsInQueueCount;
  482. gotJobsInQueueCall = false;
  483. // submit same job, different run key
  484. details.m_jobEntry = JobEntry(AssetProcessor::SourceAssetReference("d:/test", "test1.txt"), AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "pc" ,{ "desktop", "renderer" } }, "Test Job", 1234, 2, sourceId);
  485. m_rcController->JobSubmitted(details);
  486. QCoreApplication::processEvents(QEventLoop::AllEvents);
  487. EXPECT_FALSE(gotJobsInQueueCall);
  488. // submit same job but different platform:
  489. details.m_jobEntry = JobEntry(AssetProcessor::SourceAssetReference("d:/test", "test1.txt"), AZ::Uuid("{7954065D-CFD1-4666-9E4C-3F36F417C7AC}"), { "android" ,{ "mobile", "renderer" } }, "Test Job", 1234, 3, sourceId);
  490. m_rcController->JobSubmitted(details);
  491. QCoreApplication::processEvents(QEventLoop::AllEvents);
  492. EXPECT_TRUE(gotJobsInQueueCall);
  493. EXPECT_EQ(jobsInQueueCount, priorJobs);
  494. }
  495. TEST_F(RCcontrollerUnitTests, TestRCController_StartRCJobWithCriticalLocking_BlocksOnceLockReleased)
  496. {
  497. QDir assetRootPath(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
  498. // test task generation while a file is in still in use
  499. QString fileInUsePath = AssetUtilities::NormalizeFilePath(assetRootPath.absoluteFilePath("subfolder4/needsLock.tiff"));
  500. EXPECT_TRUE(UnitTestUtils::CreateDummyFile(fileInUsePath, "xxx"));
  501. QFile lockFileTest(fileInUsePath);
  502. #if defined(AZ_PLATFORM_WINDOWS)
  503. // on windows, its enough to just open the file:
  504. lockFileTest.open(QFile::ReadOnly);
  505. #elif defined(AZ_PLATFORM_LINUX)
  506. int handleOfLock = open(fileInUsePath.toUtf8().constData(), O_RDONLY | O_EXCL | O_NONBLOCK);
  507. EXPECT_NE(handleOfLock, -1);
  508. #else
  509. int handleOfLock = open(fileInUsePath.toUtf8().constData(), O_RDONLY | O_EXLOCK | O_NONBLOCK);
  510. EXPECT_NE(handleOfLock, -1);
  511. #endif
  512. AZ::Uuid uuidOfSource = AZ::Uuid("{D013122E-CF2C-4534-A87D-F82570FBC2CD}");
  513. MockRCJob rcJob;
  514. AssetProcessor::JobDetails jobDetailsToInitWith;
  515. jobDetailsToInitWith.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(fileInUsePath);
  516. jobDetailsToInitWith.m_jobEntry.m_platformInfo = { "pc", { "tools", "editor"} };
  517. jobDetailsToInitWith.m_jobEntry.m_jobKey = "Text files";
  518. jobDetailsToInitWith.m_jobEntry.m_sourceFileUUID = uuidOfSource;
  519. jobDetailsToInitWith.m_scanFolder = &TestScanFolderInfo;
  520. rcJob.Init(jobDetailsToInitWith);
  521. bool beginWork = false;
  522. QObject::connect(&rcJob, &RCJob::BeginWork, this, [&beginWork]()
  523. {
  524. beginWork = true;
  525. }
  526. );
  527. bool jobFinished = false;
  528. QObject::connect(&rcJob, &RCJob::JobFinished, this, [&jobFinished](AssetBuilderSDK::ProcessJobResponse /*result*/)
  529. {
  530. jobFinished = true;
  531. }
  532. );
  533. rcJob.SetCheckExclusiveLock(true);
  534. rcJob.Start();
  535. #if defined(AZ_PLATFORM_WINDOWS)
  536. // on windows, opening a file for reading locks it
  537. // but on other platforms, this is not the case.
  538. // we only expect work to begin when we can gain an exclusive lock on this file.
  539. // Use a short wait time here because the test will have to wait this entire time to detect the failure
  540. static constexpr int WaitTimeMs = 500;
  541. EXPECT_FALSE(UnitTestUtils::BlockUntil(beginWork, WaitTimeMs));
  542. // Once we release the file, it should process normally
  543. lockFileTest.close();
  544. #else
  545. close(handleOfLock);
  546. #endif
  547. //Once we release the lock we should see jobStarted and jobFinished
  548. EXPECT_TRUE(UnitTestUtils::BlockUntil(jobFinished, MaxProcessingWaitTimeMs));
  549. EXPECT_TRUE(beginWork);
  550. EXPECT_TRUE(rcJob.m_DoWorkCalled);
  551. // make sure the source UUID made its way all the way from create jobs to process jobs.
  552. EXPECT_EQ(rcJob.m_capturedParams.m_processJobRequest.m_sourceFileUUID, uuidOfSource);
  553. }
  554. TEST_F(RCcontrollerUnitTests, TestRCController_FeedJobsWithDependencies_DispatchJobsInOrder)
  555. {
  556. QDir assetRootPath(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
  557. QString fileA = AssetUtilities::NormalizeFilePath(assetRootPath.absoluteFilePath("FileA.txt"));
  558. QString fileB = AssetUtilities::NormalizeFilePath(assetRootPath.absoluteFilePath("FileB.txt"));
  559. QString fileC = AssetUtilities::NormalizeFilePath(assetRootPath.absoluteFilePath("FileC.txt"));
  560. QString fileD = AssetUtilities::NormalizeFilePath(assetRootPath.absoluteFilePath("FileD.txt"));
  561. EXPECT_TRUE(UnitTestUtils::CreateDummyFile(fileA, "xxx"));
  562. EXPECT_TRUE(UnitTestUtils::CreateDummyFile(fileB, "xxx"));
  563. EXPECT_TRUE(UnitTestUtils::CreateDummyFile(fileC, "xxx"));
  564. EXPECT_TRUE(UnitTestUtils::CreateDummyFile(fileD, "xxx"));
  565. Reset();
  566. m_assetBuilderDesc.m_name = "Job Dependency UnitTest";
  567. m_assetBuilderDesc.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
  568. m_assetBuilderDesc.m_busId = BuilderUuid;
  569. m_assetBuilderDesc.m_processJobFunction = []
  570. ([[maybe_unused]] const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
  571. {
  572. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  573. };
  574. m_rcController->SetDispatchPaused(true);
  575. // Job B has an order job dependency on Job A
  576. // Setting up JobA
  577. MockRCJob* jobA = new MockRCJob(m_rcJobListModel);
  578. JobDetails jobdetailsA;
  579. jobdetailsA.m_scanFolder = &TestScanFolderInfo;
  580. jobdetailsA.m_assetBuilderDesc = m_assetBuilderDesc;
  581. jobdetailsA.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "fileA.txt");
  582. jobdetailsA.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
  583. jobdetailsA.m_jobEntry.m_jobKey = "TestJobA";
  584. jobdetailsA.m_jobEntry.m_builderGuid = BuilderUuid;
  585. jobA->Init(jobdetailsA);
  586. m_rcQueueSortModel->AddJobIdEntry(jobA);
  587. m_rcJobListModel->addNewJob(jobA);
  588. bool beginWorkA = false;
  589. QObject::connect(jobA, &RCJob::BeginWork, this, [&beginWorkA]()
  590. {
  591. beginWorkA = true;
  592. }
  593. );
  594. bool jobFinishedA = false;
  595. QObject::connect(jobA, &RCJob::JobFinished, this, [&jobFinishedA](AssetBuilderSDK::ProcessJobResponse /*result*/)
  596. {
  597. jobFinishedA = true;
  598. }
  599. );
  600. // Setting up JobB
  601. JobDetails jobdetailsB;
  602. jobdetailsB.m_scanFolder = &TestScanFolderInfo;
  603. jobdetailsA.m_assetBuilderDesc = m_assetBuilderDesc;
  604. jobdetailsB.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "fileB.txt");
  605. jobdetailsB.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
  606. jobdetailsB.m_jobEntry.m_jobKey = "TestJobB";
  607. jobdetailsB.m_jobEntry.m_builderGuid = BuilderUuid;
  608. jobdetailsB.m_critical = true; //make jobB critical so that it will be analyzed first even though we want JobA to run first
  609. AssetBuilderSDK::SourceFileDependency sourceFileADependency;
  610. sourceFileADependency.m_sourceFileDependencyPath = (AZ::IO::Path(TestScanFolderInfo.ScanPath().toUtf8().constData()) / "fileA.txt").Native();
  611. // Make job B has an order job dependency on Job A
  612. AssetBuilderSDK::JobDependency jobDependencyA("TestJobA", "pc", AssetBuilderSDK::JobDependencyType::Order, sourceFileADependency);
  613. jobdetailsB.m_jobDependencyList.push_back({ jobDependencyA });
  614. //Setting JobB
  615. MockRCJob* jobB = new MockRCJob(m_rcJobListModel);
  616. jobB->Init(jobdetailsB);
  617. m_rcQueueSortModel->AddJobIdEntry(jobB);
  618. m_rcJobListModel->addNewJob(jobB);
  619. bool beginWorkB = false;
  620. QMetaObject::Connection conn = QObject::connect(jobB, &RCJob::BeginWork, this, [&beginWorkB, &jobFinishedA]()
  621. {
  622. // JobA should finish first before JobB starts
  623. EXPECT_TRUE(jobFinishedA);
  624. beginWorkB = true;
  625. }
  626. );
  627. bool jobFinishedB = false;
  628. QObject::connect(jobB, &RCJob::JobFinished, this, [&jobFinishedB](AssetBuilderSDK::ProcessJobResponse /*result*/)
  629. {
  630. jobFinishedB = true;
  631. }
  632. );
  633. JobEntry completedJob;
  634. bool allJobsCompleted = false;
  635. ConnectJobSignalsAndSlots(allJobsCompleted, completedJob);
  636. m_rcController->SetDispatchPaused(false);
  637. m_rcController->DispatchJobs();
  638. EXPECT_TRUE(UnitTestUtils::BlockUntil(allJobsCompleted, MaxProcessingWaitTimeMs));
  639. EXPECT_TRUE(jobFinishedB);
  640. }
  641. TEST_F(RCcontrollerUnitTests, TestRCController_FeedJobsWithCyclicDependencies_AllJobsFinish)
  642. {
  643. // Now test the use case where we have a cyclic dependency,
  644. // although the order in which these job will start is not defined but we can ensure that
  645. // all the jobs finish and RCController goes Idle
  646. JobEntry completedJob;
  647. bool allJobsCompleted = false;
  648. ConnectJobSignalsAndSlots(allJobsCompleted, completedJob);
  649. m_rcController->SetDispatchPaused(true);
  650. //Setting up JobC
  651. JobDetails jobdetailsC;
  652. jobdetailsC.m_scanFolder = &TestScanFolderInfo;
  653. jobdetailsC.m_assetBuilderDesc = m_assetBuilderDesc;
  654. jobdetailsC.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "fileC.txt");
  655. jobdetailsC.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
  656. jobdetailsC.m_jobEntry.m_jobKey = "TestJobC";
  657. jobdetailsC.m_jobEntry.m_builderGuid = BuilderUuid;
  658. AssetBuilderSDK::SourceFileDependency sourceFileCDependency;
  659. sourceFileCDependency.m_sourceFileDependencyPath =
  660. (AZ::IO::Path(TestScanFolderInfo.ScanPath().toUtf8().constData()) / "fileC.txt").Native();
  661. //Setting up Job D
  662. JobDetails jobdetailsD;
  663. jobdetailsD.m_scanFolder = &TestScanFolderInfo;
  664. jobdetailsD.m_assetBuilderDesc = m_assetBuilderDesc;
  665. jobdetailsD.m_jobEntry.m_sourceAssetReference = AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "fileD.txt");
  666. jobdetailsD.m_jobEntry.m_platformInfo = { "pc" ,{ "desktop", "renderer" } };
  667. jobdetailsD.m_jobEntry.m_jobKey = "TestJobD";
  668. jobdetailsD.m_jobEntry.m_builderGuid = BuilderUuid;
  669. AssetBuilderSDK::SourceFileDependency sourceFileDDependency;
  670. sourceFileDDependency.m_sourceFileDependencyPath =
  671. (AZ::IO::Path(TestScanFolderInfo.ScanPath().toUtf8().constData()) / "fileD.txt").Native();
  672. //creating cyclic job order dependencies i.e JobC and JobD have order job dependency on each other
  673. AssetBuilderSDK::JobDependency jobDependencyC("TestJobC", "pc", AssetBuilderSDK::JobDependencyType::Order, sourceFileCDependency);
  674. AssetBuilderSDK::JobDependency jobDependencyD("TestJobD", "pc", AssetBuilderSDK::JobDependencyType::Order, sourceFileDDependency);
  675. jobdetailsC.m_jobDependencyList.push_back({ jobDependencyD });
  676. jobdetailsD.m_jobDependencyList.push_back({ jobDependencyC });
  677. MockRCJob* jobD = new MockRCJob(m_rcJobListModel);
  678. MockRCJob* jobC = new MockRCJob(m_rcJobListModel);
  679. jobC->Init(jobdetailsC);
  680. m_rcQueueSortModel->AddJobIdEntry(jobC);
  681. m_rcJobListModel->addNewJob(jobC);
  682. jobD->Init(jobdetailsD);
  683. m_rcQueueSortModel->AddJobIdEntry(jobD);
  684. m_rcJobListModel->addNewJob(jobD);
  685. m_rcController->SetDispatchPaused(false);
  686. m_rcController->DispatchJobs();
  687. EXPECT_TRUE(UnitTestUtils::BlockUntil(allJobsCompleted, MaxProcessingWaitTimeMs));
  688. // Test case when source file is deleted before it started processing
  689. {
  690. int prevJobCount = m_rcJobListModel->itemCount();
  691. MockRCJob rcJobAddAndDelete;
  692. AssetProcessor::JobDetails jobDetailsToInitWithInsideScope;
  693. jobDetailsToInitWithInsideScope.m_jobEntry.m_sourceAssetReference =
  694. AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "someFile0.txt");
  695. jobDetailsToInitWithInsideScope.m_jobEntry.m_platformInfo = { "pc",{ "tools", "editor" } };
  696. jobDetailsToInitWithInsideScope.m_jobEntry.m_jobKey = "Text files";
  697. jobDetailsToInitWithInsideScope.m_jobEntry.m_sourceFileUUID = AZ::Uuid("{D013122E-CF2C-4534-A87D-F82570FBC2CD}");
  698. rcJobAddAndDelete.Init(jobDetailsToInitWithInsideScope);
  699. m_rcJobListModel->addNewJob(&rcJobAddAndDelete);
  700. // verify that job was added
  701. EXPECT_EQ(m_rcJobListModel->itemCount(), prevJobCount + 1);
  702. m_rcController->RemoveJobsBySource(AssetProcessor::SourceAssetReference(TestScanFolderInfo.ScanPath(), "someFile0.txt"));
  703. // verify that job was removed
  704. EXPECT_EQ(m_rcJobListModel->itemCount(), prevJobCount);
  705. }
  706. }