AssetManagerTestingBase.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  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/Component/Entity.h>
  9. #include <AzCore/Jobs/JobContext.h>
  10. #include <AzCore/Jobs/JobManager.h>
  11. #include <AzCore/Jobs/JobManagerComponent.h>
  12. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  13. #include <AzCore/std/parallel/binary_semaphore.h>
  14. #include <AzFramework/IO/LocalFileIO.h>
  15. #include <QCoreApplication>
  16. #include <native/tests/assetmanager/AssetManagerTestingBase.h>
  17. #include <native/utilities/AssetUtilEBusHelper.h>
  18. #include <unittests/UnitTestUtils.h>
  19. #include <AzCore/Utils/Utils.h>
  20. #include <AzCore/Serialization/Json/JsonSystemComponent.h>
  21. namespace UnitTests
  22. {
  23. const char* JOB_PROCESS_FAIL_TEXT = "AUTO_FAIL_JOB";
  24. void TestingAssetProcessorManager::CheckActiveFiles(int count)
  25. {
  26. ASSERT_EQ(m_activeFiles.size(), count);
  27. }
  28. void TestingAssetProcessorManager::CheckFilesToExamine(int count)
  29. {
  30. ASSERT_EQ(m_filesToExamine.size(), count);
  31. }
  32. void TestingAssetProcessorManager::CheckJobEntries(int count)
  33. {
  34. ASSERT_EQ(m_jobEntries.size(), count);
  35. }
  36. void AssetManagerTestingBase::SetUp()
  37. {
  38. LeakDetectionFixture::SetUp();
  39. // File IO is needed to hash the files
  40. if (AZ::IO::FileIOBase::GetInstance() == nullptr)
  41. {
  42. m_localFileIo = aznew AZ::IO::LocalFileIO();
  43. AZ::IO::FileIOBase::SetInstance(m_localFileIo);
  44. }
  45. // Specify the database lives in the temp directory
  46. AZ::IO::Path assetRootDir(m_databaseLocationListener.GetAssetRootDir());
  47. // We need a settings registry in order for APM to figure out the cache path
  48. m_settingsRegistry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
  49. AZ::SettingsRegistry::Register(m_settingsRegistry.get());
  50. // Make sure that the entire system doesn't somehow find the "real" project but instead finds our fake project folder.
  51. m_settingsRegistry->Set("/O3DE/Runtime/Internal/project_root_scan_up_path", assetRootDir.c_str());
  52. // The engine is actually pretty good at finding the real project folder and tries to do so a number of ways, including
  53. // overwriting all the keys we're about to set if we allow it to.
  54. auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
  55. m_settingsRegistry->Set(projectPathKey, assetRootDir.c_str());
  56. // we need to also set up the cache root:
  57. auto cacheRootPathKey =
  58. AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder);
  59. m_settingsRegistry->Set(cacheRootPathKey, (assetRootDir / "Cache").c_str());
  60. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry);
  61. // We need a QCoreApplication set up in order for QCoreApplication::processEvents to function
  62. m_qApp = AZStd::make_unique<QCoreApplication>(m_argc, m_argv);
  63. qRegisterMetaType<AssetProcessor::JobEntry>("JobEntry");
  64. qRegisterMetaType<AssetBuilderSDK::ProcessJobResponse>("ProcessJobResponse");
  65. qRegisterMetaType<AZStd::string>("AZStd::string");
  66. qRegisterMetaType<AssetProcessor::AssetScanningStatus>("AssetProcessor::AssetScanningStatus");
  67. qRegisterMetaType<QSet<AssetProcessor::AssetFileInfo>>("QSet<AssetFileInfo>");
  68. qRegisterMetaType<AssetProcessor::SourceAssetReference>("SourceAssetReference");
  69. // Platform config with an enabled platform and scanfolder required by APM to function and find the files
  70. m_platformConfig = AZStd::make_unique<AssetProcessor::PlatformConfiguration>();
  71. m_platformConfig->EnablePlatform(AssetBuilderSDK::PlatformInfo{ "pc", { "test" } });
  72. m_platformConfig->EnableCommonPlatform();
  73. AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms;
  74. m_platformConfig->PopulatePlatformsForScanFolder(platforms);
  75. m_platformConfig->ReadMetaDataFromSettingsRegistry();
  76. SetupScanfolders(assetRootDir, platforms);
  77. m_platformConfig->AddIntermediateScanFolder();
  78. // Create the APM
  79. m_assetProcessorManager = AZStd::make_unique<TestingAssetProcessorManager>(m_platformConfig.get());
  80. m_assetProcessorManager->SetMetaCreationDelay(0);
  81. // Cache the db pointer because the TEST_F generates a subclass which can't access this private member
  82. m_stateData = m_assetProcessorManager->m_stateData;
  83. // Cache the scanfolder db entry, for convenience
  84. ASSERT_TRUE(m_stateData->GetScanFolderByPortableKey("folder", m_scanfolder));
  85. // Configure our mock builder so APM can find the builder and run CreateJobs
  86. m_builderInfoHandler.CreateBuilderDesc(
  87. "test", AZ::Uuid::CreateRandom().ToFixedString().c_str(),
  88. { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) }, {});
  89. m_builderInfoHandler.BusConnect();
  90. // Set up the Job Context, required for the PathDependencyManager to do its work
  91. // Set up serialize and json context
  92. m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
  93. m_jsonRegistrationContext = AZStd::make_unique<AZ::JsonRegistrationContext>();
  94. m_componentApplication = AZStd::make_unique<testing::NiceMock<MockComponentApplication>>();
  95. using namespace testing;
  96. ON_CALL(*m_componentApplication.get(), GetSerializeContext()).WillByDefault(Return(m_serializeContext.get()));
  97. ON_CALL(*m_componentApplication.get(), GetJsonRegistrationContext()).WillByDefault(Return(m_jsonRegistrationContext.get()));
  98. ON_CALL(*m_componentApplication.get(), AddEntity(_)).WillByDefault(Return(true));
  99. AZ::JsonSystemComponent::Reflect(m_jsonRegistrationContext.get());
  100. m_descriptor = AZ::JobManagerComponent::CreateDescriptor();
  101. m_descriptor->Reflect(m_serializeContext.get());
  102. m_jobManagerEntity = aznew AZ::Entity{};
  103. m_jobManagerEntity->CreateComponent<AZ::JobManagerComponent>();
  104. m_jobManagerEntity->Init();
  105. m_jobManagerEntity->Activate();
  106. AzToolsFramework::MetadataManager::Reflect(m_serializeContext.get());
  107. AzToolsFramework::UuidUtilComponent::Reflect(m_serializeContext.get());
  108. // Set up a mock disk space responder, required for RCController to process a job
  109. m_diskSpaceResponder = AZStd::make_unique<::testing::NiceMock<MockDiskSpaceResponder>>();
  110. ON_CALL(*m_diskSpaceResponder, CheckSufficientDiskSpace(::testing::_, ::testing::_))
  111. .WillByDefault(::testing::Return(true));
  112. QObject::connect(
  113. m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetToProcess,
  114. [this](AssetProcessor::JobDetails jobDetails)
  115. {
  116. m_jobDetailsList.push_back(jobDetails);
  117. });
  118. AZ::IO::Path scanFolderDir(m_scanfolder.m_scanFolder);
  119. AZStd::string testFilename = "test.stage1";
  120. m_testFilePath = (scanFolderDir / testFilename).AsPosix().c_str();
  121. AZ::Utils::WriteFile("unit test file", m_testFilePath);
  122. m_rc = AZStd::make_unique<TestingRCController>(1, 1);
  123. m_rc->SetDispatchPaused(false);
  124. QObject::connect(
  125. m_rc.get(),
  126. &AssetProcessor::RCController::FileFailed,
  127. [this]([[maybe_unused]] auto entryIn)
  128. {
  129. m_fileFailed = true;
  130. });
  131. QObject::connect(
  132. m_rc.get(),
  133. &AssetProcessor::RCController::FileCompiled,
  134. [this](auto jobEntry, auto response)
  135. {
  136. m_fileCompiled = true;
  137. m_processedJobEntry = jobEntry;
  138. m_processJobResponse = response;
  139. });
  140. AZ::IO::FileIOBase::GetInstance()->SetAlias("@log@", (AZ::IO::Path(m_databaseLocationListener.GetAssetRootDir()) / "logs").c_str());
  141. }
  142. void AssetManagerTestingBase::TearDown()
  143. {
  144. m_diskSpaceResponder = nullptr;
  145. m_builderInfoHandler.BusDisconnect();
  146. AZ::SettingsRegistry::Unregister(m_settingsRegistry.get());
  147. m_jsonRegistrationContext->EnableRemoveReflection();
  148. AZ::JsonSystemComponent::Reflect(m_jsonRegistrationContext.get());
  149. m_jsonRegistrationContext->DisableRemoveReflection();
  150. m_jsonRegistrationContext.reset();
  151. m_serializeContext.reset();
  152. if (m_localFileIo)
  153. {
  154. delete m_localFileIo;
  155. m_localFileIo = nullptr;
  156. AZ::IO::FileIOBase::SetInstance(nullptr);
  157. }
  158. m_jobManagerEntity->Deactivate();
  159. delete m_jobManagerEntity;
  160. delete m_descriptor;
  161. m_stateData.reset();
  162. m_assetProcessorManager.reset();
  163. LeakDetectionFixture::TearDown();
  164. }
  165. void AssetManagerTestingBase::SetupScanfolders(AZ::IO::Path assetRootDir, const AZStd::vector<AssetBuilderSDK::PlatformInfo>& platforms)
  166. {
  167. m_platformConfig->AddScanFolder(
  168. AssetProcessor::ScanFolderInfo{ (assetRootDir / "folder").c_str(), "folder", "folder", false, true, platforms });
  169. }
  170. void AssetManagerTestingBase::RunFile(int expectedJobCount, int expectedFileCount, int dependencyFileCount)
  171. {
  172. m_jobDetailsList.clear();
  173. m_assetProcessorManager->CheckActiveFiles(expectedFileCount);
  174. AZStd::atomic_bool delayed = false;
  175. QObject::connect(
  176. m_assetProcessorManager.get(),
  177. &AssetProcessor::AssetProcessorManager::ProcessingDelayed,
  178. [&delayed]([[maybe_unused]] QString filePath)
  179. {
  180. delayed = true;
  181. });
  182. QObject::connect(
  183. m_assetProcessorManager.get(),
  184. &AssetProcessor::AssetProcessorManager::ProcessingResumed,
  185. [&delayed]([[maybe_unused]] QString filePath)
  186. {
  187. delayed = false;
  188. });
  189. QCoreApplication::processEvents(); // execute CheckSource
  190. if (delayed)
  191. {
  192. // Wait for the QTimer to elapse. This should be a very quick, sub 10ms wait.
  193. // Add 5ms just to be sure the required time has elapsed.
  194. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(MetadataProcessingDelayMs + 5));
  195. ASSERT_TRUE(delayed);
  196. QCoreApplication::processEvents(); // Process the timer
  197. // Sometimes the above processEvents runs CheckSource
  198. if (delayed)
  199. {
  200. QCoreApplication::processEvents(); // execute CheckSource again
  201. }
  202. ASSERT_FALSE(delayed);
  203. }
  204. m_assetProcessorManager->CheckActiveFiles(0);
  205. m_assetProcessorManager->CheckFilesToExamine(expectedFileCount + dependencyFileCount);
  206. QCoreApplication::processEvents(); // execute ProcessFilesToExamineQueue
  207. if (expectedJobCount > 0)
  208. {
  209. m_assetProcessorManager->CheckJobEntries(expectedFileCount + dependencyFileCount);
  210. QCoreApplication::processEvents(); // execute CheckForIdle
  211. }
  212. ASSERT_EQ(m_jobDetailsList.size(), expectedJobCount + dependencyFileCount);
  213. }
  214. void AssetManagerTestingBase::ProcessJob(AssetProcessor::RCController& rcController, const AssetProcessor::JobDetails& jobDetails)
  215. {
  216. rcController.JobSubmitted(jobDetails);
  217. UnitTests::JobSignalReceiver receiver;
  218. WaitForNextJobToProcess(receiver);
  219. }
  220. void AssetManagerTestingBase::WaitForNextJobToProcess(UnitTests::JobSignalReceiver &receiver)
  221. {
  222. QCoreApplication::processEvents(); // RCController::DispatchJobsImpl : Once to get the job started
  223. receiver.WaitForFinish(); // Wait for the RCJob to signal it has completed working
  224. QCoreApplication::processEvents(); // RCJob::Finished : Once more to trigger the JobFinished event
  225. QCoreApplication::processEvents(); // RCController::FinishJob : Again to trigger the Finished event
  226. }
  227. AssetBuilderSDK::CreateJobFunction AssetManagerTestingBase::CreateJobStage(
  228. const AZStd::string& name, bool commonPlatform, const AzToolsFramework::AssetDatabase::PathOrUuid& sourceDependency)
  229. {
  230. using namespace AssetBuilderSDK;
  231. // Note: capture by copy because we need these to stay around for a long time
  232. return [name, commonPlatform, sourceDependency]([[maybe_unused]] const CreateJobsRequest& request, CreateJobsResponse& response)
  233. {
  234. if (commonPlatform)
  235. {
  236. response.m_createJobOutputs.push_back(JobDescriptor{ "fingerprint", name, CommonPlatformName });
  237. }
  238. else
  239. {
  240. for (const auto& platform : request.m_enabledPlatforms)
  241. {
  242. response.m_createJobOutputs.push_back(JobDescriptor{ "fingerprint", name, platform.m_identifier.c_str() });
  243. }
  244. }
  245. if (sourceDependency)
  246. {
  247. response.m_sourceFileDependencyList.push_back(
  248. SourceFileDependency{ sourceDependency.IsUuid() ? "" : sourceDependency.GetPath(), sourceDependency.IsUuid() ? sourceDependency.GetUuid() : AZ::Uuid::CreateNull() });
  249. }
  250. response.m_result = CreateJobsResultCode::Success;
  251. };
  252. }
  253. AssetBuilderSDK::ProcessJobFunction AssetManagerTestingBase::ProcessJobStage(
  254. const AZStd::string& outputExtension, AssetBuilderSDK::ProductOutputFlags flags, bool outputExtraFile, AZ::Data::AssetId dependencyId)
  255. {
  256. using namespace AssetBuilderSDK;
  257. // Capture by copy because we need these to stay around a long time
  258. return [outputExtension, flags, outputExtraFile, dependencyId](const ProcessJobRequest& request, ProcessJobResponse& response)
  259. {
  260. // If tests put the text "FAIL_JOB" at the beginning of the source file, then fail this job instead.
  261. // This lets tests easily handle cases where they want job processing to fail.
  262. auto readResult = AZ::Utils::ReadFile<AZStd::string>(request.m_fullPath, AZStd::numeric_limits<size_t>::max());
  263. // Don't fail if the read fails, there may be existing tests that create unreadable files.
  264. if (readResult.IsSuccess())
  265. {
  266. if (readResult.GetValue().starts_with(JOB_PROCESS_FAIL_TEXT))
  267. {
  268. response.m_resultCode = ProcessJobResult_Failed;
  269. return;
  270. }
  271. }
  272. AZ::IO::FixedMaxPath outputFile = AZ::IO::FixedMaxPath(request.m_sourceFile);
  273. outputFile.ReplaceExtension(outputExtension.c_str());
  274. outputFile = outputFile.Filename();
  275. AZ::IO::Result result = AZ::IO::FileIOBase::GetInstance()->Copy(
  276. request.m_fullPath.c_str(), (AZ::IO::FixedMaxPath(request.m_tempDirPath) / outputFile).c_str());
  277. EXPECT_TRUE(result);
  278. auto product = JobProduct{ outputFile.c_str(), AZ::Data::AssetType::CreateName(outputExtension.c_str()), AssetSubId };
  279. product.m_outputFlags = flags;
  280. product.m_dependenciesHandled = true;
  281. if (dependencyId.IsValid())
  282. {
  283. product.m_dependencies.push_back(AssetBuilderSDK::ProductDependency(dependencyId, {}));
  284. }
  285. response.m_outputProducts.push_back(product);
  286. if (outputExtraFile)
  287. {
  288. auto extraFilePath =
  289. AZ::IO::Path(request.m_tempDirPath) / "z_extra.txt"; // Z prefix to place at end of list when sorting for processing
  290. AZ::Utils::WriteFile("unit test file", extraFilePath.Native());
  291. auto extraProduct = JobProduct{ extraFilePath.c_str(), AZ::Data::AssetType::CreateName("extra"), ExtraAssetSubId };
  292. extraProduct.m_outputFlags = flags;
  293. extraProduct.m_dependenciesHandled = true;
  294. response.m_outputProducts.push_back(extraProduct);
  295. }
  296. response.m_resultCode = ProcessJobResult_Success;
  297. };
  298. }
  299. const char* AssetManagerTestingBase::GetJobProcessFailText()
  300. {
  301. return JOB_PROCESS_FAIL_TEXT;
  302. }
  303. AZ::IO::Path AssetManagerTestingBase::GetCacheDir()
  304. {
  305. return AZ::IO::Path(m_databaseLocationListener.GetAssetRootDir()) / "Cache";
  306. }
  307. AZ::IO::FixedMaxPath AssetManagerTestingBase::GetIntermediateAssetsDir()
  308. {
  309. return AssetUtilities::GetIntermediateAssetsFolder(GetCacheDir());
  310. }
  311. void AssetManagerTestingBase::CreateBuilder(
  312. const char* name,
  313. const char* inputFilter,
  314. const char* outputExtension,
  315. bool createJobCommonPlatform,
  316. AssetBuilderSDK::ProductOutputFlags outputFlags,
  317. bool outputExtraFile)
  318. {
  319. using namespace AssetBuilderSDK;
  320. m_builderInfoHandler.CreateBuilderDesc(
  321. name,
  322. AZ::Uuid::CreateRandom().ToFixedString().c_str(),
  323. { AssetBuilderPattern{ inputFilter, AssetBuilderPattern::Wildcard } },
  324. CreateJobStage(name, createJobCommonPlatform),
  325. ProcessJobStage(outputExtension, outputFlags, outputExtraFile),
  326. "fingerprint");
  327. }
  328. void AssetManagerTestingBase::SetCatalogToUpdateOnJobCompletion()
  329. {
  330. using namespace AssetBuilderSDK;
  331. QObject::connect(
  332. m_rc.get(),
  333. &AssetProcessor::RCController::FileCompiled,
  334. [this](AssetProcessor::JobEntry entry, [[maybe_unused]] AssetBuilderSDK::ProcessJobResponse response)
  335. {
  336. QMetaObject::invokeMethod(m_rc.get(), "OnAddedToCatalog", Qt::QueuedConnection, Q_ARG(AssetProcessor::JobEntry, entry));
  337. });
  338. }
  339. AZStd::string AssetManagerTestingBase::MakePath(const char* filename, bool intermediate)
  340. {
  341. auto cacheDir = GetCacheDir();
  342. if (intermediate)
  343. {
  344. cacheDir = AssetUtilities::GetIntermediateAssetsFolder(cacheDir);
  345. return (cacheDir / filename).StringAsPosix();
  346. }
  347. return (cacheDir / "pc" / filename).StringAsPosix();
  348. }
  349. void AssetManagerTestingBase::CheckProduct(const char* relativePath, bool exists)
  350. {
  351. auto expectedProductPath = MakePath(relativePath, false);
  352. EXPECT_EQ(AZ::IO::SystemFile::Exists(expectedProductPath.c_str()), exists) << expectedProductPath.c_str();
  353. }
  354. void AssetManagerTestingBase::CheckIntermediate(const char* relativePath, bool exists, bool hasMetadata)
  355. {
  356. auto expectedIntermediatePath = MakePath(relativePath, true);
  357. auto expectedMetadataPath = AzToolsFramework::MetadataManager::ToMetadataPath(expectedIntermediatePath);
  358. EXPECT_EQ(AZ::IO::SystemFile::Exists(expectedIntermediatePath.c_str()), exists) << expectedIntermediatePath.c_str();
  359. EXPECT_EQ(AZ::IO::SystemFile::Exists(expectedMetadataPath.c_str()), hasMetadata) << expectedMetadataPath.c_str();
  360. }
  361. void AssetManagerTestingBase::ProcessSingleStep(int expectedJobCount, int expectedFileCount, int jobToRun, bool expectSuccess)
  362. {
  363. // Reset state
  364. m_jobDetailsList.clear();
  365. m_fileCompiled = false;
  366. m_fileFailed = false;
  367. RunFile(expectedJobCount, expectedFileCount);
  368. std::stable_sort(
  369. m_jobDetailsList.begin(),
  370. m_jobDetailsList.end(),
  371. [](const AssetProcessor::JobDetails& a, const AssetProcessor::JobDetails& b) -> bool
  372. {
  373. return a.m_jobEntry.m_sourceAssetReference < b.m_jobEntry.m_sourceAssetReference;
  374. });
  375. ProcessJob(*m_rc, m_jobDetailsList[jobToRun]);
  376. if (expectSuccess)
  377. {
  378. ASSERT_TRUE(m_fileCompiled);
  379. m_assetProcessorManager->AssetProcessed(m_processedJobEntry, m_processJobResponse);
  380. }
  381. else
  382. {
  383. ASSERT_TRUE(m_fileFailed);
  384. }
  385. }
  386. void AssetManagerTestingBase::ProcessFileMultiStage(
  387. int endStage, bool doProductOutputCheck, AssetProcessor::SourceAssetReference sourceAsset, int startStage, bool expectAutofail, bool hasExtraFile)
  388. {
  389. if (!sourceAsset)
  390. {
  391. sourceAsset = AssetProcessor::SourceAssetReference(m_testFilePath.c_str());
  392. }
  393. QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, sourceAsset.AbsolutePath().c_str()));
  394. QCoreApplication::processEvents();
  395. for (int i = startStage; i <= endStage; ++i)
  396. {
  397. int expectedJobCount = 1;
  398. int expectedFileCount = 1;
  399. int jobToRun = 0;
  400. // If there's an extra file output, it'll only show up after the 1st iteration
  401. if (i > startStage && hasExtraFile)
  402. {
  403. expectedJobCount = 2;
  404. expectedFileCount = 2;
  405. }
  406. else if (expectAutofail)
  407. {
  408. expectedJobCount = 2;
  409. jobToRun = 1;
  410. }
  411. ProcessSingleStep(expectedJobCount, expectedFileCount, jobToRun, true);
  412. if (expectAutofail)
  413. {
  414. ASSERT_GT(m_jobDetailsList.size(), 0);
  415. EXPECT_TRUE(m_jobDetailsList[0].m_autoFail);
  416. }
  417. if (i < endStage)
  418. {
  419. auto expectedIntermediatePath =
  420. MakePath(sourceAsset.RelativePath().ReplaceExtension(AZStd::string::format("stage%d", i + 1).c_str()).c_str(), true);
  421. EXPECT_TRUE(AZ::IO::SystemFile::Exists(expectedIntermediatePath.c_str())) << expectedIntermediatePath.c_str();
  422. }
  423. // Only first job should have an autofail due to a conflict
  424. expectAutofail = false;
  425. }
  426. m_assetProcessorManager->CheckFilesToExamine(0);
  427. m_assetProcessorManager->CheckActiveFiles(0);
  428. m_assetProcessorManager->CheckJobEntries(0);
  429. if (doProductOutputCheck)
  430. {
  431. CheckProduct(sourceAsset.RelativePath().ReplaceExtension(AZStd::string::format("stage%d", endStage + 1).c_str()).c_str());
  432. }
  433. }
  434. } // namespace UnitTests