8#include "absl/status/statusor.h"
9#include "absl/strings/match.h"
10#include "absl/strings/str_cat.h"
11#include "absl/strings/str_format.h"
12#include "absl/strings/str_replace.h"
13#include "absl/synchronization/mutex.h"
14#include "absl/time/clock.h"
15#include "absl/time/time.h"
23#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
25#include "imgui_internal.h"
39#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
42#if __has_include("imgui_test_engine/imgui_te_engine.h")
43#include "imgui_test_engine/imgui_te_engine.h"
44#elif __has_include("imgui_te_engine.h")
45#include "imgui_te_engine.h"
47#error "ImGui Test Engine headers not found"
57 std::filesystem::path base_dir = std::filesystem::temp_directory_path() /
58 "yaze" /
"test-results" / test_id;
60 std::filesystem::create_directories(base_dir, ec);
62 const int64_t timestamp_ms = absl::ToUnixMillis(absl::Now());
63 std::filesystem::path file_path =
64 base_dir / std::filesystem::path(absl::StrFormat(
65 "failure_%lld.bmp",
static_cast<long long>(timestamp_ms)));
66 return file_path.string();
117 return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
120#if defined(YAZE_WITH_GRPC)
121const char* HarnessStatusToString(HarnessTestStatus status) {
123 case HarnessTestStatus::kPassed:
125 case HarnessTestStatus::kFailed:
127 case HarnessTestStatus::kTimeout:
129 case HarnessTestStatus::kRunning:
131 case HarnessTestStatus::kQueued:
133 case HarnessTestStatus::kUnspecified:
135 return "unspecified";
139HarnessTestStatus HarnessStatusFromString(absl::string_view status) {
140 if (absl::EqualsIgnoreCase(status,
"passed")) {
141 return HarnessTestStatus::kPassed;
143 if (absl::EqualsIgnoreCase(status,
"failed")) {
144 return HarnessTestStatus::kFailed;
146 if (absl::EqualsIgnoreCase(status,
"timeout")) {
147 return HarnessTestStatus::kTimeout;
149 if (absl::EqualsIgnoreCase(status,
"running")) {
150 return HarnessTestStatus::kRunning;
152 if (absl::EqualsIgnoreCase(status,
"queued")) {
153 return HarnessTestStatus::kQueued;
155 return HarnessTestStatus::kUnspecified;
171#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
176#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
178 if (!ui_test_engine_) {
180 if (ImGui::GetCurrentContext() ==
nullptr) {
182 "ImGui context not ready, deferring test engine initialization");
186 ui_test_engine_ = ImGuiTestEngine_CreateContext();
187 if (ui_test_engine_) {
188 ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(ui_test_engine_);
189 test_io.ConfigVerboseLevel = ImGuiTestVerboseLevel_Info;
190 test_io.ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Debug;
191 test_io.ConfigRunSpeed = ImGuiTestRunSpeed_Fast;
194 ImGuiTestEngine_Start(ui_test_engine_, ImGui::GetCurrentContext());
201 "ImGuiTestEngine initialized with dungeon + overworld UI tests");
207 if (ui_test_engine_ && ImGui::GetCurrentContext() !=
nullptr) {
208 ImGuiTestEngine_PostSwap(ui_test_engine_);
213 if (ui_test_engine_ && ImGui::GetCurrentContext() !=
nullptr) {
214 ImGuiTestEngine_Stop(ui_test_engine_);
219 if (ui_test_engine_) {
220 ImGuiTestEngine_DestroyContext(ui_test_engine_);
221 ui_test_engine_ =
nullptr;
234 return absl::FailedPreconditionError(
"Tests are already running");
243 if (suite->IsEnabled()) {
258 return absl::OkStatus();
263 return absl::FailedPreconditionError(
"Tests are already running");
271 std::vector<TestSuite*> filtered_suites;
273 if (suite->IsEnabled() && suite->GetCategory() == category) {
274 filtered_suites.push_back(suite.get());
278 for (
auto* suite : filtered_suites) {
292 return absl::OkStatus();
297 return absl::FailedPreconditionError(
"Tests are already running");
302 return absl::NotFoundError(
"Test suite not found: " + suite_name);
321 std::string name = suite->GetName();
328 std::vector<std::string> names;
331 names.push_back(suite->GetName());
348 return absl::InvalidArgumentError(
"Test suite is null");
369 size_t completed = 0;
371 if (suite->IsEnabled()) {
381 stats.
timestamp = std::chrono::steady_clock::now();
408 bool* dashboard_flag = show_dashboard ? show_dashboard : &
show_dashboard_;
411 ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
413 if (!ImGui::Begin(
"Test Dashboard", dashboard_flag,
414 ImGuiWindowFlags_MenuBar)) {
423 static int frame_counter = 0;
425 if (frame_counter % 60 == 0) {
428 "TestManager ROM status check - Frame %d: ROM %p, loaded: %s",
429 frame_counter, (
void*)
current_rom_, has_rom ?
"true" :
"false");
432 if (ImGui::BeginTable(
"ROM_Status_Table", 2, ImGuiTableFlags_BordersInner)) {
433 ImGui::TableSetupColumn(
"Property", ImGuiTableColumnFlags_WidthFixed, 120);
434 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch);
436 ImGui::TableNextRow();
437 ImGui::TableNextColumn();
438 ImGui::Text(
"ROM Status:");
439 ImGui::TableNextColumn();
444 ImGui::TableNextRow();
445 ImGui::TableNextColumn();
446 ImGui::Text(
"ROM Title:");
447 ImGui::TableNextColumn();
450 ImGui::TableNextRow();
451 ImGui::TableNextColumn();
452 ImGui::Text(
"File Name:");
453 ImGui::TableNextColumn();
456 ImGui::TableNextRow();
457 ImGui::TableNextColumn();
458 ImGui::Text(
"Size:");
459 ImGui::TableNextColumn();
463 ImGui::TableNextRow();
464 ImGui::TableNextColumn();
465 ImGui::Text(
"Modified:");
466 ImGui::TableNextColumn();
473 ImGui::TableNextRow();
474 ImGui::TableNextColumn();
475 ImGui::Text(
"ROM Pointer:");
476 ImGui::TableNextColumn();
479 ImGui::TableNextRow();
480 ImGui::TableNextColumn();
481 ImGui::Text(
"Actions:");
482 ImGui::TableNextColumn();
483 if (ImGui::Button(
"Refresh ROM Reference")) {
490 ImGui::TableNextRow();
491 ImGui::TableNextColumn();
492 ImGui::Text(
"ROM Pointer:");
493 ImGui::TableNextColumn();
495 ImGui::TableNextRow();
496 ImGui::TableNextColumn();
497 ImGui::Text(
"Status:");
498 ImGui::TableNextColumn();
499 ImGui::Text(
"ROM-dependent tests will be skipped");
501 ImGui::TableNextRow();
502 ImGui::TableNextColumn();
503 ImGui::Text(
"Actions:");
504 ImGui::TableNextColumn();
505 if (ImGui::Button(
"Refresh ROM Reference")) {
509 if (ImGui::Button(
"Debug ROM State")) {
510 LOG_INFO(
"TestManager",
"=== ROM DEBUG INFO ===");
511 LOG_INFO(
"TestManager",
"current_rom_ pointer: %p",
514 LOG_INFO(
"TestManager",
"ROM title: '%s'",
517 LOG_INFO(
"TestManager",
"ROM is_loaded(): %s",
519 LOG_INFO(
"TestManager",
"ROM data pointer: %p",
522 LOG_INFO(
"TestManager",
"======================");
531 if (ImGui::BeginMenuBar()) {
532 if (ImGui::BeginMenu(
"Run")) {
533 if (ImGui::MenuItem(
"All Tests",
"Ctrl+T",
false, !
is_running_)) {
537 if (ImGui::MenuItem(
"Unit Tests",
nullptr,
false, !
is_running_)) {
540 if (ImGui::MenuItem(
"Integration Tests",
nullptr,
false, !
is_running_)) {
541 [[maybe_unused]]
auto status =
544 if (ImGui::MenuItem(
"UI Tests",
nullptr,
false, !
is_running_)) {
547 if (ImGui::MenuItem(
"Performance Tests",
nullptr,
false, !
is_running_)) {
548 [[maybe_unused]]
auto status =
551 if (ImGui::MenuItem(
"Memory Tests",
nullptr,
false, !
is_running_)) {
552 [[maybe_unused]]
auto status =
558 if (ImGui::BeginMenu(
"View")) {
563 if (ImGui::MenuItem(
"Export Results",
nullptr,
false,
570 if (ImGui::BeginMenu(
"ROM")) {
571 if (ImGui::MenuItem(
"Test Current ROM",
nullptr,
false,
573 [[maybe_unused]]
auto status =
576 if (ImGui::MenuItem(
"Load ROM for Testing...")) {
580 if (ImGui::MenuItem(
"Refresh ROM Reference")) {
586 if (ImGui::BeginMenu(
"Configure")) {
587 if (ImGui::MenuItem(
"Test Configuration")) {
592 if (ImGui::MenuItem(
"Use NFD File Dialog",
nullptr, &nfd_mode)) {
594 LOG_INFO(
"TestManager",
"Global file dialog mode changed to: %s",
595 nfd_mode ?
"NFD" :
"Bespoke");
604 int enabled_count = 0;
606 static const std::vector<std::string> all_test_names = {
607 "ROM_Header_Validation_Test",
"ROM_Data_Access_Test",
608 "ROM_Graphics_Extraction_Test",
"ROM_Overworld_Loading_Test",
609 "Tile16_Editor_Test",
"Comprehensive_Save_Test",
610 "ROM_Sprite_Data_Test",
"ROM_Music_Data_Test"};
612 for (
const auto& test_name : all_test_names) {
621 if (enabled_count < total_count) {
624 "(Some tests disabled - check Configuration)");
635 ImGui::ProgressBar(
progress_, ImVec2(-1, 0),
636 absl::StrFormat(
"%.0f%%",
progress_ * 100.0f).c_str());
650 if (ImGui::Button(absl::StrCat(
ICON_MD_SPEED,
" Quick Test").c_str(),
658 std::optional<gui::StyleColorGuard> rom_btn_guard;
665 [[maybe_unused]]
auto status =
670 if (ImGui::IsItemHovered()) {
672 ImGui::SetTooltip(
"Run tests on current ROM: %s",
675 ImGui::SetTooltip(
"Load a ROM to enable ROM-dependent tests");
680 if (ImGui::Button(absl::StrCat(
ICON_MD_CLEAR,
" Clear").c_str(),
708 pass_rate, ImVec2(-1, 0),
709 absl::StrFormat(
"Pass Rate: %.1f%%", pass_rate * 100.0f).c_str());
728 if (ImGui::CollapsingHeader(
"Test Suite Breakdown")) {
729 std::unordered_map<std::string, std::pair<size_t, size_t>>
732 suite_stats[result.suite_name].second++;
734 suite_stats[result.suite_name].first++;
738 for (
const auto& [suite_name, stats] : suite_stats) {
739 float suite_pass_rate =
740 stats.second > 0 ?
static_cast<float>(stats.first) / stats.second
742 ImGui::Text(
"%s: %zu/%zu (%.0f%%)", suite_name.c_str(), stats.first,
743 stats.second, suite_pass_rate * 100.0f);
754 const char* categories[] = {
"All",
"Unit",
"Integration",
755 "UI",
"Performance",
"Memory"};
756 static int selected_category = 0;
757 if (ImGui::Combo(
"Category", &selected_category, categories,
758 IM_ARRAYSIZE(categories))) {
759 switch (selected_category) {
782 static char filter_buffer[256] =
"";
783 ImGui::SetNextItemWidth(-80);
784 if (ImGui::InputTextWithHint(
"##filter",
"Search tests...", filter_buffer,
785 sizeof(filter_buffer))) {
789 if (ImGui::Button(
"Clear")) {
790 filter_buffer[0] =
'\0';
797 if (ImGui::BeginTabBar(
"TestResultsTabs", ImGuiTabBarFlags_None)) {
799 if (ImGui::BeginTabItem(
"Test Results")) {
800 if (ImGui::BeginChild(
"TestResults", ImVec2(0, 0),
true)) {
803 "No test results to display. Run some tests to "
804 "see results here.");
808 bool category_match = (selected_category == 0) ||
813 result.suite_name.find(
test_filter_) != std::string::npos;
815 if (!category_match || !text_match) {
819 ImGui::PushID(&result);
823 switch (result.status) {
841 status_icon, result.suite_name.c_str(),
842 result.name.c_str());
845 if (ImGui::GetContentRegionAvail().x > 200) {
848 result.duration.count());
853 !result.error_message.empty()) {
859 result.error_message.c_str());
866 !result.error_message.empty()) {
872 result.error_message.c_str());
886#if defined(YAZE_WITH_GRPC)
887 if (ImGui::BeginTabItem(
"GUI Automation Tests")) {
888 if (ImGui::BeginChild(
"HarnessTests", ImVec2(0, 0),
true)) {
890 auto summaries = ListHarnessTestSummaries();
892 if (summaries.empty()) {
895 "No GUI automation test results yet.\n\n"
896 "These tests are run via the ImGuiTestHarness gRPC service.\n"
897 "Results will appear here after running GUI automation tests.");
900 ImGui::Text(
"Total Tests: %zu", summaries.size());
904 if (ImGui::BeginTable(
"HarnessTestTable", 6,
905 ImGuiTableFlags_Borders |
906 ImGuiTableFlags_RowBg |
907 ImGuiTableFlags_Resizable)) {
908 ImGui::TableSetupColumn(
"Status", ImGuiTableColumnFlags_WidthFixed,
910 ImGui::TableSetupColumn(
"Test Name",
911 ImGuiTableColumnFlags_WidthStretch);
912 ImGui::TableSetupColumn(
"Category",
913 ImGuiTableColumnFlags_WidthFixed, 100);
914 ImGui::TableSetupColumn(
"Runs", ImGuiTableColumnFlags_WidthFixed,
916 ImGui::TableSetupColumn(
"Pass Rate",
917 ImGuiTableColumnFlags_WidthFixed, 80);
918 ImGui::TableSetupColumn(
"Duration",
919 ImGuiTableColumnFlags_WidthFixed, 80);
920 ImGui::TableHeadersRow();
922 for (
const auto& summary : summaries) {
923 const auto& exec = summary.latest_execution;
925 ImGui::TableNextRow();
926 ImGui::TableNextColumn();
930 const char* status_icon;
931 const char* status_text;
933 switch (exec.status) {
934 case HarnessTestStatus::kPassed:
937 status_text =
"Passed";
939 case HarnessTestStatus::kFailed:
942 status_text =
"Failed";
944 case HarnessTestStatus::kTimeout:
947 status_text =
"Timeout";
949 case HarnessTestStatus::kRunning:
952 status_text =
"Running";
954 case HarnessTestStatus::kQueued:
957 status_text =
"Queued";
962 status_text =
"Unknown";
966 ImGui::TextColored(status_color,
"%s %s", status_icon,
969 ImGui::TableNextColumn();
970 ImGui::Text(
"%s", exec.name.c_str());
973 if (exec.status == HarnessTestStatus::kFailed &&
974 !exec.error_message.empty()) {
977 exec.error_message.c_str());
980 ImGui::TableNextColumn();
981 ImGui::Text(
"%s", exec.category.c_str());
983 ImGui::TableNextColumn();
984 ImGui::Text(
"%d", summary.total_runs);
986 ImGui::TableNextColumn();
987 if (summary.total_runs > 0) {
989 static_cast<float>(summary.pass_count) / summary.total_runs;
993 ImGui::TextColored(rate_color,
"%.0f%%", pass_rate * 100.0f);
998 ImGui::TableNextColumn();
1000 absl::ToDoubleMilliseconds(summary.total_duration);
1001 if (summary.total_runs > 0) {
1002 ImGui::Text(
"%.0f ms", duration_ms / summary.total_runs);
1008 if (ImGui::TreeNode((
"Details##" + exec.test_id).c_str())) {
1009 ImGui::Text(
"Test ID: %s", exec.test_id.c_str());
1010 ImGui::Text(
"Total Runs: %d (Pass: %d, Fail: %d)",
1011 summary.total_runs, summary.pass_count,
1012 summary.fail_count);
1014 if (!exec.logs.empty()) {
1017 for (
const auto& log : exec.logs) {
1018 ImGui::BulletText(
"%s", log.c_str());
1022 if (!exec.assertion_failures.empty()) {
1026 for (
const auto& failure : exec.assertion_failures) {
1027 ImGui::BulletText(
"%s", failure.c_str());
1031 if (!exec.screenshot_path.empty()) {
1034 exec.screenshot_path.c_str());
1035 ImGui::Text(
"Size: %.2f KB",
1036 exec.screenshot_size_bytes / 1024.0);
1048 ImGui::EndTabItem();
1059 ImGui::Begin(absl::StrCat(
ICON_MD_MONITOR,
" Resource Monitor").c_str(),
1064 ImGui::Text(
"%s Textures: %zu",
ICON_MD_TEXTURE, latest.texture_count);
1065 ImGui::Text(
"%s Surfaces: %zu",
ICON_MD_LAYERS, latest.surface_count);
1066 ImGui::Text(
"%s Memory: %zu MB",
ICON_MD_MEMORY, latest.memory_usage_mb);
1067 ImGui::Text(
"%s FPS: %.1f",
ICON_MD_SPEED, latest.frame_rate);
1071 std::vector<float> texture_counts;
1072 std::vector<float> surface_counts;
1077 texture_counts.push_back(
static_cast<float>(stats.texture_count));
1078 surface_counts.push_back(
static_cast<float>(stats.surface_count));
1081 ImGui::PlotLines(
"Textures", texture_counts.data(),
1082 static_cast<int>(texture_counts.size()), 0,
nullptr,
1083 0.0f, FLT_MAX, ImVec2(0, 80));
1084 ImGui::PlotLines(
"Surfaces", surface_counts.data(),
1085 static_cast<int>(surface_counts.size()), 0,
nullptr,
1086 0.0f, FLT_MAX, ImVec2(0, 80));
1095 ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver);
1100#ifdef YAZE_ENABLE_GTEST
1101 ImGui::Text(
"Google Test framework is available");
1103 if (ImGui::Button(
"Run All Google Tests")) {
1105 LOG_INFO(
"TestManager",
"Running Google Tests...");
1109 if (ImGui::Button(
"Run Specific Test Suite")) {
1114 ImGui::Text(
"Available Test Suites:");
1115 ImGui::BulletText(
"Unit Tests");
1116 ImGui::BulletText(
"Integration Tests");
1117 ImGui::BulletText(
"Performance Tests");
1120 "%s Google Test framework not available",
1122 ImGui::Text(
"Enable YAZE_ENABLE_GTEST to use Google Test integration");
1130 ImGui::SetNextWindowSize(ImVec2(700, 500), ImGuiCond_FirstUseEver);
1139 if (ImGui::CollapsingHeader(
"ROM Data Integrity",
1140 ImGuiTreeNodeFlags_DefaultOpen)) {
1144 if (ImGui::Button(
"Run Data Integrity Check")) {
1146 [[maybe_unused]]
auto suite_status =
1151 if (ImGui::CollapsingHeader(
"Save/Load Testing")) {
1152 ImGui::Text(
"Test ROM save and load operations");
1154 if (ImGui::Button(
"Test Save Operations")) {
1159 if (ImGui::Button(
"Test Load Operations")) {
1164 if (ImGui::CollapsingHeader(
"Editor Integration")) {
1165 ImGui::Text(
"Test editor components with current ROM");
1167 if (ImGui::Button(
"Test Overworld Editor")) {
1172 if (ImGui::Button(
"Test Tile16 Editor")) {
1187 ImGui::SetNextWindowSize(ImVec2(400, 200), ImGuiCond_Appearing);
1189 ImGuiWindowFlags_NoResize)) {
1193 ImGui::Text(
"Select a ROM file to run tests on:");
1195 if (ImGui::Button(
"Browse ROM File...", ImVec2(-1, 0))) {
1202 if (ImGui::Button(
"Cancel", ImVec2(-1, 0))) {
1211 ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
1217 if (ImGui::CollapsingHeader(
"File Dialog Settings",
1218 ImGuiTreeNodeFlags_DefaultOpen)) {
1219 ImGui::Text(
"File Dialog Implementation:");
1222 if (ImGui::RadioButton(
"NFD (Native File Dialog)", nfd_mode)) {
1224 LOG_INFO(
"TestManager",
"Global file dialog mode set to: NFD");
1226 if (ImGui::IsItemHovered()) {
1228 "Use NFD library for native OS file dialogs (global setting)");
1231 if (ImGui::RadioButton(
"Bespoke Implementation", !nfd_mode)) {
1233 LOG_INFO(
"TestManager",
"Global file dialog mode set to: Bespoke");
1235 if (ImGui::IsItemHovered()) {
1237 "Use custom file dialog implementation (global setting)");
1246 "Note: This setting affects ALL file dialogs in the application");
1248 if (ImGui::Button(
"Test Current File Dialog")) {
1250 LOG_INFO(
"TestManager",
"Testing global file dialog mode: %s",
1256 if (!result.empty()) {
1257 LOG_INFO(
"TestManager",
"File dialog test successful: %s",
1261 "File dialog test: No file selected or dialog canceled");
1266 if (ImGui::Button(
"Test NFD Directly")) {
1268 if (!result.empty()) {
1269 LOG_INFO(
"TestManager",
"NFD test successful: %s", result.c_str());
1272 "NFD test: No file selected, canceled, or error occurred");
1277 if (ImGui::Button(
"Test Bespoke Directly")) {
1279 if (!result.empty()) {
1280 LOG_INFO(
"TestManager",
"Bespoke test successful: %s",
1284 "Bespoke test: No file selected or not implemented");
1290 if (ImGui::CollapsingHeader(
"Test Selection",
1291 ImGuiTreeNodeFlags_DefaultOpen)) {
1292 ImGui::Text(
"Enable/Disable Individual Tests:");
1296 static const std::vector<std::pair<std::string, std::string>>
1298 {
"ROM_Header_Validation_Test",
1299 "Safe - Read-only ROM header validation"},
1300 {
"ROM_Data_Access_Test",
1301 "Safe - Basic ROM data access testing"},
1302 {
"ROM_Graphics_Extraction_Test",
1303 "Safe - Graphics data extraction testing"},
1304 {
"ROM_Overworld_Loading_Test",
1305 "Safe - Overworld data loading testing"},
1306 {
"Tile16_Editor_Test",
1307 "Moderate - Tile16 editor initialization"},
1308 {
"Comprehensive_Save_Test",
1309 "DANGEROUS - Known to crash, uses ROM copies"},
1310 {
"ROM_Sprite_Data_Test",
"Safe - Sprite data validation"},
1311 {
"ROM_Music_Data_Test",
"Safe - Music data validation"}};
1314 static bool initialized_defaults =
false;
1315 if (!initialized_defaults) {
1318 initialized_defaults =
true;
1321 if (ImGui::BeginTable(
1323 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
1324 ImGui::TableSetupColumn(
"Test Name", ImGuiTableColumnFlags_WidthFixed,
1326 ImGui::TableSetupColumn(
"Risk Level",
1327 ImGuiTableColumnFlags_WidthStretch);
1328 ImGui::TableSetupColumn(
"Status", ImGuiTableColumnFlags_WidthFixed,
1330 ImGui::TableSetupColumn(
"Action", ImGuiTableColumnFlags_WidthFixed,
1332 ImGui::TableHeadersRow();
1334 for (
const auto& [test_name, description] : known_tests) {
1337 ImGui::TableNextRow();
1338 ImGui::TableNextColumn();
1339 ImGui::Text(
"%s", test_name.c_str());
1341 ImGui::TableNextColumn();
1343 if (description.find(
"DANGEROUS") != std::string::npos) {
1345 description.c_str());
1346 }
else if (description.find(
"Moderate") != std::string::npos) {
1348 description.c_str());
1351 description.c_str());
1354 ImGui::TableNextColumn();
1363 ImGui::TableNextColumn();
1364 ImGui::PushID(test_name.c_str());
1366 if (ImGui::Button(
"Disable")) {
1368 LOG_INFO(
"TestManager",
"Disabled test: %s", test_name.c_str());
1371 if (ImGui::Button(
"Enable")) {
1373 LOG_INFO(
"TestManager",
"Enabled test: %s", test_name.c_str());
1383 ImGui::Text(
"Quick Actions:");
1385 if (ImGui::Button(
"Enable Safe Tests Only")) {
1386 for (
const auto& [test_name, description] : known_tests) {
1387 if (description.find(
"Safe") != std::string::npos) {
1393 LOG_INFO(
"TestManager",
"Enabled only safe tests");
1397 if (ImGui::Button(
"Enable All Tests")) {
1398 for (
const auto& [test_name, description] : known_tests) {
1402 "Enabled all tests (including dangerous ones)");
1406 if (ImGui::Button(
"Disable All Tests")) {
1407 for (
const auto& [test_name, description] : known_tests) {
1410 LOG_INFO(
"TestManager",
"Disabled all tests");
1416 " Recommendation: Use 'Enable Safe Tests Only' to avoid crashes");
1420 if (ImGui::CollapsingHeader(
"Platform Settings")) {
1421 ImGui::Text(
"macOS Tahoe Compatibility:");
1422 ImGui::BulletText(
"NFD may have issues on macOS Sequoia+");
1423 ImGui::BulletText(
"Bespoke dialog provides fallback option");
1425 "Global setting affects File → Open, Project dialogs, etc.");
1428 ImGui::Text(
"Test Both Implementations:");
1430 if (ImGui::Button(
"Quick Test NFD")) {
1432 LOG_INFO(
"TestManager",
"NFD test result: %s",
1433 result.empty() ?
"Failed/Canceled" : result.c_str());
1436 if (ImGui::Button(
"Quick Test Bespoke")) {
1438 LOG_INFO(
"TestManager",
"Bespoke test result: %s",
1439 result.empty() ?
"Failed/Not Implemented" : result.c_str());
1444 "Note: These tests don't change the global setting");
1452 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1453 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1454 ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_Appearing);
1457 ImGuiWindowFlags_NoResize)) {
1461 ImGui::Text(
"A test ROM has been created with your modifications:");
1466 auto last_slash = display_filename.find_last_of(
"/\\");
1467 if (last_slash != std::string::npos) {
1468 display_filename = display_filename.substr(last_slash + 1);
1472 ImGui::Text(
"Would you like to open this test ROM in a new session?");
1475 absl::StrFormat(
"%s Open in New Session",
ICON_MD_TAB).c_str(),
1480 "User requested to open test ROM in new session: %s",
1487 absl::StrFormat(
"%s Keep Current Session",
ICON_MD_CLOSE).c_str(),
1495 "Note: Test ROM contains your modifications and can be");
1497 "opened later using File → Open");
1504 LOG_INFO(
"TestManager",
"=== TestManager ROM Refresh ===");
1509 LOG_INFO(
"TestManager",
"ROM is_loaded(): %s",
1513 LOG_INFO(
"TestManager",
"ROM size: %.2f MB",
1515 LOG_INFO(
"TestManager",
"ROM dirty: %s",
1519 LOG_INFO(
"TestManager",
"TestManager ROM pointer is null");
1521 "Note: ROM should be set by EditorManager when ROM is loaded");
1523 LOG_INFO(
"TestManager",
"===============================");
1527 std::unique_ptr<Rom>& test_rom) {
1528 if (!source_rom || !source_rom->
is_loaded()) {
1529 return absl::FailedPreconditionError(
"Source ROM not loaded");
1532 LOG_INFO(
"TestManager",
"Creating test ROM copy from: %s",
1533 source_rom->
title().c_str());
1536 test_rom = std::make_unique<Rom>();
1539 auto rom_data = source_rom->
vector();
1540 auto load_status = test_rom->LoadFromData(rom_data);
1541 if (!load_status.ok()) {
1545 LOG_INFO(
"TestManager",
"Test ROM copy created successfully (size: %.2f MB)",
1546 test_rom->size() / 1048576.0f);
1547 return absl::OkStatus();
1552 auto now = std::chrono::system_clock::now();
1553 auto time_t = std::chrono::system_clock::to_time_t(now);
1554 auto local_time = *std::localtime(&time_t);
1556 std::string timestamp =
1557 absl::StrFormat(
"%04d%02d%02d_%02d%02d%02d", local_time.tm_year + 1900,
1558 local_time.tm_mon + 1, local_time.tm_mday,
1559 local_time.tm_hour, local_time.tm_min, local_time.tm_sec);
1561 std::string base_filename = base_name;
1563 auto last_slash = base_filename.find_last_of(
"/\\");
1564 if (last_slash != std::string::npos) {
1565 base_filename = base_filename.substr(last_slash + 1);
1567 auto last_dot = base_filename.find_last_of(
'.');
1568 if (last_dot != std::string::npos) {
1569 base_filename = base_filename.substr(0, last_dot);
1572 return absl::StrFormat(
"%s_test_%s.sfc", base_filename.c_str(),
1583 Rom* source_rom, std::function<absl::Status(
Rom*)> test_function) {
1584 if (!source_rom || !source_rom->
is_loaded()) {
1585 return absl::FailedPreconditionError(
"Source ROM not loaded");
1589 std::unique_ptr<Rom> test_rom;
1592 LOG_INFO(
"TestManager",
"Executing test function on ROM copy");
1595 auto test_result = test_function(test_rom.get());
1597 LOG_INFO(
"TestManager",
"Test function completed with status: %s",
1598 test_result.ToString().c_str());
1606 LOG_INFO(
"TestManager",
"Request to load ROM for testing: %s",
1608 return absl::UnimplementedError(
1609 "ROM loading for testing not yet implemented");
1614 if (ImGui::Begin(
"ROM Comparison Results")) {
1618 if (ImGui::BeginTable(
"RomComparison", 3, ImGuiTableFlags_Borders)) {
1619 ImGui::TableSetupColumn(
"Property");
1620 ImGui::TableSetupColumn(
"Before");
1621 ImGui::TableSetupColumn(
"After");
1622 ImGui::TableHeadersRow();
1624 ImGui::TableNextRow();
1625 ImGui::TableNextColumn();
1626 ImGui::Text(
"Size");
1627 ImGui::TableNextColumn();
1628 ImGui::Text(
"%.2f MB", before.
size() / 1048576.0f);
1629 ImGui::TableNextColumn();
1630 ImGui::Text(
"%.2f MB", after.
size() / 1048576.0f);
1632 ImGui::TableNextRow();
1633 ImGui::TableNextColumn();
1634 ImGui::Text(
"Modified");
1635 ImGui::TableNextColumn();
1636 ImGui::Text(
"%s", before.
dirty() ?
"Yes" :
"No");
1637 ImGui::TableNextColumn();
1638 ImGui::Text(
"%s", after.
dirty() ?
"Yes" :
"No");
1648 return absl::FailedPreconditionError(
"No ROM loaded for testing");
1653 LOG_INFO(
"TestManager",
"Testing ROM save/load operations on copy: %s",
1654 test_rom->
title().c_str());
1663 auto save_status = test_rom->
SaveToFile(settings);
1664 if (!save_status.ok()) {
1668 LOG_INFO(
"TestManager",
"Test ROM saved successfully to: %s",
1674 return absl::OkStatus();
1680 return absl::FailedPreconditionError(
"No ROM loaded for testing");
1686 LOG_INFO(
"TestManager",
"Testing ROM data integrity on copy: %s",
1687 test_rom->
title().c_str());
1693 if (test_rom->
size() < 0x100000) {
1694 return absl::FailedPreconditionError(
1695 "ROM file too small for A Link to the Past");
1700 if (!header_status.ok()) {
1701 return header_status.status();
1704 LOG_INFO(
"TestManager",
"ROM integrity check passed for: %s",
1705 test_rom->
title().c_str());
1706 return absl::OkStatus();
1710#if defined(YAZE_WITH_GRPC)
1712 const std::string& category) {
1713 absl::MutexLock lock(&harness_history_mutex_);
1714 std::string test_id =
1715 absl::StrCat(
"harness_", absl::ToUnixMicros(absl::Now()),
"_",
1716 harness_history_.size());
1717 HarnessTestExecution execution;
1718 execution.test_id = test_id;
1719 execution.name = name;
1720 execution.category = category;
1721 execution.status = HarnessTestStatus::kQueued;
1722 execution.queued_at = absl::Now();
1723 execution.logs.reserve(32);
1724 harness_history_[test_id] = execution;
1725 TrimHarnessHistoryLocked();
1726 HarnessAggregate& aggregate = harness_aggregates_[name];
1727 aggregate.latest_execution = execution;
1728 harness_history_order_.push_back(test_id);
1733 const std::string& category) {
1740#if defined(YAZE_WITH_GRPC)
1741void TestManager::MarkHarnessTestRunning(
const std::string& test_id) {
1742 absl::MutexLock lock(&harness_history_mutex_);
1743 auto it = harness_history_.find(test_id);
1744 if (it == harness_history_.end()) {
1747 HarnessTestExecution& execution = it->second;
1748 execution.status = HarnessTestStatus::kRunning;
1749 execution.started_at = absl::Now();
1753#if defined(YAZE_WITH_GRPC)
1754void TestManager::MarkHarnessTestCompleted(
1755 const std::string& test_id, HarnessTestStatus status,
1756 const std::string& message,
1757 const std::vector<std::string>& assertion_failures,
1758 const std::vector<std::string>& logs,
1759 const std::map<std::string, int32_t>& metrics) {
1760 absl::MutexLock lock(&harness_history_mutex_);
1761 auto it = harness_history_.find(test_id);
1762 if (it == harness_history_.end()) {
1765 HarnessTestExecution& execution = it->second;
1766 execution.status = status;
1767 execution.completed_at = absl::Now();
1768 execution.duration = execution.completed_at - execution.started_at;
1769 execution.error_message = message;
1770 execution.metrics = metrics;
1771 execution.assertion_failures = assertion_failures;
1772 execution.logs.insert(execution.logs.end(), logs.begin(), logs.end());
1774 bool capture_failure_context = status == HarnessTestStatus::kFailed ||
1775 status == HarnessTestStatus::kTimeout;
1777 harness_aggregates_[execution.name].latest_execution = execution;
1778 harness_aggregates_[execution.name].total_runs += 1;
1779 if (status == HarnessTestStatus::kPassed) {
1780 harness_aggregates_[execution.name].pass_count += 1;
1781 }
else if (status == HarnessTestStatus::kFailed ||
1782 status == HarnessTestStatus::kTimeout) {
1783 harness_aggregates_[execution.name].fail_count += 1;
1785 harness_aggregates_[execution.name].total_duration += execution.duration;
1786 harness_aggregates_[execution.name].last_run = execution.completed_at;
1788 if (capture_failure_context) {
1792 if (harness_listener_) {
1793 harness_listener_->OnHarnessTestUpdated(execution);
1798#if defined(YAZE_WITH_GRPC)
1799void TestManager::AppendHarnessTestLog(
const std::string& test_id,
1800 const std::string& log_entry) {
1801 absl::MutexLock lock(&harness_history_mutex_);
1802 auto it = harness_history_.find(test_id);
1803 if (it == harness_history_.end()) {
1806 HarnessTestExecution& execution = it->second;
1807 execution.logs.push_back(log_entry);
1808 harness_aggregates_[execution.name].latest_execution.logs = execution.logs;
1812#if defined(YAZE_WITH_GRPC)
1813absl::StatusOr<HarnessTestExecution> TestManager::GetHarnessTestExecution(
1814 const std::string& test_id)
const {
1815 absl::MutexLock lock(&harness_history_mutex_);
1816 auto it = harness_history_.find(test_id);
1817 if (it == harness_history_.end()) {
1818 return absl::NotFoundError(
1819 absl::StrFormat(
"Test ID '%s' not found", test_id));
1825#if defined(YAZE_WITH_GRPC)
1826std::vector<HarnessTestSummary> TestManager::ListHarnessTestSummaries(
1827 const std::string& category_filter)
const {
1828 absl::MutexLock lock(&harness_history_mutex_);
1829 std::vector<HarnessTestSummary> summaries;
1830 summaries.reserve(harness_aggregates_.size());
1831 for (
const auto& [name, aggregate] : harness_aggregates_) {
1832 if (!category_filter.empty() && aggregate.category != category_filter) {
1835 HarnessTestSummary summary;
1836 summary.latest_execution = aggregate.latest_execution;
1837 summary.total_runs = aggregate.total_runs;
1838 summary.pass_count = aggregate.pass_count;
1839 summary.fail_count = aggregate.fail_count;
1840 summary.total_duration = aggregate.total_duration;
1841 summaries.push_back(summary);
1843 std::sort(summaries.begin(), summaries.end(),
1844 [](
const HarnessTestSummary& a,
const HarnessTestSummary& b) {
1845 absl::Time time_a = a.latest_execution.completed_at;
1846 if (time_a == absl::InfinitePast()) {
1847 time_a = a.latest_execution.queued_at;
1849 absl::Time time_b = b.latest_execution.completed_at;
1850 if (time_b == absl::InfinitePast()) {
1851 time_b = b.latest_execution.queued_at;
1853 return time_a > time_b;
1859#if defined(YAZE_WITH_GRPC)
1860void TestManager::CaptureFailureContext(
const std::string& test_id) {
1861 absl::MutexLock lock(&harness_history_mutex_);
1862 auto it = harness_history_.find(test_id);
1863 if (it == harness_history_.end()) {
1866 HarnessTestExecution& execution = it->second;
1869 absl::MutexLock unlock_guard(&harness_history_mutex_);
1874 auto screenshot_artifact = test::CaptureHarnessScreenshot(
"harness_failures");
1875 std::string failure_context;
1876 if (screenshot_artifact.ok()) {
1877 failure_context =
"Harness failure context captured successfully";
1879 failure_context =
"Harness failure context capture unavailable";
1882 execution.failure_context = failure_context;
1883 if (screenshot_artifact.ok()) {
1884 execution.screenshot_path = screenshot_artifact->file_path;
1885 execution.screenshot_size_bytes = screenshot_artifact->file_size_bytes;
1888 if (harness_listener_) {
1889 harness_listener_->OnHarnessTestUpdated(execution);
1893void TestManager::CaptureFailureContext(
const std::string& test_id) {
1898#if defined(YAZE_WITH_GRPC)
1899void TestManager::TrimHarnessHistoryLocked() {
1900 while (harness_history_order_.size() > harness_history_limit_) {
1901 const std::string& oldest_test = harness_history_order_.front();
1902 harness_history_order_.pop_front();
1903 harness_history_.erase(oldest_test);
1907absl::Status TestManager::ReplayLastPlan() {
1908 return absl::FailedPreconditionError(
"Harness plan replay not available");
1911void TestManager::SetHarnessListener(HarnessListener* listener) {
1912 absl::MutexLock lock(&mutex_);
1913 harness_listener_ = listener;
1916absl::Status TestManager::ReplayLastPlan() {
1917 return absl::UnimplementedError(
"Harness features require YAZE_WITH_GRPC");
1921absl::Status TestManager::ShowHarnessDashboard() {
1924#if defined(YAZE_WITH_GRPC)
1925 return absl::OkStatus();
1927 return absl::UnimplementedError(
"Harness features require YAZE_WITH_GRPC");
1931absl::Status TestManager::ShowHarnessActiveTests() {
1932#if defined(YAZE_WITH_GRPC)
1933 return absl::OkStatus();
1935 return absl::UnimplementedError(
"Harness features require YAZE_WITH_GRPC");
1939void TestManager::RecordPlanSummary(
const std::string& summary) {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
const auto & vector() const
absl::Status SaveToFile(const SaveSettings &settings)
RAII guard for ImGui style colors.
bool show_resource_monitor_
std::string GenerateTestRomFilename(const std::string &base_name)
void CaptureFailureContext(const std::string &test_id)
std::string current_test_name_
bool show_test_configuration_
bool show_test_session_dialog_
TestCategory category_filter_
std::vector< ResourceStats > resource_history_
void RegisterTestSuite(std::unique_ptr< TestSuite > suite)
absl::Status TestRomWithCopy(Rom *source_rom, std::function< absl::Status(Rom *)> test_function)
std::unordered_map< std::string, TestSuite * > suite_lookup_
absl::Status CreateTestRomCopy(Rom *source_rom, std::unique_ptr< Rom > &test_rom)
bool IsTestEnabled(const std::string &test_name) const
absl::Status RunTestSuite(const std::string &suite_name)
std::string RegisterHarnessTest(const std::string &name, const std::string &category)
absl::Status ExecuteTestSuite(TestSuite *suite)
bool show_rom_test_results_
void DestroyUITestingContext()
std::string test_rom_path_for_session_
bool show_rom_file_dialog_
absl::Status TestRomDataIntegrity(Rom *rom)
static constexpr size_t kMaxResourceHistorySize
void TrimResourceHistory()
void DrawTestDashboard(bool *show_dashboard=nullptr)
TestSuite * GetTestSuite(const std::string &name)
absl::Status RunAllTests()
static TestManager & Get()
void OfferTestSessionCreation(const std::string &test_rom_path)
TestResults last_results_
void EnableTest(const std::string &test_name)
void CollectResourceStats()
void DisableTest(const std::string &test_name)
std::vector< std::unique_ptr< TestSuite > > test_suites_
absl::Status TestRomSaveLoad(Rom *rom)
absl::Status RunTestsByCategory(TestCategory category)
void UpdateResourceStats()
void ShowRomComparisonResults(const Rom &before, const Rom &after)
void InitializeUITesting()
std::vector< std::string > GetTestSuiteNames() const
absl::Status LoadRomForTesting(const std::string &filename)
virtual absl::Status RunTests(TestResults &results)=0
static std::string ShowOpenFileDialogBespoke()
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
static std::string ShowOpenFileDialogNFD()
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_ASSESSMENT
#define ICON_MD_CAMERA_ALT
#define ICON_MD_CHECKLIST
#define ICON_MD_PLAY_ARROW
#define ICON_MD_PLAY_CIRCLE_FILLED
#define ICON_MD_TIMER_OFF
#define ICON_MD_ERROR_OUTLINE
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_SKIP_NEXT
#define ICON_MD_DESCRIPTION
#define ICON_MD_FILTER_LIST
#define ICON_MD_ANALYTICS
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
ButtonColorSet GetSuccessButtonColors()
ImVec4 GetDisabledColor()
std::string GenerateFailureScreenshotPath(const std::string &test_id)
const char * TestStatusToString(TestStatus status)
void RegisterOverworldUITests(ImGuiTestEngine *)
void RegisterDungeonUITests(ImGuiTestEngine *)
const char * TestCategoryToString(TestCategory category)
ImVec4 GetTestStatusColor(TestStatus status)
#define RETURN_IF_ERROR(expr)
bool kUseNativeFileDialog
std::chrono::time_point< std::chrono::steady_clock > timestamp
std::vector< TestResult > individual_results
std::chrono::milliseconds total_duration
float GetPassRate() const