IntermediateAssetTests.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  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 <native/tests/assetmanager/IntermediateAssetTests.h>
  9. #include <QCoreApplication>
  10. #include <native/unittests/UnitTestUtils.h>
  11. #include <native/utilities/ProductOutputUtil.h>
  12. #include <AzFramework/IO/LocalFileIO.h>
  13. namespace UnitTests
  14. {
  15. void IntermediateAssetTests::SetUp()
  16. {
  17. AssetManagerTestingBase::SetUp();
  18. }
  19. void IntermediateAssetTests::TearDown()
  20. {
  21. AssetManagerTestingBase::TearDown();
  22. }
  23. void IntermediateAssetTests::IncorrectBuilderConfigurationTest(bool commonPlatform, AssetBuilderSDK::ProductOutputFlags flags)
  24. {
  25. using namespace AssetBuilderSDK;
  26. CreateBuilder("stage1", "*.stage1", "stage2", commonPlatform, flags);
  27. QMetaObject::invokeMethod(
  28. m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, m_testFilePath.c_str()));
  29. QCoreApplication::processEvents();
  30. RunFile(1);
  31. m_errorChecker.Begin();
  32. ProcessJob(*m_rc, m_jobDetailsList[0]);
  33. m_errorChecker.End(1);
  34. ASSERT_TRUE(m_fileFailed);
  35. }
  36. TEST_F(IntermediateAssetTests, FileProcessedAsIntermediateIntoProduct_NotEnabledType_PathBasedUUID)
  37. {
  38. using namespace AssetBuilderSDK;
  39. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  40. CreateBuilder("stage2", "*.stage2","stage3", false, ProductOutputFlags::ProductAsset);
  41. ProcessFileMultiStage(2, true);
  42. // Verify the UUID is generated based on the Source:Builder:SubId and not some randomly generated one
  43. AssetProcessor::BuilderInfoList builders;
  44. AssetProcessor::AssetBuilderInfoBus::Broadcast(
  45. &AssetProcessor::AssetBuilderInfoBus::Events::GetMatchingBuildersInfo, MakePath("test.stage1", true), builders);
  46. ASSERT_EQ(builders.size(), 1);
  47. auto builderUuid = builders[0].m_busId;
  48. auto sourceUuid = AssetUtilities::GetSourceUuid(AssetProcessor::SourceAssetReference(m_testFilePath.c_str())).GetValueOr(AZ::Uuid());
  49. auto actualIntermediateUuid = AssetUtilities::GetSourceUuid(AssetProcessor::SourceAssetReference(MakePath("test.stage2", true).c_str())).GetValueOr(AZ::Uuid());
  50. auto uuidFormat = AZStd::string::format(
  51. AZ_STRING_FORMAT ":" AZ_STRING_FORMAT ":%d",
  52. AZ_STRING_ARG(sourceUuid.ToFixedString()),
  53. AZ_STRING_ARG(builderUuid.ToFixedString()),
  54. AssetSubId);
  55. auto expectedIntermediateUuid = AZ::Uuid::CreateName(uuidFormat);
  56. EXPECT_NE(actualIntermediateUuid, expectedIntermediateUuid);
  57. }
  58. TEST_F(IntermediateAssetTests, FileProcessedAsIntermediateIntoProduct_EnabledType_SourceBasedUUID)
  59. {
  60. using namespace AssetBuilderSDK;
  61. auto* uuidInterface = AZ::Interface<AssetProcessor::IUuidRequests>::Get();
  62. ASSERT_TRUE(uuidInterface);
  63. uuidInterface->EnableGenerationForTypes({ ".stage1" });
  64. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  65. CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
  66. ProcessFileMultiStage(2, true);
  67. // Verify the UUID is generated based on the Source:Builder:SubId and not some randomly generated one
  68. AssetProcessor::BuilderInfoList builders;
  69. AssetProcessor::AssetBuilderInfoBus::Broadcast(
  70. &AssetProcessor::AssetBuilderInfoBus::Events::GetMatchingBuildersInfo, MakePath("test.stage1", true), builders);
  71. ASSERT_EQ(builders.size(), 1);
  72. auto builderUuid = builders[0].m_busId;
  73. auto sourceUuid = AssetUtilities::GetSourceUuid(AssetProcessor::SourceAssetReference(m_testFilePath.c_str()));
  74. auto actualIntermediateUuid =
  75. AssetUtilities::GetSourceUuid(AssetProcessor::SourceAssetReference(MakePath("test.stage2", true).c_str()));
  76. ASSERT_TRUE(sourceUuid);
  77. ASSERT_TRUE(actualIntermediateUuid);
  78. auto uuidFormat = AZStd::string::format(
  79. AZ_STRING_FORMAT ":" AZ_STRING_FORMAT ":%d",
  80. AZ_STRING_ARG(sourceUuid.GetValue().ToFixedString()),
  81. AZ_STRING_ARG(builderUuid.ToFixedString()),
  82. AssetSubId);
  83. auto expectedIntermediateUuid = AZ::Uuid::CreateName(uuidFormat);
  84. EXPECT_STREQ(actualIntermediateUuid.GetValue().ToFixedString().c_str(), expectedIntermediateUuid.ToFixedString().c_str());
  85. }
  86. TEST_F(IntermediateAssetTests, IntermediateOutputWithWrongPlatform_CausesFailure)
  87. {
  88. IncorrectBuilderConfigurationTest(false, AssetBuilderSDK::ProductOutputFlags::IntermediateAsset);
  89. }
  90. TEST_F(IntermediateAssetTests, ProductOutputWithWrongPlatform_CausesFailure)
  91. {
  92. IncorrectBuilderConfigurationTest(true, AssetBuilderSDK::ProductOutputFlags::ProductAsset);
  93. }
  94. TEST_F(IntermediateAssetTests, IntermediateAndProductOutputFlags_NormalPlatform_CausesFailure)
  95. {
  96. IncorrectBuilderConfigurationTest(false, AssetBuilderSDK::ProductOutputFlags::IntermediateAsset | AssetBuilderSDK::ProductOutputFlags::ProductAsset);
  97. }
  98. TEST_F(IntermediateAssetTests, IntermediateAndProductOutputFlags_CommonPlatform_CausesFailure)
  99. {
  100. IncorrectBuilderConfigurationTest(true, AssetBuilderSDK::ProductOutputFlags::IntermediateAsset | AssetBuilderSDK::ProductOutputFlags::ProductAsset);
  101. }
  102. TEST_F(IntermediateAssetTests, NoFlags_CausesFailure)
  103. {
  104. IncorrectBuilderConfigurationTest(false, (AssetBuilderSDK::ProductOutputFlags)(0));
  105. }
  106. TEST_F(IntermediateAssetTests, ABALoop_CausesFailure)
  107. {
  108. using namespace AssetBuilderSDK;
  109. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  110. CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
  111. CreateBuilder("stage3", "*.stage3", "stage2", true, ProductOutputFlags::IntermediateAsset); // Loop back to an intermediate
  112. ProcessFileMultiStage(3, false);
  113. ASSERT_EQ(m_jobDetailsList.size(), 3);
  114. EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
  115. EXPECT_TRUE(m_jobDetailsList[2].m_autoFail);
  116. EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage3");
  117. EXPECT_EQ(m_jobDetailsList[2].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
  118. }
  119. TEST_F(IntermediateAssetTests, AALoop_CausesFailure)
  120. {
  121. using namespace AssetBuilderSDK;
  122. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  123. CreateBuilder("stage2", "*.stage2", "stage1", true, ProductOutputFlags::IntermediateAsset); // Loop back to the source
  124. ProcessFileMultiStage(2, false);
  125. ASSERT_EQ(m_jobDetailsList.size(), 3);
  126. EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
  127. EXPECT_TRUE(m_jobDetailsList[2].m_autoFail);
  128. EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage2");
  129. EXPECT_EQ(m_jobDetailsList[2].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
  130. }
  131. TEST_F(IntermediateAssetTests, SelfLoop_CausesFailure)
  132. {
  133. using namespace AssetBuilderSDK;
  134. CreateBuilder("stage1", "*.stage1", "stage1", true, ProductOutputFlags::IntermediateAsset); // Loop back to the source with a single job
  135. ProcessFileMultiStage(1, false);
  136. ASSERT_EQ(m_jobDetailsList.size(), 2);
  137. EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
  138. EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
  139. m_assetProcessorManager->AssessDeletedFile(MakePath("test.stage1", true).c_str());
  140. RunFile(0);
  141. m_assetProcessorManager->CheckFilesToExamine(0);
  142. m_assetProcessorManager->CheckActiveFiles(0);
  143. m_assetProcessorManager->CheckJobEntries(0);
  144. }
  145. TEST_F(IntermediateAssetTests, CopyJob_Works)
  146. {
  147. using namespace AssetBuilderSDK;
  148. CreateBuilder("stage1", "*.stage1", "stage1", false, ProductOutputFlags::ProductAsset); // Copy jobs are ok
  149. ProcessFileMultiStage(1, false);
  150. auto expectedProduct = AZ::IO::Path(m_databaseLocationListener.GetAssetRootDir()) / "Cache" / "pc" / "test.stage1";
  151. ASSERT_EQ(m_jobDetailsList.size(), 1);
  152. EXPECT_TRUE(AZ::IO::SystemFile::Exists(expectedProduct.c_str())) << expectedProduct.c_str();
  153. }
  154. TEST_F(IntermediateAssetTests, DeleteSourceIntermediate_DeletesAllProducts)
  155. {
  156. using namespace AssetBuilderSDK;
  157. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  158. CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
  159. CreateBuilder("stage3", "*.stage3", "stage4", false, ProductOutputFlags::ProductAsset);
  160. ProcessFileMultiStage(3, true);
  161. AZ::IO::SystemFile::Delete(m_testFilePath.c_str());
  162. m_assetProcessorManager->AssessDeletedFile(m_testFilePath.c_str());
  163. RunFile(0);
  164. CheckIntermediate("test.stage2", false);
  165. CheckIntermediate("test.stage3", false);
  166. CheckProduct("test.stage4", false);
  167. }
  168. void IntermediateAssetTests::DeleteIntermediateTest(const char* deleteFilePath)
  169. {
  170. using namespace AssetBuilderSDK;
  171. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  172. CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
  173. CreateBuilder("stage3", "*.stage3", "stage4", false, ProductOutputFlags::ProductAsset);
  174. ProcessFileMultiStage(3, true);
  175. AZ::IO::SystemFile::Delete(deleteFilePath);
  176. m_assetProcessorManager->AssessDeletedFile(deleteFilePath);
  177. RunFile(0); // Process the delete
  178. // Reprocess the file
  179. m_jobDetailsList.clear();
  180. // Unfortunately we need to just process the events a few times without doing any checks here
  181. // due to the previous step queuing work which is sometimes executed immediately.
  182. // Without a way to consistently be sure whether the work has been done or not, we need to just run enough until the job is emitted
  183. QCoreApplication::processEvents();
  184. QCoreApplication::processEvents();
  185. QCoreApplication::processEvents();
  186. ASSERT_EQ(m_jobDetailsList.size(), 1);
  187. ProcessJob(*m_rc, m_jobDetailsList[0]);
  188. ASSERT_TRUE(m_fileCompiled);
  189. m_assetProcessorManager->AssetProcessed(m_processedJobEntry, m_processJobResponse);
  190. CheckIntermediate("test.stage2");
  191. CheckIntermediate("test.stage3");
  192. CheckProduct("test.stage4");
  193. }
  194. TEST_F(IntermediateAssetTests, DeleteIntermediateProduct_Reprocesses)
  195. {
  196. DeleteIntermediateTest(MakePath("test.stage2", true).c_str());
  197. }
  198. TEST_F(IntermediateAssetTests, DeleteFinalProduct_Reprocesses)
  199. {
  200. DeleteIntermediateTest(MakePath("test.stage4", false).c_str());
  201. }
  202. TEST_F(IntermediateAssetTests, Override_NormalFileProcessedFirst_NormalFileOutputsIntermediate_FirstStageCausesFailure)
  203. {
  204. // Test that a file outputting an intermediate that conflicts with an existing source which outputs an intermediate fails
  205. using namespace AssetBuilderSDK;
  206. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  207. CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
  208. CreateBuilder("stage3", "*.stage3", "stage4", false, ProductOutputFlags::ProductAsset);
  209. constexpr int NumberOfStages = 3;
  210. // Make and process a source file which matches an intermediate output name we will create later
  211. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  212. AZStd::string testFilename = "test.stage2";
  213. AZStd::string testFilePath = (scanFolderDir / testFilename).AsPosix();
  214. UnitTestUtils::CreateDummyFile(testFilePath.c_str(), "unit test file");
  215. ProcessFileMultiStage(NumberOfStages, true, AssetProcessor::SourceAssetReference(testFilePath.c_str()), 2);
  216. // Now process another file which produces intermediates that conflict with the existing source file above
  217. // Only go to stage 1 since we're expecting a failure at that point
  218. ProcessFileMultiStage(1, false);
  219. // Expect 2 jobs for the same file, 1 is the job that processed successfully and detected the problem, the 2nd is an autofail job
  220. // used to actually mark the file as failed
  221. ASSERT_EQ(m_jobDetailsList.size(), 2);
  222. EXPECT_FALSE(m_jobDetailsList[0].m_autoFail);
  223. EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
  224. EXPECT_EQ(m_jobDetailsList[0].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
  225. EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
  226. }
  227. TEST_F(IntermediateAssetTests, Override_NormalFileProcessedFirst_NormalFileOutputsIntermediate_SecondStageCausesFailure)
  228. {
  229. // Test that an intermediate outputting an intermediate that conflicts with an existing source which outputs an intermediate fails
  230. using namespace AssetBuilderSDK;
  231. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  232. CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
  233. CreateBuilder("stage3", "*.stage3", "stage4", true, ProductOutputFlags::IntermediateAsset);
  234. CreateBuilder("stage4", "*.stage4", "stage5", false, ProductOutputFlags::ProductAsset);
  235. constexpr int NumberOfStages = 4;
  236. // Make and process a source file which matches an intermediate output name we will create later
  237. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  238. AZStd::string testFilename = "test.stage3";
  239. AZStd::string testFilePath = (scanFolderDir / testFilename).AsPosix();
  240. UnitTestUtils::CreateDummyFile(testFilePath.c_str(), "unit test file");
  241. ProcessFileMultiStage(NumberOfStages, true, AssetProcessor::SourceAssetReference(testFilePath.c_str()), 3);
  242. // Now process another file which produces intermediates that conflict with the existing source file above
  243. // Only go to stage 2 since we're expecting a failure at that point
  244. ProcessFileMultiStage(2, false);
  245. // Expect 3 jobs:
  246. // 1 is the job for stage2 that was processing and detected the failure
  247. // 1 is the autofail job that was created to autofail stage2
  248. // 1 is the autofail job for the top level source (stage1)
  249. ASSERT_EQ(m_jobDetailsList.size(), 3);
  250. EXPECT_FALSE(m_jobDetailsList[0].m_autoFail);
  251. EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
  252. EXPECT_TRUE(m_jobDetailsList[2].m_autoFail);
  253. EXPECT_EQ(m_jobDetailsList[0].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage2");
  254. EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage2");
  255. EXPECT_EQ(m_jobDetailsList[2].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
  256. }
  257. TEST_F(IntermediateAssetTests, Override_NormalFileProcessedFirst_NormalFileOutputsProduct_CausesFailure)
  258. {
  259. // Test that a source outputting an intermediate that conflicts with an existing source which outputs a product fails
  260. using namespace AssetBuilderSDK;
  261. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  262. CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
  263. constexpr int NumberOfStages = 2;
  264. // Make and process a source file which matches an intermediate output name we will create later
  265. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  266. AZStd::string testFilename = "test.stage2";
  267. AZStd::string testFilePath = (scanFolderDir / testFilename).AsPosix();
  268. UnitTestUtils::CreateDummyFile(testFilePath.c_str(), "unit test file");
  269. ProcessFileMultiStage(NumberOfStages, true, AssetProcessor::SourceAssetReference(testFilePath.c_str()), 2);
  270. // Now process another file which produces intermediates that conflict with the existing source file above
  271. // Only go to stage 1 since we're expecting a failure at that point
  272. ProcessFileMultiStage(1, false);
  273. // Expect 2 jobs for the same file, 1 is the job that processed successfully and detected the problem, the 2nd is an autofail job
  274. // used to actually mark the file as failed
  275. ASSERT_EQ(m_jobDetailsList.size(), 2);
  276. EXPECT_FALSE(m_jobDetailsList[0].m_autoFail);
  277. EXPECT_TRUE(m_jobDetailsList[1].m_autoFail);
  278. EXPECT_EQ(m_jobDetailsList[0].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
  279. EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
  280. }
  281. TEST_F(IntermediateAssetTests, DeleteFileInIntermediateFolder_CorrectlyDeletesOneFile)
  282. {
  283. using namespace AzToolsFramework::AssetDatabase;
  284. // Set up the test files and database entries
  285. SourceDatabaseEntry source1{ m_scanfolder.m_scanFolderID, "folder/parent.txt", AZ::Uuid::CreateRandom(), "fingerprint" };
  286. SourceDatabaseEntry source2{ m_platformConfig->GetIntermediateAssetsScanFolderId().value(), "folder/child.txt", AZ::Uuid::CreateRandom(), "fingerprint" };
  287. auto sourceFile = AZ::IO::Path(m_scanfolder.m_scanFolder) / "folder/parent.txt"; // This file should NOT be deleted
  288. auto intermediateFile = MakePath("folder/child.txt", true); // This file should be deleted
  289. auto cacheFile = MakePath("folder/product.txt", false); // This file should NOT be deleted
  290. auto cacheFile2 = MakePath("folder/product777.txt", false); // This file should be deleted
  291. UnitTestUtils::CreateDummyFile(sourceFile.Native().c_str(), QString("tempdata"));
  292. UnitTestUtils::CreateDummyFile(intermediateFile.c_str(), QString("tempdata"));
  293. UnitTestUtils::CreateDummyFile(cacheFile.c_str(), QString("tempdata"));
  294. UnitTestUtils::CreateDummyFile(cacheFile2.c_str(), QString("tempdata"));
  295. ASSERT_TRUE(m_stateData->SetSource(source1));
  296. ASSERT_TRUE(m_stateData->SetSource(source2));
  297. JobDatabaseEntry job1{ source1.m_sourceID,
  298. "Mock Job",
  299. 1234,
  300. "pc",
  301. m_builderInfoHandler.m_builderDescMap.begin()->second.m_busId,
  302. AzToolsFramework::AssetSystem::JobStatus::Completed,
  303. 999 };
  304. JobDatabaseEntry job2{ source2.m_sourceID,
  305. "Mock Job",
  306. 1234,
  307. "pc",
  308. m_builderInfoHandler.m_builderDescMap.begin()->second.m_busId,
  309. AzToolsFramework::AssetSystem::JobStatus::Completed,
  310. 888 };
  311. ASSERT_TRUE(m_stateData->SetJob(job1));
  312. ASSERT_TRUE(m_stateData->SetJob(job2));
  313. ProductDatabaseEntry product1{ job1.m_jobID,
  314. 0,
  315. "pc/folder/product.txt",
  316. AZ::Uuid::CreateName("one"),
  317. AZ::Uuid::CreateName("product.txt"),
  318. 0,
  319. static_cast<int>(AssetBuilderSDK::ProductOutputFlags::ProductAsset) };
  320. ProductDatabaseEntry product2{ job2.m_jobID,
  321. 777,
  322. "pc/folder/product777.txt",
  323. AZ::Uuid::CreateName("two"),
  324. AZ::Uuid::CreateName("product777.txt"),
  325. 0,
  326. static_cast<int>(AssetBuilderSDK::ProductOutputFlags::ProductAsset) };
  327. ASSERT_TRUE(m_stateData->SetProduct(product1));
  328. ASSERT_TRUE(m_stateData->SetProduct(product2));
  329. // Record the folder so its marked as a known folder
  330. auto folderPath = MakePath("folder", true);
  331. m_assetProcessorManager->RecordFoldersFromScanner(
  332. QSet{ AssetProcessor::AssetFileInfo{ folderPath.c_str(), QDateTime::currentDateTime(), 0,
  333. m_platformConfig->GetScanFolderForFile(folderPath.c_str()), true } });
  334. // Delete the file and folder in the intermediate folder
  335. AZ::IO::LocalFileIO::GetInstance()->DestroyPath(folderPath.c_str());
  336. QMetaObject::invokeMethod(
  337. m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection,
  338. Q_ARG(QString, MakePath("folder", true).c_str()));
  339. QCoreApplication::processEvents();
  340. RunFile(0);
  341. QCoreApplication::processEvents(); // execute ProcessFilesToExamineQueue
  342. m_assetProcessorManager->CheckActiveFiles(0);
  343. m_assetProcessorManager->CheckFilesToExamine(0);
  344. m_assetProcessorManager->CheckJobEntries(0);
  345. ProductDatabaseEntry checkEntry;
  346. ASSERT_TRUE(m_stateData->GetProductByProductID(product1.m_productID, checkEntry));
  347. ASSERT_FALSE(m_stateData->GetProductByProductID(product2.m_productID, checkEntry));
  348. ASSERT_TRUE(AZ::IO::SystemFile::Exists(sourceFile.c_str()));
  349. ASSERT_FALSE(AZ::IO::SystemFile::Exists(intermediateFile.c_str()));
  350. }
  351. TEST_F(IntermediateAssetTests, Override_IntermediateFileProcessedFirst_NormalFileOutputsIntermediate_CausesFailure)
  352. {
  353. using namespace AssetBuilderSDK;
  354. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  355. CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
  356. CreateBuilder("stage3", "*.stage3", "stage4", false, ProductOutputFlags::ProductAsset);
  357. constexpr int NumberOfStages = 3;
  358. // Process a file from stage1 -> stage4, this will create several intermediates
  359. ProcessFileMultiStage(NumberOfStages, true);
  360. // Now make a source file which is the same name as an existing intermediate and process it
  361. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  362. AZStd::string testFilename = "test.stage2";
  363. AZStd::string testFilePath = (scanFolderDir / testFilename).AsPosix();
  364. UnitTestUtils::CreateDummyFile(testFilePath.c_str(), "unit test file");
  365. ProcessFileMultiStage(NumberOfStages, true, AssetProcessor::SourceAssetReference(testFilePath.c_str()), 2, true);
  366. ASSERT_EQ(m_jobDetailsList.size(), 1);
  367. EXPECT_FALSE(m_jobDetailsList[0].m_autoFail);
  368. EXPECT_EQ(m_jobDetailsList[0].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage3");
  369. }
  370. TEST_F(IntermediateAssetTests, Override_IntermediateFileProcessedFirst_NormalFileOutputsProduct_CausesFailure)
  371. {
  372. using namespace AssetBuilderSDK;
  373. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  374. CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
  375. constexpr int NumberOfStages = 2;
  376. // Process a file from stage1 -> stage4, this will create several intermediates
  377. ProcessFileMultiStage(NumberOfStages, true);
  378. // Now make a source file which is the same name as an existing intermediate and process it
  379. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  380. AZStd::string testFilename = "test.stage2";
  381. AZStd::string testFilePath = (scanFolderDir / testFilename).AsPosix();
  382. UnitTestUtils::CreateDummyFile(testFilePath.c_str(), "unit test file");
  383. ProcessFileMultiStage(NumberOfStages, true, AssetProcessor::SourceAssetReference(testFilePath.c_str()), 2, true);
  384. ASSERT_EQ(m_jobDetailsList.size(), 2);
  385. EXPECT_TRUE(m_jobDetailsList[0].m_autoFail);
  386. EXPECT_FALSE(m_jobDetailsList[1].m_autoFail);
  387. EXPECT_EQ(m_jobDetailsList[0].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage1");
  388. EXPECT_EQ(m_jobDetailsList[1].m_jobEntry.m_sourceAssetReference.RelativePath().Native(), "test.stage2");
  389. }
  390. TEST_F(IntermediateAssetTests, DuplicateOutputs_CausesFailure)
  391. {
  392. using namespace AssetBuilderSDK;
  393. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset, true);
  394. CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
  395. ProcessFileMultiStage(2, true, {}, 1, false, true);
  396. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  397. AZStd::string testFilename = "test2.stage1";
  398. UnitTestUtils::CreateDummyFile((scanFolderDir / testFilename).c_str(), "unit test file");
  399. QMetaObject::invokeMethod(
  400. m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, (scanFolderDir / testFilename).c_str()));
  401. QCoreApplication::processEvents();
  402. RunFile(1);
  403. ProcessJob(*m_rc, m_jobDetailsList[0]);
  404. ASSERT_TRUE(m_fileCompiled);
  405. m_jobDetailsList.clear();
  406. m_assetProcessorManager->AssetProcessed(m_processedJobEntry, m_processJobResponse);
  407. ASSERT_EQ(m_jobDetailsList.size(), 1);
  408. EXPECT_TRUE(m_jobDetailsList[0].m_autoFail);
  409. }
  410. TEST_F(IntermediateAssetTests, SourceAsset_SourceDependencyOnIntermediate_Reprocesses)
  411. {
  412. using namespace AssetBuilderSDK;
  413. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  414. CreateBuilder("stage2", "*.stage2", "stage3", false, ProductOutputFlags::ProductAsset);
  415. // Builder for the normal file, with a source dependency on the .stage2 intermediate
  416. m_builderInfoHandler.CreateBuilderDesc(
  417. "normal file builder", AZ::Uuid::CreateName("normal file builder").ToFixedString().c_str(),
  418. { AssetBuilderPattern{ "*.test", AssetBuilderPattern::Wildcard } },
  419. UnitTests::MockMultiBuilderInfoHandler::AssetBuilderExtraInfo{ "", "test.stage2", "", "", {} });
  420. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  421. AZStd::string testFilename = "one.test";
  422. UnitTestUtils::CreateDummyFile((scanFolderDir / testFilename).c_str(), "unit test file");
  423. // Process the intermediate-style file first
  424. ProcessFileMultiStage(2, true);
  425. // Process the regular source second
  426. ProcessFileMultiStage(1, false, AssetProcessor::SourceAssetReference((scanFolderDir / testFilename).c_str()));
  427. // Modify the intermediate-style file so it will be processed again
  428. QFile writer(m_testFilePath.c_str());
  429. ASSERT_TRUE(writer.open(QFile::WriteOnly));
  430. {
  431. QTextStream ts(&writer);
  432. ts.setCodec("UTF-8");
  433. ts << "modified test file";
  434. }
  435. // Start processing the test.stage1 file again
  436. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, m_testFilePath.c_str()));
  437. QCoreApplication::processEvents();
  438. // Process test.stage1, which should queue up test.stage2
  439. ProcessSingleStep();
  440. // Start processing test.stage2, this should cause one.test to also be placed in the processing queue
  441. RunFile(1, 1, 1);
  442. }
  443. TEST_F(IntermediateAssetTests, IntermediateAsset_SourceDependencyOnSourceAsset_Reprocesses)
  444. {
  445. using namespace AssetBuilderSDK;
  446. using namespace AzToolsFramework::AssetDatabase;
  447. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  448. m_builderInfoHandler.CreateBuilderDesc(
  449. "stage2", AZ::Uuid::CreateRandom().ToFixedString().c_str(), { AssetBuilderPattern{ "*.stage2", AssetBuilderPattern::Wildcard } },
  450. CreateJobStage("stage2", false, PathOrUuid("one.test")),
  451. ProcessJobStage("stage3", ProductOutputFlags::ProductAsset, false), "fingerprint");
  452. CreateBuilder("normal file builder", "*.test", "test", false, ProductOutputFlags::ProductAsset);
  453. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  454. AZStd::string testFilename = "one.test";
  455. UnitTestUtils::CreateDummyFile((scanFolderDir / testFilename).c_str(), "unit test file");
  456. // Process the normal source first
  457. ProcessFileMultiStage(1, false, AssetProcessor::SourceAssetReference((scanFolderDir / testFilename).c_str()));
  458. // Process the intermediate-style source second
  459. ProcessFileMultiStage(2, true);
  460. // Modify the normal source so it will be processed again
  461. QFile writer((scanFolderDir / testFilename).c_str());
  462. ASSERT_TRUE(writer.open(QFile::WriteOnly));
  463. {
  464. QTextStream ts(&writer);
  465. ts.setCodec("UTF-8");
  466. ts << "modified test file";
  467. }
  468. // Start processing the one.test file again
  469. QMetaObject::invokeMethod(
  470. m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, (scanFolderDir / testFilename).c_str()));
  471. QCoreApplication::processEvents();
  472. // Start processing one.test, this should cause test.stage2 to also be placed in the processing queue
  473. RunFile(1, 1, 1);
  474. }
  475. TEST_F(IntermediateAssetTests, RequestReprocess_ReprocessesAllIntermediates)
  476. {
  477. using namespace AssetBuilderSDK;
  478. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  479. CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
  480. CreateBuilder("stage3", "*.stage3", "stage4", false, ProductOutputFlags::ProductAsset);
  481. ProcessFileMultiStage(3, true);
  482. EXPECT_EQ(m_assetProcessorManager->RequestReprocess(m_testFilePath.c_str()), 3);
  483. EXPECT_EQ(m_assetProcessorManager->RequestReprocess(MakePath("test.stage2", true).c_str()), 3);
  484. }
  485. TEST_F(IntermediateAssetTests, PriorProductsAreCleanedUp)
  486. {
  487. using namespace AssetBuilderSDK;
  488. int counter = 0;
  489. CreateBuilder("stage1", "*.stage1", "stage2", true, ProductOutputFlags::IntermediateAsset);
  490. // Custom builder with random output
  491. m_builderInfoHandler.CreateBuilderDesc(
  492. "stage2",
  493. AZ::Uuid::CreateRandom().ToFixedString().c_str(),
  494. { AssetBuilderPattern{ "*.stage2", AssetBuilderPattern::Wildcard } },
  495. CreateJobStage("stage2", true),
  496. [&counter](const ProcessJobRequest& request, ProcessJobResponse& response)
  497. {
  498. ++counter;
  499. AZ::IO::Path outputFile = request.m_sourceFile;
  500. outputFile.ReplaceExtension(AZStd::string::format("stage3_%d", counter).c_str());
  501. AZ::IO::LocalFileIO::GetInstance()->Copy(
  502. request.m_fullPath.c_str(), (AZ::IO::Path(request.m_tempDirPath) / outputFile).c_str());
  503. auto product = JobProduct{ outputFile.c_str(), AZ::Data::AssetType::CreateName("stage2"), 1 };
  504. product.m_outputFlags = ProductOutputFlags::IntermediateAsset;
  505. product.m_dependenciesHandled = true;
  506. response.m_outputProducts.push_back(product);
  507. response.m_resultCode = ProcessJobResult_Success;
  508. },
  509. "fingerprint");
  510. CreateBuilder("stage3", "*.stage3_*", "stage4", false, ProductOutputFlags::ProductAsset);
  511. QMetaObject::invokeMethod(
  512. m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, m_testFilePath.c_str()));
  513. QCoreApplication::processEvents();
  514. // Process test.stage1, which should queue up test.stage2
  515. // We're going to do this manually instead of using the helper because this test uses a different file naming convention
  516. ProcessSingleStep();
  517. CheckIntermediate("test.stage2");
  518. ProcessSingleStep();
  519. CheckIntermediate("test.stage3_1");
  520. ProcessSingleStep();
  521. CheckProduct("test.stage4");
  522. // Modify the source file
  523. UnitTestUtils::CreateDummyFile(m_testFilePath.c_str(), "modified unit test file");
  524. // Run again, this time expecting the stage3_2 to be output instead of stage3_1
  525. QMetaObject::invokeMethod(
  526. m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, m_testFilePath.c_str()));
  527. QCoreApplication::processEvents();
  528. ProcessSingleStep();
  529. CheckIntermediate("test.stage2");
  530. ProcessSingleStep();
  531. CheckIntermediate("test.stage3_1", false); // Prior intermediate is deleted
  532. CheckIntermediate("test.stage3_2", true); // New intermediate created
  533. ProcessSingleStep();
  534. CheckProduct("test.stage4"); // Same product result at the end
  535. }
  536. TEST_F(IntermediateAssetTests, UpdateSource_OutputDoesntChange_IntermediateDoesNotReprocess)
  537. {
  538. using namespace AssetBuilderSDK;
  539. // Custom builder with fixed product output
  540. m_builderInfoHandler.CreateBuilderDesc(
  541. "stage1",
  542. AZ::Uuid::CreateRandom().ToFixedString().c_str(),
  543. { AssetBuilderPattern{ "*.stage1", AssetBuilderPattern::Wildcard } },
  544. CreateJobStage("stage1", true),
  545. [](const ProcessJobRequest& request, ProcessJobResponse& response)
  546. {
  547. AZ::IO::Path outputFile = request.m_sourceFile;
  548. outputFile.ReplaceExtension("stage2");
  549. auto* fileIo = AZ::IO::LocalFileIO::GetInstance();
  550. AZ::IO::HandleType fileHandle;
  551. AZStd::string data = "hello world"; // We'll output the same product every time no matter what the input source is
  552. fileIo->Open((AZ::IO::Path(request.m_tempDirPath) / outputFile).c_str(), AZ::IO::OpenMode::ModeWrite, fileHandle);
  553. fileIo->Write(fileHandle, data.data(), data.size());
  554. fileIo->Close(fileHandle);
  555. auto product = JobProduct{ outputFile.c_str(), AZ::Data::AssetType::CreateName("stage2"), 1 };
  556. product.m_outputFlags = ProductOutputFlags::IntermediateAsset;
  557. product.m_dependenciesHandled = true;
  558. response.m_outputProducts.push_back(product);
  559. response.m_resultCode = ProcessJobResult_Success;
  560. }, "fingerprint");
  561. CreateBuilder("stage2", "*.stage2", "stage3", true, ProductOutputFlags::IntermediateAsset);
  562. CreateBuilder("stage3", "*.stage3", "stage4", false, ProductOutputFlags::ProductAsset);
  563. // Process once
  564. ProcessFileMultiStage(3, true);
  565. // Modify the source file
  566. UnitTestUtils::CreateDummyFile(m_testFilePath.c_str(), "modified unit test file");
  567. // Start processing the test.stage1 file again
  568. QMetaObject::invokeMethod(
  569. m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, m_testFilePath.c_str()));
  570. QCoreApplication::processEvents();
  571. // Process test.stage1, which should queue up test.stage2
  572. ProcessSingleStep();
  573. // Start processing test.stage2, this shouldn't create a job since the input is the same
  574. RunFile(0, 1, 0);
  575. }
  576. } // namespace UnitTests