AssetProcessorMessagesTests.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzTest/AzTest.h>
  9. #include <native/FileWatcher/FileWatcher.h>
  10. #include <utilities/BatchApplicationManager.h>
  11. #include <utilities/ApplicationServer.h>
  12. #include <AzFramework/Asset/AssetSystemComponent.h>
  13. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  14. #if !defined(Q_MOC_RUN)
  15. #include <AzCore/UnitTest/TestTypes.h>
  16. #endif
  17. #include <AzCore/Utils/Utils.h>
  18. #include <connection/connectionManager.h>
  19. #include <QCoreApplication>
  20. #include <AzFramework/Network/AssetProcessorConnection.h>
  21. #include <native/tests/MockAssetDatabaseRequestsHandler.h>
  22. #include <gmock/gmock.h>
  23. namespace AssetProcessorMessagesTests
  24. {
  25. using namespace testing;
  26. using namespace AssetProcessor;
  27. using namespace AssetBuilderSDK;
  28. static constexpr unsigned short AssetProcessorPort{65535u};
  29. class AssetProcessorMessages;
  30. class MockFileWatcher;
  31. // a 'nice' mock doesn't complain if its methods are called without a prior 'expect_call'.
  32. using NiceMockFileWatcher = ::testing::NiceMock<MockFileWatcher>;
  33. class MockFileWatcher : public FileWatcherBase
  34. {
  35. public:
  36. MOCK_METHOD2(AddFolderWatch, void(QString, bool));
  37. MOCK_METHOD0(ClearFolderWatches, void());
  38. MOCK_METHOD0(StartWatching, void());
  39. MOCK_METHOD0(StopWatching, void());
  40. MOCK_METHOD2(InstallDefaultExclusionRules, void(QString, QString));
  41. MOCK_METHOD1(AddExclusion, void(const AssetBuilderSDK::FilePatternMatcher&));
  42. MOCK_CONST_METHOD1(IsExcluded, bool(QString));
  43. };
  44. struct UnitTestBatchApplicationManager
  45. : BatchApplicationManager
  46. {
  47. UnitTestBatchApplicationManager(int* argc, char*** argv, QObject* parent)
  48. : BatchApplicationManager(argc, argv, parent)
  49. {
  50. }
  51. void InitFileStateCache() override
  52. {
  53. m_fileStateCache = AZStd::make_unique<AssetProcessor::FileStatePassthrough>();
  54. }
  55. friend class AssetProcessorMessages;
  56. };
  57. struct MockAssetCatalog : AssetProcessor::AssetCatalog
  58. {
  59. MockAssetCatalog(QObject* parent, AssetProcessor::PlatformConfiguration* platformConfiguration)
  60. : AssetCatalog(parent, platformConfiguration)
  61. {
  62. }
  63. AzFramework::AssetSystem::GetUnresolvedDependencyCountsResponse HandleGetUnresolvedDependencyCountsRequest(MessageData<AzFramework::AssetSystem::GetUnresolvedDependencyCountsRequest> messageData) override
  64. {
  65. m_called = true;
  66. return AssetCatalog::HandleGetUnresolvedDependencyCountsRequest(messageData);
  67. }
  68. bool m_called = false;
  69. };
  70. struct MockAssetRequestHandler : AssetRequestHandler
  71. {
  72. bool InvokeHandler(MessageData<AzFramework::AssetSystem::BaseAssetProcessorMessage> message) override
  73. {
  74. m_invoked = true;
  75. return AssetRequestHandler::InvokeHandler(message);
  76. }
  77. // Mimic the necessary behavior in the standard AssetRequestHandler, so the event gets called.
  78. void OnNewIncomingRequest(unsigned int connId, unsigned int serial, QByteArray payload, QString platform) override
  79. {
  80. AZ::SerializeContext* serializeContext = nullptr;
  81. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  82. AZStd::shared_ptr<AzFramework::AssetSystem::BaseAssetProcessorMessage> message{
  83. AZ::Utils::LoadObjectFromBuffer<AzFramework::AssetSystem::BaseAssetProcessorMessage>(
  84. payload.constData(), payload.size(), serializeContext)
  85. };
  86. NetworkRequestID key(connId, serial);
  87. int fenceFileId = 0;
  88. m_pendingFenceRequestMap[fenceFileId] = AZStd::move(AssetRequestHandler::RequestInfo(key, AZStd::move(message), platform));
  89. OnFenceFileDetected(fenceFileId);
  90. }
  91. AZStd::atomic_bool m_invoked = false;
  92. };
  93. class AssetProcessorMessages
  94. : public ::UnitTest::LeakDetectionFixture
  95. {
  96. public:
  97. void SetUp() override
  98. {
  99. AssetUtilities::ResetGameName();
  100. m_dbConn.OpenDatabase();
  101. int argC = 0;
  102. m_batchApplicationManager = AZStd::make_unique<UnitTestBatchApplicationManager>(&argC, nullptr, nullptr);
  103. auto registry = AZ::SettingsRegistry::Get();
  104. EXPECT_NE(registry, nullptr);
  105. constexpr AZ::SettingsRegistryInterface::FixedValueString bootstrapKey{
  106. AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey
  107. };
  108. constexpr AZ::SettingsRegistryInterface::FixedValueString projectPathKey{ bootstrapKey + "/project_path" };
  109. AZ::IO::FixedMaxPath enginePath;
  110. registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
  111. registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
  112. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
  113. // Force the branch token into settings registry before starting the application manager.
  114. // This avoids writing the asset_processor.setreg file which can cause fileIO errors.
  115. constexpr AZ::SettingsRegistryInterface::FixedValueString branchTokenKey{ bootstrapKey + "/assetProcessor_branch_token" };
  116. AZStd::string token;
  117. AZ::StringFunc::AssetPath::CalculateBranchToken(enginePath.c_str(), token);
  118. registry->Set(branchTokenKey, token.c_str());
  119. auto status = m_batchApplicationManager->BeforeRun();
  120. ASSERT_EQ(status, ApplicationManager::BeforeRunStatus::Status_Success);
  121. m_batchApplicationManager->m_platformConfiguration = new PlatformConfiguration();
  122. AZStd::vector<ApplicationManagerBase::APCommandLineSwitch> commandLineInfo;
  123. m_batchApplicationManager->InitAssetProcessorManager(commandLineInfo);
  124. m_assetCatalog = AZStd::make_unique<MockAssetCatalog>(nullptr, m_batchApplicationManager->m_platformConfiguration);
  125. m_batchApplicationManager->m_assetCatalog = m_assetCatalog.get();
  126. m_batchApplicationManager->InitRCController();
  127. m_batchApplicationManager->InitFileStateCache();
  128. m_batchApplicationManager->InitFileMonitor(AZStd::make_unique<NiceMockFileWatcher>());
  129. m_batchApplicationManager->InitApplicationServer();
  130. m_batchApplicationManager->InitConnectionManager();
  131. // Note this must be constructed after InitConnectionManager is called since it will interact with the connection manager
  132. m_assetRequestHandler = new MockAssetRequestHandler();
  133. m_batchApplicationManager->InitAssetRequestHandler(m_assetRequestHandler);
  134. m_batchApplicationManager->ConnectAssetCatalog();
  135. QObject::connect(
  136. m_batchApplicationManager->m_connectionManager,
  137. &ConnectionManager::ConnectionError,
  138. [](unsigned /*connId*/, [[maybe_unused]] QString error)
  139. {
  140. AZ_Error("ConnectionManager", false, "%s", error.toUtf8().constData());
  141. });
  142. ASSERT_TRUE(m_batchApplicationManager->m_applicationServer->startListening(AssetProcessorPort));
  143. using namespace AzFramework;
  144. m_assetSystemComponent = AZStd::make_unique<AssetSystem::AssetSystemComponent>();
  145. m_assetSystemComponent->Init();
  146. m_assetSystemComponent->Activate();
  147. QCoreApplication::processEvents();
  148. RunNetworkRequest([]()
  149. {
  150. AZStd::string appBranchToken;
  151. AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, appBranchToken);
  152. AzFramework::AssetSystem::ConnectionSettings connectionSettings;
  153. connectionSettings.m_assetProcessorIp = "127.0.0.1";
  154. connectionSettings.m_assetProcessorPort = AssetProcessorPort;
  155. connectionSettings.m_branchToken = appBranchToken;
  156. connectionSettings.m_projectName = "AutomatedTesting";
  157. connectionSettings.m_assetPlatform = "pc";
  158. connectionSettings.m_connectionIdentifier = "UNITTEST";
  159. connectionSettings.m_connectTimeout = AZStd::chrono::seconds(15);
  160. connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
  161. connectionSettings.m_waitUntilAssetProcessorIsReady = false;
  162. connectionSettings.m_launchAssetProcessorOnFailedConnection = false;
  163. bool result = false;
  164. AzFramework::AssetSystemRequestBus::BroadcastResult(result,
  165. &AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
  166. ASSERT_TRUE(result);
  167. });
  168. }
  169. void TearDown() override
  170. {
  171. if (m_batchApplicationManager->m_connectionManager)
  172. {
  173. QEventLoop eventLoop;
  174. QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit);
  175. m_batchApplicationManager->m_connectionManager->QuitRequested();
  176. eventLoop.exec();
  177. }
  178. if (m_assetSystemComponent)
  179. {
  180. m_assetSystemComponent->Deactivate();
  181. }
  182. m_batchApplicationManager->Destroy();
  183. m_assetCatalog.reset();
  184. m_assetSystemComponent.reset();
  185. m_batchApplicationManager.reset();
  186. }
  187. void RunNetworkRequest(AZStd::function<void()> func) const
  188. {
  189. AZStd::atomic_bool finished = false;
  190. auto start = AZStd::chrono::steady_clock::now();
  191. auto thread = AZStd::thread({/*m_name =*/ "MessageTests"}, [&finished, &func]()
  192. {
  193. func();
  194. finished = true;
  195. }
  196. );
  197. constexpr int MaxWaitTime = 5;
  198. while (!finished && AZStd::chrono::steady_clock::now() - start < AZStd::chrono::seconds(MaxWaitTime))
  199. {
  200. QCoreApplication::processEvents();
  201. }
  202. ASSERT_TRUE(finished) << "Timeout";
  203. thread.join();
  204. }
  205. protected:
  206. MockAssetRequestHandler* m_assetRequestHandler{}; // Not owned, AP will delete this pointer
  207. AZStd::unique_ptr<UnitTestBatchApplicationManager> m_batchApplicationManager;
  208. AZStd::unique_ptr<AzFramework::AssetSystem::AssetSystemComponent> m_assetSystemComponent;
  209. AssetProcessor::MockAssetDatabaseRequestsHandler m_databaseLocationListener;
  210. AZStd::unique_ptr<MockAssetCatalog> m_assetCatalog = nullptr;
  211. AZStd::string m_databaseLocation;
  212. AssetDatabaseConnection m_dbConn;
  213. };
  214. struct MessagePair
  215. {
  216. AZStd::unique_ptr<AzFramework::AssetSystem::BaseAssetProcessorMessage> m_request;
  217. AZStd::unique_ptr<AzFramework::AssetSystem::BaseAssetProcessorMessage> m_response;
  218. };
  219. TEST_F(AssetProcessorMessages, All)
  220. {
  221. // Test that we can successfully send network messages and have them arrive for processing
  222. // For messages that have a response, it also verifies the response comes back
  223. // Note that several harmless warnings will be triggered due to the messages not having any data set
  224. using namespace AzFramework::AssetSystem;
  225. using namespace AzToolsFramework::AssetSystem;
  226. AZStd::vector<MessagePair> testMessages;
  227. AZStd::unordered_map<int, AZStd::string> nameMap; // This is just for debugging, so we can output the name of failed messages
  228. AZ::SerializeContext* serializeContext = nullptr;
  229. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  230. auto addPairFunc = [&testMessages, &nameMap, serializeContext](auto* request, auto* response)
  231. {
  232. testMessages.emplace_back(MessagePair{
  233. AZStd::unique_ptr<AZStd::remove_pointer_t<decltype(request)>>(request),
  234. AZStd::unique_ptr<AZStd::remove_pointer_t<decltype(response)>>(response)
  235. });
  236. auto data = serializeContext->FindClassData(request->RTTI_GetType());
  237. nameMap[request->GetMessageType()] = data->m_name;
  238. };
  239. auto addRequestFunc = [&testMessages, &nameMap, serializeContext](auto* request)
  240. {
  241. testMessages.emplace_back(MessagePair{AZStd::unique_ptr<AZStd::remove_pointer_t<decltype(request)>>(request), nullptr });
  242. auto data = serializeContext->FindClassData(request->RTTI_GetType());
  243. nameMap[request->GetMessageType()] = data->m_name;
  244. };
  245. addPairFunc(new GetFullSourcePathFromRelativeProductPathRequest(), new GetFullSourcePathFromRelativeProductPathResponse());
  246. addPairFunc(new GetRelativeProductPathFromFullSourceOrProductPathRequest(), new GetRelativeProductPathFromFullSourceOrProductPathResponse());
  247. addPairFunc(
  248. new GenerateRelativeSourcePathRequest(),
  249. new GenerateRelativeSourcePathResponse());
  250. addPairFunc(new SourceAssetInfoRequest(), new SourceAssetInfoResponse());
  251. addPairFunc(new SourceAssetProductsInfoRequest(), new SourceAssetProductsInfoResponse());
  252. addPairFunc(new GetScanFoldersRequest(), new GetScanFoldersResponse());
  253. addPairFunc(new GetAssetSafeFoldersRequest(), new GetAssetSafeFoldersResponse());
  254. addRequestFunc(new RegisterSourceAssetRequest());
  255. addRequestFunc(new UnregisterSourceAssetRequest());
  256. addPairFunc(new AssetInfoRequest(), new AssetInfoResponse());
  257. addPairFunc(new AssetDependencyInfoRequest(), new AssetDependencyInfoResponse());
  258. addRequestFunc(new RequestEscalateAsset());
  259. addPairFunc(new RequestAssetStatus(), new ResponseAssetStatus());
  260. addPairFunc(new AssetFingerprintClearRequest(), new AssetFingerprintClearResponse());
  261. RunNetworkRequest([&testMessages, &nameMap, this]()
  262. {
  263. for(auto&& pair : testMessages)
  264. {
  265. AZStd::string messageName = nameMap[pair.m_request->GetMessageType()];
  266. m_assetRequestHandler->m_invoked = false;
  267. if(pair.m_response)
  268. {
  269. EXPECT_TRUE(SendRequest(*pair.m_request.get(), *pair.m_response.get())) << "Message " << messageName.c_str() << " failed to send";
  270. }
  271. else
  272. {
  273. EXPECT_TRUE(SendRequest(*pair.m_request.get())) << "Message " << messageName.c_str() << " failed to send";
  274. }
  275. // Even if there is a response, the send request may finish before the response finishes, so wait a few seconds to see if the message has sent.
  276. // This exits early if the message is invoked.
  277. constexpr int MaxWaitTimeSeconds = 5;
  278. auto start = AZStd::chrono::steady_clock::now();
  279. while (!m_assetRequestHandler->m_invoked &&
  280. AZStd::chrono::steady_clock::now() - start < AZStd::chrono::seconds(MaxWaitTimeSeconds))
  281. {
  282. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
  283. }
  284. EXPECT_TRUE(m_assetRequestHandler->m_invoked) << "Message " << messageName.c_str() << " was not received";
  285. }
  286. });
  287. }
  288. TEST_F(AssetProcessorMessages, GetUnresolvedProductReferences_Succeeds)
  289. {
  290. using namespace AzToolsFramework::AssetDatabase;
  291. // Setup the database with all needed info
  292. ScanFolderDatabaseEntry scanfolder1("scanfolder1", "scanfolder1", "scanfolder1");
  293. ASSERT_TRUE(m_dbConn.SetScanFolder(scanfolder1));
  294. SourceDatabaseEntry source1(scanfolder1.m_scanFolderID, "source1.png", AZ::Uuid::CreateRandom(), "Fingerprint");
  295. SourceDatabaseEntry source2(scanfolder1.m_scanFolderID, "source2.png", AZ::Uuid::CreateRandom(), "Fingerprint");
  296. ASSERT_TRUE(m_dbConn.SetSource(source1));
  297. ASSERT_TRUE(m_dbConn.SetSource(source2));
  298. JobDatabaseEntry job1(source1.m_sourceID, "jobkey", 1234, "pc", AZ::Uuid::CreateRandom(), AzToolsFramework::AssetSystem::JobStatus::Completed, 1111);
  299. JobDatabaseEntry job2(source2.m_sourceID, "jobkey", 1234, "pc", AZ::Uuid::CreateRandom(), AzToolsFramework::AssetSystem::JobStatus::Completed, 2222);
  300. ASSERT_TRUE(m_dbConn.SetJob(job1));
  301. ASSERT_TRUE(m_dbConn.SetJob(job2));
  302. ProductDatabaseEntry product1(job1.m_jobID, 5, "source1.product", AZ::Data::AssetType::CreateRandom());
  303. ProductDatabaseEntry product2(job2.m_jobID, 15, "source2.product", AZ::Data::AssetType::CreateRandom());
  304. ASSERT_TRUE(m_dbConn.SetProduct(product1));
  305. ASSERT_TRUE(m_dbConn.SetProduct(product2));
  306. ProductDependencyDatabaseEntry dependency1(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileA.txt", ProductDependencyDatabaseEntry::DependencyType::ProductDep_SourceFile);
  307. ProductDependencyDatabaseEntry dependency2(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileB.txt", ProductDependencyDatabaseEntry::DependencyType::ProductDep_ProductFile);
  308. ProductDependencyDatabaseEntry dependency3(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileC.txt");
  309. ProductDependencyDatabaseEntry dependency4(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, ":somefileD.txt"); // Exclusion
  310. ProductDependencyDatabaseEntry dependency5(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileE*.txt"); // Wildcard
  311. ASSERT_TRUE(m_dbConn.SetProductDependency(dependency1));
  312. ASSERT_TRUE(m_dbConn.SetProductDependency(dependency2));
  313. ASSERT_TRUE(m_dbConn.SetProductDependency(dependency3));
  314. ASSERT_TRUE(m_dbConn.SetProductDependency(dependency4));
  315. ASSERT_TRUE(m_dbConn.SetProductDependency(dependency5));
  316. // Setup the asset catalog
  317. AzFramework::AssetSystem::AssetNotificationMessage assetNotificationMessage("source1.product", AzFramework::AssetSystem::AssetNotificationMessage::NotificationType::AssetChanged, AZ::Data::AssetType::CreateRandom(), "pc");
  318. assetNotificationMessage.m_assetId = AZ::Data::AssetId(source1.m_sourceGuid, product1.m_subID);
  319. assetNotificationMessage.m_dependencies.push_back(AZ::Data::ProductDependency(AZ::Data::AssetId(source2.m_sourceGuid, product2.m_subID), {}));
  320. m_assetCatalog->OnAssetMessage(assetNotificationMessage);
  321. // Run the actual test
  322. RunNetworkRequest([&source1, &product1]()
  323. {
  324. using namespace AzFramework;
  325. AZ::u32 assetReferenceCount, pathReferenceCount;
  326. AZ::Data::AssetId assetId = AZ::Data::AssetId(source1.m_sourceGuid, product1.m_subID);
  327. AssetSystemRequestBus::Broadcast(&AssetSystemRequestBus::Events::GetUnresolvedProductReferences, assetId, assetReferenceCount, pathReferenceCount);
  328. ASSERT_EQ(assetReferenceCount, 1);
  329. ASSERT_EQ(pathReferenceCount, 3);
  330. });
  331. ASSERT_TRUE(m_assetCatalog->m_called);
  332. }
  333. }