AssetProcessorManagerUnitTests.cpp 165 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082
  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 <AzCore/Casting/lossy_cast.h>
  9. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  10. #include <AzTest/AzTest.h>
  11. #include <native/tests/AssetProcessorTest.h>
  12. #include <native/tests/MockAssetDatabaseRequestsHandler.h>
  13. #include <native/unittests/AssetProcessorManagerUnitTests.h>
  14. #include <native/unittests/MockApplicationManager.h>
  15. #include <native/unittests/MockConnectionHandler.h>
  16. #include <QCoreApplication>
  17. namespace AssetProcessor
  18. {
  19. using namespace UnitTestUtils;
  20. using namespace AzFramework::AssetSystem;
  21. using namespace AzToolsFramework::AssetSystem;
  22. using namespace AzToolsFramework::AssetDatabase;
  23. class AssetProcessorManagerUnit_Test
  24. : public AssetProcessorManager
  25. {
  26. public:
  27. explicit AssetProcessorManagerUnit_Test(PlatformConfiguration* config, QObject* parent = 0)
  28. : AssetProcessorManager(config, parent)
  29. {}
  30. friend class AssetProcessorManagerUnitTests;
  31. public:
  32. using GetRelativeProductPathFromFullSourceOrProductPathRequest = AzFramework::AssetSystem::GetRelativeProductPathFromFullSourceOrProductPathRequest;
  33. using GetRelativeProductPathFromFullSourceOrProductPathResponse = AzFramework::AssetSystem::GetRelativeProductPathFromFullSourceOrProductPathResponse;
  34. using GenerateRelativeSourcePathRequest = AzFramework::AssetSystem::GenerateRelativeSourcePathRequest;
  35. using GenerateRelativeSourcePathResponse = AzFramework::AssetSystem::GenerateRelativeSourcePathResponse;
  36. using GetFullSourcePathFromRelativeProductPathRequest = AzFramework::AssetSystem::GetFullSourcePathFromRelativeProductPathRequest;
  37. using GetFullSourcePathFromRelativeProductPathResponse = AzFramework::AssetSystem::GetFullSourcePathFromRelativeProductPathResponse;
  38. };
  39. AssetProcessorManagerUnitTests::~AssetProcessorManagerUnitTests()
  40. {
  41. }
  42. void AssetProcessorManagerUnitTests::SetUp()
  43. {
  44. UnitTest::AssetProcessorUnitTestBase::SetUp();
  45. m_fileStateCache = AZStd::make_unique<AssetProcessor::FileStatePassthrough>();
  46. // update the engine root
  47. QDir oldRoot;
  48. AssetUtilities::ComputeAssetRoot(oldRoot);
  49. AssetUtilities::ResetAssetRoot();
  50. m_sourceRoot = QDir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
  51. QString canonicalAssetRootDirPath = AssetUtilities::NormalizeDirectoryPath(m_sourceRoot.canonicalPath());
  52. m_changeDir = AZStd::make_unique<UnitTestUtils::ScopedDir>(canonicalAssetRootDirPath);
  53. AssetUtilities::ResetAssetRoot();
  54. QDir newRoot;
  55. AssetUtilities::ComputeAssetRoot(newRoot, &m_sourceRoot);
  56. // create a dummy file in the cache folder, so the folder structure gets created
  57. // Override the cache folder to be the within the asset root directory
  58. auto projectCacheRootKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey);
  59. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  60. {
  61. settingsRegistry->Set(projectCacheRootKey, m_sourceRoot.absoluteFilePath("Cache").toUtf8().data());
  62. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry);
  63. }
  64. EXPECT_TRUE(AssetUtilities::ComputeProjectCacheRoot(m_cacheRoot));
  65. CreateDummyFile(m_cacheRoot.absoluteFilePath("placeholder.txt"));
  66. // make sure it picked up the one in the cache and not for example the real working folder
  67. QString normalizedCacheRootPath = AssetUtilities::NormalizeDirectoryPath(m_cacheRoot.canonicalPath());
  68. QString normalizedDirPathCheck = AssetUtilities::NormalizeDirectoryPath(QDir(canonicalAssetRootDirPath).absoluteFilePath("Cache"));
  69. EXPECT_EQ(normalizedCacheRootPath, normalizedDirPathCheck);
  70. m_cacheRoot = QDir(normalizedCacheRootPath);
  71. constexpr const char* AssetProcessorManagerTestGameProject = "AutomatedTesting";
  72. QString gameName = AssetUtilities::ComputeProjectName(AssetProcessorManagerTestGameProject);
  73. EXPECT_FALSE(gameName.isEmpty());
  74. m_config.EnablePlatform({ "pc",{ "desktop", "renderer" } }, true);
  75. m_config.EnablePlatform({ "android",{ "mobile", "renderer" } }, true);
  76. m_config.EnablePlatform({ "fandago",{ "console", "renderer" } }, false);
  77. AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms;
  78. m_config.PopulatePlatformsForScanFolder(platforms);
  79. // PATH DisplayName PortKey outputfolder root recurse platforms order
  80. m_config.AddScanFolder(ScanFolderInfo(m_sourceRoot.filePath("subfolder4"), "subfolder4", "subfolder4", false, false, platforms, -6)); // subfolder 4 overrides subfolder3
  81. m_config.AddScanFolder(ScanFolderInfo(m_sourceRoot.filePath("subfolder3"), "subfolder3", "subfolder3", false, false, platforms,-5)); // subfolder 3 overrides subfolder2
  82. m_config.AddScanFolder(ScanFolderInfo(m_sourceRoot.filePath("subfolder2"), "subfolder2", "subfolder2", false, true, platforms, -2)); // subfolder 2 overrides subfolder1
  83. m_config.AddScanFolder(ScanFolderInfo(m_sourceRoot.filePath("subfolder1"), "subfolder1", "subfolder1", false, true, platforms, -1)); // subfolder1 overrides root
  84. m_config.AddScanFolder(ScanFolderInfo(m_sourceRoot.absolutePath(), "root", "rootfolder", true, false, platforms, 0)); // add the root
  85. m_config.AddIntermediateScanFolder();
  86. m_config.AddMetaDataType("exportsettings", QString());
  87. // Configure asset processor manager
  88. m_assetProcessorManager = AZStd::make_unique<AssetProcessorManagerUnit_Test>(&m_config); // note, this will 'push' the scan folders in to the db.
  89. m_assetProcessorConnections.append(connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess,
  90. this, [&](JobDetails details)
  91. {
  92. m_processResults.push_back(AZStd::move(details));
  93. }));
  94. m_assetProcessorConnections.append(connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetMessage,
  95. this, [&](AzFramework::AssetSystem::AssetNotificationMessage message)
  96. {
  97. m_assetMessages.push_back( message);
  98. }));
  99. m_assetProcessorConnections.append(connect(m_assetProcessorManager.get(), &AssetProcessorManager::InputAssetProcessed,
  100. this, [&](QString relativePath, QString platform)
  101. {
  102. m_changedInputResults.push_back(QPair<QString, QString>(relativePath, platform));
  103. }));
  104. m_assetProcessorConnections.append(connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetProcessorManagerIdleState,
  105. this, [&](bool state)
  106. {
  107. m_idling = state;
  108. }));
  109. }
  110. void AssetProcessorManagerUnitTests::TearDown()
  111. {
  112. // Stop file watching, disconnect everything and process all events so nothing gets called after the method finishes
  113. m_fileWatcher.StopWatching();
  114. for (const QMetaObject::Connection& connection : m_assetProcessorConnections)
  115. {
  116. QObject::disconnect(connection);
  117. }
  118. QCoreApplication::processEvents(QEventLoop::AllEvents);
  119. m_assetProcessorManager.reset();
  120. m_changeDir.reset();
  121. m_fileStateCache.reset();
  122. UnitTest::AssetProcessorUnitTestBase::TearDown();
  123. }
  124. // Takes an absolute cache path and returns the portion after cache/platform/
  125. AZStd::string AssetProcessorManagerUnitTests::AbsProductPathToRelative(const QString& absolutePath)
  126. {
  127. AZ::IO::Path platformRelativePath = absolutePath.toUtf8().constData();
  128. platformRelativePath = platformRelativePath.LexicallyRelative(m_cacheRoot.absolutePath().toUtf8().constData());
  129. return (*++platformRelativePath.begin()).StringAsPosix();
  130. };
  131. void AssetProcessorManagerUnitTests::VerifyProductPaths(const JobDetails& jobDetails)
  132. {
  133. QString platformFolder = m_cacheRoot.filePath(QString::fromUtf8(jobDetails.m_jobEntry.m_platformInfo.m_identifier.c_str()));
  134. platformFolder = AssetUtilities::NormalizeDirectoryPath(platformFolder);
  135. AZ::IO::Path expectedCachePath = m_cacheRoot.absoluteFilePath(platformFolder).toUtf8().constData();
  136. AZ::IO::FixedMaxPath intermediateAssetsFolder = AssetUtilities::GetIntermediateAssetsFolder(m_cacheRoot.absolutePath().toUtf8().constData());
  137. EXPECT_EQ(jobDetails.m_cachePath, expectedCachePath);
  138. EXPECT_EQ(jobDetails.m_intermediatePath, intermediateAssetsFolder);
  139. };
  140. namespace AssetProcessorManagerUnitTestUtils
  141. {
  142. class MockAssetBuilderInfoHandler
  143. : public AssetProcessor::AssetBuilderInfoBus::Handler
  144. {
  145. public:
  146. // AssetProcessor::AssetBuilderInfoBus::Handler
  147. void GetMatchingBuildersInfo(const AZStd::string& assetPath, AssetProcessor::BuilderInfoList& builderInfoList)
  148. {
  149. AZ_UNUSED(assetPath);
  150. builderInfoList.push_back(m_assetBuilderDesc);
  151. }
  152. void GetAllBuildersInfo(AssetProcessor::BuilderInfoList& builderInfoList)
  153. {
  154. builderInfoList.push_back(m_assetBuilderDesc);
  155. }
  156. AssetBuilderSDK::AssetBuilderDesc m_assetBuilderDesc;
  157. };
  158. void CreateExpectedFiles(const QSet<QString>& expectedFiles)
  159. {
  160. QDateTime fileTime = QDateTime::currentDateTime();
  161. for (const QString& expect : expectedFiles)
  162. {
  163. EXPECT_TRUE(CreateDummyFile(expect));
  164. // Set a different timestamp for each file.
  165. QFile file(expect);
  166. ASSERT_TRUE(file.open(QIODevice::Append | QIODevice::Text))
  167. << AZStd::string::format("Failed to open %s", expect.toUtf8().data()).c_str();
  168. EXPECT_TRUE(file.setFileTime(fileTime, QFileDevice::FileModificationTime))
  169. << AZStd::string::format("Failed to modify the creation time of %s", expect.toUtf8().data()).c_str();
  170. file.close();
  171. //Add 2 seconds to the next file timestamp since the file time resolution is one second on platforms other than Windows.
  172. fileTime = fileTime.addSecs(2);
  173. }
  174. }
  175. /// This functions sorts the processed result list by platform name
  176. /// if platform is same than it sorts by job description
  177. void SortAssetToProcessResultList(QList<JobDetails>& processResults)
  178. {
  179. //Sort the processResults based on platforms
  180. std::sort(processResults.begin(), processResults.end(),
  181. [](const JobDetails& first, const JobDetails& second)
  182. {
  183. if (first.m_jobEntry.m_platformInfo.m_identifier == second.m_jobEntry.m_platformInfo.m_identifier)
  184. {
  185. return first.m_jobEntry.m_jobKey.toLower() < second.m_jobEntry.m_jobKey.toLower();
  186. }
  187. return first.m_jobEntry.m_platformInfo.m_identifier < second.m_jobEntry.m_platformInfo.m_identifier;
  188. });
  189. }
  190. void ComputeFingerprints(unsigned int& fingerprintForPC, unsigned int& fingerprintForANDROID, PlatformConfiguration& config, QString scanFolderPath, QString relPath)
  191. {
  192. QString extraInfoForPC;
  193. QString extraInfoForANDROID;
  194. RecognizerPointerContainer output;
  195. QString filePath = scanFolderPath + "/" + relPath;
  196. config.GetMatchingRecognizers(filePath, output);
  197. for (const AssetRecognizer* assetRecogniser : output)
  198. {
  199. extraInfoForPC.append(assetRecogniser->m_platformSpecs.at("pc") == AssetInternalSpec::Copy ? "copy" : "skip");
  200. extraInfoForANDROID.append(assetRecogniser->m_platformSpecs.at("android") == AssetInternalSpec::Copy ? "copy" : "skip");
  201. extraInfoForPC.append(assetRecogniser->m_version.c_str());
  202. extraInfoForANDROID.append(assetRecogniser->m_version.c_str());
  203. }
  204. //Calculating fingerprints for the file for pc and android platforms
  205. AZ::Uuid sourceId = AZ::Uuid("{2206A6E0-FDBC-45DE-B6FE-C2FC63020BD5}");
  206. JobEntry jobEntryPC(SourceAssetReference(scanFolderPath, relPath), {}, { "pc", {"desktop", "renderer"} }, "", 0, 1, sourceId);
  207. JobEntry jobEntryANDROID(SourceAssetReference(scanFolderPath, relPath), {}, { "android", {"mobile", "renderer"} }, "", 0, 2, sourceId);
  208. JobDetails jobDetailsPC;
  209. jobDetailsPC.m_extraInformationForFingerprinting = extraInfoForPC.toUtf8().constData();
  210. jobDetailsPC.m_jobEntry = jobEntryPC;
  211. JobDetails jobDetailsANDROID;
  212. jobDetailsANDROID.m_extraInformationForFingerprinting = extraInfoForANDROID.toUtf8().constData();
  213. jobDetailsANDROID.m_jobEntry = jobEntryANDROID;
  214. fingerprintForPC = AssetUtilities::GenerateFingerprint(jobDetailsPC);
  215. fingerprintForANDROID = AssetUtilities::GenerateFingerprint(jobDetailsANDROID);
  216. }
  217. }
  218. // the asset processor manager is generally sitting on top of many other systems.
  219. // we have tested those systems individually in other unit tests, but we need to create
  220. // a simulated environment to test the manager itself.
  221. // for the manager, the only things we care about is that it emits the correct signals
  222. // when the appropriate stimulus is given and that state is appropriately updated.
  223. TEST_F(AssetProcessorManagerUnitTests, SkipProcessing_FeedFilesToIgnore_NoTasksGenerated)
  224. {
  225. MockApplicationManager mockAppManager;
  226. mockAppManager.BusConnect();
  227. // txt recognizer
  228. AssetRecognizer rec;
  229. const char* builderTxt1Name = "txt files";
  230. rec.m_name = builderTxt1Name;
  231. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  232. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  233. rec.m_platformSpecs.insert({"android", AssetInternalSpec::Copy});
  234. m_config.AddRecognizer(rec);
  235. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  236. // Ignore recognizer
  237. AssetRecognizer ignore_rec;
  238. ignore_rec.m_name = "ignore files";
  239. ignore_rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.ignore", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  240. ignore_rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  241. ignore_rec.m_platformSpecs.insert({"android", AssetInternalSpec::Skip});
  242. m_config.AddRecognizer(ignore_rec);
  243. mockAppManager.RegisterAssetRecognizerAsBuilder(ignore_rec);
  244. QSet<QString> expectedFiles;
  245. // subfolder3 is not recursive so none of these should show up in any scan or override check
  246. expectedFiles << m_sourceRoot.absoluteFilePath("subfolder3/aaa/basefile.txt");
  247. expectedFiles << m_sourceRoot.absoluteFilePath("subfolder3/uniquefile.ignore"); // only exists in subfolder3
  248. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles(expectedFiles);
  249. // the following is a file which does exist but should not be processed as it is in a non-watched folder (not recursive)
  250. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, m_sourceRoot.absoluteFilePath("subfolder3/aaa/basefile.txt")));
  251. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  252. EXPECT_TRUE(m_processResults.isEmpty());
  253. EXPECT_TRUE(m_changedInputResults.isEmpty());
  254. EXPECT_TRUE(m_assetMessages.isEmpty());
  255. // an imaginary non-existent file should also fail even if it matches filters:
  256. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, m_sourceRoot.absoluteFilePath("subfolder3/basefileaaaaa.txt")));
  257. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  258. EXPECT_TRUE(m_processResults.isEmpty());
  259. EXPECT_TRUE(m_changedInputResults.isEmpty());
  260. EXPECT_TRUE(m_assetMessages.isEmpty());
  261. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, m_sourceRoot.absoluteFilePath("basefileaaaaa.txt")));
  262. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  263. EXPECT_TRUE(m_processResults.isEmpty());
  264. EXPECT_TRUE(m_changedInputResults.isEmpty());
  265. EXPECT_TRUE(m_assetMessages.isEmpty());
  266. // block until no more events trickle in:
  267. QCoreApplication::processEvents(QEventLoop::AllEvents);
  268. m_processResults.clear();
  269. QString inputIgnoreFilePath = AssetUtilities::NormalizeFilePath(m_sourceRoot.absoluteFilePath("subfolder3/uniquefile.ignore"));
  270. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, inputIgnoreFilePath));
  271. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  272. // block until no more events trickle in:
  273. QCoreApplication::processEvents(QEventLoop::AllEvents);
  274. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  275. EXPECT_EQ(m_processResults.size(), 1); // 1, since we have one recognizer for .ignore, but the 'android' platform is marked as skip
  276. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "pc");
  277. // block until no more events trickle in:
  278. QCoreApplication::processEvents(QEventLoop::AllEvents);
  279. mockAppManager.BusDisconnect();
  280. }
  281. TEST_F(AssetProcessorManagerUnitTests, ProcessFile_FeedFileToProcess_TasksGenerated)
  282. {
  283. MockApplicationManager mockAppManager;
  284. mockAppManager.BusConnect();
  285. QList<QPair<unsigned int, QByteArray>> payloadList;
  286. AssetProcessor::MockConnectionHandler connection;
  287. connection.BusConnect(1);
  288. connection.m_callback = [&payloadList](unsigned int type, [[maybe_unused]] unsigned int serial, const QByteArray payload)
  289. {
  290. payloadList.append(qMakePair(type, payload));
  291. };
  292. AssetRecognizer rec;
  293. const char* builderTxt1Name = "txt files";
  294. rec.m_name = builderTxt1Name;
  295. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  296. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  297. rec.m_platformSpecs.insert({"android", AssetInternalSpec::Copy});
  298. m_config.AddRecognizer(rec);
  299. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  300. // test dual-recognisers - two recognisers for the same pattern.
  301. rec.m_name = "txt files 2 (builder2)";
  302. m_config.AddRecognizer(rec);
  303. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  304. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/test\\/.*\\.format", AssetBuilderSDK::AssetBuilderPattern::Regex);
  305. rec.m_name = "format files that live in a folder called test";
  306. m_config.AddRecognizer(rec);
  307. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  308. QString relativePathFromWatchFolder = "uniquefile.txt";
  309. QString watchFolderPath = m_sourceRoot.absoluteFilePath("subfolder3");
  310. QString absolutePath = AssetUtilities::NormalizeFilePath(watchFolderPath + "/" + relativePathFromWatchFolder);
  311. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles({absolutePath});
  312. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  313. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  314. // block until no more events trickle in:
  315. QCoreApplication::processEvents(QEventLoop::AllEvents);
  316. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  317. EXPECT_EQ(m_processResults.size(), 4); // 2 each for pc and android,since we have two recognizer for .txt file
  318. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, m_processResults[1].m_jobEntry.m_platformInfo.m_identifier);
  319. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, m_processResults[3].m_jobEntry.m_platformInfo.m_identifier);
  320. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  321. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "android");
  322. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, "pc");
  323. EXPECT_EQ(m_processResults[3].m_jobEntry.m_platformInfo.m_identifier, "pc");
  324. QList<int> androidJobsIndex;
  325. QList<int> pcJobsIndex;
  326. for (int checkIdx = 0; checkIdx < 4; ++checkIdx)
  327. {
  328. EXPECT_NE(m_processResults[checkIdx].m_jobEntry.m_computedFingerprint, 0);
  329. EXPECT_NE(m_processResults[checkIdx].m_jobEntry.m_jobRunKey, 0);
  330. EXPECT_EQ(QString(m_processResults[checkIdx].m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str()), AssetUtilities::NormalizeFilePath(watchFolderPath));
  331. EXPECT_EQ(m_processResults[checkIdx].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "uniquefile.txt");
  332. QString platformFolder = m_cacheRoot.filePath(QString::fromUtf8(m_processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()));
  333. platformFolder = AssetUtilities::NormalizeDirectoryPath(platformFolder);
  334. AZ::IO::Path expectedCachePath = m_cacheRoot.absoluteFilePath(platformFolder).toUtf8().constData();
  335. AZ::IO::FixedMaxPath intermediateAssetsFolder = AssetUtilities::GetIntermediateAssetsFolder(m_cacheRoot.path().toUtf8().constData());
  336. EXPECT_EQ(m_processResults[checkIdx].m_cachePath, expectedCachePath);
  337. EXPECT_EQ(m_processResults[checkIdx].m_intermediatePath, intermediateAssetsFolder);
  338. EXPECT_NE(m_processResults[checkIdx].m_jobEntry.m_computedFingerprint, 0);
  339. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "OnJobStatusChanged", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[checkIdx].m_jobEntry), Q_ARG(JobStatus, JobStatus::Queued));
  340. QCoreApplication::processEvents(QEventLoop::AllEvents);
  341. // create log files, so that we can test the correct retrieval
  342. // we create all of them except for #1
  343. if (checkIdx != 1)
  344. {
  345. JobInfo info;
  346. info.m_jobRunKey = m_processResults[checkIdx].m_jobEntry.m_jobRunKey;
  347. info.m_builderGuid = m_processResults[checkIdx].m_jobEntry.m_builderGuid;
  348. info.m_jobKey = m_processResults[checkIdx].m_jobEntry.m_jobKey.toUtf8().data();
  349. info.m_platform = m_processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str();
  350. info.m_sourceFile = m_processResults[checkIdx].m_jobEntry.m_sourceAssetReference.RelativePath().c_str();
  351. info.m_watchFolder = m_processResults[checkIdx].m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str();
  352. AZStd::string logFolder = AZStd::string::format("%s/%s", AssetUtilities::ComputeJobLogFolder().c_str(), AssetUtilities::ComputeJobLogFileName(info).c_str());
  353. AZ::IO::HandleType logHandle;
  354. AZ::IO::LocalFileIO::GetInstance()->CreatePath(AssetUtilities::ComputeJobLogFolder().c_str());
  355. EXPECT_TRUE(AZ::IO::LocalFileIO::GetInstance()->Open(logFolder.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, logHandle));
  356. AZStd::string logLine = AZStd::string::format("Log stored for job run key %lli\n", m_processResults[checkIdx].m_jobEntry.m_jobRunKey);
  357. AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
  358. AZ::IO::LocalFileIO::GetInstance()->Close(logHandle);
  359. }
  360. }
  361. // ----------------------- test job info requests, while we have some assets in flight ---------------------------
  362. // by this time, querying for the status of those jobs should be possible since the "OnJobStatusChanged" event should have bubbled through
  363. {
  364. QCoreApplication::processEvents(QEventLoop::AllEvents);
  365. AssetJobsInfoRequest requestInfo;
  366. AssetJobsInfoResponse jobResponse;
  367. requestInfo.m_searchTerm = absolutePath.toUtf8().constData();
  368. m_assetProcessorManager->ProcessGetAssetJobsInfoRequest(requestInfo, jobResponse);
  369. EXPECT_TRUE(jobResponse.m_isSuccess);
  370. EXPECT_EQ(jobResponse.m_jobList.size(), m_processResults.size());
  371. // make sure each job corresponds to one in the process results list (but note that the order is not important).
  372. for (int oldJobIdx = azlossy_cast<int>(jobResponse.m_jobList.size()) - 1; oldJobIdx >= 0; --oldJobIdx)
  373. {
  374. bool foundIt = false;
  375. const JobInfo& jobInfo = jobResponse.m_jobList[oldJobIdx];
  376. // validate EVERY field
  377. EXPECT_EQ(jobInfo.m_status, JobStatus::Queued);
  378. EXPECT_FALSE(jobInfo.m_sourceFile.empty());
  379. EXPECT_FALSE(jobInfo.m_platform.empty());
  380. EXPECT_FALSE(jobInfo.m_jobKey.empty());
  381. EXPECT_FALSE(jobInfo.m_builderGuid.IsNull());
  382. EXPECT_NE(jobInfo.m_jobRunKey, 0);
  383. for (const JobDetails& details : m_processResults)
  384. {
  385. if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_sourceAssetReference.RelativePath().c_str(), Qt::CaseSensitive) == 0) &&
  386. (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str(), Qt::CaseSensitive) ==
  387. 0) &&
  388. (QString::compare(jobInfo.m_platform.c_str(), details.m_jobEntry.m_platformInfo.m_identifier.c_str(), Qt::CaseInsensitive) == 0) &&
  389. (QString::compare(jobInfo.m_jobKey.c_str(), details.m_jobEntry.m_jobKey, Qt::CaseInsensitive) == 0) &&
  390. (jobInfo.m_builderGuid == details.m_jobEntry.m_builderGuid) &&
  391. (jobInfo.m_jobRunKey == details.m_jobEntry.m_jobRunKey) &&
  392. (jobInfo.GetHash() == details.m_jobEntry.GetHash()))
  393. {
  394. foundIt = true;
  395. break;
  396. }
  397. }
  398. EXPECT_TRUE(foundIt);
  399. }
  400. }
  401. // ------------- JOB LOG TEST -------------------
  402. for (int checkIdx = 0; checkIdx < 4; ++checkIdx)
  403. {
  404. const JobDetails& details = m_processResults[checkIdx];
  405. // create log files, so that we can test the correct retrieval
  406. // we create all of them except for #1
  407. if (checkIdx != 1)
  408. {
  409. AZStd::string logFolder = AZStd::string::format("%s/%s", AssetUtilities::ComputeJobLogFolder().c_str(), AssetUtilities::ComputeJobLogFileName(details.m_jobEntry).c_str());
  410. AZ::IO::HandleType logHandle;
  411. AZ::IO::LocalFileIO::GetInstance()->CreatePath(AssetUtilities::ComputeJobLogFolder().c_str());
  412. EXPECT_TRUE(AZ::IO::LocalFileIO::GetInstance()->Open(logFolder.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, logHandle));
  413. AZStd::string logLine = AZStd::string::format("Log stored for job %u\n", m_processResults[checkIdx].m_jobEntry.GetHash());
  414. AZ::IO::LocalFileIO::GetInstance()->Write(logHandle, logLine.c_str(), logLine.size());
  415. AZ::IO::LocalFileIO::GetInstance()->Close(logHandle);
  416. }
  417. }
  418. for (int checkIdx = 0; checkIdx < 4; ++checkIdx)
  419. {
  420. const JobDetails& details = m_processResults[checkIdx];
  421. // request job logs.
  422. AssetJobLogRequest requestLog;
  423. AssetJobLogResponse requestResponse;
  424. requestLog.m_jobRunKey = details.m_jobEntry.m_jobRunKey;
  425. {
  426. // send our request:
  427. m_assetProcessorManager->ProcessGetAssetJobLogRequest(requestLog, requestResponse);
  428. if (checkIdx != 1)
  429. {
  430. EXPECT_TRUE(requestResponse.m_isSuccess);
  431. EXPECT_FALSE(requestResponse.m_jobLog.empty());
  432. AZStd::string checkString = AZStd::string::format("Log stored for job %u\n", m_processResults[checkIdx].m_jobEntry.GetHash());
  433. EXPECT_NE(requestResponse.m_jobLog.find(checkString.c_str()), AZStd::string::npos);
  434. }
  435. else
  436. {
  437. // the [1] index was not written so it should be failed and empty
  438. EXPECT_FALSE(requestResponse.m_isSuccess);
  439. }
  440. }
  441. }
  442. // now indicate the job has started.
  443. for (const JobDetails& details : m_processResults)
  444. {
  445. m_assetProcessorManager->OnJobStatusChanged(details.m_jobEntry, JobStatus::InProgress);
  446. }
  447. QCoreApplication::processEvents(QEventLoop::AllEvents);
  448. // ----------------------- test job info requests, while we have some assets in flight ---------------------------
  449. // by this time, querying for the status of those jobs should be possible since the "OnJobStatusChanged" event should have bubbled through
  450. // and this time, it should be "in progress"
  451. {
  452. QCoreApplication::processEvents(QEventLoop::AllEvents);
  453. AssetJobsInfoRequest requestInfo;
  454. AssetJobsInfoResponse jobResponse;
  455. requestInfo.m_searchTerm = absolutePath.toUtf8().constData();
  456. {
  457. // send our request:
  458. payloadList.clear();
  459. connection.m_sent = false;
  460. m_assetProcessorManager->ProcessGetAssetJobsInfoRequest(requestInfo, jobResponse);
  461. }
  462. EXPECT_TRUE(jobResponse.m_isSuccess);
  463. EXPECT_EQ(jobResponse.m_jobList.size(), m_processResults.size());
  464. // make sure each job corresponds to one in the process results list (but note that the order is not important).
  465. for (int oldJobIdx = azlossy_cast<int>(jobResponse.m_jobList.size()) - 1; oldJobIdx >= 0; --oldJobIdx)
  466. {
  467. bool foundIt = false;
  468. const JobInfo& jobInfo = jobResponse.m_jobList[oldJobIdx];
  469. // validate EVERY field
  470. EXPECT_EQ(jobInfo.m_status, JobStatus::InProgress);
  471. EXPECT_FALSE(jobInfo.m_sourceFile.empty());
  472. EXPECT_FALSE(jobInfo.m_platform.empty());
  473. EXPECT_FALSE(jobInfo.m_jobKey.empty());
  474. EXPECT_FALSE(jobInfo.m_builderGuid.IsNull());
  475. for (const JobDetails& details : m_processResults)
  476. {
  477. if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_sourceAssetReference.RelativePath().c_str(), Qt::CaseSensitive) == 0) &&
  478. (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str(), Qt::CaseSensitive) == 0) &&
  479. (QString::compare(jobInfo.m_platform.c_str(), details.m_jobEntry.m_platformInfo.m_identifier.c_str(), Qt::CaseInsensitive) == 0) &&
  480. (QString::compare(jobInfo.m_jobKey.c_str(), details.m_jobEntry.m_jobKey, Qt::CaseInsensitive) == 0) &&
  481. (jobInfo.m_builderGuid == details.m_jobEntry.m_builderGuid) &&
  482. (jobInfo.GetHash() == details.m_jobEntry.GetHash()))
  483. {
  484. foundIt = true;
  485. break;
  486. }
  487. }
  488. EXPECT_TRUE(foundIt);
  489. }
  490. }
  491. QStringList androidouts;
  492. androidouts.push_back(m_cacheRoot.filePath(QString("android/basefile.arc1")));
  493. androidouts.push_back(m_cacheRoot.filePath(QString("android/basefile.arc2")));
  494. // feed it the messages its waiting for (create the files)
  495. EXPECT_TRUE(CreateDummyFile(androidouts[0], "products."));
  496. EXPECT_TRUE(CreateDummyFile(androidouts[1], "products."));
  497. //Invoke Asset Processed for android platform , txt files job description
  498. AssetBuilderSDK::ProcessJobResponse response;
  499. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  500. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts[0]), AZ::Uuid::CreateNull(), 1));
  501. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts[1]), AZ::Uuid::CreateNull(), 2));
  502. // make sure legacy SubIds get stored in the DB and in asset response messages.
  503. // also make sure they don't get filed for the wrong asset.
  504. response.m_outputProducts[0].m_legacySubIDs.push_back(1234);
  505. response.m_outputProducts[0].m_legacySubIDs.push_back(5678);
  506. response.m_outputProducts[1].m_legacySubIDs.push_back(2222);
  507. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  508. // let events bubble through:
  509. QCoreApplication::processEvents(QEventLoop::AllEvents);
  510. QCoreApplication::processEvents(QEventLoop::AllEvents);
  511. EXPECT_EQ(m_assetMessages.size(), 2);
  512. EXPECT_EQ(m_changedInputResults.size(), 1);
  513. // always RELATIVE, always with the product name.
  514. EXPECT_EQ(m_assetMessages[0].m_platform, "android");
  515. EXPECT_EQ(m_assetMessages[1].m_platform, "android");
  516. EXPECT_EQ(m_assetMessages[0].m_data, "basefile.arc1");
  517. EXPECT_EQ(m_assetMessages[1].m_data, "basefile.arc2");
  518. EXPECT_EQ(m_assetMessages[0].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetChanged);
  519. EXPECT_EQ(m_assetMessages[1].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetChanged);
  520. EXPECT_NE(m_assetMessages[0].m_sizeBytes, 0);
  521. EXPECT_NE(m_assetMessages[1].m_sizeBytes, 0);
  522. EXPECT_TRUE(m_assetMessages[0].m_assetId.IsValid());
  523. EXPECT_TRUE(m_assetMessages[1].m_assetId.IsValid());
  524. EXPECT_EQ(AssetUtilities::NormalizeFilePath(m_changedInputResults[0].first), AssetUtilities::NormalizeFilePath(absolutePath));
  525. // ----------------------- test job info requests, when some assets are done.
  526. {
  527. QCoreApplication::processEvents(QEventLoop::AllEvents);
  528. AssetJobsInfoRequest requestInfo;
  529. bool escalated = false;
  530. int numEscalated = 0;
  531. requestInfo.m_escalateJobs = true;
  532. requestInfo.m_searchTerm = absolutePath.toUtf8().constData();
  533. auto connectionMade = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::EscalateJobs, this, [&escalated, &numEscalated](AssetProcessor::JobIdEscalationList jobList)
  534. {
  535. escalated = true;
  536. numEscalated = jobList.size();
  537. });
  538. AssetJobsInfoResponse jobResponse;
  539. // send our request:
  540. m_assetProcessorManager->ProcessGetAssetJobsInfoRequest(requestInfo, jobResponse);
  541. // wait for it to process:
  542. QCoreApplication::processEvents(QEventLoop::AllEvents);
  543. QObject::disconnect(connectionMade);
  544. EXPECT_TRUE(escalated);
  545. EXPECT_GT(numEscalated, 0);
  546. EXPECT_TRUE(jobResponse.m_isSuccess);
  547. EXPECT_EQ(jobResponse.m_jobList.size(), m_processResults.size());
  548. // make sure each job corresponds to one in the process results list (but note that the order is not important).
  549. for (int oldJobIdx = azlossy_cast<int>(jobResponse.m_jobList.size()) - 1; oldJobIdx >= 0; --oldJobIdx)
  550. {
  551. bool foundIt = false;
  552. const JobInfo& jobInfo = jobResponse.m_jobList[oldJobIdx];
  553. // validate EVERY field
  554. EXPECT_FALSE(jobInfo.m_sourceFile.empty());
  555. EXPECT_FALSE(jobInfo.m_platform.empty());
  556. EXPECT_FALSE(jobInfo.m_jobKey.empty());
  557. EXPECT_FALSE(jobInfo.m_builderGuid.IsNull());
  558. for (int detailsIdx = 0; detailsIdx < m_processResults.size(); ++detailsIdx)
  559. {
  560. const JobDetails& details = m_processResults[detailsIdx];
  561. if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_sourceAssetReference.RelativePath().c_str(), Qt::CaseSensitive) == 0) &&
  562. (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str(), Qt::CaseSensitive) == 0) &&
  563. (QString::compare(jobInfo.m_jobKey.c_str(), details.m_jobEntry.m_jobKey, Qt::CaseInsensitive) == 0) &&
  564. (jobInfo.m_builderGuid == details.m_jobEntry.m_builderGuid) &&
  565. (jobInfo.GetHash() == details.m_jobEntry.GetHash()))
  566. {
  567. foundIt = true;
  568. if (detailsIdx == 0) // we only said that the first job was done
  569. {
  570. EXPECT_TRUE(jobInfo.m_status == JobStatus::Completed);
  571. }
  572. else
  573. {
  574. EXPECT_EQ(jobInfo.m_status, JobStatus::InProgress);
  575. }
  576. break;
  577. }
  578. }
  579. EXPECT_TRUE(foundIt);
  580. }
  581. }
  582. m_changedInputResults.clear();
  583. m_assetMessages.clear();
  584. androidouts.clear();
  585. androidouts.push_back(m_cacheRoot.filePath(QString("android/basefile.azm")));
  586. EXPECT_TRUE(CreateDummyFile(androidouts[0], "products."));
  587. //Invoke Asset Processed for android platform , txt files2 job description
  588. response.m_outputProducts.clear();
  589. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  590. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts[0])));
  591. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[1].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  592. // let events bubble through:
  593. QCoreApplication::processEvents(QEventLoop::AllEvents);
  594. QCoreApplication::processEvents(QEventLoop::AllEvents);
  595. EXPECT_EQ(m_assetMessages.size(), 1);
  596. EXPECT_EQ(m_changedInputResults.size(), 1);
  597. // always RELATIVE, always with the product name.
  598. EXPECT_EQ(m_assetMessages[0].m_platform, "android");
  599. EXPECT_EQ(m_assetMessages[0].m_data, "basefile.azm");
  600. m_changedInputResults.clear();
  601. m_assetMessages.clear();
  602. QStringList pcouts;
  603. pcouts.push_back(m_cacheRoot.filePath(QString("pc/basefile.arc1")));
  604. EXPECT_TRUE(CreateDummyFile(pcouts[0], "products."));
  605. response.m_outputProducts.clear();
  606. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  607. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[0])));
  608. //Invoke Asset Processed for pc platform , txt files job description
  609. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[2].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  610. // let events bubble through:
  611. QCoreApplication::processEvents(QEventLoop::AllEvents);
  612. QCoreApplication::processEvents(QEventLoop::AllEvents);
  613. EXPECT_EQ(m_assetMessages.size(), 1);
  614. EXPECT_EQ(m_changedInputResults.size(), 1);
  615. // always RELATIVE, always with the product name.
  616. EXPECT_EQ(m_assetMessages[0].m_platform, "pc");
  617. EXPECT_EQ(m_assetMessages[0].m_data, "basefile.arc1");
  618. EXPECT_EQ(AssetUtilities::NormalizeFilePath(m_changedInputResults[0].first), AssetUtilities::NormalizeFilePath(absolutePath));
  619. m_changedInputResults.clear();
  620. m_assetMessages.clear();
  621. pcouts.clear();
  622. pcouts.push_back(m_cacheRoot.filePath(QString("pc/basefile.azm")));
  623. EXPECT_TRUE(CreateDummyFile(pcouts[0], "products."));
  624. response.m_outputProducts.clear();
  625. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  626. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[0])));
  627. //Invoke Asset Processed for pc platform , txt files 2 job description
  628. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[3].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  629. // let events bubble through:
  630. QCoreApplication::processEvents(QEventLoop::AllEvents);
  631. QCoreApplication::processEvents(QEventLoop::AllEvents);
  632. EXPECT_EQ(m_assetMessages.size(), 1);
  633. EXPECT_EQ(m_changedInputResults.size(), 1);
  634. // always RELATIVE, always with the product name.
  635. EXPECT_EQ(m_assetMessages[0].m_platform, "pc");
  636. EXPECT_EQ(m_assetMessages[0].m_data, "basefile.azm");
  637. EXPECT_EQ(AssetUtilities::NormalizeFilePath(m_changedInputResults[0].first), AssetUtilities::NormalizeFilePath(absolutePath));
  638. // all four should now be complete:
  639. // ----------------------- test job info requests, now that all are done ---------------------------
  640. // by this time, querying for the status of those jobs should be possible since the "OnJobStatusChanged" event should have bubbled through
  641. // and this time, it should be "in progress"
  642. {
  643. QCoreApplication::processEvents(QEventLoop::AllEvents);
  644. AssetJobsInfoRequest requestInfo;
  645. AssetJobsInfoResponse jobResponse;
  646. requestInfo.m_searchTerm = absolutePath.toUtf8().constData();
  647. // send our request:
  648. m_assetProcessorManager->ProcessGetAssetJobsInfoRequest(requestInfo, jobResponse);
  649. EXPECT_TRUE(jobResponse.m_isSuccess);
  650. EXPECT_EQ(jobResponse.m_jobList.size(), m_processResults.size());
  651. // make sure each job corresponds to one in the process results list (but note that the order is not important).
  652. for (int oldJobIdx = azlossy_cast<int>(jobResponse.m_jobList.size()) - 1; oldJobIdx >= 0; --oldJobIdx)
  653. {
  654. bool foundIt = false;
  655. const JobInfo& jobInfo = jobResponse.m_jobList[oldJobIdx];
  656. // validate EVERY field
  657. EXPECT_EQ(jobInfo.m_status, JobStatus::Completed);
  658. EXPECT_FALSE(jobInfo.m_sourceFile.empty());
  659. EXPECT_FALSE(jobInfo.m_platform.empty());
  660. EXPECT_FALSE(jobInfo.m_jobKey.empty());
  661. EXPECT_FALSE(jobInfo.m_builderGuid.IsNull());
  662. for (const JobDetails& details : m_processResults)
  663. {
  664. if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_sourceAssetReference.RelativePath().c_str(), Qt::CaseSensitive) == 0) &&
  665. (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str(), Qt::CaseSensitive) == 0) &&
  666. (QString::compare(jobInfo.m_platform.c_str(), details.m_jobEntry.m_platformInfo.m_identifier.c_str(), Qt::CaseInsensitive) == 0) &&
  667. (QString::compare(jobInfo.m_jobKey.c_str(), details.m_jobEntry.m_jobKey, Qt::CaseInsensitive) == 0) &&
  668. (jobInfo.m_builderGuid == details.m_jobEntry.m_builderGuid) &&
  669. (jobInfo.GetHash() == details.m_jobEntry.GetHash()))
  670. {
  671. foundIt = true;
  672. break;
  673. }
  674. }
  675. EXPECT_TRUE(foundIt);
  676. }
  677. }
  678. m_changedInputResults.clear();
  679. m_assetMessages.clear();
  680. m_processResults.clear();
  681. // feed it the exact same file again.
  682. // this should result in NO ADDITIONAL processes since nothing has changed.
  683. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  684. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  685. EXPECT_TRUE(m_processResults.isEmpty());
  686. EXPECT_TRUE(m_changedInputResults.isEmpty());
  687. EXPECT_TRUE(m_assetMessages.isEmpty());
  688. // delete one of the products and tell it that it changed
  689. // it should reprocess that file, for that platform only:
  690. payloadList.clear();
  691. connection.m_sent = false;
  692. AssetNotificationMessage assetNotifMessage;
  693. SourceFileNotificationMessage sourceFileChangedMessage;
  694. // this should result in NO ADDITIONAL processes since nothing has changed.
  695. EXPECT_TRUE(QFile::remove(pcouts[0]));
  696. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, pcouts[0]));
  697. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  698. // We should not be receiving any sourcefile notification message here since the source file hasn't changed
  699. EXPECT_EQ(payloadList.size(), 0);
  700. // should have asked to launch only the PC process because the other assets are already done for the other plat
  701. EXPECT_EQ(m_processResults.size(), 1);
  702. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "pc");
  703. EXPECT_EQ(AssetUtilities::NormalizeFilePath(m_processResults[0].m_jobEntry.GetAbsoluteSourcePath()), AssetUtilities::NormalizeFilePath(absolutePath));
  704. EXPECT_TRUE(CreateDummyFile(pcouts[0], "products2"));
  705. // tell it were done again!
  706. m_changedInputResults.clear();
  707. m_assetMessages.clear();
  708. response.m_outputProducts.clear();
  709. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  710. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[0])));
  711. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  712. // let events bubble through:
  713. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  714. EXPECT_EQ(m_assetMessages.size(), 1);
  715. EXPECT_EQ(m_changedInputResults.size(), 1);
  716. // always RELATIVE, always with the product name.
  717. EXPECT_EQ(m_assetMessages[0].m_data, "basefile.azm");
  718. EXPECT_EQ(m_assetMessages[0].m_platform, "pc");
  719. EXPECT_EQ(AssetUtilities::NormalizeFilePath(m_changedInputResults[0].first), AssetUtilities::NormalizeFilePath(absolutePath));
  720. m_changedInputResults.clear();
  721. m_assetMessages.clear();
  722. m_processResults.clear();
  723. connection.m_sent = false;
  724. payloadList.clear();
  725. // modify the input file, then
  726. // feed it the exact same file again.
  727. // it should spawn BOTH compilers:
  728. EXPECT_TRUE(QFile::remove(absolutePath));
  729. EXPECT_TRUE(CreateDummyFile(absolutePath, "new!"));
  730. AZ_TracePrintf(AssetProcessor::DebugChannel, "-------------------------------------------\n");
  731. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  732. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  733. EXPECT_TRUE(connection.m_sent);
  734. EXPECT_EQ(payloadList.size(), 1);// We should always receive only one of these messages
  735. EXPECT_TRUE(AZ::Utils::LoadObjectFromBufferInPlace(payloadList.at(0).second.data(), payloadList.at(0).second.size(), sourceFileChangedMessage));
  736. QDir scanFolder(sourceFileChangedMessage.m_scanFolder.c_str());
  737. QString pathToCheck = scanFolder.filePath(sourceFileChangedMessage.m_relativeSourcePath.c_str());
  738. EXPECT_EQ(QString::compare(absolutePath, pathToCheck, Qt::CaseSensitive), 0);
  739. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  740. // --------- same result as above ----------
  741. EXPECT_EQ(m_processResults.size(), 4); // 2 each for pc and android,since we have two recognizer for .txt file
  742. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, m_processResults[1].m_jobEntry.m_platformInfo.m_identifier);
  743. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, m_processResults[3].m_jobEntry.m_platformInfo.m_identifier);
  744. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  745. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "android");
  746. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, "pc");
  747. EXPECT_EQ(m_processResults[3].m_jobEntry.m_platformInfo.m_identifier, "pc");
  748. EXPECT_NE(m_processResults[0].m_jobEntry.m_computedFingerprint, 0);
  749. EXPECT_NE(m_processResults[1].m_jobEntry.m_computedFingerprint, 0);
  750. for (int checkIdx = 0; checkIdx < 4; ++checkIdx)
  751. {
  752. QString processFile1 = m_processResults[checkIdx].m_jobEntry.GetAbsoluteSourcePath();
  753. EXPECT_EQ(AssetUtilities::NormalizeFilePath(processFile1), AssetUtilities::NormalizeFilePath(absolutePath));
  754. QString platformFolder = m_cacheRoot.filePath(QString::fromUtf8(m_processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()));
  755. platformFolder = AssetUtilities::NormalizeDirectoryPath(platformFolder);
  756. AZ::IO::Path expectedCachePath = m_cacheRoot.absoluteFilePath(platformFolder).toUtf8().constData();
  757. AZ::IO::FixedMaxPath intermediateAssetsFolder = AssetUtilities::GetIntermediateAssetsFolder(m_cacheRoot.path().toUtf8().constData());
  758. EXPECT_EQ(m_processResults[checkIdx].m_cachePath, expectedCachePath);
  759. EXPECT_EQ(m_processResults[checkIdx].m_intermediatePath, intermediateAssetsFolder);
  760. EXPECT_NE(m_processResults[checkIdx].m_jobEntry.m_computedFingerprint, 0);
  761. }
  762. // this time make different products:
  763. QStringList oldandroidouts;
  764. QStringList oldpcouts;
  765. oldandroidouts = androidouts;
  766. oldpcouts.append(pcouts);
  767. QStringList androidouts2;
  768. QStringList pcouts2;
  769. androidouts.clear();
  770. pcouts.clear();
  771. androidouts.push_back(m_cacheRoot.filePath(QString("android/basefilea.arc1")));
  772. androidouts2.push_back(m_cacheRoot.filePath(QString("android/basefilea.azm")));
  773. // note that the android outs have changed
  774. // but the pc outs are still the same.
  775. pcouts.push_back(m_cacheRoot.filePath(QString("pc/basefile.arc1")));
  776. pcouts2.push_back(m_cacheRoot.filePath(QString("pc/basefile.azm")));
  777. // feed it the messages its waiting for (create the files)
  778. EXPECT_TRUE(CreateDummyFile(androidouts[0], "newfile."));
  779. EXPECT_TRUE(CreateDummyFile(pcouts[0], "newfile."));
  780. EXPECT_TRUE(CreateDummyFile(androidouts2[0], "newfile."));
  781. EXPECT_TRUE(CreateDummyFile(pcouts2[0], "newfile."));
  782. QCoreApplication::processEvents(QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents, 50);
  783. m_changedInputResults.clear();
  784. m_assetMessages.clear();
  785. // send all the done messages simultaneously:
  786. response.m_outputProducts.clear();
  787. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  788. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts[0])));
  789. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  790. response.m_outputProducts.clear();
  791. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  792. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts2[0])));
  793. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[1].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  794. response.m_outputProducts.clear();
  795. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  796. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[0])));
  797. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[2].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  798. response.m_outputProducts.clear();
  799. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  800. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts2[0])));
  801. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[3].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  802. // let events bubble through:
  803. QCoreApplication::processEvents(QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents, 50);
  804. EXPECT_EQ(m_changedInputResults.size(), 4);
  805. EXPECT_EQ(m_assetMessages.size(), 7);
  806. // what we expect to happen here is that it tells us that 3 files were removed, and 4 files were changed.
  807. // The files removed should be the ones we did not emit this time
  808. // note that order isn't guarantee but an example output it this
  809. // [0] Removed: ANDROID, basefile.arc1
  810. // [1] Removed: ANDROID, basefile.arc2
  811. // [2] Changed: ANDROID, basefilea.arc1 (added)
  812. // [3] Removed: ANDROID, basefile.azm
  813. // [4] Changed: ANDROID, basefilea.azm (added)
  814. // [5] changed: PC, basefile.arc1 (changed)
  815. // [6] changed: PC, basefile.azm (changed)
  816. for (auto element : m_assetMessages)
  817. {
  818. if (element.m_data == "basefile.arc1")
  819. {
  820. if (element.m_platform == "pc")
  821. {
  822. EXPECT_EQ(element.m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetChanged);
  823. }
  824. else
  825. {
  826. EXPECT_EQ(element.m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetRemoved);
  827. }
  828. }
  829. if (element.m_data == "basefilea.arc1")
  830. {
  831. EXPECT_EQ(element.m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetChanged);
  832. EXPECT_EQ(element.m_platform, "android");
  833. }
  834. if (element.m_data == "basefile.arc2")
  835. {
  836. EXPECT_EQ(element.m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetRemoved);
  837. EXPECT_EQ(element.m_platform, "android");
  838. }
  839. }
  840. // original products must no longer exist since it should have found and deleted them!
  841. for (QString outFile: oldandroidouts)
  842. {
  843. EXPECT_FALSE(QFile::exists(outFile));
  844. }
  845. // the old pc products should still exist because they were emitted this time around.
  846. for (QString outFile: oldpcouts)
  847. {
  848. EXPECT_TRUE(QFile::exists(outFile));
  849. }
  850. m_changedInputResults.clear();
  851. m_assetMessages.clear();
  852. m_processResults.clear();
  853. // add a fingerprint file thats next to the original file
  854. // feed it the exportsettings file again.
  855. // it should spawn BOTH compilers again.
  856. EXPECT_TRUE(CreateDummyFile(absolutePath + ".exportsettings", "new!"));
  857. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath + ".exportsettings"));
  858. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  859. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  860. // --------- same result as above ----------
  861. EXPECT_EQ(m_processResults.size(), 4); // pc and android
  862. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, m_processResults[1].m_jobEntry.m_platformInfo.m_identifier);
  863. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, m_processResults[3].m_jobEntry.m_platformInfo.m_identifier);
  864. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  865. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "android");
  866. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, "pc");
  867. EXPECT_EQ(m_processResults[3].m_jobEntry.m_platformInfo.m_identifier, "pc");
  868. EXPECT_NE(m_processResults[0].m_jobEntry.m_computedFingerprint, 0);
  869. // send all the done messages simultaneously:
  870. for (int checkIdx = 0; checkIdx < 4; ++checkIdx)
  871. {
  872. QString processFile1 = m_processResults[checkIdx].m_jobEntry.GetAbsoluteSourcePath();
  873. EXPECT_EQ(AssetUtilities::NormalizeFilePath(processFile1), AssetUtilities::NormalizeFilePath(absolutePath));
  874. VerifyProductPaths(m_processResults[checkIdx]);
  875. EXPECT_NE(m_processResults[checkIdx].m_jobEntry.m_computedFingerprint, 0);
  876. }
  877. response.m_outputProducts.clear();
  878. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  879. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts[0])));
  880. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  881. response.m_outputProducts.clear();
  882. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  883. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts2[0])));
  884. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[1].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  885. response.m_outputProducts.clear();
  886. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  887. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[0])));
  888. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[2].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  889. response.m_outputProducts.clear();
  890. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  891. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts2[0])));
  892. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[3].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  893. // let events bubble through:
  894. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  895. // --- delete the input asset and make sure it cleans up all products.
  896. m_changedInputResults.clear();
  897. m_assetMessages.clear();
  898. m_processResults.clear();
  899. // first, delete the fingerprint file, this should result in normal reprocess:
  900. QFile::remove(absolutePath + ".exportsettings");
  901. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath + ".exportsettings"));
  902. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  903. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  904. // --------- same result as above ----------
  905. EXPECT_EQ(m_processResults.size(), 4); // 2 each for pc and android,since we have two recognizer for .txt file
  906. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, m_processResults[1].m_jobEntry.m_platformInfo.m_identifier);
  907. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, m_processResults[3].m_jobEntry.m_platformInfo.m_identifier);
  908. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  909. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "android");
  910. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, "pc");
  911. EXPECT_EQ(m_processResults[3].m_jobEntry.m_platformInfo.m_identifier, "pc");
  912. EXPECT_NE(m_processResults[0].m_jobEntry.m_computedFingerprint, 0);
  913. EXPECT_NE(m_processResults[1].m_jobEntry.m_computedFingerprint, 0);
  914. // send all the done messages simultaneously:
  915. response.m_outputProducts.clear();
  916. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  917. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts[0])));
  918. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  919. response.m_outputProducts.clear();
  920. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  921. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts2[0])));
  922. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[1].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  923. response.m_outputProducts.clear();
  924. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  925. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[0])));
  926. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[2].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  927. response.m_outputProducts.clear();
  928. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  929. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts2[0])));
  930. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[3].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  931. // let events bubble through:
  932. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  933. // deleting the fingerprint file should not have erased the products
  934. EXPECT_TRUE(QFile::exists(pcouts[0]));
  935. EXPECT_TRUE(QFile::exists(androidouts[0]));
  936. EXPECT_TRUE(QFile::exists(pcouts2[0]));
  937. EXPECT_TRUE(QFile::exists(androidouts2[0]));
  938. m_changedInputResults.clear();
  939. m_assetMessages.clear();
  940. m_processResults.clear();
  941. connection.m_sent = false;
  942. payloadList.clear();
  943. // delete the original input.
  944. QFile::remove(absolutePath);
  945. SourceFileNotificationMessage sourceFileRemovedMessage;
  946. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  947. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  948. // 9 messages because there's one source file with 4 products so:
  949. // 1 * file remove for the source file.
  950. // 4 * file claimed for the produce file to be able to update it safely.
  951. // 4 * file released for the produce file so it's free for other tools to use it again.
  952. EXPECT_EQ(payloadList.size(), 9);
  953. unsigned int messageLoadCount = 0;
  954. for (auto payload : payloadList)
  955. {
  956. if (payload.first == SourceFileNotificationMessage::MessageType)
  957. {
  958. EXPECT_TRUE(AZ::Utils::LoadObjectFromBufferInPlace(payload.second.data(), payload.second.size(), sourceFileRemovedMessage));
  959. EXPECT_EQ(sourceFileRemovedMessage.m_type, SourceFileNotificationMessage::FileRemoved);
  960. ++messageLoadCount;
  961. }
  962. else if (payload.first == AssetNotificationMessage::MessageType)
  963. {
  964. AssetNotificationMessage message;
  965. EXPECT_TRUE(AZ::Utils::LoadObjectFromBufferInPlace(payload.second.data(), payload.second.size(), message));
  966. EXPECT_TRUE(
  967. message.m_type == AssetNotificationMessage::NotificationType::JobFileClaimed ||
  968. message.m_type == AssetNotificationMessage::NotificationType::JobFileReleased);
  969. ++messageLoadCount;
  970. }
  971. }
  972. EXPECT_TRUE(connection.m_sent);
  973. EXPECT_EQ(messageLoadCount, azlossy_cast<unsigned>(payloadList.size())); // make sure all messages are accounted for
  974. scanFolder = QDir(sourceFileRemovedMessage.m_scanFolder.c_str());
  975. pathToCheck = scanFolder.filePath(sourceFileRemovedMessage.m_relativeSourcePath.c_str());
  976. EXPECT_EQ(QString::compare(absolutePath, pathToCheck, Qt::CaseSensitive), 0);
  977. // nothing to process, but products should be gone!
  978. EXPECT_TRUE(m_processResults.isEmpty());
  979. EXPECT_TRUE(m_changedInputResults.isEmpty());
  980. // should have gotten four "removed" messages for its products:
  981. EXPECT_EQ(m_assetMessages.size(), 4);
  982. for (auto element : m_assetMessages)
  983. {
  984. EXPECT_EQ(element.m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetRemoved);
  985. }
  986. EXPECT_FALSE(QFile::exists(pcouts[0]));
  987. EXPECT_FALSE(QFile::exists(androidouts[0]));
  988. EXPECT_FALSE(QFile::exists(pcouts2[0]));
  989. EXPECT_FALSE(QFile::exists(androidouts2[0]));
  990. m_changedInputResults.clear();
  991. m_assetMessages.clear();
  992. m_processResults.clear();
  993. // test: if an asset fails, it should recompile it next time, and not report success
  994. EXPECT_TRUE(CreateDummyFile(absolutePath, "new2"));
  995. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  996. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  997. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  998. // --------- same result as above ----------
  999. EXPECT_EQ(m_processResults.size(), 4); // 2 each for pc and android,since we have two recognizer for .txt file
  1000. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, m_processResults[1].m_jobEntry.m_platformInfo.m_identifier);
  1001. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, m_processResults[3].m_jobEntry.m_platformInfo.m_identifier);
  1002. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  1003. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "android");
  1004. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1005. EXPECT_EQ(m_processResults[3].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1006. EXPECT_NE(m_processResults[0].m_jobEntry.m_computedFingerprint, 0);
  1007. EXPECT_TRUE(CreateDummyFile(androidouts[0], "newfile."));
  1008. EXPECT_TRUE(CreateDummyFile(androidouts2[0], "newfile."));
  1009. EXPECT_TRUE(CreateDummyFile(pcouts2[0], "newfile."));
  1010. // send both done messages simultaneously!
  1011. response.m_outputProducts.clear();
  1012. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1013. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts[0])));
  1014. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1015. response.m_outputProducts.clear();
  1016. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1017. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts2[0])));
  1018. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[1].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1019. // send one failure only for PC :
  1020. response.m_outputProducts.clear();
  1021. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1022. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[0])));
  1023. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetFailed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[2].m_jobEntry));
  1024. response.m_outputProducts.clear();
  1025. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1026. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts2[0])));
  1027. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[3].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1028. // let events bubble through:
  1029. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1030. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1031. // ----------------------- test job info requests, some assets have failed (specifically, the [2] index job entry
  1032. {
  1033. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1034. AssetJobsInfoRequest requestInfo;
  1035. requestInfo.m_searchTerm = absolutePath.toUtf8().constData();
  1036. payloadList.clear();
  1037. AssetJobsInfoResponse jobResponse;
  1038. m_assetProcessorManager->ProcessGetAssetJobsInfoRequest(requestInfo, jobResponse);
  1039. EXPECT_TRUE(jobResponse.m_isSuccess);
  1040. EXPECT_EQ(jobResponse.m_jobList.size(), m_processResults.size());
  1041. // make sure each job corresponds to one in the process results list (but note that the order is not important).
  1042. for (int oldJobIdx = azlossy_cast<int>(jobResponse.m_jobList.size()) - 1; oldJobIdx >= 0; --oldJobIdx)
  1043. {
  1044. bool foundIt = false;
  1045. const JobInfo& jobInfo = jobResponse.m_jobList[oldJobIdx];
  1046. // validate EVERY field
  1047. EXPECT_FALSE(jobInfo.m_sourceFile.empty());
  1048. EXPECT_FALSE(jobInfo.m_platform.empty());
  1049. EXPECT_FALSE(jobInfo.m_jobKey.empty());
  1050. EXPECT_FALSE(jobInfo.m_builderGuid.IsNull());
  1051. for (int detailsIdx = 0; detailsIdx < m_processResults.size(); ++detailsIdx)
  1052. {
  1053. const JobDetails& details = m_processResults[detailsIdx];
  1054. if ((QString::compare(jobInfo.m_sourceFile.c_str(), details.m_jobEntry.m_sourceAssetReference.RelativePath().c_str(), Qt::CaseSensitive) == 0) &&
  1055. (QString::compare(jobInfo.m_watchFolder.c_str(), details.m_jobEntry.m_sourceAssetReference.ScanFolderPath().c_str(), Qt::CaseSensitive) == 0) &&
  1056. (QString::compare(jobInfo.m_platform.c_str(), details.m_jobEntry.m_platformInfo.m_identifier.c_str(), Qt::CaseInsensitive) == 0) &&
  1057. (QString::compare(jobInfo.m_jobKey.c_str(), details.m_jobEntry.m_jobKey, Qt::CaseInsensitive) == 0) &&
  1058. (jobInfo.m_builderGuid == details.m_jobEntry.m_builderGuid) &&
  1059. (jobInfo.GetHash() == details.m_jobEntry.GetHash()))
  1060. {
  1061. foundIt = true;
  1062. if (detailsIdx == 2) // we only said that the index [2] job was dead
  1063. {
  1064. EXPECT_EQ(jobInfo.m_status, JobStatus::Failed);
  1065. }
  1066. else
  1067. {
  1068. EXPECT_EQ(jobInfo.m_status, JobStatus::Completed);
  1069. }
  1070. break;
  1071. }
  1072. }
  1073. EXPECT_TRUE(foundIt);
  1074. }
  1075. }
  1076. // we should have get three success:
  1077. EXPECT_EQ(m_changedInputResults.size(), 3);
  1078. EXPECT_EQ(m_assetMessages.size(), 3);
  1079. // which should be for the ANDROID:
  1080. EXPECT_EQ(AssetUtilities::NormalizeFilePath(m_changedInputResults[0].first), absolutePath);
  1081. // always RELATIVE, always with the product name.
  1082. EXPECT_TRUE(m_assetMessages[0].m_data == "basefilea.arc1" || m_assetMessages[0].m_data == "basefilea.azm");
  1083. EXPECT_EQ(m_assetMessages[0].m_platform, "android");
  1084. for (auto& payload : payloadList)
  1085. {
  1086. if (payload.first == SourceFileNotificationMessage::MessageType)
  1087. {
  1088. EXPECT_TRUE(AZ::Utils::LoadObjectFromBufferInPlace(payload.second.data(), payload.second.size(), sourceFileRemovedMessage));
  1089. EXPECT_EQ(sourceFileRemovedMessage.m_type, SourceFileNotificationMessage::FileRemoved);
  1090. }
  1091. }
  1092. scanFolder = QDir(sourceFileRemovedMessage.m_scanFolder.c_str());
  1093. pathToCheck = scanFolder.filePath(sourceFileRemovedMessage.m_relativeSourcePath.c_str());
  1094. EXPECT_EQ(QString::compare(absolutePath, pathToCheck, Qt::CaseSensitive), 0);
  1095. // now if we notify again, only the pc should process:
  1096. m_changedInputResults.clear();
  1097. m_assetMessages.clear();
  1098. m_processResults.clear();
  1099. payloadList.clear();
  1100. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  1101. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1102. // --------- same result as above ----------
  1103. EXPECT_EQ(m_processResults.size(), 1); // pc only
  1104. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1105. EXPECT_TRUE(CreateDummyFile(pcouts[0], "new1"));
  1106. // send one failure only for PC :
  1107. response.m_outputProducts.clear();
  1108. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1109. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[0])));
  1110. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1111. // let events bubble through:
  1112. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1113. // we should have got only one success:
  1114. EXPECT_EQ(m_changedInputResults.size(), 1);
  1115. EXPECT_EQ(m_assetMessages.size(), 1);
  1116. // always RELATIVE, always with the product name.
  1117. EXPECT_EQ(m_assetMessages[0].m_data, "basefile.arc1");
  1118. EXPECT_EQ(m_assetMessages[0].m_platform, "pc");
  1119. connection.BusDisconnect(1);
  1120. mockAppManager.BusDisconnect();
  1121. }
  1122. TEST_F(AssetProcessorManagerUnitTests, ValidatePlatformSpecificAssetRecognizer_FeedFileToProcess_PlatformSpecificTaskGenerated)
  1123. {
  1124. //Test the ProcessGetFullAssetPath function
  1125. MockApplicationManager mockAppManager;
  1126. mockAppManager.BusConnect();
  1127. AssetRecognizer rec;
  1128. rec.m_name = "random files";
  1129. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.random", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  1130. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  1131. m_config.AddRecognizer(rec);
  1132. EXPECT_TRUE(mockAppManager.RegisterAssetRecognizerAsBuilder(rec));
  1133. QString absolutePath = AssetUtilities::NormalizeFilePath(m_sourceRoot.absoluteFilePath("subfolder3/somerandomfile.random"));
  1134. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles({absolutePath});
  1135. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  1136. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1137. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1138. EXPECT_EQ(m_processResults.size(), 1); // 1 for pc
  1139. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1140. QStringList pcouts;
  1141. pcouts.push_back(m_cacheRoot.filePath(QString("pc/subfolder3/randomfileoutput.random")));
  1142. pcouts.push_back(m_cacheRoot.filePath(QString("pc/subfolder3/randomfileoutput.random1")));
  1143. pcouts.push_back(m_cacheRoot.filePath(QString("pc/subfolder3/randomfileoutput.random2")));
  1144. EXPECT_TRUE(CreateDummyFile(pcouts[0], "products."));
  1145. EXPECT_TRUE(CreateDummyFile(pcouts[1], "products."));
  1146. EXPECT_TRUE(CreateDummyFile(pcouts[2], "products."));
  1147. //Invoke Asset Processed for pc platform , txt files job description
  1148. AssetBuilderSDK::ProcessJobResponse response;
  1149. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1150. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[0]), AZ::Uuid::CreateNull(), 1));
  1151. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[1]), AZ::Uuid::CreateNull(), 2));
  1152. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[2]), AZ::Uuid::CreateNull(), 3));
  1153. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1154. // let events bubble through:
  1155. QCoreApplication::processEvents(QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents, 1000);
  1156. EXPECT_EQ(m_assetMessages.size(), 3);
  1157. EXPECT_EQ(m_changedInputResults.size(), 1);
  1158. mockAppManager.BusDisconnect();
  1159. }
  1160. TEST_F(AssetProcessorManagerUnitTests, ValidateOverrideSystem_FeedFilesWithSameNameButUnderDifferentScanFolders_TasksGeneratedBasedOnOverrideRules)
  1161. {
  1162. // There is a sub-case of handling mixed cases, but is only supported on case-insensitive filesystems.
  1163. #if defined(AZ_PLATFORM_LINUX)
  1164. // Linux is case-sensitive, so 'basefile.txt' will stay the same case as the other subfolder versions
  1165. constexpr const char* subfolder3BaseFilePath = "subfolder3/basefile.txt";
  1166. #else
  1167. constexpr const char* subfolder3BaseFilePath = "subfolder3/BaseFile.txt";
  1168. #endif
  1169. MockApplicationManager mockAppManager;
  1170. mockAppManager.BusConnect();
  1171. AssetRecognizer rec;
  1172. const char* builderTxt1Name = "txt files";
  1173. rec.m_name = builderTxt1Name;
  1174. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  1175. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  1176. rec.m_platformSpecs.insert({"android", AssetInternalSpec::Copy});
  1177. m_config.AddRecognizer(rec);
  1178. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1179. // test dual-recognisers - two recognisers for the same pattern.
  1180. rec.m_name = "txt files 2 (builder2)";
  1181. m_config.AddRecognizer(rec);
  1182. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1183. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/test\\/.*\\.format", AssetBuilderSDK::AssetBuilderPattern::Regex);
  1184. rec.m_name = "format files that live in a folder called test";
  1185. m_config.AddRecognizer(rec);
  1186. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1187. rec.m_platformSpecs.clear();
  1188. rec.m_testLockSource = false;
  1189. rec.m_name = "xxx files";
  1190. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.xxx", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  1191. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  1192. rec.m_platformSpecs.insert({"android", AssetInternalSpec::Copy});
  1193. m_config.AddRecognizer(rec);
  1194. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1195. // two recognizers for the same pattern.
  1196. rec.m_name = "xxx files 2 (builder2)";
  1197. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  1198. rec.m_platformSpecs.insert({"android", AssetInternalSpec::Copy});
  1199. m_config.AddRecognizer(rec);
  1200. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1201. QSet<QString> expectedFiles;
  1202. expectedFiles << m_sourceRoot.absoluteFilePath("subfolder1/basefile.txt");
  1203. expectedFiles << m_sourceRoot.absoluteFilePath("subfolder2/basefile.txt");
  1204. expectedFiles << m_sourceRoot.absoluteFilePath(subfolder3BaseFilePath);
  1205. expectedFiles << m_sourceRoot.absoluteFilePath("subfolder3/somefile.xxx");
  1206. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles(expectedFiles);
  1207. // set up by letting it compile basefile.txt from subfolder3:
  1208. QString absolutePath = AssetUtilities::NormalizeFilePath(m_sourceRoot.absoluteFilePath(subfolder3BaseFilePath));
  1209. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  1210. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1211. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1212. // --------- same result as above ----------
  1213. EXPECT_EQ(m_processResults.size(), 4); // 2 each for pc and android,since we have two recognizer for .txt file
  1214. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, m_processResults[1].m_jobEntry.m_platformInfo.m_identifier);
  1215. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, m_processResults[3].m_jobEntry.m_platformInfo.m_identifier);
  1216. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  1217. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "android");
  1218. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1219. EXPECT_EQ(m_processResults[3].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1220. EXPECT_NE(m_processResults[0].m_jobEntry.m_computedFingerprint, 0);
  1221. QStringList pcouts;
  1222. QStringList androidouts;
  1223. QStringList androidouts2;
  1224. QStringList pcouts2;
  1225. androidouts.push_back(m_cacheRoot.filePath(QString("android/basefilez.arc2")));
  1226. androidouts2.push_back(m_cacheRoot.filePath(QString("android/basefileaz.azm2")));
  1227. // note that the android outs have changed
  1228. // but the pc outs are still the same.
  1229. pcouts.push_back(m_cacheRoot.filePath(QString("pc/basefile.arc2")));
  1230. pcouts2.push_back(m_cacheRoot.filePath(QString("pc/basefile.azm2")));
  1231. EXPECT_TRUE(CreateDummyFile(androidouts[0], "newfile."));
  1232. EXPECT_TRUE(CreateDummyFile(pcouts[0], "newfile."));
  1233. EXPECT_TRUE(CreateDummyFile(androidouts2[0], "newfile."));
  1234. EXPECT_TRUE(CreateDummyFile(pcouts2[0], "newfile."));
  1235. m_changedInputResults.clear();
  1236. m_assetMessages.clear();
  1237. // send all the done messages simultaneously:
  1238. AssetBuilderSDK::ProcessJobResponse response;
  1239. response.m_outputProducts.clear();
  1240. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1241. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts[0]), AZ::Uuid::CreateNull(), 1));
  1242. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1243. response.m_outputProducts.clear();
  1244. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1245. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(androidouts2[0]), AZ::Uuid::CreateNull(), 2));
  1246. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[1].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1247. response.m_outputProducts.clear();
  1248. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1249. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts[0]), AZ::Uuid::CreateNull(), 3));
  1250. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[2].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1251. response.m_outputProducts.clear();
  1252. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1253. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(AbsProductPathToRelative(pcouts2[0]), AZ::Uuid::CreateNull(), 4));
  1254. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[3].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1255. // let events bubble through:
  1256. QCoreApplication::processEvents(QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents, 1000);
  1257. // we should have got only one success:
  1258. EXPECT_EQ(m_changedInputResults.size(), 4);
  1259. EXPECT_EQ(m_assetMessages.size(), 4);
  1260. // ------------- setup complete, now do the test...
  1261. // now feed it a file that has been overridden by a more important later file
  1262. absolutePath = AssetUtilities::NormalizeFilePath(m_sourceRoot.absoluteFilePath("subfolder1/basefile.txt"));
  1263. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles({absolutePath});
  1264. m_changedInputResults.clear();
  1265. m_assetMessages.clear();
  1266. m_processResults.clear();
  1267. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  1268. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1269. EXPECT_TRUE(m_processResults.isEmpty());
  1270. EXPECT_TRUE(m_changedInputResults.isEmpty());
  1271. EXPECT_TRUE(m_assetMessages.isEmpty());
  1272. // since it was overridden, nothing should occur.
  1273. //AZ_TracePrintf("Asset Processor", "Preparing the assessDeletedFiles invocation...\n");
  1274. // delete the highest priority override file and ensure that it generates tasks
  1275. // for the next highest priority! Basically, deleting this file should "reveal" the file underneath it in the other subfolder
  1276. QString deletedFile = m_sourceRoot.absoluteFilePath(subfolder3BaseFilePath);
  1277. QString expectedReplacementInputFile = AssetUtilities::NormalizeFilePath(m_sourceRoot.absoluteFilePath("subfolder2/basefile.txt"));
  1278. EXPECT_TRUE(QFile::remove(deletedFile));
  1279. // sometimes the above deletion actually takes a moment to trickle, for some reason, and it doesn't actually get that the file was erased.
  1280. QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
  1281. EXPECT_FALSE(QFile::exists(deletedFile));
  1282. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, deletedFile));
  1283. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1284. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1285. // On Linux, because we cannot change the case of the source file, the job fingerprint is not updated due the case-switch.
  1286. // The reason the fingerprint for subfolder3/basefile.txt and subfolder2/basefile.txt are the same ON LINUX is because the
  1287. // fingerprint of the file includes the filename (also both files have the same contents). Additionally, when this test is set up,
  1288. // subfolder3BaseFilePath ON LINUX is set to basefile.txt whereas it is set to BaseFile.txt on windows. That is why the hash is the
  1289. // same only for linux but different for other platforms. Note that if this test breaks on linux, it can be debugged on windows by
  1290. // setting subfolder3BaseFilePath = basefile.txt on windows.
  1291. // We still expect linux to produce the same result as other platforms however because we no longer query sources using just the relative path.
  1292. // This means the override file which has not been processed yet MUST be processed, regardless of whether it just happens to have the same fingerprint
  1293. // on linux.
  1294. // --------- same result as above ----------
  1295. EXPECT_EQ(m_processResults.size(), 4); // 2 each for pc and android,since we have two recognizer for .txt file
  1296. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, m_processResults[1].m_jobEntry.m_platformInfo.m_identifier);
  1297. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, m_processResults[3].m_jobEntry.m_platformInfo.m_identifier);
  1298. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  1299. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "android");
  1300. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1301. EXPECT_EQ(m_processResults[3].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1302. EXPECT_NE(m_processResults[0].m_jobEntry.m_computedFingerprint, 0);
  1303. for (int checkIdx = 0; checkIdx < 4; ++checkIdx)
  1304. {
  1305. QString processFile1 = m_processResults[checkIdx].m_jobEntry.GetAbsoluteSourcePath();
  1306. EXPECT_EQ(processFile1, expectedReplacementInputFile);
  1307. VerifyProductPaths(m_processResults[checkIdx]);
  1308. EXPECT_NE(m_processResults[checkIdx].m_jobEntry.m_computedFingerprint, 0);
  1309. }
  1310. QString relativePathFromWatchFolder = "somefile.xxx";
  1311. QString watchFolderPath = m_sourceRoot.absoluteFilePath("subfolder3");
  1312. absolutePath = watchFolderPath + "/" + relativePathFromWatchFolder;
  1313. unsigned int fingerprintForPC = 0;
  1314. unsigned int fingerprintForANDROID = 0;
  1315. AssetProcessorManagerUnitTestUtils::ComputeFingerprints(fingerprintForPC, fingerprintForANDROID, m_config, watchFolderPath, relativePathFromWatchFolder);
  1316. m_processResults.clear();
  1317. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  1318. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1319. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1320. EXPECT_EQ(m_processResults.size(), 4); // // 2 each for pc and android,since we have two recognizer for .xxx file
  1321. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, m_processResults[1].m_jobEntry.m_platformInfo.m_identifier);
  1322. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, m_processResults[3].m_jobEntry.m_platformInfo.m_identifier);
  1323. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  1324. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "android");
  1325. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1326. EXPECT_EQ(m_processResults[3].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1327. m_config.RemoveRecognizer("xxx files 2 (builder2)");
  1328. EXPECT_TRUE(mockAppManager.UnRegisterAssetRecognizerAsBuilder("xxx files 2 (builder2)"));
  1329. //Changing specs for pc
  1330. rec.m_platformSpecs.insert({ "pc", AssetInternalSpec::Copy });
  1331. m_config.AddRecognizer(rec);
  1332. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1333. m_processResults.clear();
  1334. absolutePath = AssetUtilities::NormalizeFilePath(absolutePath);
  1335. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  1336. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1337. // we never actually submitted any fingerprints or indicated success, so the same number of jobs should occur as before
  1338. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1339. EXPECT_EQ(m_processResults.size(), 4); // // 2 each for pc and android,since we have two recognizer for .xxx file
  1340. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, m_processResults[1].m_jobEntry.m_platformInfo.m_identifier);
  1341. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, m_processResults[3].m_jobEntry.m_platformInfo.m_identifier);
  1342. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  1343. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "android");
  1344. EXPECT_EQ(m_processResults[2].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1345. EXPECT_EQ(m_processResults[3].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1346. // tell it that all those assets are now successfully done:
  1347. AZ::u32 resultIdx = 0;
  1348. for (const auto& processResult : m_processResults)
  1349. {
  1350. ++resultIdx;
  1351. AZStd::string filename = ("doesn'tmatter.dds" + processResult.m_jobEntry.m_jobKey).toUtf8().constData();
  1352. QString outputFile = (processResult.m_cachePath / filename).AsPosix().c_str();
  1353. CreateDummyFile(outputFile);
  1354. response = {};
  1355. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1356. response.m_outputProducts.push_back(
  1357. AssetBuilderSDK::JobProduct((processResult.m_relativePath / filename).StringAsPosix(), AZ::Uuid::CreateNull(), resultIdx));
  1358. m_assetProcessorManager->AssetProcessed(processResult.m_jobEntry, response);
  1359. }
  1360. m_config.RemoveRecognizer("xxx files 2 (builder2)");
  1361. mockAppManager.UnRegisterAssetRecognizerAsBuilder("xxx files 2 (builder2)");
  1362. //Changing version
  1363. rec.m_version = "1.0";
  1364. m_config.AddRecognizer(rec);
  1365. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1366. m_processResults.clear();
  1367. absolutePath = AssetUtilities::NormalizeFilePath(absolutePath);
  1368. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  1369. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1370. EXPECT_EQ(m_processResults.size(), 2); // pc and android
  1371. EXPECT_NE(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, m_processResults[1].m_jobEntry.m_platformInfo.m_identifier);
  1372. EXPECT_TRUE((m_processResults[0].m_jobEntry.m_platformInfo.m_identifier == "pc") || (m_processResults[0].m_jobEntry.m_platformInfo.m_identifier == "android"));
  1373. EXPECT_TRUE((m_processResults[1].m_jobEntry.m_platformInfo.m_identifier == "pc") || (m_processResults[1].m_jobEntry.m_platformInfo.m_identifier == "android"));
  1374. mockAppManager.BusDisconnect();
  1375. }
  1376. TEST_F(AssetProcessorManagerUnitTests, QueryAssetStatus_FeedFileToProcess_AssetStatusRetrieved)
  1377. {
  1378. MockApplicationManager mockAppManager;
  1379. mockAppManager.BusConnect();
  1380. AssetRecognizer rec;
  1381. // tiff file recognizer
  1382. rec.m_name = "tiff files";
  1383. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.tiff", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  1384. rec.m_platformSpecs.clear();
  1385. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  1386. rec.m_testLockSource = true;
  1387. m_config.AddRecognizer(rec);
  1388. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1389. QString absolutePath = m_sourceRoot.absoluteFilePath("subfolder2/folder/ship.tiff");
  1390. absolutePath = AssetUtilities::NormalizeFilePath(absolutePath);
  1391. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles({absolutePath});
  1392. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  1393. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1394. AZ::u32 resultIdx = 0;
  1395. for (const JobDetails& processResult : m_processResults)
  1396. {
  1397. ++resultIdx;
  1398. AZStd::string filename = "ship_nrm.dds";
  1399. QString outputFile = (processResult.m_cachePath / filename).AsPosix().c_str();
  1400. CreateDummyFile(outputFile);
  1401. AssetBuilderSDK::ProcessJobResponse jobResponse;
  1402. jobResponse.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1403. jobResponse.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(
  1404. (processResult.m_relativePath / filename).StringAsPosix(), AZ::Uuid::CreateNull(), resultIdx));
  1405. m_assetProcessorManager->AssetProcessed(processResult.m_jobEntry, jobResponse);
  1406. }
  1407. // let events bubble through:
  1408. QCoreApplication::processEvents(QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents, 1000);
  1409. bool foundIt = false;
  1410. auto connectionMade = connect(m_assetProcessorManager.get(), &AssetProcessorManager::SendAssetExistsResponse,
  1411. this, [&foundIt]([[maybe_unused]] NetworkRequestID requestId, bool result)
  1412. {
  1413. foundIt = result;
  1414. });
  1415. const char* successCases[] =
  1416. {
  1417. "ship.tiff", // source
  1418. "ship", // source no extension
  1419. "ship_nrm.dds", // product
  1420. "ship_nrm", // product no extension
  1421. };
  1422. NetworkRequestID requestId(1, 1);
  1423. // Test source without path, should all fail
  1424. for (const auto& testCase : successCases)
  1425. {
  1426. foundIt = false;
  1427. m_assetProcessorManager->OnRequestAssetExists(requestId, "pc", testCase, AZ::Data::AssetId());
  1428. EXPECT_FALSE(foundIt);
  1429. }
  1430. // Test source with the path included
  1431. for (const auto& testCase : successCases)
  1432. {
  1433. foundIt = false;
  1434. AZStd::string withPath = AZStd::string("folder/") + testCase;
  1435. m_assetProcessorManager->OnRequestAssetExists(requestId, "pc", withPath.c_str(), AZ::Data::AssetId());
  1436. EXPECT_TRUE(foundIt);
  1437. }
  1438. const char* failCases[] =
  1439. {
  1440. "folder/ships.tiff",
  1441. "otherfolder/ship.tiff",
  1442. "otherfolder/ship_nrm.dds",
  1443. "folder/ship_random.other/random",
  1444. "folder/ship.dds", // source wrong extension
  1445. "folder/ship_nrm.tiff", // product wrong extension
  1446. "folder/ship_color.dds", // product that doesn't exist
  1447. };
  1448. for (const auto& testCase : failCases)
  1449. {
  1450. foundIt = false;
  1451. m_assetProcessorManager->OnRequestAssetExists(requestId, "pc", testCase, AZ::Data::AssetId());
  1452. EXPECT_FALSE(foundIt);
  1453. }
  1454. mockAppManager.BusDisconnect();
  1455. }
  1456. TEST_F(AssetProcessorManagerUnitTests, RenameFolders_RenameSourceOrCacheFolders_AssetsReprocessedAccordingly)
  1457. {
  1458. MockApplicationManager mockAppManager;
  1459. mockAppManager.BusConnect();
  1460. AssetRecognizer rec;
  1461. const char* builderTxt1Name = "txt files";
  1462. rec.m_name = builderTxt1Name;
  1463. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  1464. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  1465. rec.m_platformSpecs.insert({"android", AssetInternalSpec::Copy});
  1466. m_config.AddRecognizer(rec);
  1467. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1468. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/test\\/.*\\.format", AssetBuilderSDK::AssetBuilderPattern::Regex);
  1469. rec.m_name = "format files that live in a folder called test";
  1470. m_config.AddRecognizer(rec);
  1471. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1472. // Test: Rename a source folder
  1473. QString fileToMove1 = m_sourceRoot.absoluteFilePath("subfolder1/rename_this/somefile1.txt");
  1474. QString fileToMove2 = m_sourceRoot.absoluteFilePath("subfolder1/rename_this/somefolder/somefile2.txt");
  1475. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles({fileToMove1, fileToMove2});
  1476. m_processResults.clear();
  1477. // put the two files on the map:
  1478. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, fileToMove1));
  1479. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, fileToMove2));
  1480. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1481. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1482. EXPECT_EQ(m_processResults.size(), 4); // 2 fils on 2 platforms
  1483. AssetBuilderSDK::ProcessJobResponse response;
  1484. for (int index = 0; index < m_processResults.size(); ++index)
  1485. {
  1486. QFileInfo fi(m_processResults[index].m_jobEntry.GetAbsoluteSourcePath());
  1487. AZStd::string filename = fi.fileName().toUtf8().constData();
  1488. QString pcout = (m_processResults[index].m_cachePath / filename).c_str();
  1489. EXPECT_TRUE(CreateDummyFile(pcout, "products."));
  1490. response.m_outputProducts.clear();
  1491. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1492. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct((m_processResults[index].m_relativePath / filename).StringAsPosix(), AZ::Uuid::CreateNull(), index));
  1493. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[index].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1494. }
  1495. // let events bubble through:
  1496. QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
  1497. // setup complete. now RENAME that folder.
  1498. QDir renamer;
  1499. EXPECT_TRUE(renamer.rename(m_sourceRoot.absoluteFilePath("subfolder1/rename_this"), m_sourceRoot.absoluteFilePath("subfolder1/done_renaming")));
  1500. // renames appear as a delete then add of that folder:
  1501. m_processResults.clear();
  1502. m_assetMessages.clear();
  1503. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, m_sourceRoot.absoluteFilePath("subfolder1/rename_this")));
  1504. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1505. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1506. EXPECT_EQ(m_processResults.size(), 0); // nothing to process
  1507. // we are aware that 4 products went missing (android and pc versions of the 2 files since we renamed the SOURCE folder)
  1508. EXPECT_EQ(m_assetMessages.size(), 4);
  1509. for (auto element : m_assetMessages)
  1510. {
  1511. EXPECT_EQ(element.m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetRemoved);
  1512. }
  1513. m_processResults.clear();
  1514. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, m_sourceRoot.absoluteFilePath("subfolder1/done_renaming")));
  1515. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1516. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1517. EXPECT_EQ(m_processResults.size(), 4); // 2 files on 2 platforms
  1518. // Test: Rename a cache folder
  1519. for (int index = 0; index < m_processResults.size(); ++index)
  1520. {
  1521. QFileInfo fi(m_processResults[index].m_jobEntry.GetAbsoluteSourcePath());
  1522. AZStd::string filename = fi.fileName().toUtf8().constData();
  1523. QString pcout = (m_processResults[index].m_cachePath / filename).c_str();
  1524. EXPECT_TRUE(CreateDummyFile(pcout, "products."));
  1525. response.m_outputProducts.clear();
  1526. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1527. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(
  1528. (m_processResults[index].m_relativePath / filename).StringAsPosix(), AZ::Uuid::CreateNull(), index));
  1529. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[index].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1530. }
  1531. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1532. // it now believes that there are a whole bunch of assets in subfolder1/done_renaming and they resulted in
  1533. // a whole bunch of files to have been created in the asset cache, listed in m_processResults, and they exist in outputscreated...
  1534. // rename the output folder:
  1535. QString originalCacheFolderName = m_cacheRoot.absoluteFilePath("pc") + "/done_renaming";
  1536. QString newCacheFolderName = m_cacheRoot.absoluteFilePath("pc") + "/renamed_again";
  1537. EXPECT_TRUE(renamer.rename(originalCacheFolderName, newCacheFolderName));
  1538. // tell it that the products moved:
  1539. m_processResults.clear();
  1540. m_assetMessages.clear();
  1541. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, originalCacheFolderName));
  1542. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, newCacheFolderName));
  1543. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1544. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1545. // at this point, we should NOT get 2 removed products - we should only get those messages later
  1546. // once the processing queue actually processes these assets - not prematurely as it discovers them missing.
  1547. EXPECT_EQ(m_assetMessages.size(), 0);
  1548. // We've already (above) verified that the product list should be ok, this is just to avoid a crash instead of a failure.
  1549. EXPECT_GT(m_processResults.size(), 1);
  1550. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1551. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1552. // Test: Rename folders that did not have files in them (but had child files, this was a bug at a point)
  1553. fileToMove1 = m_sourceRoot.absoluteFilePath("subfolder1/rename_this_secondly/somefolder/somefile2.txt");
  1554. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles({fileToMove1});
  1555. m_processResults.clear();
  1556. // put the two files on the map:
  1557. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, fileToMove1));
  1558. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1559. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1560. EXPECT_EQ(m_processResults.size(), 2); // 1 file on 2 platforms
  1561. for (int index = 0; index < m_processResults.size(); ++index)
  1562. {
  1563. QFileInfo fi(m_processResults[index].m_jobEntry.GetAbsoluteSourcePath());
  1564. AZStd::string filename = fi.fileName().toUtf8().constData();
  1565. QString pcout = (m_processResults[index].m_cachePath / filename).c_str();
  1566. EXPECT_TRUE(CreateDummyFile(pcout, "products."));
  1567. response.m_outputProducts.clear();
  1568. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1569. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(
  1570. (m_processResults[index].m_relativePath / filename).StringAsPosix(), AZ::Uuid::CreateNull(), index));
  1571. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[index].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1572. }
  1573. // let events bubble through:
  1574. QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
  1575. QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
  1576. // setup complete. now RENAME that folder.
  1577. originalCacheFolderName = m_cacheRoot.absoluteFilePath("pc") + "/rename_this_secondly";
  1578. newCacheFolderName = m_cacheRoot.absoluteFilePath("pc") + "/done_renaming_again";
  1579. EXPECT_TRUE(renamer.rename(originalCacheFolderName, newCacheFolderName));
  1580. // tell it that the products moved:
  1581. m_processResults.clear();
  1582. m_assetMessages.clear();
  1583. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, originalCacheFolderName));
  1584. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, newCacheFolderName));
  1585. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1586. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1587. EXPECT_EQ(m_assetMessages.size(), 0); // we don't prematurely emit "AssetRemoved" until we actually finish process.
  1588. EXPECT_EQ(m_processResults.size(), 1); // ONLY the PC files need to be re-processed because only those were renamed.
  1589. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1590. mockAppManager.BusDisconnect();
  1591. }
  1592. TEST_F(AssetProcessorManagerUnitTests, DeleteSource_RemoveFileAfterProcessing_ProductDeleted)
  1593. {
  1594. MockApplicationManager mockAppManager;
  1595. mockAppManager.BusConnect();
  1596. AssetRecognizer rec;
  1597. const char* builderTxt1Name = "txt files";
  1598. rec.m_name = builderTxt1Name;
  1599. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  1600. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  1601. rec.m_platformSpecs.insert({"android", AssetInternalSpec::Copy});
  1602. m_config.AddRecognizer(rec);
  1603. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1604. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/test\\/.*\\.format", AssetBuilderSDK::AssetBuilderPattern::Regex);
  1605. rec.m_name = "format files that live in a folder called test";
  1606. m_config.AddRecognizer(rec);
  1607. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1608. // first, set up a whole pipeline to create, notify, and consume the file:
  1609. QString fileToMove1 = m_sourceRoot.absoluteFilePath("subfolder1/to_be_deleted/some_deleted_file.txt");
  1610. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles({fileToMove1});
  1611. // put the two files on the map:
  1612. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, fileToMove1));
  1613. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1614. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1615. EXPECT_EQ(m_processResults.size(), 2); // 1 file on 2 platforms
  1616. QStringList createdDummyFiles;
  1617. AssetBuilderSDK::ProcessJobResponse response;
  1618. for (int index = 0; index < m_processResults.size(); ++index)
  1619. {
  1620. QFileInfo fi(m_processResults[index].m_jobEntry.GetAbsoluteSourcePath());
  1621. AZStd::string filename = fi.fileName().toUtf8().constData();
  1622. QString pcout = (m_processResults[index].m_cachePath / filename).c_str();
  1623. EXPECT_TRUE(CreateDummyFile(pcout, "products."));
  1624. response.m_outputProducts.clear();
  1625. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1626. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(
  1627. (m_processResults[index].m_relativePath / filename).StringAsPosix(), AZ::Uuid::CreateNull(), index));
  1628. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[index].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1629. }
  1630. // let events bubble through:
  1631. QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
  1632. m_processResults.clear();
  1633. m_assetMessages.clear();
  1634. // setup complete. now delete the source file:
  1635. QDir renamer;
  1636. EXPECT_TRUE(renamer.remove(fileToMove1));
  1637. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, fileToMove1));
  1638. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1639. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1640. EXPECT_EQ(m_assetMessages.size(), 2); // all products must be removed
  1641. EXPECT_EQ(m_processResults.size(), 0); // nothing should process
  1642. for (int index = 0; index < createdDummyFiles.size(); ++index)
  1643. {
  1644. QFileInfo fi(createdDummyFiles[index]);
  1645. EXPECT_FALSE(fi.exists());
  1646. // in fact, the directory must also no longer exist in the cache:
  1647. EXPECT_FALSE(fi.dir().exists());
  1648. }
  1649. mockAppManager.BusDisconnect();
  1650. }
  1651. TEST_F(AssetProcessorManagerUnitTests, ReprocessSource_ModifyFileAfterProcessing_ProductsRegenerated)
  1652. {
  1653. // --------------------------------------------------------------------------------------------------
  1654. // - TEST SOURCE FILE REPROCESSING RESULTING IN FEWER PRODUCTS NEXT TIME ----------------------------
  1655. // (it needs to delete the products and it needs to notify listeners about it)
  1656. // --------------------------------------------------------------------------------------------------
  1657. MockApplicationManager mockAppManager;
  1658. mockAppManager.BusConnect();
  1659. AssetRecognizer rec;
  1660. const char* builderTxt1Name = "txt files";
  1661. rec.m_name = builderTxt1Name;
  1662. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  1663. rec.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  1664. rec.m_platformSpecs.insert({"android", AssetInternalSpec::Copy});
  1665. m_config.AddRecognizer(rec);
  1666. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1667. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/test\\/.*\\.format", AssetBuilderSDK::AssetBuilderPattern::Regex);
  1668. rec.m_name = "format files that live in a folder called test";
  1669. m_config.AddRecognizer(rec);
  1670. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  1671. // first, set up a whole pipeline to create, notify, and consume the file:
  1672. QString fileToMove1 = m_sourceRoot.absoluteFilePath("subfolder1/fewer_products/test.txt");
  1673. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles({fileToMove1});
  1674. m_processResults.clear();
  1675. // put the two files on the map:
  1676. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, fileToMove1));
  1677. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1678. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1679. EXPECT_EQ(m_processResults.size(), 2); // 1 file on 2 platforms
  1680. QStringList createdDummyFiles; // keep track of the files which we expect to be gone next time
  1681. AssetBuilderSDK::ProcessJobResponse response;
  1682. for (int index = 0; index < m_processResults.size(); ++index)
  1683. {
  1684. response.m_outputProducts.clear();
  1685. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1686. // this time, ouput 2 files for each job instead of just one:
  1687. QFileInfo fi(m_processResults[index].m_jobEntry.GetAbsoluteSourcePath());
  1688. AZStd::string filename0 = (fi.fileName() + ".0.txt").toUtf8().constData();
  1689. AZStd::string filename1 = (fi.fileName() + ".1.txt").toUtf8().constData();
  1690. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(
  1691. (m_processResults[index].m_relativePath / filename0).StringAsPosix(), AZ::Uuid::CreateNull(), index));
  1692. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(
  1693. (m_processResults[index].m_relativePath / filename1).StringAsPosix(), AZ::Uuid::CreateNull(), index + 100));
  1694. createdDummyFiles.push_back((m_processResults[index].m_cachePath / filename0).c_str()); // we're only gong to delete this one out of the two, which is why we don't push the other one.
  1695. EXPECT_TRUE(CreateDummyFile((m_processResults[index].m_cachePath / filename0).c_str(), "product 0"));
  1696. EXPECT_TRUE(CreateDummyFile((m_processResults[index].m_cachePath / filename1).c_str(), "product 1"));
  1697. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[index].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1698. }
  1699. // let events bubble through:
  1700. QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
  1701. // at this point, we have a cache with the four files (2 for each platform)
  1702. // we're going to resubmit the job with different data
  1703. QDir renamer;
  1704. EXPECT_TRUE(renamer.remove(fileToMove1));
  1705. EXPECT_TRUE(CreateDummyFile(fileToMove1, "fresh data!"));
  1706. m_processResults.clear();
  1707. // tell file changed:
  1708. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, fileToMove1));
  1709. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1710. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1711. EXPECT_EQ(m_processResults.size(), 2); // 1 file on 2 platforms
  1712. m_assetMessages.clear();
  1713. for (int index = 0; index < m_processResults.size(); ++index)
  1714. {
  1715. response.m_outputProducts.clear();
  1716. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1717. // this time, ouput only one file for each job instead of just one:
  1718. QFileInfo fi(m_processResults[index].m_jobEntry.GetAbsoluteSourcePath());
  1719. AZStd::string filename = (fi.fileName() + ".1.txt").toUtf8().constData();
  1720. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(
  1721. (m_processResults[index].m_relativePath / filename).StringAsPosix(), AZ::Uuid::CreateNull(), index));
  1722. EXPECT_TRUE(CreateDummyFile((m_processResults[index].m_cachePath / filename).c_str(), "product 1 changed"));
  1723. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[index].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1724. }
  1725. // let events bubble through:
  1726. QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
  1727. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1728. // we should have gotten 2 product removed, 2 product changed, total of 4 asset messages
  1729. EXPECT_EQ(m_assetMessages.size(), 4);
  1730. EXPECT_NE(m_assetMessages[0].m_assetId, AZ::Data::AssetId());
  1731. EXPECT_NE(m_assetMessages[1].m_assetId, AZ::Data::AssetId());
  1732. EXPECT_NE(m_assetMessages[2].m_assetId, AZ::Data::AssetId());
  1733. EXPECT_NE(m_assetMessages[3].m_assetId, AZ::Data::AssetId());
  1734. EXPECT_EQ(m_assetMessages[0].m_platform, "android");
  1735. EXPECT_EQ(m_assetMessages[1].m_platform, "android");
  1736. EXPECT_EQ(m_assetMessages[2].m_platform, "pc");
  1737. EXPECT_EQ(m_assetMessages[3].m_platform, "pc");
  1738. EXPECT_EQ(m_assetMessages[0].m_data, "fewer_products/test.txt.0.txt");
  1739. EXPECT_EQ(m_assetMessages[1].m_data, "fewer_products/test.txt.1.txt");
  1740. EXPECT_EQ(m_assetMessages[2].m_data, "fewer_products/test.txt.0.txt");
  1741. EXPECT_EQ(m_assetMessages[3].m_data, "fewer_products/test.txt.1.txt");
  1742. EXPECT_EQ(m_assetMessages[0].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetRemoved);
  1743. EXPECT_EQ(m_assetMessages[1].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetChanged);
  1744. EXPECT_EQ(m_assetMessages[2].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetRemoved);
  1745. EXPECT_EQ(m_assetMessages[3].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetChanged);
  1746. // and finally, the actual removed products should be gone from the HDD:
  1747. for (int index = 0; index < createdDummyFiles.size(); ++index)
  1748. {
  1749. QFileInfo fi(createdDummyFiles[index]);
  1750. EXPECT_FALSE(fi.exists());
  1751. // the directory must still exist because there were other files in there (no accidental deletions!)
  1752. EXPECT_TRUE(fi.dir().exists());
  1753. }
  1754. mockAppManager.BusDisconnect();
  1755. }
  1756. TEST_F(AssetProcessorManagerUnitTests, ValidateAssetBuilder_FeedFileToProcess_ProductsGenerated)
  1757. {
  1758. MockApplicationManager mockAppManager;
  1759. mockAppManager.BusConnect();
  1760. AssetRecognizer abt_rec1;
  1761. abt_rec1.m_name = "UnitTestTextBuilder1";
  1762. abt_rec1.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  1763. abt_rec1.m_platformSpecs.insert({"android", AssetInternalSpec::Copy});
  1764. mockAppManager.RegisterAssetRecognizerAsBuilder(abt_rec1);
  1765. AssetRecognizer abt_rec2;
  1766. abt_rec2.m_name = "UnitTestTextBuilder2";
  1767. abt_rec2.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  1768. abt_rec2.m_platformSpecs.insert({"pc", AssetInternalSpec::Copy});
  1769. mockAppManager.RegisterAssetRecognizerAsBuilder(abt_rec2);
  1770. m_processResults.clear();
  1771. QString absolutePath = AssetUtilities::NormalizeFilePath(m_sourceRoot.absoluteFilePath("subfolder3/uniquefile.txt"));
  1772. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles({absolutePath});
  1773. // Pass the txt file through the asset pipeline
  1774. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
  1775. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1776. EXPECT_EQ(mockAppManager.GetMatchingBuildersInfoFunctionCalls(), 1);
  1777. EXPECT_EQ(mockAppManager.GetMockBuilderCreateJobCalls(), 2); // Since we have two text builder registered
  1778. AssetProcessor::BuilderInfoList builderInfoList;
  1779. mockAppManager.GetMatchingBuildersInfo(AZStd::string(absolutePath.toUtf8().constData()), builderInfoList);
  1780. auto builderInfoListCount = builderInfoList.size();
  1781. EXPECT_EQ(builderInfoListCount, 2);
  1782. for (auto& buildInfo : builderInfoList)
  1783. {
  1784. AZStd::shared_ptr<InternalMockBuilder> builder;
  1785. EXPECT_TRUE(mockAppManager.GetBuilderByID(buildInfo.m_name, builder));
  1786. EXPECT_EQ(builder->GetCreateJobCalls(), 1);
  1787. // note, uuid does not include watch folder name. This is a quick test to make sure that the source file UUID actually makes it into the CreateJobRequest.
  1788. // the ProcessJobRequest is populated frmo the CreateJobRequest.
  1789. EXPECT_EQ(builder->GetLastCreateJobRequest().m_sourceFileUUID, AssetUtilities::GetSourceUuid(SourceAssetReference(absolutePath)).GetValueOr(AZ::Uuid()));
  1790. QString watchedFolder(AssetUtilities::NormalizeFilePath(builder->GetLastCreateJobRequest().m_watchFolder.c_str()));
  1791. QString expectedWatchedFolder(m_sourceRoot.absoluteFilePath("subfolder3"));
  1792. EXPECT_EQ(QString::compare(watchedFolder, expectedWatchedFolder, Qt::CaseInsensitive), 0); // verify watchfolder
  1793. QString filename(AssetUtilities::NormalizeFilePath(builder->GetLastCreateJobRequest().m_sourceFile.c_str()));
  1794. QString expectFileName("uniquefile.txt");
  1795. EXPECT_EQ(QString::compare(filename, expectFileName, Qt::CaseInsensitive), 0); // verify filename
  1796. builder->ResetCounters();
  1797. }
  1798. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1799. EXPECT_EQ(m_processResults.size(), 2); // 1 for pc and android
  1800. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  1801. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1802. EXPECT_EQ(QString::compare(m_processResults[0].m_jobEntry.GetAbsoluteSourcePath(), absolutePath, Qt::CaseInsensitive), 0);
  1803. EXPECT_EQ(QString::compare(m_processResults[1].m_jobEntry.GetAbsoluteSourcePath(), absolutePath, Qt::CaseInsensitive), 0);
  1804. EXPECT_EQ(QString::compare(QString(m_processResults[0].m_jobEntry.m_jobKey), QString(abt_rec1.m_name.c_str())), 0);
  1805. EXPECT_EQ(QString::compare(QString(m_processResults[1].m_jobEntry.m_jobKey), QString(abt_rec2.m_name.c_str())), 0);
  1806. mockAppManager.BusDisconnect();
  1807. }
  1808. TEST_F(AssetProcessorManagerUnitTests, ValidateJobsWithDifferentKeys_FeedFileToProcess_GetJobsToProcess)
  1809. {
  1810. // Test Strategy
  1811. // Tell the mock builder to create two jobs for the same source file and platform but having different job keys.
  1812. // Feed the source file to the asset pipeline and ensure we get two jobs to be processed.
  1813. // Register products for those jobs in the asset database.
  1814. // Delete all products for one of those jobs and feed the source file to the asset pipeline, ensure that we get only one job to be processed.
  1815. // Tell the mock builder to create one job now for the same source file and platform.
  1816. // Feed the source file to the asset pipeline and ensure that we do not get any new jobs to be processed and also ensure that all the products of the missing jobs are deleted from disk.
  1817. // Tell the mock builder to create two jobs again for the same source file and platform but having different job keys.
  1818. // Feed the source file to the asset pipeline and ensure that we do get a new job to be process this time.
  1819. // attach a file monitor to ensure this occurs.
  1820. MockAssetBuilderInfoHandler mockAssetBuilderInfoHandler;
  1821. QString sourceFile = m_sourceRoot.absoluteFilePath("subfolder1/basefile.foo");
  1822. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles({ sourceFile });
  1823. mockAssetBuilderInfoHandler.m_numberOfJobsToCreate = 2; //Create two jobs for this file
  1824. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFile));
  1825. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1826. // block until no more events trickle in:
  1827. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1828. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1829. EXPECT_EQ(m_processResults.size(), 2);
  1830. for (int idx = 0; idx < m_processResults.size(); idx++)
  1831. {
  1832. EXPECT_EQ(m_processResults[idx].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1833. EXPECT_TRUE(m_processResults[idx].m_jobEntry.m_sourceAssetReference.RelativePath().Native().starts_with("basefile.foo"));
  1834. }
  1835. EXPECT_NE(m_processResults[0].m_jobEntry.m_jobKey.compare(m_processResults[1].m_jobEntry.m_jobKey), 0);
  1836. QStringList pcouts;
  1837. pcouts.push_back(m_cacheRoot.filePath(QString("pc/basefile.arc1")));
  1838. pcouts.push_back(m_cacheRoot.filePath(QString("pc/basefile.arc2")));
  1839. // Create the product files for the first job
  1840. EXPECT_TRUE(CreateDummyFile(pcouts[0], "product1"));
  1841. EXPECT_TRUE(CreateDummyFile(pcouts[1], "product2"));
  1842. // Invoke Asset Processed for pc platform for the first job
  1843. AssetBuilderSDK::ProcessJobResponse response;
  1844. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  1845. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("basefile.arc1", AZ::Uuid::CreateNull(), 1));
  1846. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("basefile.arc2", AZ::Uuid::CreateNull(), 2));
  1847. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1848. // let events bubble through:
  1849. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1850. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1851. EXPECT_EQ(m_assetMessages.size(), 2);
  1852. EXPECT_EQ(m_changedInputResults.size(), 1);
  1853. EXPECT_EQ(m_assetMessages[0].m_platform, "pc");
  1854. EXPECT_EQ(m_assetMessages[1].m_platform, "pc");
  1855. EXPECT_EQ(m_assetMessages[0].m_data, "basefile.arc1");
  1856. EXPECT_EQ(m_assetMessages[1].m_data, "basefile.arc2");
  1857. EXPECT_EQ(m_assetMessages[0].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetChanged);
  1858. EXPECT_EQ(m_assetMessages[1].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetChanged);
  1859. EXPECT_EQ(AssetUtilities::NormalizeFilePath(m_changedInputResults[0].first), AssetUtilities::NormalizeFilePath(sourceFile));
  1860. pcouts.clear();
  1861. pcouts.push_back(m_cacheRoot.filePath(QString("pc/basefile.arc3")));
  1862. // Create the product files for the second job
  1863. EXPECT_TRUE(CreateDummyFile(pcouts[0], "product1"));
  1864. // Invoke Asset Processed for pc platform for the second job
  1865. response.m_outputProducts.clear();
  1866. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("basefile.arc3"));
  1867. m_assetMessages.clear();
  1868. m_changedInputResults.clear();
  1869. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[1].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  1870. // let events bubble through:
  1871. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1872. EXPECT_EQ(m_assetMessages.size(), 1);
  1873. EXPECT_EQ(m_changedInputResults.size(), 1);
  1874. EXPECT_EQ(m_assetMessages[0].m_platform, "pc");
  1875. EXPECT_EQ(m_assetMessages[0].m_data, "basefile.arc3");
  1876. EXPECT_EQ(m_assetMessages[0].m_type, AzFramework::AssetSystem::AssetNotificationMessage::AssetChanged);
  1877. EXPECT_EQ(AssetUtilities::NormalizeFilePath(m_changedInputResults[0].first), AssetUtilities::NormalizeFilePath(sourceFile));
  1878. //Delete the product of the second job
  1879. EXPECT_TRUE(QFile::remove(pcouts[0]));
  1880. m_processResults.clear();
  1881. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFile));
  1882. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1883. // block until no more events trickle in:
  1884. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1885. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  1886. EXPECT_EQ(m_processResults.size(), 1); // We should only have one job to process here
  1887. for (int idx = 0; idx < m_processResults.size(); idx++)
  1888. {
  1889. EXPECT_EQ(m_processResults[idx].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1890. EXPECT_TRUE(m_processResults[idx].m_jobEntry.m_sourceAssetReference.RelativePath().Native().starts_with("basefile.foo"));
  1891. }
  1892. mockAssetBuilderInfoHandler.m_numberOfJobsToCreate = 1; //Create one job for this file this time
  1893. m_processResults.clear();
  1894. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFile));
  1895. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1896. // block until no more events trickle in:
  1897. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1898. EXPECT_EQ(m_processResults.size(), 0); // We should not have any job to process here
  1899. // products of the second job should not exists any longer
  1900. for (QString outFile : pcouts)
  1901. {
  1902. EXPECT_FALSE(QFile::exists(pcouts[0]));
  1903. }
  1904. mockAssetBuilderInfoHandler.m_numberOfJobsToCreate = 2; //Again create two jobs for this file, this should result in one additional job
  1905. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFile));
  1906. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  1907. // block until no more events trickle in:
  1908. QCoreApplication::processEvents(QEventLoop::AllEvents);
  1909. EXPECT_EQ(m_processResults.size(), 1); // We should see a job to process here
  1910. for (int idx = 0; idx < m_processResults.size(); idx++)
  1911. {
  1912. EXPECT_EQ(m_processResults[idx].m_jobEntry.m_platformInfo.m_identifier, "pc");
  1913. EXPECT_TRUE(m_processResults[idx].m_jobEntry.m_sourceAssetReference.RelativePath().Native().starts_with("basefile.foo"));
  1914. }
  1915. mockAssetBuilderInfoHandler.BusDisconnect();
  1916. }
  1917. TEST_F(AssetProcessorManagerUnitTests, ValidateScanFolders_ModifyPortableKeys_GetCorrectScanFolderIds)
  1918. {
  1919. using namespace AzToolsFramework::AssetDatabase;
  1920. ScanFolderDatabaseEntryContainer entryContainer;
  1921. auto puller = [&entryContainer](ScanFolderDatabaseEntry& entry)
  1922. {
  1923. entryContainer.push_back(entry);
  1924. return true;
  1925. };
  1926. {
  1927. AssetDatabaseConnection connection;
  1928. EXPECT_TRUE(connection.OpenDatabase());
  1929. // make sure we find the scan folders.
  1930. entryContainer.clear();
  1931. connection.QueryScanFoldersTable(puller);
  1932. EXPECT_EQ(m_config.GetScanFolderCount(), entryContainer.size());
  1933. // make sure they are all present and have port key:
  1934. for (int idx = 0; idx < m_config.GetScanFolderCount(); ++idx)
  1935. {
  1936. AssetProcessor::ScanFolderInfo& scanFolderInConfig = m_config.GetScanFolderAt(idx);
  1937. auto found = AZStd::find_if(entryContainer.begin(), entryContainer.end(), [&scanFolderInConfig](const ScanFolderDatabaseEntry& target)
  1938. {
  1939. return (
  1940. (target.m_scanFolderID == scanFolderInConfig.ScanFolderID()) &&
  1941. (scanFolderInConfig.GetPortableKey() == target.m_portableKey.c_str()) &&
  1942. (scanFolderInConfig.ScanPath() == target.m_scanFolder.c_str()) &&
  1943. (scanFolderInConfig.GetDisplayName() == target.m_displayName.c_str())
  1944. );
  1945. }
  1946. );
  1947. EXPECT_NE(found, entryContainer.end());
  1948. }
  1949. }
  1950. // now make a different config with different scan folders but with some of the same portable keys but new paths.
  1951. PlatformConfiguration config2;
  1952. AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms2;
  1953. config2.PopulatePlatformsForScanFolder(platforms2);
  1954. // PATH DisplayName PortKey outputfolder root recurse platforms order
  1955. // case 1: same absolute path, but the same portable key - should use same ID as before.
  1956. config2.AddScanFolder(ScanFolderInfo(m_sourceRoot.filePath("subfolder4"), "subfolder4", "subfolder4", false, false, platforms2, -6)); // subfolder 4 overrides subfolder3
  1957. // case 2: A new absolute path, but same portable key - should use same id as before
  1958. config2.AddScanFolder(ScanFolderInfo(m_sourceRoot.filePath("newfolder3"), "subfolder3", "subfolder3", false, false, platforms2, -5)); // subfolder 3 overrides subfolder2
  1959. // case 3: same absolute path, new portable key - should use a new ID
  1960. config2.AddScanFolder(ScanFolderInfo(m_sourceRoot.filePath("subfolder1"), "subfolder3", "newfolder3", false, false, platforms2, -5)); // subfolder 3 overrides subfolder2
  1961. // case 4: subfolder2 is missing - it should be gone.
  1962. {
  1963. // create this, which will write those scan folders into the db as-is
  1964. AssetProcessorManagerUnit_Test apm(&config2);
  1965. apm.CheckMissingFiles();
  1966. }
  1967. {
  1968. AssetDatabaseConnection connection;
  1969. EXPECT_TRUE(connection.OpenDatabase());
  1970. // make sure we find the scan folders.
  1971. entryContainer.clear();
  1972. connection.QueryScanFoldersTable(puller);
  1973. EXPECT_EQ(config2.GetScanFolderCount(), entryContainer.size());
  1974. // make sure they are all present and have port key:
  1975. for (int idx = 0; idx < config2.GetScanFolderCount(); ++idx)
  1976. {
  1977. AssetProcessor::ScanFolderInfo& scanFolderInConfig = config2.GetScanFolderAt(idx);
  1978. auto found = AZStd::find_if(entryContainer.begin(), entryContainer.end(), [&scanFolderInConfig](const ScanFolderDatabaseEntry& target)
  1979. {
  1980. return (
  1981. (target.m_scanFolderID == scanFolderInConfig.ScanFolderID()) &&
  1982. (scanFolderInConfig.GetPortableKey() == target.m_portableKey.c_str()) &&
  1983. (scanFolderInConfig.ScanPath() == target.m_scanFolder.c_str()) &&
  1984. (scanFolderInConfig.GetDisplayName() == target.m_displayName.c_str())
  1985. );
  1986. }
  1987. );
  1988. EXPECT_NE(found, entryContainer.end());
  1989. }
  1990. }
  1991. const AssetProcessor::ScanFolderInfo* subfolder4InConfig1 = nullptr;
  1992. const AssetProcessor::ScanFolderInfo* subfolder4InConfig2 = nullptr;
  1993. const AssetProcessor::ScanFolderInfo* subfolder3InConfig1 = nullptr;
  1994. const AssetProcessor::ScanFolderInfo* subfolder3InConfig2 = nullptr;
  1995. AZStd::unordered_set<AZ::s64> idsInConfig1;
  1996. for (int idx = 0; idx < m_config.GetScanFolderCount(); ++idx)
  1997. {
  1998. AssetProcessor::ScanFolderInfo& scanFolderInConfig = m_config.GetScanFolderAt(idx);
  1999. idsInConfig1.insert(scanFolderInConfig.ScanFolderID());
  2000. if (scanFolderInConfig.GetPortableKey() == "subfolder4")
  2001. {
  2002. subfolder4InConfig1 = &scanFolderInConfig;
  2003. }
  2004. if (scanFolderInConfig.GetPortableKey() == "subfolder3")
  2005. {
  2006. subfolder3InConfig1 = &scanFolderInConfig;
  2007. }
  2008. }
  2009. for (int idx = 0; idx < config2.GetScanFolderCount(); ++idx)
  2010. {
  2011. AssetProcessor::ScanFolderInfo& scanFolderInConfig = m_config.GetScanFolderAt(idx);
  2012. if (scanFolderInConfig.GetPortableKey() == "subfolder4")
  2013. {
  2014. subfolder4InConfig2 = &scanFolderInConfig;
  2015. }
  2016. if (scanFolderInConfig.GetPortableKey() == "subfolder3")
  2017. {
  2018. subfolder3InConfig2 = &scanFolderInConfig;
  2019. }
  2020. if (scanFolderInConfig.GetPortableKey() == "newfolder3")
  2021. {
  2022. // it must be a new ID, so it can't reuse any ids.
  2023. EXPECT_EQ(idsInConfig1.find(scanFolderInConfig.ScanFolderID()), idsInConfig1.end()); // must not be found
  2024. }
  2025. }
  2026. EXPECT_TRUE(subfolder3InConfig2);
  2027. EXPECT_TRUE(subfolder3InConfig1);
  2028. EXPECT_TRUE(subfolder4InConfig2);
  2029. EXPECT_TRUE(subfolder4InConfig1);
  2030. // the above scan folders should not have changed id
  2031. EXPECT_EQ(subfolder3InConfig1->ScanFolderID(), subfolder3InConfig2->ScanFolderID());
  2032. EXPECT_EQ(subfolder4InConfig1->ScanFolderID(), subfolder4InConfig2->ScanFolderID());
  2033. }
  2034. TEST_F(AssetProcessorManagerUnitTests, ValidateJobDependencies_FeedHierarchyOfFiles_JobsProcessedInOrder)
  2035. {
  2036. // in this test, we create a hierarchy of files
  2037. // where Job C depends on job B, which depends on job A.
  2038. // if all three are in the queue, then Job A should be thus the first to be allowed to proceed.
  2039. using namespace AzToolsFramework::AssetDatabase;
  2040. AZ::Uuid builderUuid = AZ::Uuid::CreateString("{3A1E7DE0-3E89-4F52-8B2D-B822D137D4F0}");
  2041. AZ::Uuid sourceFileBUuid;
  2042. bool fileBJobDependentOnFileAJob = false;
  2043. bool changeJobAFingerprint = false;
  2044. bool fileCJobDependentOnFileBJob = false;
  2045. AssetProcessorManagerUnitTestUtils::MockAssetBuilderInfoHandler assetBuilderInfoHandler;
  2046. assetBuilderInfoHandler.m_assetBuilderDesc.m_name = "Job Dependency UnitTest";
  2047. assetBuilderInfoHandler.m_assetBuilderDesc.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
  2048. assetBuilderInfoHandler.m_assetBuilderDesc.m_busId = builderUuid;
  2049. assetBuilderInfoHandler.m_assetBuilderDesc.m_analysisFingerprint = "xyz"; // Normally this would include the same fingerprint info from the job but for the purposes of testing, we just need something here
  2050. assetBuilderInfoHandler.m_assetBuilderDesc.m_createJobFunction = [&fileBJobDependentOnFileAJob, &changeJobAFingerprint, &fileCJobDependentOnFileBJob, &sourceFileBUuid]
  2051. (const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
  2052. {
  2053. for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
  2054. {
  2055. AssetBuilderSDK::JobDescriptor descriptor;
  2056. descriptor.m_jobKey = "xxx";
  2057. descriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
  2058. AssetBuilderSDK::SourceFileDependency sourceFileDependency;
  2059. QString sourceFile(request.m_sourceFile.c_str());
  2060. // if we are analyzing job B...
  2061. if (fileBJobDependentOnFileAJob && sourceFile.endsWith("FileB.txt"))
  2062. {
  2063. AssetBuilderSDK::JobDescriptor secondDescriptor = descriptor;
  2064. secondDescriptor.m_jobKey = "yyy";
  2065. #if defined(AZ_PLATFORM_WINDOWS)
  2066. sourceFileDependency.m_sourceFileDependencyPath = "some\\random/Folders/FILEa.TxT";
  2067. #else
  2068. sourceFileDependency.m_sourceFileDependencyPath = "some/random/folders/FileA.txt";
  2069. #endif // defined(AZ_PLATFORM_WINDOWS)
  2070. // ... declare a job dependency on job A ('FileA.txt', 'xxx', platform)
  2071. AssetBuilderSDK::JobDependency jobDependency("xxx", platformInfo.m_identifier.c_str(), AssetBuilderSDK::JobDependencyType::Fingerprint, sourceFileDependency);
  2072. secondDescriptor.m_jobDependencyList.push_back(jobDependency);
  2073. response.m_createJobOutputs.push_back(secondDescriptor);
  2074. }
  2075. else if (changeJobAFingerprint && sourceFile.endsWith("FileA.txt"))
  2076. {
  2077. // if we are analyzing job A...
  2078. descriptor.m_additionalFingerprintInfo = "data";
  2079. }
  2080. else if (fileCJobDependentOnFileBJob && sourceFile.endsWith("FileC.txt"))
  2081. {
  2082. // if we are analyzing job C...
  2083. AssetBuilderSDK::JobDescriptor secondDescriptor = descriptor;
  2084. secondDescriptor.m_jobKey = "zzz";
  2085. sourceFileDependency.m_sourceFileDependencyUUID = sourceFileBUuid;
  2086. // ... declare a job dependency on job B ('FileB.txt', 'yyy', platform)
  2087. AssetBuilderSDK::JobDependency jobDependency("yyy", platformInfo.m_identifier.c_str(), AssetBuilderSDK::JobDependencyType::Fingerprint, sourceFileDependency);
  2088. secondDescriptor.m_jobDependencyList.push_back(jobDependency);
  2089. response.m_createJobOutputs.push_back(secondDescriptor);
  2090. }
  2091. response.m_createJobOutputs.push_back(descriptor);
  2092. }
  2093. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  2094. };
  2095. assetBuilderInfoHandler.BusConnect();
  2096. QString sourceFileAPath = m_sourceRoot.absoluteFilePath("subfolder1/some/random/folders/FileA.txt");
  2097. QString sourceFileBPath = m_sourceRoot.absoluteFilePath("subfolder1/FileB.txt");
  2098. QString sourceFileCPath = m_sourceRoot.absoluteFilePath("FileC.txt");
  2099. EXPECT_TRUE(CreateDummyFile(sourceFileAPath, ""));
  2100. EXPECT_TRUE(CreateDummyFile(sourceFileBPath, ""));
  2101. EXPECT_TRUE(CreateDummyFile(sourceFileCPath, ""));
  2102. sourceFileBUuid = AssetUtilities::GetSourceUuid(SourceAssetReference(sourceFileBPath)).GetValueOr(AZ::Uuid());
  2103. EXPECT_FALSE(sourceFileBUuid.IsNull());
  2104. constexpr const char* productFileAFilename = "fileaproduct.txt";
  2105. constexpr const char* productFileBFilename = "filebproduct1.txt";
  2106. constexpr const char* product2FileBFilename = "filebproduct2.txt";
  2107. constexpr const char* productFileCFilename = "filecproduct.txt";
  2108. constexpr const char* product2FileCFilename = "filecproduct2.txt";
  2109. QString productFileAPath = m_cacheRoot.filePath(QString("pc/") + productFileAFilename);
  2110. QString productFileBPath = m_cacheRoot.filePath(QString("pc/") + productFileBFilename);
  2111. QString product2FileBPath = m_cacheRoot.filePath(QString("pc/") + product2FileBFilename);
  2112. QString productFileCPath = m_cacheRoot.filePath(QString("pc/") + productFileCFilename);
  2113. QString product2FileCPath = m_cacheRoot.filePath(QString("pc/") + product2FileCFilename);
  2114. EXPECT_TRUE(CreateDummyFile(productFileAPath, "product"));
  2115. EXPECT_TRUE(CreateDummyFile(productFileBPath, "product"));
  2116. EXPECT_TRUE(CreateDummyFile(product2FileBPath, "product"));
  2117. EXPECT_TRUE(CreateDummyFile(productFileCPath, "product"));
  2118. EXPECT_TRUE(CreateDummyFile(product2FileCPath, "product"));
  2119. AZStd::string cacheWithPlatform = m_cacheRoot.absoluteFilePath("pc").toUtf8().constData();
  2120. m_config.EnablePlatform({ "android",{ "mobile", "renderer" } }, false);
  2121. // Analyze FileA
  2122. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileAPath));
  2123. EXPECT_TRUE(BlockUntil(m_idling, 500000));
  2124. EXPECT_EQ(m_processResults.size(), 1);
  2125. EXPECT_FALSE(m_processResults[0].m_jobDependencyList.size());
  2126. // Invoke Asset Processed for pc platform for the FileA job
  2127. AssetBuilderSDK::ProcessJobResponse response;
  2128. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  2129. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(productFileAFilename));
  2130. response.m_outputProducts.back().m_outputPathOverride = cacheWithPlatform;
  2131. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  2132. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2133. m_processResults.clear();
  2134. response.m_outputProducts.clear();
  2135. // Analyze FileB, one of the jobs should declare a job dependency on the FileA job
  2136. fileBJobDependentOnFileAJob = true;
  2137. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileBPath));
  2138. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2139. EXPECT_EQ(m_processResults.size(), 2);
  2140. bool onlyOneJobHaveJobDependency = false;
  2141. for (JobDetails& jobDetail : m_processResults)
  2142. {
  2143. if (jobDetail.m_jobDependencyList.size())
  2144. {
  2145. EXPECT_FALSE(onlyOneJobHaveJobDependency);
  2146. onlyOneJobHaveJobDependency = true;
  2147. EXPECT_EQ(jobDetail.m_jobDependencyList.size(), 1);
  2148. JobDependencyInternal& jobDependencyInternal = jobDetail.m_jobDependencyList[0];
  2149. EXPECT_NE(jobDependencyInternal.m_builderUuidList.find(builderUuid), jobDependencyInternal.m_builderUuidList.end());
  2150. EXPECT_TRUE(QString(jobDependencyInternal.m_jobDependency.m_sourceFile.m_sourceFileDependencyPath.c_str()).endsWith("FileA.txt", Qt::CaseSensitivity::CaseInsensitive));
  2151. }
  2152. }
  2153. EXPECT_TRUE(onlyOneJobHaveJobDependency);
  2154. // Invoke Asset Processed for pc platform for the first FileB job
  2155. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(productFileBFilename));
  2156. response.m_outputProducts.back().m_outputPathOverride = cacheWithPlatform;
  2157. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[0].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  2158. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2159. response.m_outputProducts.clear();
  2160. // Invoke Asset Processed for pc platform for the second FileB job
  2161. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(product2FileBFilename));
  2162. response.m_outputProducts.back().m_outputPathOverride = cacheWithPlatform;
  2163. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, m_processResults[1].m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  2164. EXPECT_TRUE(BlockUntil(m_idling, 5000000));
  2165. m_processResults.clear();
  2166. response.m_outputProducts.clear();
  2167. // Change the fingerprint of the FileA job and analyze the file again
  2168. // This time it should not only process its job again but should also process the dependent FileB job
  2169. changeJobAFingerprint = true;
  2170. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileAPath));
  2171. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2172. EXPECT_EQ(m_processResults.size(), 2);
  2173. for (JobDetails& jobDetail : m_processResults)
  2174. {
  2175. EXPECT_EQ(m_processResults.size(), 2); // Repeat to ensure count doesn't change while looping
  2176. if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileB.txt"))
  2177. {
  2178. // Ensure that we are processing the right FileB job
  2179. EXPECT_EQ(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy"), 0);
  2180. response.m_outputProducts.clear();
  2181. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(product2FileBFilename));
  2182. response.m_outputProducts.back().m_outputPathOverride = cacheWithPlatform;
  2183. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetail.m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  2184. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2185. }
  2186. else
  2187. {
  2188. response.m_outputProducts.clear();
  2189. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(productFileAFilename));
  2190. response.m_outputProducts.back().m_outputPathOverride = cacheWithPlatform;
  2191. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetail.m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  2192. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2193. }
  2194. }
  2195. m_processResults.clear();
  2196. response.m_outputProducts.clear();
  2197. // Modify FileA and analyze the file again.
  2198. // This time also it should not only process its job again but should also process the dependent FileB job
  2199. EXPECT_TRUE(QFile::remove(sourceFileAPath));
  2200. EXPECT_TRUE(CreateDummyFile(sourceFileAPath, "changed"));
  2201. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileAPath));
  2202. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2203. EXPECT_EQ(m_processResults.size(), 2);
  2204. for (JobDetails& jobDetail : m_processResults)
  2205. {
  2206. if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileB.txt"))
  2207. {
  2208. // Ensure that we are processing the right FileB job
  2209. EXPECT_EQ(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy"), 0);
  2210. response.m_outputProducts.clear();
  2211. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(product2FileBFilename));
  2212. response.m_outputProducts.back().m_outputPathOverride = cacheWithPlatform;
  2213. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetail.m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  2214. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2215. }
  2216. else
  2217. {
  2218. response.m_outputProducts.clear();
  2219. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(productFileAFilename));
  2220. response.m_outputProducts.back().m_outputPathOverride = cacheWithPlatform;
  2221. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetail.m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  2222. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2223. }
  2224. }
  2225. // First we will analyze File C
  2226. // This should make Job("FileC","zzz", "pc") depends on Job("FileB", "yyy", "pc") which already depends on Job("FileA", "xxx", "pc")
  2227. // After that we will change the fingerprint of Job("FileA", "xxx", "pc") and analyze FileA again,
  2228. // which should process all the three jobs once again.
  2229. m_processResults.clear();
  2230. fileCJobDependentOnFileBJob = true;
  2231. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileCPath));
  2232. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2233. EXPECT_EQ(m_processResults.size(), 2);
  2234. for (JobDetails& jobDetail : m_processResults)
  2235. {
  2236. EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileC.txt"));
  2237. if (jobDetail.m_jobDependencyList.size())
  2238. {
  2239. // Verify FileC jobinfo
  2240. AssetBuilderSDK::SourceFileDependency& source = jobDetail.m_jobDependencyList[0].m_jobDependency.m_sourceFile;
  2241. EXPECT_EQ(source.m_sourceFileDependencyUUID, sourceFileBUuid);
  2242. EXPECT_EQ(QString(jobDetail.m_jobDependencyList[0].m_jobDependency.m_jobKey.c_str()).compare("yyy"), 0);
  2243. response.m_outputProducts.clear();
  2244. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(product2FileCFilename));
  2245. response.m_outputProducts.back().m_outputPathOverride = cacheWithPlatform;
  2246. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetail.m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  2247. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2248. }
  2249. else
  2250. {
  2251. response.m_outputProducts.clear();
  2252. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(productFileCFilename));
  2253. response.m_outputProducts.back().m_outputPathOverride = cacheWithPlatform;
  2254. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetail.m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  2255. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2256. }
  2257. }
  2258. m_processResults.clear();
  2259. // Modify fingerprint of Job("FileA", "xxx", "pc") and analyze FileA again,
  2260. changeJobAFingerprint = false; // This will revert back the changes in the extra info used for fingerprinting of this job
  2261. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileAPath));
  2262. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2263. //One of the FileC job("FileC.txt","zzz") depends on the FileB job("FileB.txt", "yyy") which depends on FileA job("FileA.txt", "xxx")
  2264. EXPECT_EQ(m_processResults.size(), 3);
  2265. for (JobDetails& jobDetail : m_processResults)
  2266. {
  2267. if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileA.txt"))
  2268. {
  2269. // Verify FileA jobinfo
  2270. EXPECT_EQ(QString(jobDetail.m_jobEntry.m_jobKey).compare("xxx"), 0);
  2271. }
  2272. else if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileB.txt"))
  2273. {
  2274. // Verify FileB jobinfo
  2275. EXPECT_EQ(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy"), 0);
  2276. }
  2277. else if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileC.txt"))
  2278. {
  2279. // Verify FileC jobinfo
  2280. EXPECT_EQ(QString(jobDetail.m_jobEntry.m_jobKey).compare("zzz"), 0);
  2281. }
  2282. }
  2283. // Since one of the FileC job("FileC.txt","zzz") have emitted a job dependency on a FileB job("FileB.txt", "yyy")
  2284. // which also have a job dependency on a FileA job("FileA.txt", "xxx") therefore deleting File A source file should
  2285. // cause both jobs (File B and File C) to be processed again.
  2286. m_processResults.clear();
  2287. QFile::remove(sourceFileAPath);
  2288. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileAPath));
  2289. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2290. EXPECT_EQ(m_processResults.size(), 2);
  2291. for (JobDetails& jobDetail : m_processResults)
  2292. {
  2293. if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileB.txt"))
  2294. {
  2295. // Verify FileB jobinfo
  2296. EXPECT_EQ(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy"), 0);
  2297. }
  2298. else if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileC.txt"))
  2299. {
  2300. // Verify FileC jobinfo
  2301. EXPECT_EQ(QString(jobDetail.m_jobEntry.m_jobKey).compare("zzz"), 0);
  2302. }
  2303. else
  2304. {
  2305. // invalid job info
  2306. EXPECT_TRUE(false);
  2307. }
  2308. }
  2309. m_processResults.clear();
  2310. // Adding FileA back should cause all the three jobs to be processed again.
  2311. EXPECT_TRUE(CreateDummyFile(sourceFileAPath, "reappear"));
  2312. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString , sourceFileAPath));
  2313. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2314. EXPECT_EQ(m_processResults.size(), 3);
  2315. for (JobDetails& jobDetail : m_processResults)
  2316. {
  2317. if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileA.txt"))
  2318. {
  2319. // Verify FileA jobinfo
  2320. EXPECT_EQ(QString(jobDetail.m_jobEntry.m_jobKey).compare("xxx"), 0);
  2321. }
  2322. else if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileB.txt"))
  2323. {
  2324. // Verify FileB jobinfo
  2325. EXPECT_EQ(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy"), 0);
  2326. }
  2327. else if (QString(jobDetail.m_jobEntry.m_sourceAssetReference.RelativePath().c_str()).endsWith("FileC.txt"))
  2328. {
  2329. // Verify FileC jobinfo
  2330. EXPECT_EQ(QString(jobDetail.m_jobEntry.m_jobKey).compare("zzz"), 0);
  2331. }
  2332. }
  2333. assetBuilderInfoHandler.BusDisconnect();
  2334. }
  2335. // Helper function, processes assets and blocks until complete
  2336. void AssetProcessorManagerUnitTests::ProcessAssetBlockUntilComplete(QString& assetToProcess)
  2337. {
  2338. m_processResults.clear();
  2339. QMetaObject::invokeMethod(
  2340. m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, assetToProcess));
  2341. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2342. QCoreApplication::processEvents(QEventLoop::AllEvents);
  2343. AssetProcessorManagerUnitTestUtils::SortAssetToProcessResultList(m_processResults);
  2344. }
  2345. // This test verifies the fingerprint clearing command causes assets to reprocess.
  2346. // It does this by processing assets once to setup. Then processes them a second time, to verify
  2347. // they don't reprocess due to identical fingerprints. After that, it clears the fingerprint from the DB,
  2348. // and processes a final time, to verify the assets process because of the fingerprint change.
  2349. TEST_F(AssetProcessorManagerUnitTests, FingerprintClearRequest_ClearsFingerprints_AssetsReprocess)
  2350. {
  2351. // Step 1: Setup.
  2352. // 1. Create the asset recognizer.
  2353. // 2. Create the source asset.
  2354. // 3. Add the source asset to the file table.
  2355. // This is normally handled by the file processor,
  2356. // which is not enabled for this test to restrict dependencies.
  2357. // 4. Process assets.
  2358. // 5. Write job information to the database.
  2359. MockApplicationManager mockAppManager;
  2360. mockAppManager.BusConnect();
  2361. // Create the recognizer, so the assets can be setup with a source/product and a job.
  2362. AssetRecognizer rec;
  2363. rec.m_name = "txt files";
  2364. rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
  2365. rec.m_platformSpecs.insert({ "pc", AssetInternalSpec::Copy });
  2366. rec.m_platformSpecs.insert({ "android", AssetInternalSpec::Copy });
  2367. m_config.AddRecognizer(rec);
  2368. mockAppManager.RegisterAssetRecognizerAsBuilder(rec);
  2369. // Define the source asset, and the path to it.
  2370. QString watchFolder("subfolder1");
  2371. QString fileName("basefile.txt");
  2372. QString relativePathToSourceFile = watchFolder + QDir::separator() + fileName;
  2373. QString absoluteFilePath = m_sourceRoot.absoluteFilePath(relativePathToSourceFile);
  2374. // Create the source asset.
  2375. QSet<QString> expectedFiles;
  2376. expectedFiles << absoluteFilePath;
  2377. AssetProcessorManagerUnitTestUtils::CreateExpectedFiles(expectedFiles);
  2378. // Add the source asset to the asset database
  2379. const ScanFolderInfo* scanFolderInfo = m_config.GetScanFolderByPath(m_sourceRoot.absoluteFilePath(watchFolder));
  2380. ASSERT_TRUE(scanFolderInfo != nullptr);
  2381. AzToolsFramework::AssetDatabase::FileDatabaseEntry file;
  2382. file.m_scanFolderPK = scanFolderInfo->ScanFolderID();
  2383. file.m_fileName = fileName.toUtf8().constData();
  2384. file.m_isFolder = false;
  2385. // Create a scoped asset database connection, so it's only kept as long as it's needed.
  2386. {
  2387. AssetDatabaseConnection connection;
  2388. EXPECT_TRUE(connection.OpenDatabase());
  2389. // Init to the opposite of the expected value, to verify that it gets set to what's expected.
  2390. bool entryAlreadyExists = true;
  2391. connection.InsertFile(file, entryAlreadyExists);
  2392. EXPECT_FALSE(entryAlreadyExists);
  2393. }
  2394. // ProcessAssetBlockUntilComplete calls AssessModifiedFile, but the file needs to get added first.
  2395. QMetaObject::invokeMethod(
  2396. m_assetProcessorManager.get(),
  2397. "AssessAddedFile",
  2398. Qt::QueuedConnection, Q_ARG(QString, absoluteFilePath));
  2399. // Process once, to setup the test and have the asset processed.
  2400. ProcessAssetBlockUntilComplete(absoluteFilePath);
  2401. EXPECT_EQ(m_processResults.size(), 2);
  2402. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  2403. EXPECT_NE(m_processResults[0].m_jobEntry.m_computedFingerprint, 0);
  2404. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "pc");
  2405. EXPECT_NE(m_processResults[1].m_jobEntry.m_computedFingerprint, 0);
  2406. // Create the product assets.
  2407. EXPECT_TRUE(CreateDummyFile(m_cacheRoot.filePath(QString("android") + QDir::separator() + fileName), ""));
  2408. EXPECT_TRUE(CreateDummyFile(m_cacheRoot.filePath(QString("pc") + QDir::separator() + fileName), ""));
  2409. // Create the job response, so the asset processor writes the fingerprints to the database.
  2410. AssetBuilderSDK::ProcessJobResponse response;
  2411. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  2412. response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(fileName.toUtf8().data(), AZ::Uuid::CreateNull(), 1));
  2413. for (auto& processResult : m_processResults)
  2414. {
  2415. QMetaObject::invokeMethod(
  2416. m_assetProcessorManager.get(),
  2417. "AssetProcessed",
  2418. Qt::QueuedConnection,
  2419. Q_ARG(JobEntry, processResult.m_jobEntry),
  2420. Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
  2421. }
  2422. // Wait on asset processing events, so everything runs.
  2423. EXPECT_TRUE(BlockUntil(m_idling, 5000));
  2424. QCoreApplication::processEvents(QEventLoop::AllEvents);
  2425. // Step 2: Verify assets don't re-process on a second call to process them.
  2426. // This occurs because the fingerprints are identical.
  2427. ProcessAssetBlockUntilComplete(absoluteFilePath);
  2428. EXPECT_EQ(m_processResults.size(), 0);
  2429. // Step 3: Clear the fingerprint from the database.
  2430. AssetFingerprintClearRequest fingerprintClearRequest;
  2431. fingerprintClearRequest.m_searchTerm = absoluteFilePath.toUtf8().data();
  2432. AssetFingerprintClearResponse fingerprintClearResponse;
  2433. m_assetProcessorManager->ProcessFingerprintClearRequest(fingerprintClearRequest, fingerprintClearResponse);
  2434. QCoreApplication::processEvents(QEventLoop::AllEvents);
  2435. EXPECT_TRUE(fingerprintClearResponse.m_isSuccess);
  2436. // Step 4: Process again, and verify the assets did re-process, because the fingerprint changed.
  2437. ProcessAssetBlockUntilComplete(absoluteFilePath);
  2438. EXPECT_EQ(m_processResults.size(), 2);
  2439. EXPECT_EQ(m_processResults[0].m_jobEntry.m_platformInfo.m_identifier, "android");
  2440. EXPECT_NE(m_processResults[0].m_jobEntry.m_computedFingerprint, 0);
  2441. EXPECT_EQ(m_processResults[1].m_jobEntry.m_platformInfo.m_identifier, "pc");
  2442. EXPECT_NE(m_processResults[1].m_jobEntry.m_computedFingerprint, 0);
  2443. mockAppManager.BusDisconnect();
  2444. }
  2445. } // namespace AssetProcessor