UnitTestUtils.h 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. #pragma once
  9. #if !defined(Q_MOC_RUN)
  10. #include <QString>
  11. #include <AzCore/Debug/TraceMessageBus.h>
  12. #include <QDir>
  13. #include <AzFramework/IO/LocalFileIO.h>
  14. #include <AzCore/UnitTest/UnitTest.h>
  15. #endif
  16. // ----------------- UTILITY FUNCTIONS --------------------
  17. namespace UnitTestUtils
  18. {
  19. /** sleep for the minimum amount of time that the file system can store.
  20. * Different file systems (windows, mac, for example) have differing resolutions that they have for
  21. * file times. HFS stores only 'seconds' precision, for example. so tests that need to wait so that
  22. * mod times have changed, must wait for this amount of time
  23. **/
  24. void SleepForMinimumFileSystemTime();
  25. //! Create a dummy file using AZ::IO APIs which support mocking
  26. bool CreateDummyFileAZ(AZ::IO::PathView fullPathToFile, AZStd::string_view contents = "");
  27. //! Create a dummy file, with optional contents. Will create directories for it too.
  28. bool CreateDummyFile(const QString& fullPathToFile, QString contents = "");
  29. //! This function pumps the Qt event queue until either the varToWatch becomes true or the specified millisecond elapse.
  30. bool BlockUntil(bool& varToWatch, int millisecondsMax);
  31. // the Assert Absorber here is used to absorb asserts and errors during unit tests.
  32. // it only absorbs asserts spawned by this thread;
  33. class AssertAbsorber : public AZ::Debug::TraceMessageBus::Handler
  34. {
  35. public:
  36. AssertAbsorber(bool debugMessages = false) : m_debugMessages{debugMessages}
  37. {
  38. m_debugMessages = debugMessages;
  39. // only absorb asserts when this object is on scope in the thread that this object is on scope in.
  40. BusConnect();
  41. }
  42. void ExpectCheck(const int& numAbsorbed, int expectedAbsorbed, const char* errorType, const AZStd::vector<AZStd::string>& messageList)
  43. {
  44. if (numAbsorbed != expectedAbsorbed)
  45. {
  46. BusDisconnect();
  47. AZ_Printf("AssertAbsorber", "Incorrect number of %s absobed:\n\n", errorType);
  48. for (auto& thisMessage : messageList)
  49. {
  50. AZ_Printf("Absorbed", thisMessage.c_str());
  51. }
  52. BusConnect();
  53. }
  54. ASSERT_EQ(numAbsorbed, expectedAbsorbed);
  55. }
  56. void AssertCheck(const int& numAbsorbed, int expectedAbsorbed, const char* errorType, const AZStd::vector<AZStd::string>& messageList)
  57. {
  58. if (numAbsorbed != expectedAbsorbed)
  59. {
  60. BusDisconnect();
  61. AZ_Printf("AssertAbsorber", "Incorrect number of %s absorbed:\n\n", errorType);
  62. for (auto& thisMessage : messageList)
  63. {
  64. AZ_Printf("Absorbed", thisMessage.c_str());
  65. }
  66. BusConnect();
  67. }
  68. ASSERT_EQ(numAbsorbed, expectedAbsorbed);
  69. }
  70. void ExpectWarnings(int expectValue)
  71. {
  72. ExpectCheck(m_numWarningsAbsorbed, expectValue, "warnings", m_warningMessages);
  73. }
  74. void ExpectErrors(int expectValue)
  75. {
  76. ExpectCheck(m_numErrorsAbsorbed, expectValue, "errors", m_errorMessages);
  77. }
  78. void ExpectAsserts(int expectValue)
  79. {
  80. ExpectCheck(m_numAssertsAbsorbed, expectValue, "asserts", m_assertMessages);
  81. }
  82. void AssertWarnings(int expectValue)
  83. {
  84. AssertCheck(m_numWarningsAbsorbed, expectValue, "warnings", m_warningMessages);
  85. }
  86. void AssertErrors(int expectValue)
  87. {
  88. AssertCheck(m_numErrorsAbsorbed, expectValue, "errors", m_errorMessages);
  89. }
  90. void AssertAsserts(int expectValue)
  91. {
  92. AssertCheck(m_numAssertsAbsorbed, expectValue, "asserts", m_assertMessages);
  93. }
  94. bool OnPreWarning([[maybe_unused]] const char* window, [[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* message) override
  95. {
  96. ++m_numWarningsAbsorbed;
  97. if (m_debugMessages)
  98. {
  99. m_warningMessages.push_back(AZStd::string::format("%s\n File: %s Line: %d Func: %s\n", message, fileName, line, func));
  100. }
  101. return true;
  102. }
  103. bool OnPreAssert([[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* message) override
  104. {
  105. // Print out absorbed asserts since asserts are pretty important and accidentally absorbing unintended ones can lead to difficult-to-detect issues
  106. UnitTest::ColoredPrintf(UnitTest::COLOR_YELLOW, "Absorbed Assert: %s\n", message);
  107. ++m_numAssertsAbsorbed;
  108. if (m_debugMessages)
  109. {
  110. m_assertMessages.push_back(AZStd::string::format("%s\n File: %s Line: %d Func: %s\n", message, fileName, line, func));
  111. }
  112. return true; // I handled this, do not forward it
  113. }
  114. bool OnPreError([[maybe_unused]] const char* window, [[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* message) override
  115. {
  116. ++m_numErrorsAbsorbed;
  117. if (m_debugMessages)
  118. {
  119. m_errorMessages.push_back(AZStd::string::format("%s\n File: %s Line: %d Func: %s\n", message, fileName, line, func));
  120. }
  121. return true; // I handled this, do not forward it
  122. }
  123. bool OnPrintf(const char* /*window*/, const char* /*message*/) override
  124. {
  125. ++m_numMessagesAbsorbed;
  126. return true;
  127. }
  128. void PrintAbsorbed()
  129. {
  130. BusDisconnect();
  131. AZ_Printf("AssertAbsorber", "Warnings Absorbed:\n");
  132. for (auto& thisMessage : m_warningMessages)
  133. {
  134. AZ_Printf("AbsorbedWarning", thisMessage.c_str());
  135. }
  136. AZ_Printf("AssertAbsorber", "Errors Absorbed:\n");
  137. for (auto& thisMessage : m_errorMessages)
  138. {
  139. AZ_Printf("AbsorbedError", thisMessage.c_str());
  140. }
  141. AZ_Printf("AssertAbsorber", "Warnings Absorbed:\n");
  142. for (auto& thisMessage : m_assertMessages)
  143. {
  144. AZ_Printf("AbsorbedAssert", thisMessage.c_str());
  145. }
  146. BusConnect();
  147. }
  148. ~AssertAbsorber()
  149. {
  150. BusDisconnect();
  151. }
  152. void Clear()
  153. {
  154. m_numMessagesAbsorbed = 0;
  155. m_numWarningsAbsorbed = 0;
  156. m_numAssertsAbsorbed = 0;
  157. m_numErrorsAbsorbed = 0;
  158. m_warningMessages.clear();
  159. m_errorMessages.clear();
  160. m_assertMessages.clear();
  161. }
  162. AZStd::vector<AZStd::string> m_assertMessages;
  163. AZStd::vector<AZStd::string> m_warningMessages;
  164. AZStd::vector<AZStd::string> m_errorMessages;
  165. int m_numMessagesAbsorbed = 0;
  166. int m_numWarningsAbsorbed = 0;
  167. int m_numAssertsAbsorbed = 0;
  168. int m_numErrorsAbsorbed = 0;
  169. bool m_debugMessages{ false };
  170. };
  171. //! Automatically restore current directory when this leaves scope:
  172. class ScopedDir
  173. {
  174. public:
  175. ScopedDir() = default;
  176. ScopedDir(QString newDir)
  177. {
  178. Setup(newDir);
  179. }
  180. void Setup(QString newDir)
  181. {
  182. m_originalDir = QDir::currentPath();
  183. newDir = QDir::cleanPath(newDir);
  184. QDir::setCurrent(newDir);
  185. m_localFileIO = aznew AZ::IO::LocalFileIO();
  186. m_priorFileIO = AZ::IO::FileIOBase::GetInstance();
  187. if (m_priorFileIO)
  188. {
  189. AZ::IO::FileIOBase::SetInstance(nullptr);
  190. }
  191. AZ::IO::FileIOBase::SetInstance(m_localFileIO);
  192. m_localFileIO->SetAlias("@products@", (newDir + QString("/ALIAS/assets")).toUtf8().constData());
  193. m_localFileIO->SetAlias("@log@", (newDir + QString("/ALIAS/logs")).toUtf8().constData());
  194. m_localFileIO->SetAlias("@usercache@", (newDir + QString("/ALIAS/cache")).toUtf8().constData());
  195. m_localFileIO->SetAlias("@user@", (newDir + QString("/ALIAS/user")).toUtf8().constData());
  196. }
  197. ~ScopedDir()
  198. {
  199. AZ::IO::FileIOBase::SetInstance(nullptr);
  200. delete m_localFileIO;
  201. m_localFileIO = nullptr;
  202. if (m_priorFileIO)
  203. {
  204. AZ::IO::FileIOBase::SetInstance(m_priorFileIO);
  205. }
  206. QDir::setCurrent(m_originalDir);
  207. }
  208. private:
  209. QString m_originalDir;
  210. AZ::IO::FileIOBase* m_priorFileIO = nullptr;
  211. AZ::IO::FileIOBase* m_localFileIO = nullptr;
  212. };
  213. }