AssetProcessorServerUnitTests.cpp 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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/unittests/AssetProcessorServerUnitTests.h>
  9. #include <native/unittests/UnitTestUtils.h>
  10. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  11. #include <AzCore/UserSettings/UserSettingsComponent.h>
  12. #include <AzFramework/Application/Application.h>
  13. #include <AzFramework/Network/AssetProcessorConnection.h>
  14. #include <native/connection/connection.h>
  15. #include <native/connection/connectionManager.h>
  16. #include <native/utilities/assetUtils.h> // for AssetUtilities::ComputeProjectName
  17. #include <native/utilities/BatchApplicationManager.h>
  18. #include <native/utilities/ApplicationServer.h>
  19. #include <native/utilities/BatchApplicationServer.h>
  20. #include <native/utilities/PlatformConfiguration.h>
  21. #include <QApplication>
  22. #define FEATURE_TEST_LISTEN_PORT 12125
  23. // Enable this define only if you are debugging a deadlock/timing issue etc in the AssetProcessorConnection, which you are unable to reproduce otherwise.
  24. // enabling this define will result in a lot more number of connections that connect/disconnect with AP and therefore will result in the unit tests taking a lot more time to complete
  25. // if you do enable this, consider disabling the timeout detection in AssetProcessorTests ("Legacy test deadlocked or timed out.") since it can take a long time to run.
  26. //#define DEBUG_ASSETPROCESSORCONNECTION
  27. #if defined(DEBUG_ASSETPROCESSORCONNECTION)
  28. #define NUMBER_OF_CONNECTION 16
  29. #define NUMBER_OF_TRIES 10
  30. #define NUMBER_OF_ITERATION 100
  31. #else
  32. // NUMBER_OF_CONNECTION is how many parallel threads to create that will be starting and killing connections
  33. #define NUMBER_OF_CONNECTION 4
  34. // NUMBER_OF_TRIES is how many times each thread tries to disconnect and reconnect before finishing.
  35. #define NUMBER_OF_TRIES 5
  36. // NUMBER_OF_ITERATION is how many times the entire test is restarted
  37. #define NUMBER_OF_ITERATION 2
  38. #endif
  39. namespace UnitTest
  40. {
  41. AssetProcessorServerUnitTest::AssetProcessorServerUnitTest()
  42. : AssetProcessorUnitTestBase()
  43. {
  44. }
  45. AssetProcessorServerUnitTest::~AssetProcessorServerUnitTest()
  46. {
  47. }
  48. void AssetProcessorServerUnitTest::SetUp()
  49. {
  50. AssetProcessorUnitTestBase::SetUp();
  51. m_applicationServer = AZStd::make_unique<BatchApplicationServer>();
  52. m_applicationServer->startListening(FEATURE_TEST_LISTEN_PORT); // a port that is not the normal port
  53. connect(m_applicationServer.get(), SIGNAL(newIncomingConnection(qintptr)), ConnectionManager::Get(), SLOT(NewConnection(qintptr)));
  54. }
  55. void AssetProcessorServerUnitTest::RunAssetProcessorConnectionStressTest(bool failNegotiation)
  56. {
  57. AZStd::string azBranchToken;
  58. AzFramework::ApplicationRequests::Bus::Broadcast(
  59. &AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, azBranchToken);
  60. QString branchToken(azBranchToken.c_str());
  61. if (failNegotiation)
  62. {
  63. branchToken = branchToken.append("invalid"); // invalid branch token will result in negotiation to fail
  64. }
  65. AZStd::atomic_int numberOfConnection(0);
  66. AZStd::atomic_bool failureOccurred = false;
  67. enum : int
  68. {
  69. totalConnections = NUMBER_OF_CONNECTION * NUMBER_OF_TRIES
  70. };
  71. AZStd::function<void(int)> StartConnection =
  72. [&branchToken, &numberOfConnection, &failureOccurred, failNegotiation](int numTimeWait)
  73. {
  74. for (int idx = 0; idx < NUMBER_OF_TRIES; ++idx)
  75. {
  76. AzFramework::AssetSystem::AssetProcessorConnection connection;
  77. connection.Configure(
  78. branchToken.toUtf8().data(), "pc", "UNITTEST",
  79. AssetUtilities::ComputeProjectName()
  80. .toUtf8()
  81. .constData()); // UNITTEST identifier will skip the processID validation during negotiation
  82. connection.Connect("127.0.0.1", FEATURE_TEST_LISTEN_PORT);
  83. while (!connection.IsConnected() && !connection.NegotiationFailed())
  84. {
  85. AZStd::this_thread::yield();
  86. }
  87. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(numTimeWait + idx));
  88. numberOfConnection.fetch_sub(1);
  89. if (connection.NegotiationFailed() != failNegotiation)
  90. {
  91. failureOccurred = true;
  92. }
  93. EXPECT_EQ(connection.NegotiationFailed(), failNegotiation);
  94. }
  95. };
  96. AZStd::vector<AZStd::thread> assetProcessorConnectionList;
  97. for (int iteration = 0; iteration < NUMBER_OF_ITERATION; ++iteration)
  98. {
  99. #if defined(DEBUG_ASSETPROCESSORCONNECTION)
  100. printf("Iteration %4i/%4i...\n", iteration, NUMBER_OF_ITERATION);
  101. #endif
  102. numberOfConnection = totalConnections;
  103. for (int idx = 0; idx < NUMBER_OF_CONNECTION; ++idx)
  104. {
  105. // each thread should sleep after each test for a different amount of time so that they
  106. // end up trying all different overlapping parts of the code.
  107. int sleepTime = iteration * (idx + 1);
  108. assetProcessorConnectionList.push_back(AZStd::thread(AZStd::bind(StartConnection, sleepTime)));
  109. };
  110. // We need to process all events, since AssetProcessorServer is also on the same thread
  111. while (numberOfConnection.load() && !failureOccurred)
  112. {
  113. QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
  114. QCoreApplication::processEvents();
  115. }
  116. EXPECT_FALSE(failureOccurred);
  117. for (int idx = 0; idx < NUMBER_OF_CONNECTION; ++idx)
  118. {
  119. if (assetProcessorConnectionList[idx].joinable())
  120. {
  121. assetProcessorConnectionList[idx].join();
  122. }
  123. }
  124. assetProcessorConnectionList.clear();
  125. }
  126. }
  127. void AssetProcessorServerUnitTest::AssetProcessorConnectionStressTest()
  128. {
  129. // UnitTest for testing the AssetProcessorConnection by creating lot of connections that connects to AP and then disconnecting them
  130. // at different times This test should detect any deadlocks that can arise due to rapidly connecting/disconnecting connections
  131. UnitTestUtils::AssertAbsorber assertAbsorber;
  132. // Testing the case when negotiation succeeds
  133. RunAssetProcessorConnectionStressTest(false);
  134. EXPECT_EQ(assertAbsorber.m_numErrorsAbsorbed, 0);
  135. EXPECT_EQ(assertAbsorber.m_numAssertsAbsorbed, 0);
  136. // Testing the case when negotiation fails
  137. RunAssetProcessorConnectionStressTest(true);
  138. EXPECT_EQ(assertAbsorber.m_numErrorsAbsorbed, 0);
  139. EXPECT_EQ(assertAbsorber.m_numAssertsAbsorbed, 0);
  140. }
  141. void AssetProcessorServerUnitTest::ConnectionErrorForNonProxyMode(unsigned int connId, QString error)
  142. {
  143. if ((connId == 10 || connId == 11))
  144. {
  145. if (QString::compare(error, "Attempted to negotiate with self") == 0)
  146. {
  147. m_gotNegotiationWithSelfError = true;
  148. }
  149. ++m_numberOfDisconnectionReceived;
  150. }
  151. if (m_numberOfDisconnectionReceived == 2)
  152. {
  153. ConnectionManager::Get()->removeConnection(m_connectionId);
  154. disconnect(m_connection);
  155. EXPECT_TRUE(m_gotNegotiationWithSelfError);
  156. if (m_gotNegotiationWithSelfError)
  157. {
  158. AssetProcessorConnectionStressTest();
  159. }
  160. m_eventWasPosted = true;
  161. }
  162. }
  163. TEST_F(AssetProcessorServerUnitTest, RunFirstPartOfUnitTestsForAssetProcessorServer)
  164. {
  165. m_numberOfDisconnectionReceived = 0;
  166. m_connection = connect(
  167. ConnectionManager::Get(), SIGNAL(ConnectionError(unsigned int, QString)), this,
  168. SLOT(ConnectionErrorForNonProxyMode(unsigned int, QString)));
  169. m_connectionId = ConnectionManager::Get()->addConnection();
  170. Connection* connection = ConnectionManager::Get()->getConnection(m_connectionId);
  171. connection->SetPort(FEATURE_TEST_LISTEN_PORT);
  172. connection->SetIpAddress("127.0.0.1");
  173. connection->SetAutoConnect(true);
  174. QElapsedTimer time;
  175. time.start();
  176. // This is still time out based because it's waiting on a few error events to continue.
  177. // The actual test runs when ConnectionErrorForNonProxyMode is triggered with the expected
  178. // error messages. A shorter timeout can make this get missed in some environments.
  179. const int testTimeoutMS = 120 * 1000;
  180. while (time.elapsed() < testTimeoutMS && !m_eventWasPosted)
  181. {
  182. QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
  183. QCoreApplication::processEvents();
  184. }
  185. EXPECT_TRUE(m_eventWasPosted);
  186. }
  187. } // namespace UnitTest