AssetProcessorMessagesTests.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ConnectionError, [](unsigned /*connId*/, QString error)
  136. {
  137. AZ_Error("ConnectionManager", false, "%s", error.toUtf8().constData());
  138. });
  139. ASSERT_TRUE(m_batchApplicationManager->m_applicationServer->startListening(AssetProcessorPort));
  140. using namespace AzFramework;
  141. m_assetSystemComponent = AZStd::make_unique<AssetSystem::AssetSystemComponent>();
  142. m_assetSystemComponent->Init();
  143. m_assetSystemComponent->Activate();
  144. QCoreApplication::processEvents();
  145. RunNetworkRequest([]()
  146. {
  147. AZStd::string appBranchToken;
  148. AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, appBranchToken);
  149. AzFramework::AssetSystem::ConnectionSettings connectionSettings;
  150. connectionSettings.m_assetProcessorIp = "127.0.0.1";
  151. connectionSettings.m_assetProcessorPort = AssetProcessorPort;
  152. connectionSettings.m_branchToken = appBranchToken;
  153. connectionSettings.m_projectName = "AutomatedTesting";
  154. connectionSettings.m_assetPlatform = "pc";
  155. connectionSettings.m_connectionIdentifier = "UNITTEST";
  156. connectionSettings.m_connectTimeout = AZStd::chrono::seconds(15);
  157. connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
  158. connectionSettings.m_waitUntilAssetProcessorIsReady = false;
  159. connectionSettings.m_launchAssetProcessorOnFailedConnection = false;
  160. bool result = false;
  161. AzFramework::AssetSystemRequestBus::BroadcastResult(result,
  162. &AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
  163. ASSERT_TRUE(result);
  164. });
  165. }
  166. void TearDown() override
  167. {
  168. if (m_batchApplicationManager->m_connectionManager)
  169. {
  170. QEventLoop eventLoop;
  171. QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit);
  172. m_batchApplicationManager->m_connectionManager->QuitRequested();
  173. eventLoop.exec();
  174. }
  175. if (m_assetSystemComponent)
  176. {
  177. m_assetSystemComponent->Deactivate();
  178. }
  179. m_batchApplicationManager->Destroy();
  180. m_assetCatalog.reset();
  181. m_assetSystemComponent.reset();
  182. m_batchApplicationManager.reset();
  183. }
  184. void RunNetworkRequest(AZStd::function<void()> func) const
  185. {
  186. AZStd::atomic_bool finished = false;
  187. auto start = AZStd::chrono::steady_clock::now();
  188. auto thread = AZStd::thread({/*m_name =*/ "MessageTests"}, [&finished, &func]()
  189. {
  190. func();
  191. finished = true;
  192. }
  193. );
  194. constexpr int MaxWaitTime = 5;
  195. while (!finished && AZStd::chrono::steady_clock::now() - start < AZStd::chrono::seconds(MaxWaitTime))
  196. {
  197. QCoreApplication::processEvents();
  198. }
  199. ASSERT_TRUE(finished) << "Timeout";
  200. thread.join();
  201. }
  202. protected:
  203. MockAssetRequestHandler* m_assetRequestHandler{}; // Not owned, AP will delete this pointer
  204. AZStd::unique_ptr<UnitTestBatchApplicationManager> m_batchApplicationManager;
  205. AZStd::unique_ptr<AzFramework::AssetSystem::AssetSystemComponent> m_assetSystemComponent;
  206. AssetProcessor::MockAssetDatabaseRequestsHandler m_databaseLocationListener;
  207. AZStd::unique_ptr<MockAssetCatalog> m_assetCatalog = nullptr;
  208. AZStd::string m_databaseLocation;
  209. AssetDatabaseConnection m_dbConn;
  210. };
  211. struct MessagePair
  212. {
  213. AZStd::unique_ptr<AzFramework::AssetSystem::BaseAssetProcessorMessage> m_request;
  214. AZStd::unique_ptr<AzFramework::AssetSystem::BaseAssetProcessorMessage> m_response;
  215. };
  216. TEST_F(AssetProcessorMessages, All)
  217. {
  218. // Test that we can successfully send network messages and have them arrive for processing
  219. // For messages that have a response, it also verifies the response comes back
  220. // Note that several harmless warnings will be triggered due to the messages not having any data set
  221. using namespace AzFramework::AssetSystem;
  222. using namespace AzToolsFramework::AssetSystem;
  223. AZStd::vector<MessagePair> testMessages;
  224. AZStd::unordered_map<int, AZStd::string> nameMap; // This is just for debugging, so we can output the name of failed messages
  225. AZ::SerializeContext* serializeContext = nullptr;
  226. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  227. auto addPairFunc = [&testMessages, &nameMap, serializeContext](auto* request, auto* response)
  228. {
  229. testMessages.emplace_back(MessagePair{
  230. AZStd::unique_ptr<AZStd::remove_pointer_t<decltype(request)>>(request),
  231. AZStd::unique_ptr<AZStd::remove_pointer_t<decltype(response)>>(response)
  232. });
  233. auto data = serializeContext->FindClassData(request->RTTI_GetType());
  234. nameMap[request->GetMessageType()] = data->m_name;
  235. };
  236. auto addRequestFunc = [&testMessages, &nameMap, serializeContext](auto* request)
  237. {
  238. testMessages.emplace_back(MessagePair{AZStd::unique_ptr<AZStd::remove_pointer_t<decltype(request)>>(request), nullptr });
  239. auto data = serializeContext->FindClassData(request->RTTI_GetType());
  240. nameMap[request->GetMessageType()] = data->m_name;
  241. };
  242. addPairFunc(new GetFullSourcePathFromRelativeProductPathRequest(), new GetFullSourcePathFromRelativeProductPathResponse());
  243. addPairFunc(new GetRelativeProductPathFromFullSourceOrProductPathRequest(), new GetRelativeProductPathFromFullSourceOrProductPathResponse());
  244. addPairFunc(
  245. new GenerateRelativeSourcePathRequest(),
  246. new GenerateRelativeSourcePathResponse());
  247. addPairFunc(new SourceAssetInfoRequest(), new SourceAssetInfoResponse());
  248. addPairFunc(new SourceAssetProductsInfoRequest(), new SourceAssetProductsInfoResponse());
  249. addPairFunc(new GetScanFoldersRequest(), new GetScanFoldersResponse());
  250. addPairFunc(new GetAssetSafeFoldersRequest(), new GetAssetSafeFoldersResponse());
  251. addRequestFunc(new RegisterSourceAssetRequest());
  252. addRequestFunc(new UnregisterSourceAssetRequest());
  253. addPairFunc(new AssetInfoRequest(), new AssetInfoResponse());
  254. addPairFunc(new AssetDependencyInfoRequest(), new AssetDependencyInfoResponse());
  255. addRequestFunc(new RequestEscalateAsset());
  256. addPairFunc(new RequestAssetStatus(), new ResponseAssetStatus());
  257. addPairFunc(new AssetFingerprintClearRequest(), new AssetFingerprintClearResponse());
  258. RunNetworkRequest([&testMessages, &nameMap, this]()
  259. {
  260. for(auto&& pair : testMessages)
  261. {
  262. AZStd::string messageName = nameMap[pair.m_request->GetMessageType()];
  263. m_assetRequestHandler->m_invoked = false;
  264. if(pair.m_response)
  265. {
  266. EXPECT_TRUE(SendRequest(*pair.m_request.get(), *pair.m_response.get())) << "Message " << messageName.c_str() << " failed to send";
  267. }
  268. else
  269. {
  270. EXPECT_TRUE(SendRequest(*pair.m_request.get())) << "Message " << messageName.c_str() << " failed to send";
  271. }
  272. // 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.
  273. // This exits early if the message is invoked.
  274. constexpr int MaxWaitTimeSeconds = 5;
  275. auto start = AZStd::chrono::steady_clock::now();
  276. while (!m_assetRequestHandler->m_invoked &&
  277. AZStd::chrono::steady_clock::now() - start < AZStd::chrono::seconds(MaxWaitTimeSeconds))
  278. {
  279. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
  280. }
  281. EXPECT_TRUE(m_assetRequestHandler->m_invoked) << "Message " << messageName.c_str() << " was not received";
  282. }
  283. });
  284. }
  285. TEST_F(AssetProcessorMessages, GetUnresolvedProductReferences_Succeeds)
  286. {
  287. using namespace AzToolsFramework::AssetDatabase;
  288. // Setup the database with all needed info
  289. ScanFolderDatabaseEntry scanfolder1("scanfolder1", "scanfolder1", "scanfolder1");
  290. ASSERT_TRUE(m_dbConn.SetScanFolder(scanfolder1));
  291. SourceDatabaseEntry source1(scanfolder1.m_scanFolderID, "source1.png", AZ::Uuid::CreateRandom(), "Fingerprint");
  292. SourceDatabaseEntry source2(scanfolder1.m_scanFolderID, "source2.png", AZ::Uuid::CreateRandom(), "Fingerprint");
  293. ASSERT_TRUE(m_dbConn.SetSource(source1));
  294. ASSERT_TRUE(m_dbConn.SetSource(source2));
  295. JobDatabaseEntry job1(source1.m_sourceID, "jobkey", 1234, "pc", AZ::Uuid::CreateRandom(), AzToolsFramework::AssetSystem::JobStatus::Completed, 1111);
  296. JobDatabaseEntry job2(source2.m_sourceID, "jobkey", 1234, "pc", AZ::Uuid::CreateRandom(), AzToolsFramework::AssetSystem::JobStatus::Completed, 2222);
  297. ASSERT_TRUE(m_dbConn.SetJob(job1));
  298. ASSERT_TRUE(m_dbConn.SetJob(job2));
  299. ProductDatabaseEntry product1(job1.m_jobID, 5, "source1.product", AZ::Data::AssetType::CreateRandom());
  300. ProductDatabaseEntry product2(job2.m_jobID, 15, "source2.product", AZ::Data::AssetType::CreateRandom());
  301. ASSERT_TRUE(m_dbConn.SetProduct(product1));
  302. ASSERT_TRUE(m_dbConn.SetProduct(product2));
  303. ProductDependencyDatabaseEntry dependency1(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileA.txt", ProductDependencyDatabaseEntry::DependencyType::ProductDep_SourceFile);
  304. ProductDependencyDatabaseEntry dependency2(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileB.txt", ProductDependencyDatabaseEntry::DependencyType::ProductDep_ProductFile);
  305. ProductDependencyDatabaseEntry dependency3(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileC.txt");
  306. ProductDependencyDatabaseEntry dependency4(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, ":somefileD.txt"); // Exclusion
  307. ProductDependencyDatabaseEntry dependency5(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileE*.txt"); // Wildcard
  308. ASSERT_TRUE(m_dbConn.SetProductDependency(dependency1));
  309. ASSERT_TRUE(m_dbConn.SetProductDependency(dependency2));
  310. ASSERT_TRUE(m_dbConn.SetProductDependency(dependency3));
  311. ASSERT_TRUE(m_dbConn.SetProductDependency(dependency4));
  312. ASSERT_TRUE(m_dbConn.SetProductDependency(dependency5));
  313. // Setup the asset catalog
  314. AzFramework::AssetSystem::AssetNotificationMessage assetNotificationMessage("source1.product", AzFramework::AssetSystem::AssetNotificationMessage::NotificationType::AssetChanged, AZ::Data::AssetType::CreateRandom(), "pc");
  315. assetNotificationMessage.m_assetId = AZ::Data::AssetId(source1.m_sourceGuid, product1.m_subID);
  316. assetNotificationMessage.m_dependencies.push_back(AZ::Data::ProductDependency(AZ::Data::AssetId(source2.m_sourceGuid, product2.m_subID), {}));
  317. m_assetCatalog->OnAssetMessage(assetNotificationMessage);
  318. // Run the actual test
  319. RunNetworkRequest([&source1, &product1]()
  320. {
  321. using namespace AzFramework;
  322. AZ::u32 assetReferenceCount, pathReferenceCount;
  323. AZ::Data::AssetId assetId = AZ::Data::AssetId(source1.m_sourceGuid, product1.m_subID);
  324. AssetSystemRequestBus::Broadcast(&AssetSystemRequestBus::Events::GetUnresolvedProductReferences, assetId, assetReferenceCount, pathReferenceCount);
  325. ASSERT_EQ(assetReferenceCount, 1);
  326. ASSERT_EQ(pathReferenceCount, 3);
  327. });
  328. ASSERT_TRUE(m_assetCatalog->m_called);
  329. }
  330. }