ScriptReporter.h 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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. #include <AzCore/Debug/TraceMessageBus.h>
  10. #include <AzFramework/StringFunc/StringFunc.h>
  11. #include <Atom/Utils/ImageComparison.h>
  12. #include <Automation/ImageComparisonConfig.h>
  13. #include <Utils/ImGuiMessageBox.h>
  14. #include <Atom/Utils/PngFile.h>
  15. #include <imgui/imgui.h>
  16. namespace AtomSampleViewer
  17. {
  18. struct ImageComparisonToleranceLevel;
  19. namespace ScreenshotPaths
  20. {
  21. //! Returns the path to the screenshots capture folder.
  22. //! @resolvePath indicates whether to call ResolvePath() which will produce a full path, or keep the shorter asset folder path.
  23. AZStd::string GetScreenshotsFolder(bool resolvePath);
  24. //! Returns the path to the local baseline folder, which stores copies of screenshots previously taken on this machine.
  25. //! @resolvePath indicates whether to call ResolvePath() which will produce a full path, or keep the shorter asset folder path.
  26. AZStd::string GetLocalBaselineFolder(bool resolvePath);
  27. //! Returns the path to the official baseline folder, which stores copies of expected screenshots saved in source control.
  28. //! @resolvePath indicates whether to call ResolvePath() which will produce a full path, or keep the shorter asset folder path.
  29. AZStd::string GetOfficialBaselineFolder(bool resolvePath);
  30. //! Returns the path to the local baseline image that corresponds to @forScreenshotFile
  31. AZStd::string GetLocalBaseline(const AZStd::string& forScreenshotFile);
  32. //! Returns the path to the official baseline image that corresponds to @forScreenshotFile
  33. AZStd::string GetOfficialBaseline(const AZStd::string& forScreenshotFile);
  34. }
  35. //! Collects data about each script run by the ScriptManager.
  36. //! This includes counting errors, checking screenshots, and providing a final report dialog.
  37. class ScriptReporter
  38. {
  39. public:
  40. // currently set to track the ScriptReport index and the ScreenshotTestInfo index.
  41. using ReportIndex = AZStd::pair<size_t, size_t>;
  42. static constexpr const char* TestResultsFolder = "TestResults";
  43. static constexpr const char* UserFolder = "user";
  44. //! Set the list of available tolerance levels, so the report can suggest an alternate level that matches the actual results.
  45. void SetAvailableToleranceLevels(const AZStd::vector<ImageComparisonToleranceLevel>& toleranceLevels);
  46. //! Clears all recorded data.
  47. void Reset();
  48. //! Invalidates the final results when displaying a report to the user. This can be used to highlight
  49. //! local changes that were made, and remind the user that these results should not be considered official.
  50. //! Use an empty string to clear the invalidation.
  51. void SetInvalidationMessage(const AZStd::string& message);
  52. //! Indicates that a new script has started processing.
  53. //! Any subsequent errors will be included as part of this script's report.
  54. void PushScript(const AZStd::string& scriptAssetPath);
  55. //! Indicates that the current script has finished executing.
  56. //! Any subsequent errors will be included as part of the prior script's report.
  57. void PopScript();
  58. //! Returns whether there are active processing scripts (i.e. more PushScript() calls than PopScript() calls)
  59. bool HasActiveScript() const;
  60. //! Indicates that a new screenshot is about to be captured.
  61. bool AddScreenshotTest(const AZStd::string& path);
  62. //! Check the latest screenshot using default thresholds.
  63. void CheckLatestScreenshot(const ImageComparisonToleranceLevel* comparisonPreset);
  64. //! Opens the script report dialog.
  65. //! This displays all the collected script reporting data, provides links to tools for analyzing data like
  66. //! viewing screenshot diffs. It can be left open during processing and will update in real-time.
  67. void OpenReportDialog();
  68. void HideReportDialog();
  69. //! Called every frame to update the ImGui dialog
  70. void TickImGui();
  71. //! Returns true if there are any errors or asserts in the script report
  72. bool HasErrorsAssertsInReport() const;
  73. struct ScriptResultsSummary
  74. {
  75. uint32_t m_totalAsserts = 0;
  76. uint32_t m_totalErrors = 0;
  77. uint32_t m_totalWarnings = 0;
  78. uint32_t m_totalScreenshotsCount = 0;
  79. uint32_t m_totalScreenshotsFailed = 0;
  80. uint32_t m_totalScreenshotWarnings = 0;
  81. };
  82. //! Displays the script results summary in ImGui.
  83. void DisplayScriptResultsSummary();
  84. //! Retrieves the current script result summary.
  85. const ScriptResultsSummary& GetScriptResultSummary() const;
  86. struct ImageComparisonResult
  87. {
  88. enum class ResultCode
  89. {
  90. None,
  91. Pass,
  92. FileNotFound,
  93. FileNotLoaded,
  94. WrongSize,
  95. WrongFormat,
  96. NullImageComparisonToleranceLevel,
  97. ThresholdExceeded
  98. };
  99. ResultCode m_resultCode = ResultCode::None;
  100. float m_standardDiffScore = 0.0f;
  101. float m_filteredDiffScore = 0.0f; //!< The diff score after filtering out visually imperceptible differences.
  102. float m_finalDiffScore = 0.0f; //! The diff score that was used for comparison. May be m_diffScore or m_filteredDiffScore.
  103. AZStd::string GetSummaryString() const;
  104. };
  105. //! Records all the information about a screenshot comparison test.
  106. struct ScreenshotTestInfo
  107. {
  108. AZStd::string m_screenshotFilePath;
  109. AZStd::string m_officialBaselineScreenshotFilePath; //!< The path to the official baseline image that is checked into source control
  110. AZStd::string m_localBaselineScreenshotFilePath; //!< The path to a local baseline image that was established by the user
  111. ImageComparisonToleranceLevel m_toleranceLevel; //!< Tolerance for checking against the official baseline image
  112. ImageComparisonResult m_officialComparisonResult; //!< Result of comparing against the official baseline image, for reporting test failure
  113. ImageComparisonResult m_localComparisonResult; //!< Result of comparing against a local baseline, for reporting warnings
  114. };
  115. //! Records all the information about a single test script.
  116. struct ScriptReport : public AZ::Debug::TraceMessageBus::Handler
  117. {
  118. ~ScriptReport()
  119. {
  120. AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
  121. }
  122. bool OnPreAssert(const char* /*fileName*/, int /*line*/, const char* /*func*/, [[maybe_unused]] const char* message) override
  123. {
  124. ++m_assertCount;
  125. return false;
  126. }
  127. bool OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) override
  128. {
  129. if (AZStd::string::npos == AzFramework::StringFunc::Find(message, "Screenshot check failed"))
  130. {
  131. ++m_generalErrorCount;
  132. }
  133. else
  134. {
  135. ++m_screenshotErrorCount;
  136. }
  137. return false;
  138. }
  139. bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) override
  140. {
  141. if (AZStd::string::npos == AzFramework::StringFunc::Find(message, "Screenshot does not match the local baseline"))
  142. {
  143. ++m_generalWarningCount;
  144. }
  145. else
  146. {
  147. ++m_screenshotWarningCount;
  148. }
  149. return false;
  150. }
  151. AZStd::string m_scriptAssetPath;
  152. uint32_t m_assertCount = 0;
  153. uint32_t m_generalErrorCount = 0;
  154. uint32_t m_screenshotErrorCount = 0;
  155. uint32_t m_generalWarningCount = 0;
  156. uint32_t m_screenshotWarningCount = 0;
  157. AZStd::vector<ScreenshotTestInfo> m_screenshotTests;
  158. };
  159. const AZStd::vector<ScriptReport>& GetScriptReport() const { return m_scriptReports; }
  160. // For exporting test results
  161. void ExportTestResults();
  162. void ExportImageDiff(const char* filePath, const ScreenshotTestInfo& screenshotTest);
  163. AZStd::string ExportImageDiff(const ScriptReport& scriptReport, const ScreenshotTestInfo& screenshotTest);
  164. void SortScriptReports();
  165. private:
  166. static const ImGuiTreeNodeFlags FlagDefaultOpen = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_DefaultOpen;
  167. static const ImGuiTreeNodeFlags FlagDefaultClosed = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
  168. // Reports a script error using standard formatting that matches ScriptManager
  169. enum class TraceLevel
  170. {
  171. Error,
  172. Warning
  173. };
  174. // Controls which results are shown to the user
  175. // Must match static const char* DiplayOptions in .cpp file
  176. enum DisplayOption : int
  177. {
  178. AllResults,
  179. WarningsAndErrors,
  180. ErrorsOnly
  181. };
  182. static void ReportScriptError(const AZStd::string& message);
  183. static void ReportScriptWarning(const AZStd::string& message);
  184. static void ReportScriptIssue(const AZStd::string& message, TraceLevel traceLevel);
  185. static void ReportScreenshotComparisonIssue(const AZStd::string& message, const AZStd::string& expectedImageFilePath, const AZStd::string& actualImageFilePath, TraceLevel traceLevel);
  186. // Loads image data from a .png file.
  187. // @param imageComparisonResult will be set to an error code if the function fails
  188. // @param path the path the .png file
  189. // @param buffer will be filled with the raw image data from the .png file
  190. // @param size will be set to the image size of the .png file
  191. // @param format will be set to the pixel format of the .png file
  192. // @return true if the file was loaded successfully
  193. static bool LoadPngData(ImageComparisonResult& imageComparisonResult, const AZStd::string& path, AZStd::vector<uint8_t>& buffer, AZ::RHI::Size& size, AZ::RHI::Format& format, TraceLevel traceLevel);
  194. // Compares two image files and updates the ImageComparisonResult accordingly.
  195. // Returns false if an error prevented the comparison.
  196. static bool DiffImages(ImageComparisonResult& imageComparisonResult, const AZStd::string& expectedImageFilePath, const AZStd::string& actualImageFilePath, TraceLevel traceLevel);
  197. // Copies all captured screenshots to the local baseline folder. These can be used as an alternative to the central baseline for comparison.
  198. void UpdateAllLocalBaselineImages();
  199. // Copies a single captured screenshot to the local baseline folder. This can be used as an alternative to the central baseline for comparison.
  200. bool UpdateLocalBaselineImage(ScreenshotTestInfo& screenshotTest, bool showResultDialog);
  201. // Copies a single captured screenshot to the official baseline source folder.
  202. bool UpdateSourceBaselineImage(ScreenshotTestInfo& screenshotTest, bool showResultDialog);
  203. // Clears comparison result to passing with no errors or warnings
  204. void ClearImageComparisonResult(ImageComparisonResult& comparisonResult);
  205. // Show a message box to let the user know the results of updating local baseline images
  206. void ShowUpdateLocalBaselineResult(int successCount, int failureCount);
  207. const ImageComparisonToleranceLevel* FindBestToleranceLevel(float diffScore, bool filterImperceptibleDiffs) const;
  208. void ShowReportDialog();
  209. void ShowScreenshotTestInfoTreeNode(const AZStd::string& header, ScriptReport& scriptReport, ScreenshotTestInfo& screenshotResult);
  210. void ShowDiffButton(const char* buttonLabel, const AZStd::string& imagePathA, const AZStd::string& imagePathB);
  211. // Generates a path to the exported test results file.
  212. AZStd::string GenerateTimestamp() const;
  213. AZStd::string GenerateAndCreateExportedImageDiffPath(const ScriptReport& scriptReport, const ScreenshotTestInfo& screenshotTest) const;
  214. AZStd::string GenerateAndCreateExportedTestResultsPath() const;
  215. // Generates a diff between two images of the same size.
  216. void GenerateImageDiff(AZStd::span<const uint8_t> img1, AZStd::span<const uint8_t> img2, AZStd::vector<uint8_t>& buffer);
  217. ScriptReport* GetCurrentScriptReport();
  218. AZStd::string SeeConsole(uint32_t issueCount, const char* searchString);
  219. AZStd::string SeeBelow(uint32_t issueCount);
  220. void HighlightTextIf(bool shouldSet, ImVec4 color);
  221. void ResetTextHighlight();
  222. void HighlightTextFailedOrWarning(bool isFailed, bool isWarning);
  223. struct HighlightColorSettings
  224. {
  225. ImVec4 m_highlightPassed;
  226. ImVec4 m_highlightFailed;
  227. ImVec4 m_highlightWarning;
  228. void UpdateColorSettings();
  229. };
  230. AZStd::multimap<float, ReportIndex, AZStd::greater<float>> m_descendingThresholdReports;
  231. bool m_showReportsSortedByThreshold = true;
  232. ImGuiMessageBox m_messageBox;
  233. AZStd::vector<ImageComparisonToleranceLevel> m_availableToleranceLevels;
  234. AZStd::string m_invalidationMessage;
  235. AZStd::vector<ScriptReport> m_scriptReports; //< Tracks errors for the current active script
  236. AZStd::vector<size_t> m_currentScriptIndexStack; //< Tracks which of the scripts in m_scriptReports is currently active
  237. bool m_showReportDialog = false;
  238. bool m_colorHasBeenSet = false;
  239. DisplayOption m_displayOption = DisplayOption::AllResults;
  240. bool m_forceShowUpdateButtons = false; //< By default, the "Update" buttons are visible only for failed screenshots. This forces them to be visible.
  241. bool m_forceShowExportPngDiffButtons = false; //< By default, "Export Png Diff" buttons are visible only for failed screenshots. This forces them to be visible.
  242. AZStd::string m_officialBaselineSourceFolder; //< Used for updating official baseline screenshots
  243. AZStd::string m_exportedTestResultsPath = "Click the 'Export Test Results' button."; //< Path to exported test results file (if exported).
  244. AZStd::string m_uniqueTimestamp;
  245. HighlightColorSettings m_highlightSettings;
  246. ScriptResultsSummary m_resultsSummary;
  247. // Flags set and used by ShowReportDialog()
  248. bool m_showAll;
  249. bool m_showWarnings;
  250. };
  251. } // namespace AtomSampleViewer