12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <sstream>
- #include <Automation/ScriptReporter.h>
- #include <Utils/Utils.h>
- #include <Atom/RHI/Factory.h>
- #include <AzFramework/API/ApplicationAPI.h>
- #include <AzFramework/StringFunc/StringFunc.h>
- #include <AzFramework/IO/LocalFileIO.h>
- #include <AzCore/IO/SystemFile.h>
- #include <AzCore/Utils/Utils.h>
- namespace AtomSampleViewer
- {
- // Must match ScriptReporter::DisplayOption Enum
- static const char* DiplayOptions[] =
- {
- "All Results", "Warnings & Errors", "Errors Only",
- };
-
- static const char* SortOptions[] =
- {
- "Sort by Script", "Sort by Official Baseline Diff Score", "Sort by Local Baseline Diff Score",
- };
- AZStd::string ScriptReporter::ImageComparisonResult::GetSummaryString() const
- {
- AZStd::string resultString;
- if (m_resultCode == ResultCode::ThresholdExceeded || m_resultCode == ResultCode::Pass)
- {
- resultString = AZStd::string::format("Diff Score: %f", m_diffScore);
- }
- else if (m_resultCode == ResultCode::WrongSize)
- {
- resultString = "Wrong size";
- }
- else if (m_resultCode == ResultCode::FileNotFound)
- {
- resultString = "File not found";
- }
- else if (m_resultCode == ResultCode::FileNotLoaded)
- {
- resultString = "File load failed";
- }
- else if (m_resultCode == ResultCode::WrongFormat)
- {
- resultString = "Format is not supported";
- }
- else if (m_resultCode == ResultCode::NullImageComparisonToleranceLevel)
- {
- resultString = "ImageComparisonToleranceLevel not provided";
- }
- else if (m_resultCode == ResultCode::None)
- {
- // "None" could be the case if the results dialog is open while the script is running
- resultString = "No results";
- }
- else
- {
- resultString = "Unhandled Image Comparison ResultCode";
- AZ_Assert(false, "Unhandled Image Comparison ResultCode");
- }
- return resultString;
- }
- void ScriptReporter::SetAvailableToleranceLevels(const AZStd::vector<ImageComparisonToleranceLevel>& toleranceLevels)
- {
- m_availableToleranceLevels = toleranceLevels;
- }
- void ScriptReporter::Reset()
- {
- m_scriptReports.clear();
- m_reportsSortedByOfficialBaslineScore.clear();
- m_reportsSortedByLocaBaslineScore.clear();
- m_currentScriptIndexStack.clear();
- m_invalidationMessage.clear();
- m_uniqueTimestamp = GenerateTimestamp();
- }
- void ScriptReporter::SetInvalidationMessage(const AZStd::string& message)
- {
- m_invalidationMessage = message;
- // Reporting this message here instead of when running the script so it won't show up as an error in the ImGui report.
- AZ_Error("ScriptReporter", m_invalidationMessage.empty(), "Subsequent test results will be invalid because '%s'", m_invalidationMessage.c_str());
- }
- void ScriptReporter::PushScript(const AZStd::string& scriptAssetPath)
- {
- if (GetCurrentScriptReport())
- {
- // Only the current script should listen for Trace Errors
- GetCurrentScriptReport()->BusDisconnect();
- }
- m_currentScriptIndexStack.push_back(m_scriptReports.size());
- m_scriptReports.emplace_back().m_scriptAssetPath = scriptAssetPath;
- m_scriptReports.back().BusConnect();
- }
- void ScriptReporter::PopScript()
- {
- AZ_Assert(GetCurrentScriptReport(), "There is no active script");
- if (GetCurrentScriptReport())
- {
- GetCurrentScriptReport()->BusDisconnect();
- m_currentScriptIndexStack.pop_back();
- }
- if (GetCurrentScriptReport())
- {
- // Make sure the newly restored current script is listening for Trace Errors
- GetCurrentScriptReport()->BusConnect();
- }
- }
- bool ScriptReporter::HasActiveScript() const
- {
- return !m_currentScriptIndexStack.empty();
- }
- ScriptReporter::ScreenshotTestInfo::ScreenshotTestInfo(const AZStd::string& screenshotName)
- {
- AZ_Assert(!screenshotName.empty(), "The screenshot file name shouldn't be empty.");
- AZ::Render::FrameCapturePathOutcome pathOutcome;
- AZ::Render::FrameCaptureTestRequestBus::BroadcastResult(
- pathOutcome,
- &AZ::Render::FrameCaptureTestRequestBus::Events::BuildScreenshotFilePath,
- screenshotName, true);
- if (pathOutcome.IsSuccess())
- {
- m_screenshotFilePath = pathOutcome.GetValue();
- }
- else
- {
- AZ_Error("ScriptReporter", false, "%s", pathOutcome.GetError().m_errorMessage.c_str());
- }
- AZ::Render::FrameCaptureTestRequestBus::BroadcastResult(
- pathOutcome,
- &AZ::Render::FrameCaptureTestRequestBus::Events::BuildOfficialBaselineFilePath,
- screenshotName, false);
- if (pathOutcome.IsSuccess())
- {
- m_officialBaselineScreenshotFilePath = pathOutcome.GetValue();
- }
- else
- {
- AZ_Error("ScriptReporter", false, "%s", pathOutcome.GetError().m_errorMessage.c_str());
- }
- AZ::Render::FrameCaptureTestRequestBus::BroadcastResult(
- pathOutcome,
- &AZ::Render::FrameCaptureTestRequestBus::Events::BuildLocalBaselineFilePath,
- screenshotName, true);
- if (pathOutcome.IsSuccess())
- {
- m_localBaselineScreenshotFilePath = pathOutcome.GetValue();
- }
- else
- {
- AZ_Error("ScriptReporter", false, "%s", pathOutcome.GetError().m_errorMessage.c_str());
- }
- }
- bool ScriptReporter::AddScreenshotTest(const AZStd::string& imageName)
- {
- AZ_Assert(GetCurrentScriptReport(), "There is no active script");
- ScreenshotTestInfo screenshotTestInfo(imageName);
- GetCurrentScriptReport()->m_screenshotTests.push_back(AZStd::move(screenshotTestInfo));
- return true;
- }
- void ScriptReporter::TickImGui()
- {
- if (m_showReportDialog)
- {
- ShowReportDialog();
- }
- }
- bool ScriptReporter::HasErrorsAssertsInReport() const
- {
- for (const ScriptReport& scriptReport : m_scriptReports)
- {
- if (scriptReport.m_assertCount > 0 || scriptReport.m_generalErrorCount > 0 || scriptReport.m_screenshotErrorCount > 0)
- {
- return true;
- }
- }
- return false;
- }
- void ScriptReporter::DisplayScriptResultsSummary()
- {
- ImGui::Separator();
- if (HasActiveScript())
- {
- ImGui::PushStyleColor(ImGuiCol_Text, m_highlightSettings.m_highlightWarning);
- ImGui::Text("Script is running... (_ _)zzz");
- ImGui::PopStyleColor();
- }
- else if (m_resultsSummary.m_totalErrors > 0 || m_resultsSummary.m_totalAsserts > 0 || m_resultsSummary.m_totalScreenshotsFailed > 0)
- {
- ImGui::PushStyleColor(ImGuiCol_Text, m_highlightSettings.m_highlightFailed);
- ImGui::Text("(>_<) FAILED (>_<)");
- ImGui::PopStyleColor();
- }
- else
- {
- if (m_invalidationMessage.empty())
- {
- ImGui::PushStyleColor(ImGuiCol_Text, m_highlightSettings.m_highlightPassed);
- ImGui::Text("\\(^_^)/ PASSED \\(^_^)/");
- ImGui::PopStyleColor();
- }
- else
- {
- ImGui::Text("(-_-) INVALID ... but passed (-_-)");
- }
- }
- if (!m_invalidationMessage.empty())
- {
- ImGui::Separator();
- ImGui::PushStyleColor(ImGuiCol_Text, m_highlightSettings.m_highlightFailed);
- ImGui::Text("(%s)", m_invalidationMessage.c_str());
- ImGui::PopStyleColor();
- }
- ImGui::Separator();
- ImGui::Text("Test Script Count: %zu", m_scriptReports.size());
- HighlightTextIf(m_resultsSummary.m_totalAsserts > 0, m_highlightSettings.m_highlightFailed);
- ImGui::Text("Total Asserts: %u %s", m_resultsSummary.m_totalAsserts, SeeConsole(m_resultsSummary.m_totalAsserts, "Trace::Assert").c_str());
- HighlightTextIf(m_resultsSummary.m_totalErrors > 0, m_highlightSettings.m_highlightFailed);
- ImGui::Text("Total Errors: %u %s", m_resultsSummary.m_totalErrors, SeeConsole(m_resultsSummary.m_totalErrors, "Trace::Error").c_str());
- HighlightTextIf(m_resultsSummary.m_totalWarnings > 0, m_highlightSettings.m_highlightWarning);
- ImGui::Text("Total Warnings: %u %s", m_resultsSummary.m_totalWarnings, SeeConsole(m_resultsSummary.m_totalWarnings, "Trace::Warning").c_str());
- ResetTextHighlight();
- ImGui::Text("Total Screenshot Count: %u", m_resultsSummary.m_totalScreenshotsCount);
- HighlightTextIf(m_resultsSummary.m_totalScreenshotsFailed > 0, m_highlightSettings.m_highlightFailed);
- ImGui::Text("Total Screenshot Failures: %u %s", m_resultsSummary.m_totalScreenshotsFailed, SeeBelow(m_resultsSummary.m_totalScreenshotsFailed).c_str());
- HighlightTextIf(m_resultsSummary.m_totalScreenshotWarnings > 0, m_highlightSettings.m_highlightWarning);
- ImGui::Text("Total Screenshot Warnings: %u %s", m_resultsSummary.m_totalScreenshotWarnings, SeeBelow(m_resultsSummary.m_totalScreenshotWarnings).c_str());
- ResetTextHighlight();
- }
- const AtomSampleViewer::ScriptReporter::ScriptResultsSummary& ScriptReporter::GetScriptResultSummary() const
- {
- return m_resultsSummary;
- }
- void ScriptReporter::ShowDiffButton(const char* buttonLabel, const AZStd::string& imagePathA, const AZStd::string& imagePathB)
- {
- if (ImGui::Button(buttonLabel))
- {
- if (!Utils::RunDiffTool(imagePathA, imagePathB))
- {
- m_messageBox.OpenPopupMessage("Can't Diff", "Image diff is not supported on this platform, or the required diff tool is not installed.");
- }
- }
- }
- AZStd::string ScriptReporter::GenerateTimestamp() const
- {
- const AZStd::chrono::system_clock::time_point now = AZStd::chrono::system_clock::now();
- const float timeFloat = AZStd::chrono::duration<float>(now.time_since_epoch()).count();
- return AZStd::string::format("%.4f", timeFloat);
- }
- AZStd::string ScriptReporter::GenerateAndCreateExportedImageDiffPath(const ScriptReport& scriptReport, const ScreenshotTestInfo& screenshotTest) const
- {
- const auto projectPath = AZ::Utils::GetProjectPath();
- AZStd::string imageDiffPath;
- AZStd::string scriptFilenameWithouExtension;
- AzFramework::StringFunc::Path::GetFileName(scriptReport.m_scriptAssetPath.c_str(), scriptFilenameWithouExtension);
- AzFramework::StringFunc::Path::StripExtension(scriptFilenameWithouExtension);
- AZStd::string screenshotFilenameWithouExtension;
- AzFramework::StringFunc::Path::GetFileName(screenshotTest.m_screenshotFilePath.c_str(), screenshotFilenameWithouExtension);
- AzFramework::StringFunc::Path::StripExtension(screenshotFilenameWithouExtension);
- AZStd::string imageDiffFilename = "imageDiff_" + scriptFilenameWithouExtension + "_" + screenshotFilenameWithouExtension + "_" + m_uniqueTimestamp + ".png";
- AzFramework::StringFunc::Path::Join(projectPath.c_str(), UserFolder, imageDiffPath);
- AzFramework::StringFunc::Path::Join(imageDiffPath.c_str(), TestResultsFolder, imageDiffPath);
- AzFramework::StringFunc::Path::Join(imageDiffPath.c_str(), imageDiffFilename.c_str(), imageDiffPath);
- AZStd::string imageDiffFolderPath;
- AzFramework::StringFunc::Path::GetFolderPath(imageDiffPath.c_str(), imageDiffFolderPath);
- auto io = AZ::IO::LocalFileIO::GetInstance();
- io->CreatePath(imageDiffFolderPath.c_str());
- return imageDiffPath;
- }
- const ImageComparisonToleranceLevel* ScriptReporter::FindBestToleranceLevel(float diffScore, bool filterImperceptibleDiffs) const
- {
- [[maybe_unused]] float thresholdChecked = 0.0f;
- [[maybe_unused]] bool ignoringMinorDiffs = false;
- for (const ImageComparisonToleranceLevel& level : m_availableToleranceLevels)
- {
- AZ_Assert(level.m_threshold > thresholdChecked || thresholdChecked == 0.0f, "Threshold values are not sequential");
- AZ_Assert(level.m_filterImperceptibleDiffs >= ignoringMinorDiffs, "filterImperceptibleDiffs values are not sequential");
- thresholdChecked = level.m_threshold;
- ignoringMinorDiffs = level.m_filterImperceptibleDiffs;
- if (filterImperceptibleDiffs <= level.m_filterImperceptibleDiffs && diffScore <= level.m_threshold)
- {
- return &level;
- }
- }
- return nullptr;
- }
- void ScriptReporter::ShowReportDialog()
- {
- if (ImGui::Begin("Script Results", &m_showReportDialog) && !m_scriptReports.empty())
- {
- m_highlightSettings.UpdateColorSettings();
- m_colorHasBeenSet = false;
- m_resultsSummary = ScriptResultsSummary();
- for (ScriptReport& scriptReport : m_scriptReports)
- {
- m_resultsSummary.m_totalAsserts += scriptReport.m_assertCount;
- // We don't include screenshot errors and warnings in these totals because those have their own line-items.
- m_resultsSummary.m_totalErrors += scriptReport.m_generalErrorCount;
- m_resultsSummary.m_totalWarnings += scriptReport.m_generalWarningCount;
- m_resultsSummary.m_totalScreenshotWarnings += scriptReport.m_screenshotWarningCount;
- m_resultsSummary.m_totalScreenshotsFailed += scriptReport.m_screenshotErrorCount;
- // This will catch any false-negatives that could occur if the screenshot failure error messages change without also updating ScriptReport::OnPreError()
- m_resultsSummary.m_totalScreenshotsCount += aznumeric_cast<uint32_t>(scriptReport.m_screenshotTests.size());
- for (ScreenshotTestInfo& screenshotTest : scriptReport.m_screenshotTests)
- {
- if (screenshotTest.m_officialComparisonResult.m_resultCode != ImageComparisonResult::ResultCode::Pass &&
- screenshotTest.m_officialComparisonResult.m_resultCode != ImageComparisonResult::ResultCode::None)
- {
- AZ_Assert(scriptReport.m_screenshotErrorCount > 0, "If screenshot comparison failed in any way, m_screenshotErrorCount should be non-zero.");
- }
- }
- }
- DisplayScriptResultsSummary();
- ImGui::Text("Exported test results: %s", m_exportedTestResultsPath.c_str());
- if (ImGui::Button("Update All Local Baseline Images"))
- {
- m_messageBox.OpenPopupConfirmation(
- "Update All Local Baseline Images",
- "This will replace all local baseline images \n"
- "with the images captured during this test run. \n"
- "Are you sure?",
- [this]() {
- UpdateAllLocalBaselineImages();
- });
- }
- if (ImGui::Button("Export Test Results"))
- {
- m_messageBox.OpenPopupConfirmation(
- "Export Test Results",
- "All test results will be exported \n"
- "Proceed?",
- [this]() {
- ExportTestResults();
- });
- }
- int displayOption = m_displayOption;
- ImGui::Combo("Display", &displayOption, DiplayOptions, AZ_ARRAY_SIZE(DiplayOptions));
- m_displayOption = (DisplayOption)displayOption;
- int sortOption = m_currentSortOption;
- ImGui::Combo("Sort Results", &sortOption, SortOptions, AZ_ARRAY_SIZE(SortOptions));
- m_currentSortOption = (SortOption)sortOption;
- ImGui::Checkbox("Force Show 'Update' Buttons", &m_forceShowUpdateButtons);
- ImGui::Checkbox("Force Show 'Export Png Diff' Buttons", &m_forceShowExportPngDiffButtons);
- m_showWarnings = (m_displayOption == DisplayOption::AllResults) || (m_displayOption == DisplayOption::WarningsAndErrors);
- m_showAll = (m_displayOption == DisplayOption::AllResults);
- ImGui::Separator();
- if (m_currentSortOption == SortOption::Unsorted)
- {
- for (ScriptReport& scriptReport : m_scriptReports)
- {
- const bool scriptPassed = scriptReport.m_assertCount == 0 && scriptReport.m_generalErrorCount == 0 && scriptReport.m_screenshotErrorCount == 0;
- const bool scriptHasWarnings = scriptReport.m_generalWarningCount > 0 || scriptReport.m_screenshotWarningCount > 0;
- // Skip if tests passed and
- // 1). have no warnings and we don't want to show successes OR
- // 2). only have warnings and we don't want to show warnings
- bool skipReport = scriptPassed && ((!scriptHasWarnings && !m_showAll) || (scriptHasWarnings && !m_showWarnings));
- if (skipReport)
- {
- continue;
- }
- ImGuiTreeNodeFlags scriptNodeFlag = scriptPassed ? FlagDefaultClosed : FlagDefaultOpen;
- AZStd::string header = AZStd::string::format("%s %s",
- scriptPassed ? "PASSED" : "FAILED",
- scriptReport.m_scriptAssetPath.c_str()
- );
- HighlightTextFailedOrWarning(!scriptPassed, scriptHasWarnings);
- if (ImGui::TreeNodeEx(&scriptReport, scriptNodeFlag, "%s", header.c_str()))
- {
- ResetTextHighlight();
- // Number of Asserts
- HighlightTextIf(scriptReport.m_assertCount > 0, m_highlightSettings.m_highlightFailed);
- if (m_showAll || scriptReport.m_assertCount > 0)
- {
- ImGui::Text("Asserts: %u %s", scriptReport.m_assertCount, SeeConsole(scriptReport.m_assertCount, "Trace::Assert").c_str());
- }
- // Number of Errors
- HighlightTextIf(scriptReport.m_generalErrorCount > 0, m_highlightSettings.m_highlightFailed);
- if (m_showAll || scriptReport.m_generalErrorCount > 0)
- {
- ImGui::Text("Errors: %u %s", scriptReport.m_generalErrorCount, SeeConsole(scriptReport.m_generalErrorCount, "Trace::Error").c_str());
- }
- // Number of Warnings
- HighlightTextIf(scriptReport.m_generalWarningCount > 0, m_highlightSettings.m_highlightWarning);
- if (m_showAll || (m_showWarnings && scriptReport.m_generalWarningCount > 0))
- {
- ImGui::Text("Warnings: %u %s", scriptReport.m_generalWarningCount, SeeConsole(scriptReport.m_generalWarningCount, "Trace::Warning").c_str());
- }
- ResetTextHighlight();
- // Number of screenshots
- if (m_showAll || scriptReport.m_screenshotErrorCount > 0 || (m_showWarnings && scriptReport.m_screenshotWarningCount > 0))
- {
- ImGui::Text("Screenshot Test Count: %zu", scriptReport.m_screenshotTests.size());
- }
- // Number of screenshot failures
- HighlightTextIf(scriptReport.m_screenshotErrorCount > 0, m_highlightSettings.m_highlightFailed);
- if (m_showAll || scriptReport.m_screenshotErrorCount > 0)
- {
- ImGui::Text("Screenshot Tests Failed: %u %s", scriptReport.m_screenshotErrorCount, SeeBelow(scriptReport.m_screenshotErrorCount).c_str());
- }
- // Number of screenshot warnings
- HighlightTextIf(scriptReport.m_screenshotWarningCount > 0, m_highlightSettings.m_highlightWarning);
- if (m_showAll || (m_showWarnings && scriptReport.m_screenshotWarningCount > 0))
- {
- ImGui::Text("Screenshot Warnings: %u %s", scriptReport.m_screenshotWarningCount, SeeBelow(scriptReport.m_screenshotWarningCount).c_str());
- }
- ResetTextHighlight();
- for (ScreenshotTestInfo& screenshotResult : scriptReport.m_screenshotTests)
- {
- const bool screenshotPassed = screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::Pass;
- const bool localBaselineWarning = screenshotResult.m_localComparisonResult.m_resultCode != ImageComparisonResult::ResultCode::Pass;
- AZStd::string fileName;
- AzFramework::StringFunc::Path::GetFullFileName(screenshotResult.m_screenshotFilePath.c_str(), fileName);
- std::stringstream headerSummary;
- if (!screenshotPassed)
- {
- headerSummary << "(" << screenshotResult.m_officialComparisonResult.GetSummaryString().c_str() << ") ";
- }
- if (localBaselineWarning)
- {
- headerSummary << "(Local Baseline Warning)";
- }
- AZStd::string screenshotHeader = AZStd::string::format("%s %s %s",
- screenshotPassed ? "PASSED" : "FAILED",
- fileName.c_str(),
- headerSummary.str().c_str());
- ShowScreenshotTestInfoTreeNode(screenshotHeader, scriptReport, screenshotResult);
- }
- ImGui::TreePop();
- }
- ResetTextHighlight();
- }
- }
- else
- {
- const SortedReportIndexMap* sortedReportMap = nullptr;
- if (m_currentSortOption == SortOption::OfficialBaselineDiffScore)
- {
- sortedReportMap = &m_reportsSortedByOfficialBaslineScore;
- }
- else if (m_currentSortOption == SortOption::LocalBaselineDiffScore)
- {
- sortedReportMap = &m_reportsSortedByLocaBaslineScore;
- }
- AZ_Assert(sortedReportMap, "Unhandled m_currentSortOption");
- if (sortedReportMap)
- {
- for (const auto& [threshold, reportIndex] : *sortedReportMap)
- {
- ScriptReport& scriptReport = m_scriptReports[reportIndex.first];
- ScreenshotTestInfo& screenshotResult = scriptReport.m_screenshotTests[reportIndex.second];
- float diffScore = 0.0f;
- if (m_currentSortOption == SortOption::OfficialBaselineDiffScore)
- {
- diffScore = screenshotResult.m_officialComparisonResult.m_diffScore;
- }
- else if (m_currentSortOption == SortOption::LocalBaselineDiffScore)
- {
- diffScore = screenshotResult.m_localComparisonResult.m_diffScore;
- }
- const bool screenshotPassed = screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::Pass;
- AZStd::string fileName;
- AzFramework::StringFunc::Path::GetFullFileName(screenshotResult.m_screenshotFilePath.c_str(), fileName);
- AZStd::string header = AZStd::string::format("%f %s %s %s '%s'",
- diffScore,
- screenshotPassed ? "PASSED" : "FAILED",
- scriptReport.m_scriptAssetPath.c_str(),
- fileName.c_str(),
- screenshotResult.m_toleranceLevel.m_name.c_str());
- ShowScreenshotTestInfoTreeNode(header, scriptReport, screenshotResult);
- }
- }
- }
- ResetTextHighlight();
- // Repeat the m_invalidationMessage at the bottom as well, to make sure the user doesn't miss it.
- if (!m_invalidationMessage.empty())
- {
- ImGui::Separator();
- ImGui::PushStyleColor(ImGuiCol_Text, m_highlightSettings.m_highlightFailed);
- ImGui::Text("(%s)", m_invalidationMessage.c_str());
- ImGui::PopStyleColor();
- }
- }
- m_messageBox.TickPopup();
- ImGui::End();
- }
- void ScriptReporter::ShowScreenshotTestInfoTreeNode(const AZStd::string& header, ScriptReport& scriptReport, ScreenshotTestInfo& screenshotResult)
- {
- const bool screenshotPassed = screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::Pass;
- const bool localBaselineWarning = screenshotResult.m_localComparisonResult.m_resultCode != ImageComparisonResult::ResultCode::Pass;
- // Skip if tests passed without warnings and we don't want to show successes
- bool skipScreenshot = (screenshotPassed && !localBaselineWarning && !m_showAll);
- // Skip if we only have warnings only and we don't want to show warnings
- skipScreenshot = skipScreenshot || (screenshotPassed && localBaselineWarning && !m_showWarnings);
- if (skipScreenshot)
- {
- return;
- }
- ImGuiTreeNodeFlags screenshotNodeFlag = FlagDefaultClosed;
- HighlightTextFailedOrWarning(!screenshotPassed, localBaselineWarning);
- if (ImGui::TreeNodeEx(&screenshotResult, screenshotNodeFlag, "%s", header.c_str()))
- {
- ResetTextHighlight();
- ImGui::Text("Screenshot: %s", screenshotResult.m_screenshotFilePath.c_str());
- ImGui::Spacing();
- HighlightTextIf(!screenshotPassed, m_highlightSettings.m_highlightFailed);
- ImGui::Text("Official Baseline: %s", screenshotResult.m_officialBaselineScreenshotFilePath.c_str());
- // Official Baseline Result
- ImGui::Indent();
- {
- ImGui::Text("%s", screenshotResult.m_officialComparisonResult.GetSummaryString().c_str());
- if (screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::ThresholdExceeded ||
- screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::Pass)
- {
- ImGui::Text("Used Tolerance: %s", screenshotResult.m_toleranceLevel.ToString().c_str());
- const ImageComparisonToleranceLevel* suggestedTolerance = ScriptReporter::FindBestToleranceLevel(
- screenshotResult.m_officialComparisonResult.m_diffScore,
- screenshotResult.m_toleranceLevel.m_filterImperceptibleDiffs);
- if (suggestedTolerance)
- {
- ImGui::Text("Suggested Tolerance: %s", suggestedTolerance->ToString().c_str());
- }
- if (screenshotResult.m_toleranceLevel.m_filterImperceptibleDiffs)
- {
- // This gives an idea of what the tolerance level would be if the imperceptible diffs were not filtered out.
- const ImageComparisonToleranceLevel* unfilteredTolerance =
- ScriptReporter::FindBestToleranceLevel(screenshotResult.m_officialComparisonResult.m_diffScore, false);
- ImGui::Text(
- "(Unfiltered Diff Score: %f%s)", screenshotResult.m_officialComparisonResult.m_diffScore,
- unfilteredTolerance ? AZStd::string::format(" ~ '%s'", unfilteredTolerance->m_name.c_str()).c_str() : "");
- }
- }
- ResetTextHighlight();
- ImGui::PushID("Official");
- ShowDiffButton("View Diff", screenshotResult.m_officialBaselineScreenshotFilePath, screenshotResult.m_screenshotFilePath);
- ImGui::PopID();
- if ((m_forceShowExportPngDiffButtons ||
- screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::ThresholdExceeded) &&
- ImGui::Button("Export Png Diff"))
- {
- const AZStd::string imageDiffPath = GenerateAndCreateExportedImageDiffPath(scriptReport, screenshotResult);
- ExportImageDiff(imageDiffPath.c_str(), screenshotResult);
- m_messageBox.OpenPopupMessage(
- "Image Diff Exported Successfully",
- AZStd::string::format("The image diff file was saved in %s", imageDiffPath.c_str()).c_str());
- }
- if ((!screenshotPassed || m_forceShowUpdateButtons) && ImGui::Button("Update##Official"))
- {
- if (screenshotResult.m_localComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::FileNotFound)
- {
- UpdateSourceBaselineImage(screenshotResult, true);
- }
- else
- {
- m_messageBox.OpenPopupConfirmation(
- "Update Official Baseline Image",
- "This will replace the official baseline image \n"
- "with the image captured during this test run. \n"
- "Are you sure?",
- // It's important to bind screenshotResult by reference because UpdateOfficialBaselineImage will update it
- [this, &screenshotResult]()
- {
- UpdateSourceBaselineImage(screenshotResult, true);
- });
- }
- }
- }
- ImGui::Unindent();
- ImGui::Spacing();
- HighlightTextIf(localBaselineWarning, m_highlightSettings.m_highlightWarning);
- ImGui::Text("Local Baseline: %s", screenshotResult.m_localBaselineScreenshotFilePath.c_str());
- // Local Baseline Result
- ImGui::Indent();
- {
- ImGui::Text("%s", screenshotResult.m_localComparisonResult.GetSummaryString().c_str());
- ResetTextHighlight();
- ImGui::PushID("Local");
- ShowDiffButton("View Diff", screenshotResult.m_localBaselineScreenshotFilePath, screenshotResult.m_screenshotFilePath);
- ImGui::PopID();
- if ((localBaselineWarning || m_forceShowUpdateButtons) && ImGui::Button("Update##Local"))
- {
- if (screenshotResult.m_localComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::FileNotFound)
- {
- UpdateLocalBaselineImage(screenshotResult, true);
- }
- else
- {
- m_messageBox.OpenPopupConfirmation(
- "Update Local Baseline Image",
- "This will replace the local baseline image \n"
- "with the image captured during this test run. \n"
- "Are you sure?",
- // It's important to bind screenshotResult by reference because UpdateLocalBaselineImage will update it
- [this, &screenshotResult]()
- {
- UpdateLocalBaselineImage(screenshotResult, true);
- });
- }
- }
- }
- ImGui::Unindent();
- ImGui::Spacing();
- ResetTextHighlight();
- ImGui::TreePop();
- }
- }
- void ScriptReporter::OpenReportDialog()
- {
- m_showReportDialog = true;
- }
- void ScriptReporter::HideReportDialog()
- {
- m_showReportDialog = false;
- }
- ScriptReporter::ScriptReport* ScriptReporter::GetCurrentScriptReport()
- {
- if (!m_currentScriptIndexStack.empty())
- {
- return &m_scriptReports[m_currentScriptIndexStack.back()];
- }
- else
- {
- return nullptr;
- }
- }
- AZStd::string ScriptReporter::SeeConsole(uint32_t issueCount, const char* searchString)
- {
- if (issueCount == 0)
- {
- return AZStd::string{};
- }
- else
- {
- return AZStd::string::format("(See \"%s\" messages in console output)", searchString);
- }
- }
- AZStd::string ScriptReporter::SeeBelow(uint32_t issueCount)
- {
- if (issueCount == 0)
- {
- return AZStd::string{};
- }
- else
- {
- return AZStd::string::format("(See below)");
- }
- }
- void ScriptReporter::HighlightTextIf(bool shouldSet, ImVec4 color)
- {
- if (m_colorHasBeenSet)
- {
- ImGui::PopStyleColor();
- m_colorHasBeenSet = false;
- }
- if (shouldSet)
- {
- ImGui::PushStyleColor(ImGuiCol_Text, color);
- m_colorHasBeenSet = true;
- }
- }
- void ScriptReporter::ResetTextHighlight()
- {
- if (m_colorHasBeenSet)
- {
- ImGui::PopStyleColor();
- m_colorHasBeenSet = false;
- }
- }
- void ScriptReporter::HighlightTextFailedOrWarning(bool isFailed, bool isWarning)
- {
- if (m_colorHasBeenSet)
- {
- ImGui::PopStyleColor();
- m_colorHasBeenSet = false;
- }
- if (isFailed)
- {
- ImGui::PushStyleColor(ImGuiCol_Text, m_highlightSettings.m_highlightFailed);
- m_colorHasBeenSet = true;
- }
- else if (isWarning)
- {
- ImGui::PushStyleColor(ImGuiCol_Text, m_highlightSettings.m_highlightWarning);
- m_colorHasBeenSet = true;
- }
- }
- void ScriptReporter::SortScriptReports()
- {
- for (size_t i = 0; i < m_scriptReports.size(); ++i)
- {
- const AZStd::vector<ScriptReporter::ScreenshotTestInfo>& screenshotTestInfos = m_scriptReports[i].m_screenshotTests;
- for (size_t j = 0; j < screenshotTestInfos.size(); ++j)
- {
- m_reportsSortedByOfficialBaslineScore.insert(AZStd::pair<float, ReportIndex>(
- screenshotTestInfos[j].m_officialComparisonResult.m_diffScore,
- ReportIndex{ i, j }));
- m_reportsSortedByLocaBaslineScore.insert(AZStd::pair<float, ReportIndex>(
- screenshotTestInfos[j].m_localComparisonResult.m_diffScore,
- ReportIndex{ i, j }));
- }
- }
- }
- void ScriptReporter::ReportScriptError([[maybe_unused]] const AZStd::string& message)
- {
- AZ_Error("ScriptReporter", false, "Script: %s", message.c_str());
- }
- void ScriptReporter::ReportScriptWarning([[maybe_unused]] const AZStd::string& message)
- {
- AZ_Warning("ScriptReporter", false, "Script: %s", message.c_str());
- }
- void ScriptReporter::ReportScriptIssue(const AZStd::string& message, TraceLevel traceLevel)
- {
- switch (traceLevel)
- {
- case TraceLevel::Error:
- ReportScriptError(message);
- break;
- case TraceLevel::Warning:
- ReportScriptWarning(message);
- break;
- default:
- AZ_Assert(false, "Unhandled TraceLevel");
- }
- }
- void ScriptReporter::ReportScreenshotComparisonIssue(const AZStd::string& message, const AZStd::string& expectedImageFilePath, const AZStd::string& actualImageFilePath, TraceLevel traceLevel)
- {
- AZStd::string fullMessage = AZStd::string::format("%s\n Expected: '%s'\n Actual: '%s'",
- message.c_str(),
- expectedImageFilePath.c_str(),
- actualImageFilePath.c_str());
- ReportScriptIssue(fullMessage, traceLevel);
- }
- void ScriptReporter::UpdateAllLocalBaselineImages()
- {
- int failureCount = 0;
- int successCount = 0;
- for (ScriptReport& report : m_scriptReports)
- {
- for (ScreenshotTestInfo& screenshotTest : report.m_screenshotTests)
- {
- if (UpdateLocalBaselineImage(screenshotTest, false))
- {
- successCount++;
- }
- else
- {
- failureCount++;
- }
- }
- }
- ShowUpdateLocalBaselineResult(successCount, failureCount);
- }
-
- bool ScriptReporter::UpdateLocalBaselineImage(ScreenshotTestInfo& screenshotTest, bool showResultDialog)
- {
- const AZStd::string destinationFile = screenshotTest.m_localBaselineScreenshotFilePath;
- AZStd::string destinationFolder = destinationFile;
- AzFramework::StringFunc::Path::StripFullName(destinationFolder);
- bool failed = false;
- if (!AZ::IO::LocalFileIO::GetInstance()->CreatePath(destinationFolder.c_str()))
- {
- failed = true;
- AZ_Error("ScriptReporter", false, "Failed to create folder '%s'.", destinationFolder.c_str());
- }
- if (!AZ::IO::LocalFileIO::GetInstance()->Copy(screenshotTest.m_screenshotFilePath.c_str(), destinationFile.c_str()))
- {
- failed = true;
- AZ_Error("ScriptReporter", false, "Failed to copy '%s' to '%s'.", screenshotTest.m_screenshotFilePath.c_str(), destinationFile.c_str());
- }
- if (!failed)
- {
- // Since we just replaced the baseline image, we can update this screenshot test result as an exact match.
- // This will update the ImGui report dialog by the next frame.
- ClearImageComparisonResult(screenshotTest.m_localComparisonResult);
- }
- if (showResultDialog)
- {
- int successCount = !failed;
- int failureCount = failed;
- ShowUpdateLocalBaselineResult(successCount, failureCount);
- }
- return !failed;
- }
- bool ScriptReporter::UpdateSourceBaselineImage(ScreenshotTestInfo& screenshotTest, bool showResultDialog)
- {
- bool success = true;
- auto io = AZ::IO::LocalFileIO::GetInstance();
- // Get source folder
- if (m_officialBaselineSourceFolder.empty())
- {
- m_officialBaselineSourceFolder = (AZ::IO::FixedMaxPath(AZ::Utils::GetProjectPath()) / "Scripts" / "ExpectedScreenshots").String();
- if (!io->Exists(m_officialBaselineSourceFolder.c_str()))
- {
- AZ_Error("ScriptReporter", false, "Could not find source folder '%s'. Copying to source baseline can only be used on dev platforms.", m_officialBaselineSourceFolder.c_str());
- m_officialBaselineSourceFolder.clear();
- success = false;
- }
- }
- // Get official cache baseline file
- const AZStd::string cacheFilePath = screenshotTest.m_officialBaselineScreenshotFilePath;
- // Divide cache file path into components to we can access the file name and the parent folder
- AZStd::fixed_vector<AZ::IO::FixedMaxPathString, 16> reversePathComponents;
- auto GatherPathSegments = [&reversePathComponents](AZStd::string_view token)
- {
- reversePathComponents.emplace_back(token);
- };
- AzFramework::StringFunc::TokenizeVisitorReverse(cacheFilePath, GatherPathSegments, "/\\");
- // Source folder path
- // ".../AtomSampleViewer/Scripts/ExpectedScreenshots/" + "MyTestFolder/"
- AZStd::string sourceFolderPath = AZStd::string::format("%s\\%s", m_officialBaselineSourceFolder.c_str(), reversePathComponents[1].c_str());
- // Source file path
- // ".../AtomSampleViewer/Scripts/ExpectedScreenshots/MyTestFolder/" + "MyTest.png"
- AZStd::string sourceFilePath = AZStd::string::format("%s\\%s", sourceFolderPath.c_str(), reversePathComponents[0].c_str());
- // Create parent folder if it doesn't exist
- if (success && !io->CreatePath(sourceFolderPath.c_str()))
- {
- success = false;
- AZ_Error("ScriptReporter", false, "Failed to create folder '%s'.", sourceFolderPath.c_str());
- }
- // Replace source screenshot with new result
- if (success && !io->Copy(screenshotTest.m_screenshotFilePath.c_str(), sourceFilePath.c_str()))
- {
- success = false;
- AZ_Error("ScriptReporter", false, "Failed to copy '%s' to '%s'.", screenshotTest.m_screenshotFilePath.c_str(), sourceFilePath.c_str());
- }
- if (success)
- {
- // Since we just replaced the baseline image, we can update this screenshot test result as an exact match.
- // This will update the ImGui report dialog by the next frame.
- ClearImageComparisonResult(screenshotTest.m_officialComparisonResult);
- }
- if (showResultDialog)
- {
- AZStd::string message = "Destination: " + sourceFilePath + "\n";
- message += success
- ? AZStd::string::format("Copy successful!.\n")
- : AZStd::string::format("Copy failed!\n");
- m_messageBox.OpenPopupMessage("Update Baseline Image(s) Result", message);
- }
- return success;
- }
- void ScriptReporter::ClearImageComparisonResult(ImageComparisonResult& comparisonResult)
- {
- comparisonResult.m_resultCode = ImageComparisonResult::ResultCode::Pass;
- comparisonResult.m_diffScore = 0.0f;
- }
- void ScriptReporter::ShowUpdateLocalBaselineResult(int successCount, int failureCount)
- {
- AZStd::string message;
- if (failureCount == 0 && successCount == 0)
- {
- message = "No screenshots found.";
- }
- else
- {
- AZ::Render::FrameCapturePathOutcome pathOutcome;
- AZ::Render::FrameCaptureTestRequestBus::BroadcastResult(
- pathOutcome,
- &AZ::Render::FrameCaptureTestRequestBus::Events::BuildScreenshotFilePath,
- "", true);
- AZ_Error("ScriptReporter", pathOutcome.IsSuccess(), "%s", pathOutcome.GetError().m_errorMessage.c_str());
- AZStd::string localBaselineFolder = pathOutcome.IsSuccess() ? pathOutcome.GetValue() : "";
- message = "Destination: " + localBaselineFolder + "\n";
- if (successCount > 0)
- {
- message += AZStd::string::format("Successfully copied %d files.\n", successCount);
- }
- if (failureCount > 0)
- {
- message += AZStd::string::format("Failed to copy %d files.\n", failureCount);
- }
- }
- m_messageBox.OpenPopupMessage("Update Baseline Image(s) Result", message);
- }
- void ScriptReporter::CheckLatestScreenshot(const ImageComparisonToleranceLevel* toleranceLevel)
- {
- AZ_Assert(GetCurrentScriptReport(), "There is no active script");
- if (GetCurrentScriptReport() == nullptr || GetCurrentScriptReport()->m_screenshotTests.empty())
- {
- ReportScriptError("CheckLatestScreenshot() did not find any screenshots to check.");
- return;
- }
- ScreenshotTestInfo& screenshotTestInfo = GetCurrentScriptReport()->m_screenshotTests.back();
- if (toleranceLevel == nullptr)
- {
- screenshotTestInfo.m_officialComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::NullImageComparisonToleranceLevel;
- ReportScriptError("Screenshot check failed. No ImageComparisonToleranceLevel provided.");
- return;
- }
- auto io = AZ::IO::LocalFileIO::GetInstance();
- screenshotTestInfo.m_toleranceLevel = *toleranceLevel;
- static constexpr float ImperceptibleDiffFilter = 0.01;
- if (screenshotTestInfo.m_officialBaselineScreenshotFilePath.empty()
- || !io->Exists(screenshotTestInfo.m_officialBaselineScreenshotFilePath.c_str()))
- {
- ReportScriptError(AZStd::string::format("Screenshot check failed. Could not determine expected screenshot path for '%s'", screenshotTestInfo.m_screenshotFilePath.c_str()));
- screenshotTestInfo.m_officialComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::FileNotFound;
- }
- else
- {
- AZ::Render::FrameCaptureComparisonOutcome compOutcome;
- AZ::Render::FrameCaptureTestRequestBus::BroadcastResult(
- compOutcome,
- &AZ::Render::FrameCaptureTestRequestBus::Events::CompareScreenshots,
- screenshotTestInfo.m_screenshotFilePath,
- screenshotTestInfo.m_officialBaselineScreenshotFilePath,
- ImperceptibleDiffFilter
- );
- screenshotTestInfo.m_officialComparisonResult.m_diffScore = 0.0;
- if (compOutcome.IsSuccess())
- {
- screenshotTestInfo.m_officialComparisonResult.m_diffScore = toleranceLevel->m_filterImperceptibleDiffs
- ? compOutcome.GetValue().m_diffScore
- : compOutcome.GetValue().m_filteredDiffScore;
- if (screenshotTestInfo.m_officialComparisonResult.m_diffScore <= toleranceLevel->m_threshold)
- {
- screenshotTestInfo.m_officialComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::Pass;
- }
- else
- {
- // Be aware there is an automation test script that looks for the "Screenshot check failed. Diff score" string text to report failures.
- // If you change this message, be sure to update the associated tests as well located here: "C:/path/to/Lumberyard/AtomSampleViewer/Standalone/PythonTests"
- ReportScreenshotComparisonIssue(
- AZStd::string::format("Screenshot check failed. Diff score %f exceeds threshold of %f ('%s').",
- screenshotTestInfo.m_officialComparisonResult.m_diffScore, toleranceLevel->m_threshold, toleranceLevel->m_name.c_str()),
- screenshotTestInfo.m_officialBaselineScreenshotFilePath,
- screenshotTestInfo.m_screenshotFilePath,
- TraceLevel::Error);
- screenshotTestInfo.m_officialComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::ThresholdExceeded;
- }
- }
- }
- if (screenshotTestInfo.m_localBaselineScreenshotFilePath.empty()
- || !io->Exists(screenshotTestInfo.m_localBaselineScreenshotFilePath.c_str()))
- {
- ReportScriptWarning(AZStd::string::format("Screenshot check failed. Could not determine local baseline screenshot path for '%s'", screenshotTestInfo.m_screenshotFilePath.c_str()));
- screenshotTestInfo.m_localComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::FileNotFound;
- }
- else
- {
- // Local screenshots should be expected match 100% every time, otherwise warnings are reported. This will help developers track and investigate changes,
- // for example if they make local changes that impact some unrelated AtomSampleViewer sample in an unexpected way, they will see a warning about this.
- AZ::Render::FrameCaptureComparisonOutcome compOutcome;
- AZ::Render::FrameCaptureTestRequestBus::BroadcastResult(
- compOutcome,
- &AZ::Render::FrameCaptureTestRequestBus::Events::CompareScreenshots,
- screenshotTestInfo.m_screenshotFilePath,
- screenshotTestInfo.m_localBaselineScreenshotFilePath,
- ImperceptibleDiffFilter
- );
- screenshotTestInfo.m_localComparisonResult.m_diffScore = 0.0f;
- if (compOutcome.IsSuccess())
- {
- screenshotTestInfo.m_localComparisonResult.m_diffScore = compOutcome.GetValue().m_diffScore;
- if (screenshotTestInfo.m_localComparisonResult.m_diffScore == 0.0f)
- {
- screenshotTestInfo.m_localComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::Pass;
- }
- else
- {
- ReportScreenshotComparisonIssue(
- AZStd::string::format("Screenshot check failed. Screenshot does not match the local baseline; something has changed. Diff score is %f.", screenshotTestInfo.m_localComparisonResult.m_diffScore),
- screenshotTestInfo.m_localBaselineScreenshotFilePath,
- screenshotTestInfo.m_screenshotFilePath,
- TraceLevel::Warning);
- screenshotTestInfo.m_localComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::ThresholdExceeded;
- }
- }
- }
- }
- void ScriptReporter::ExportTestResults()
- {
- m_exportedTestResultsPath = GenerateAndCreateExportedTestResultsPath();
- for (const ScriptReport& scriptReport : m_scriptReports)
- {
- const AZStd::string assertLogLine = AZStd::string::format("Asserts: %u \n", scriptReport.m_assertCount);
- const AZStd::string errorsLogLine = AZStd::string::format("Errors: %u \n", scriptReport.m_generalErrorCount);
- const AZStd::string warningsLogLine = AZStd::string::format("Warnings: %u \n", scriptReport.m_generalWarningCount);
- const AZStd::string screenshotErrorsLogLine = AZStd::string::format("Screenshot errors: %u \n", scriptReport.m_screenshotErrorCount);
- const AZStd::string screenshotWarningsLogLine = AZStd::string::format("Screenshot warnings: %u \n", scriptReport.m_screenshotWarningCount);
- const AZStd::string failedScreenshotsLogLine = "\nScreenshot test info below.\n";
-
- AZ::IO::HandleType logHandle;
- auto io = AZ::IO::LocalFileIO::GetInstance();
- if (io->Open(m_exportedTestResultsPath.c_str(), AZ::IO::OpenMode::ModeWrite, logHandle))
- {
- io->Write(logHandle, assertLogLine.c_str(), assertLogLine.size());
- io->Write(logHandle, errorsLogLine.c_str(), errorsLogLine.size());
- io->Write(logHandle, warningsLogLine.c_str(), warningsLogLine.size());
- io->Write(logHandle, screenshotErrorsLogLine.c_str(), screenshotErrorsLogLine.size());
- io->Write(logHandle, screenshotWarningsLogLine.c_str(), screenshotWarningsLogLine.size());
- io->Write(logHandle, failedScreenshotsLogLine.c_str(), failedScreenshotsLogLine.size());
- for (const ScreenshotTestInfo& screenshotTest : scriptReport.m_screenshotTests)
- {
- const AZStd::string screenshotPath = AZStd::string::format("Test screenshot path: %s \n", screenshotTest.m_screenshotFilePath.c_str());
- const AZStd::string officialBaselineScreenshotPath = AZStd::string::format("Official baseline screenshot path: %s \n", screenshotTest.m_officialBaselineScreenshotFilePath.c_str());
- const AZStd::string toleranceLevelLogLine = AZStd::string::format("Tolerance level: %s \n", screenshotTest.m_toleranceLevel.ToString().c_str());
- const AZStd::string officialComparisonLogLine = AZStd::string::format("Image comparison result: %s \n", screenshotTest.m_officialComparisonResult.GetSummaryString().c_str());
- io->Write(logHandle, toleranceLevelLogLine.c_str(), toleranceLevelLogLine.size());
- io->Write(logHandle, officialComparisonLogLine.c_str(), officialComparisonLogLine.size());
- }
- io->Close(logHandle);
- }
- m_messageBox.OpenPopupMessage("Exported test results", AZStd::string::format("Results exported to %s", m_exportedTestResultsPath.c_str()));
- AZ_Printf("Test results exported to %s \n", m_exportedTestResultsPath.c_str());
- }
- }
- void ScriptReporter::ExportImageDiff(const char* filePath, const ScreenshotTestInfo& screenshotTestInfo)
- {
- using namespace AZ::Utils;
- PngFile officialBaseline = PngFile::Load(screenshotTestInfo.m_officialBaselineScreenshotFilePath.c_str());
- PngFile actualScreenshot = PngFile::Load(screenshotTestInfo.m_screenshotFilePath.c_str());
- const size_t bufferSize = officialBaseline.GetBuffer().size();
- AZStd::vector<uint8_t> diffBuffer = AZStd::vector<uint8_t>(bufferSize);
- GenerateImageDiff(officialBaseline.GetBuffer(), actualScreenshot.GetBuffer(), diffBuffer);
- AZStd::vector<uint8_t> buffer = AZStd::vector<uint8_t>(bufferSize * 3);
- memcpy(buffer.data(), officialBaseline.GetBuffer().data(), bufferSize);
- memcpy(buffer.data() + bufferSize, actualScreenshot.GetBuffer().data(), bufferSize);
- memcpy(buffer.data() + bufferSize * 2, diffBuffer.data(), bufferSize);
- PngFile imageDiff = PngFile::Create(AZ::RHI::Size(officialBaseline.GetWidth(), officialBaseline.GetHeight() * 3, 1), AZ::RHI::Format::R8G8B8A8_UNORM, buffer);
- imageDiff.Save(filePath);
- }
- AZStd::string ScriptReporter::ExportImageDiff(const ScriptReport& scriptReport, const ScreenshotTestInfo& screenshotTest)
- {
- const AZStd::string imageDiffPath = GenerateAndCreateExportedImageDiffPath(scriptReport, screenshotTest);
- ExportImageDiff(imageDiffPath.c_str(), screenshotTest);
- return imageDiffPath;
- }
- AZStd::string ScriptReporter::GenerateAndCreateExportedTestResultsPath() const
- {
- // Setup our variables for the exported test results path and .txt file.
- const auto projectPath = AZ::Utils::GetProjectPath();
- const AZStd::string exportFileName = AZStd::string::format("exportedTestResults_%s.txt", m_uniqueTimestamp.c_str());
- AZStd::string exportTestResultsFolder;
- AzFramework::StringFunc::Path::Join(projectPath.c_str(), TestResultsFolder, exportTestResultsFolder);
- // Create the exported test results path & return .txt file path.
- auto io = AZ::IO::LocalFileIO::GetInstance();
- io->CreatePath(exportTestResultsFolder.c_str());
- AZStd::string exportFile;
- AzFramework::StringFunc::Path::Join(exportTestResultsFolder.c_str(), exportFileName.c_str(), exportFile);
- return exportFile;
- }
- void ScriptReporter::GenerateImageDiff(AZStd::span<const uint8_t> img1, AZStd::span<const uint8_t> img2, AZStd::vector<uint8_t>& buffer)
- {
- static constexpr size_t BytesPerPixel = 4;
- static constexpr float MinDiffFilter = 0.01;
- static constexpr uint8_t DefaultPixelValue = 122;
- memset(buffer.data(), DefaultPixelValue, buffer.size() * sizeof(uint8_t));
- for (size_t i = 0; i < img1.size(); i += BytesPerPixel)
- {
- const int16_t maxDiff = AZ::Utils::CalcMaxChannelDifference(img1, img2, i);
- if (maxDiff / 255.0f > MinDiffFilter)
- {
- buffer[i] = aznumeric_cast<uint8_t>(maxDiff);
- buffer[i + 1] = 0;
- buffer[i + 2] = 0;
- }
- buffer[i + 3] = 255;
- }
- }
- void ScriptReporter::HighlightColorSettings::UpdateColorSettings()
- {
- const ImVec4& bgColor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
- const bool isDarkStyle = bgColor.x < 0.2 && bgColor.y < 0.2 && bgColor.z < 0.2;
- m_highlightPassed = isDarkStyle ? ImVec4{ 0.5, 1, 0.5, 1 } : ImVec4{ 0, 0.75, 0, 1 };
- m_highlightFailed = isDarkStyle ? ImVec4{ 1, 0.5, 0.5, 1 } : ImVec4{ 0.75, 0, 0, 1 };
- m_highlightWarning = isDarkStyle ? ImVec4{ 1, 1, 0.5, 1 } : ImVec4{ 0.5, 0.5, 0, 1 };
- }
- } // namespace AtomSampleViewer
|