yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
test_manager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <filesystem>
5#include <optional>
6#include <random>
7
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"
18#include "app/gui/core/icons.h"
21#include "core/features.h"
22#include "util/file_util.h"
23#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
24#include "imgui.h"
25#include "imgui_internal.h"
26#else
27#include "imgui.h"
28#endif
30#include "util/log.h"
31
32// Forward declaration to avoid circular dependency
33namespace yaze {
34namespace editor {
35class EditorManager;
36}
37} // namespace yaze
38
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"
46#else
47#error "ImGui Test Engine headers not found"
48#endif
49#endif
50
51namespace yaze {
52namespace test {
53
54namespace {
55
56std::string GenerateFailureScreenshotPath(const std::string& test_id) {
57 std::filesystem::path base_dir = std::filesystem::temp_directory_path() /
58 "yaze" / "test-results" / test_id;
59 std::error_code ec;
60 std::filesystem::create_directories(base_dir, ec);
61
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();
67}
68
69} // namespace
70
71// Utility function implementations
72const char* TestStatusToString(TestStatus status) {
73 switch (status) {
75 return "Not Run";
77 return "Running";
79 return "Passed";
81 return "Failed";
83 return "Skipped";
84 }
85 return "Unknown";
86}
87
88const char* TestCategoryToString(TestCategory category) {
89 switch (category) {
91 return "Unit";
93 return "Integration";
95 return "UI";
97 return "Performance";
99 return "Memory";
100 }
101 return "Unknown";
102}
103
105 switch (status) {
107 return gui::GetDisabledColor();
109 return gui::GetWarningColor();
111 return gui::GetSuccessColor();
113 return gui::GetErrorColor();
115 return gui::GetWarningColor();
116 }
117 return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
118}
119
120#if defined(YAZE_WITH_GRPC)
121const char* HarnessStatusToString(HarnessTestStatus status) {
122 switch (status) {
123 case HarnessTestStatus::kPassed:
124 return "passed";
125 case HarnessTestStatus::kFailed:
126 return "failed";
127 case HarnessTestStatus::kTimeout:
128 return "timeout";
129 case HarnessTestStatus::kRunning:
130 return "running";
131 case HarnessTestStatus::kQueued:
132 return "queued";
133 case HarnessTestStatus::kUnspecified:
134 default:
135 return "unspecified";
136 }
137}
138
139HarnessTestStatus HarnessStatusFromString(absl::string_view status) {
140 if (absl::EqualsIgnoreCase(status, "passed")) {
141 return HarnessTestStatus::kPassed;
142 }
143 if (absl::EqualsIgnoreCase(status, "failed")) {
144 return HarnessTestStatus::kFailed;
145 }
146 if (absl::EqualsIgnoreCase(status, "timeout")) {
147 return HarnessTestStatus::kTimeout;
148 }
149 if (absl::EqualsIgnoreCase(status, "running")) {
150 return HarnessTestStatus::kRunning;
151 }
152 if (absl::EqualsIgnoreCase(status, "queued")) {
153 return HarnessTestStatus::kQueued;
154 }
155 return HarnessTestStatus::kUnspecified;
156}
157#endif // defined(YAZE_WITH_GRPC)
158
159// TestManager implementation
161 static TestManager instance;
162 return instance;
163}
164
166 // Note: UI test engine initialization is deferred until ImGui context is
167 // ready Call InitializeUITesting() explicitly after ImGui::CreateContext()
168}
169
171#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
173#endif
174}
175
176#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
178 if (!ui_test_engine_) {
179 // Check if ImGui context is ready
180 if (ImGui::GetCurrentContext() == nullptr) {
181 LOG_WARN("TestManager",
182 "ImGui context not ready, deferring test engine initialization");
183 return;
184 }
185
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;
192
193 // Start the test engine
194 ImGuiTestEngine_Start(ui_test_engine_, ImGui::GetCurrentContext());
195
196 // Register static UI tests
197 RegisterDungeonUITests(ui_test_engine_);
198 RegisterOverworldUITests(ui_test_engine_);
199
200 LOG_INFO("TestManager",
201 "ImGuiTestEngine initialized with dungeon + overworld UI tests");
202 }
203 }
204}
205
207 if (ui_test_engine_ && ImGui::GetCurrentContext() != nullptr) {
208 ImGuiTestEngine_PostSwap(ui_test_engine_);
209 }
210}
211
213 if (ui_test_engine_ && ImGui::GetCurrentContext() != nullptr) {
214 ImGuiTestEngine_Stop(ui_test_engine_);
215 }
216}
217
219 if (ui_test_engine_) {
220 ImGuiTestEngine_DestroyContext(ui_test_engine_);
221 ui_test_engine_ = nullptr;
222 }
223}
224
226 // Complete shutdown - calls both phases
229}
230#endif
231
233 if (is_running_) {
234 return absl::FailedPreconditionError("Tests are already running");
235 }
236
237 is_running_ = true;
238 progress_ = 0.0f;
240
241 // Execute all test suites
242 for (auto& suite : test_suites_) {
243 if (suite->IsEnabled()) {
244 current_test_name_ = suite->GetName();
245 auto status = ExecuteTestSuite(suite.get());
246 if (!status.ok()) {
247 is_running_ = false;
248 return status;
249 }
251 }
252 }
253
254 is_running_ = false;
255 current_test_name_.clear();
256 progress_ = 1.0f;
257
258 return absl::OkStatus();
259}
260
262 if (is_running_) {
263 return absl::FailedPreconditionError("Tests are already running");
264 }
265
266 is_running_ = true;
267 progress_ = 0.0f;
269
270 // Filter and execute test suites by category
271 std::vector<TestSuite*> filtered_suites;
272 for (auto& suite : test_suites_) {
273 if (suite->IsEnabled() && suite->GetCategory() == category) {
274 filtered_suites.push_back(suite.get());
275 }
276 }
277
278 for (auto* suite : filtered_suites) {
279 current_test_name_ = suite->GetName();
280 auto status = ExecuteTestSuite(suite);
281 if (!status.ok()) {
282 is_running_ = false;
283 return status;
284 }
286 }
287
288 is_running_ = false;
289 current_test_name_.clear();
290 progress_ = 1.0f;
291
292 return absl::OkStatus();
293}
294
295absl::Status TestManager::RunTestSuite(const std::string& suite_name) {
296 if (is_running_) {
297 return absl::FailedPreconditionError("Tests are already running");
298 }
299
300 auto it = suite_lookup_.find(suite_name);
301 if (it == suite_lookup_.end()) {
302 return absl::NotFoundError("Test suite not found: " + suite_name);
303 }
304
305 is_running_ = true;
306 progress_ = 0.0f;
308 current_test_name_ = suite_name;
309
310 auto status = ExecuteTestSuite(it->second);
311
312 is_running_ = false;
313 current_test_name_.clear();
314 progress_ = 1.0f;
315
316 return status;
317}
318
319void TestManager::RegisterTestSuite(std::unique_ptr<TestSuite> suite) {
320 if (suite) {
321 std::string name = suite->GetName();
322 suite_lookup_[name] = suite.get();
323 test_suites_.push_back(std::move(suite));
324 }
325}
326
327std::vector<std::string> TestManager::GetTestSuiteNames() const {
328 std::vector<std::string> names;
329 names.reserve(test_suites_.size());
330 for (const auto& suite : test_suites_) {
331 names.push_back(suite->GetName());
332 }
333 return names;
334}
335
336TestSuite* TestManager::GetTestSuite(const std::string& name) {
337 auto it = suite_lookup_.find(name);
338 return it != suite_lookup_.end() ? it->second : nullptr;
339}
340
345
347 if (!suite) {
348 return absl::InvalidArgumentError("Test suite is null");
349 }
350
351 // Collect resource stats before test
353
354 // Execute the test suite
355 auto status = suite->RunTests(last_results_);
356
357 // Collect resource stats after test
359
360 return status;
361}
362
364 if (test_suites_.empty()) {
365 progress_ = 1.0f;
366 return;
367 }
368
369 size_t completed = 0;
370 for (const auto& suite : test_suites_) {
371 if (suite->IsEnabled()) {
372 completed++;
373 }
374 }
375
376 progress_ = static_cast<float>(completed) / test_suites_.size();
377}
378
380 ResourceStats stats;
381 stats.timestamp = std::chrono::steady_clock::now();
382
383 // Get Arena statistics
384 auto& arena = gfx::Arena::Get();
385 stats.texture_count = arena.GetTextureCount();
386 stats.surface_count = arena.GetSurfaceCount();
387
388 // Get frame rate from ImGui
389 stats.frame_rate = ImGui::GetIO().Framerate;
390
391 // Estimate memory usage (simplified)
392 stats.memory_usage_mb =
393 (stats.texture_count + stats.surface_count) / 1024; // Rough estimate
394
395 resource_history_.push_back(stats);
396}
397
406
407void TestManager::DrawTestDashboard(bool* show_dashboard) {
408 bool* dashboard_flag = show_dashboard ? show_dashboard : &show_dashboard_;
409
410 // Set a larger default window size
411 ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
412
413 if (!ImGui::Begin("Test Dashboard", dashboard_flag,
414 ImGuiWindowFlags_MenuBar)) {
415 ImGui::End();
416 return;
417 }
418
419 // ROM status indicator with detailed information
420 bool has_rom = current_rom_ && current_rom_->is_loaded();
421
422 // Add real-time ROM status checking
423 static int frame_counter = 0;
424 frame_counter++;
425 if (frame_counter % 60 == 0) { // Check every 60 frames
426 // Log ROM status periodically for debugging
427 LOG_INFO("TestManager",
428 "TestManager ROM status check - Frame %d: ROM %p, loaded: %s",
429 frame_counter, (void*)current_rom_, has_rom ? "true" : "false");
430 }
431
432 if (ImGui::BeginTable("ROM_Status_Table", 2, ImGuiTableFlags_BordersInner)) {
433 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 120);
434 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
435
436 ImGui::TableNextRow();
437 ImGui::TableNextColumn();
438 ImGui::Text("ROM Status:");
439 ImGui::TableNextColumn();
440 if (has_rom) {
441 ImGui::TextColored(gui::GetSuccessColor(), "%s Loaded",
443
444 ImGui::TableNextRow();
445 ImGui::TableNextColumn();
446 ImGui::Text("ROM Title:");
447 ImGui::TableNextColumn();
448 ImGui::Text("%s", current_rom_->title().c_str());
449
450 ImGui::TableNextRow();
451 ImGui::TableNextColumn();
452 ImGui::Text("File Name:");
453 ImGui::TableNextColumn();
454 ImGui::Text("%s", current_rom_->filename().c_str());
455
456 ImGui::TableNextRow();
457 ImGui::TableNextColumn();
458 ImGui::Text("Size:");
459 ImGui::TableNextColumn();
460 ImGui::Text("%.2f MB (%zu bytes)", current_rom_->size() / 1048576.0f,
461 current_rom_->size());
462
463 ImGui::TableNextRow();
464 ImGui::TableNextColumn();
465 ImGui::Text("Modified:");
466 ImGui::TableNextColumn();
467 if (current_rom_->dirty()) {
468 ImGui::TextColored(gui::GetWarningColor(), "%s Yes", ICON_MD_EDIT);
469 } else {
470 ImGui::TextColored(gui::GetSuccessColor(), "%s No", ICON_MD_CHECK);
471 }
472
473 ImGui::TableNextRow();
474 ImGui::TableNextColumn();
475 ImGui::Text("ROM Pointer:");
476 ImGui::TableNextColumn();
477 ImGui::Text("%p", (void*)current_rom_);
478
479 ImGui::TableNextRow();
480 ImGui::TableNextColumn();
481 ImGui::Text("Actions:");
482 ImGui::TableNextColumn();
483 if (ImGui::Button("Refresh ROM Reference")) {
485 }
486
487 } else {
488 ImGui::TextColored(gui::GetWarningColor(), "%s Not Loaded",
490 ImGui::TableNextRow();
491 ImGui::TableNextColumn();
492 ImGui::Text("ROM Pointer:");
493 ImGui::TableNextColumn();
494 ImGui::Text("%p", (void*)current_rom_);
495 ImGui::TableNextRow();
496 ImGui::TableNextColumn();
497 ImGui::Text("Status:");
498 ImGui::TableNextColumn();
499 ImGui::Text("ROM-dependent tests will be skipped");
500
501 ImGui::TableNextRow();
502 ImGui::TableNextColumn();
503 ImGui::Text("Actions:");
504 ImGui::TableNextColumn();
505 if (ImGui::Button("Refresh ROM Reference")) {
507 }
508 ImGui::SameLine();
509 if (ImGui::Button("Debug ROM State")) {
510 LOG_INFO("TestManager", "=== ROM DEBUG INFO ===");
511 LOG_INFO("TestManager", "current_rom_ pointer: %p",
512 (void*)current_rom_);
513 if (current_rom_) {
514 LOG_INFO("TestManager", "ROM title: '%s'",
515 current_rom_->title().c_str());
516 LOG_INFO("TestManager", "ROM size: %zu", current_rom_->size());
517 LOG_INFO("TestManager", "ROM is_loaded(): %s",
518 current_rom_->is_loaded() ? "true" : "false");
519 LOG_INFO("TestManager", "ROM data pointer: %p",
520 (void*)current_rom_->data());
521 }
522 LOG_INFO("TestManager", "======================");
523 }
524 }
525
526 ImGui::EndTable();
527 }
528 ImGui::Separator();
529
530 // Menu bar
531 if (ImGui::BeginMenuBar()) {
532 if (ImGui::BeginMenu("Run")) {
533 if (ImGui::MenuItem("All Tests", "Ctrl+T", false, !is_running_)) {
534 [[maybe_unused]] auto status = RunAllTests();
535 }
536 ImGui::Separator();
537 if (ImGui::MenuItem("Unit Tests", nullptr, false, !is_running_)) {
538 [[maybe_unused]] auto status = RunTestsByCategory(TestCategory::kUnit);
539 }
540 if (ImGui::MenuItem("Integration Tests", nullptr, false, !is_running_)) {
541 [[maybe_unused]] auto status =
543 }
544 if (ImGui::MenuItem("UI Tests", nullptr, false, !is_running_)) {
545 [[maybe_unused]] auto status = RunTestsByCategory(TestCategory::kUI);
546 }
547 if (ImGui::MenuItem("Performance Tests", nullptr, false, !is_running_)) {
548 [[maybe_unused]] auto status =
550 }
551 if (ImGui::MenuItem("Memory Tests", nullptr, false, !is_running_)) {
552 [[maybe_unused]] auto status =
554 }
555 ImGui::EndMenu();
556 }
557
558 if (ImGui::BeginMenu("View")) {
559 ImGui::MenuItem("Resource Monitor", nullptr, &show_resource_monitor_);
560 ImGui::MenuItem("Google Tests", nullptr, &show_google_tests_);
561 ImGui::MenuItem("ROM Test Results", nullptr, &show_rom_test_results_);
562 ImGui::Separator();
563 if (ImGui::MenuItem("Export Results", nullptr, false,
565 // TODO: Implement result export
566 }
567 ImGui::EndMenu();
568 }
569
570 if (ImGui::BeginMenu("ROM")) {
571 if (ImGui::MenuItem("Test Current ROM", nullptr, false,
573 [[maybe_unused]] auto status =
575 }
576 if (ImGui::MenuItem("Load ROM for Testing...")) {
578 }
579 ImGui::Separator();
580 if (ImGui::MenuItem("Refresh ROM Reference")) {
582 }
583 ImGui::EndMenu();
584 }
585
586 if (ImGui::BeginMenu("Configure")) {
587 if (ImGui::MenuItem("Test Configuration")) {
589 }
590 ImGui::Separator();
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");
596 }
597 ImGui::EndMenu();
598 }
599
600 ImGui::EndMenuBar();
601 }
602
603 // Show test configuration status
604 int enabled_count = 0;
605 int total_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"};
611
612 for (const auto& test_name : all_test_names) {
613 total_count++;
614 if (IsTestEnabled(test_name)) {
615 enabled_count++;
616 }
617 }
618
619 ImGui::Text("%s Test Status: %d/%d enabled", ICON_MD_CHECKLIST, enabled_count,
620 total_count);
621 if (enabled_count < total_count) {
622 ImGui::SameLine();
623 ImGui::TextColored(gui::GetWarningColor(),
624 "(Some tests disabled - check Configuration)");
625 }
626
627 // Enhanced test execution status
628 if (is_running_) {
629 {
630 gui::StyleColorGuard running_guard(
632 ImGui::Text("%s Running: %s", ICON_MD_PLAY_CIRCLE_FILLED,
633 current_test_name_.c_str());
634 }
635 ImGui::ProgressBar(progress_, ImVec2(-1, 0),
636 absl::StrFormat("%.0f%%", progress_ * 100.0f).c_str());
637 } else {
638 // Enhanced control buttons
639 {
640 gui::StyleColorGuard run_btn_guard(ImGuiCol_Button,
642 if (ImGui::Button(
643 absl::StrCat(ICON_MD_PLAY_ARROW, " Run All Tests").c_str(),
644 ImVec2(140, 0))) {
645 [[maybe_unused]] auto status = RunAllTests();
646 }
647 }
648
649 ImGui::SameLine();
650 if (ImGui::Button(absl::StrCat(ICON_MD_SPEED, " Quick Test").c_str(),
651 ImVec2(100, 0))) {
652 [[maybe_unused]] auto status = RunTestsByCategory(TestCategory::kMemory);
653 }
654
655 ImGui::SameLine();
656 bool has_rom = current_rom_ && current_rom_->is_loaded();
657 {
658 std::optional<gui::StyleColorGuard> rom_btn_guard;
659 if (has_rom) {
660 rom_btn_guard.emplace(ImGuiCol_Button, gui::GetInfoColor());
661 }
662 if (ImGui::Button(absl::StrCat(ICON_MD_STORAGE, " ROM Tests").c_str(),
663 ImVec2(100, 0))) {
664 if (has_rom) {
665 [[maybe_unused]] auto status =
667 }
668 }
669 }
670 if (ImGui::IsItemHovered()) {
671 if (has_rom) {
672 ImGui::SetTooltip("Run tests on current ROM: %s",
673 current_rom_->title().c_str());
674 } else {
675 ImGui::SetTooltip("Load a ROM to enable ROM-dependent tests");
676 }
677 }
678
679 ImGui::SameLine();
680 if (ImGui::Button(absl::StrCat(ICON_MD_CLEAR, " Clear").c_str(),
681 ImVec2(80, 0))) {
682 ClearResults();
683 }
684
685 ImGui::SameLine();
686 if (ImGui::Button(absl::StrCat(ICON_MD_SETTINGS, " Config").c_str(),
687 ImVec2(80, 0))) {
689 }
690 }
691
692 ImGui::Separator();
693
694 // Enhanced test results summary with better visuals
695 if (last_results_.total_tests > 0) {
696 // Test summary header
697 ImGui::Text("%s Test Results Summary", ICON_MD_ASSESSMENT);
698
699 // Progress bar showing pass rate
700 float pass_rate = last_results_.GetPassRate();
701 ImVec4 progress_color = pass_rate >= 0.9f ? gui::GetSuccessColor()
702 : pass_rate >= 0.7f ? gui::GetWarningColor()
704
705 {
706 gui::StyleColorGuard hist_guard(ImGuiCol_PlotHistogram, progress_color);
707 ImGui::ProgressBar(
708 pass_rate, ImVec2(-1, 0),
709 absl::StrFormat("Pass Rate: %.1f%%", pass_rate * 100.0f).c_str());
710 }
711
712 // Test counts with icons
713 ImGui::Text("%s Total: %zu", ICON_MD_ANALYTICS, last_results_.total_tests);
714 ImGui::SameLine();
715 ImGui::TextColored(GetTestStatusColor(TestStatus::kPassed), "%s %zu",
717 ImGui::SameLine();
718 ImGui::TextColored(GetTestStatusColor(TestStatus::kFailed), "%s %zu",
720 ImGui::SameLine();
721 ImGui::TextColored(GetTestStatusColor(TestStatus::kSkipped), "%s %zu",
723
724 ImGui::Text("%s Duration: %lld ms", ICON_MD_TIMER,
726
727 // Test suite breakdown
728 if (ImGui::CollapsingHeader("Test Suite Breakdown")) {
729 std::unordered_map<std::string, std::pair<size_t, size_t>>
730 suite_stats; // passed, total
731 for (const auto& result : last_results_.individual_results) {
732 suite_stats[result.suite_name].second++; // total
733 if (result.status == TestStatus::kPassed) {
734 suite_stats[result.suite_name].first++; // passed
735 }
736 }
737
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
741 : 0.0f;
742 ImGui::Text("%s: %zu/%zu (%.0f%%)", suite_name.c_str(), stats.first,
743 stats.second, suite_pass_rate * 100.0f);
744 }
745 }
746 }
747
748 ImGui::Separator();
749
750 // Enhanced test filter with category selection
751 ImGui::Text("%s Filter & View Options", ICON_MD_FILTER_LIST);
752
753 // Category filter
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) {
760 case 0:
762 break; // All - use Unit as default
763 case 1:
765 break;
766 case 2:
768 break;
769 case 3:
771 break;
772 case 4:
774 break;
775 case 5:
777 break;
778 }
779 }
780
781 // Text filter
782 static char filter_buffer[256] = "";
783 ImGui::SetNextItemWidth(-80);
784 if (ImGui::InputTextWithHint("##filter", "Search tests...", filter_buffer,
785 sizeof(filter_buffer))) {
786 test_filter_ = std::string(filter_buffer);
787 }
788 ImGui::SameLine();
789 if (ImGui::Button("Clear")) {
790 filter_buffer[0] = '\0';
791 test_filter_.clear();
792 }
793
794 ImGui::Separator();
795
796 // Tabs for different test result views
797 if (ImGui::BeginTabBar("TestResultsTabs", ImGuiTabBarFlags_None)) {
798 // Standard test results tab
799 if (ImGui::BeginTabItem("Test Results")) {
800 if (ImGui::BeginChild("TestResults", ImVec2(0, 0), true)) {
801 if (last_results_.individual_results.empty()) {
802 ImGui::TextColored(gui::GetDisabledColor(),
803 "No test results to display. Run some tests to "
804 "see results here.");
805 } else {
806 for (const auto& result : last_results_.individual_results) {
807 // Apply filters
808 bool category_match = (selected_category == 0) ||
809 (result.category == category_filter_);
810 bool text_match =
811 test_filter_.empty() ||
812 result.name.find(test_filter_) != std::string::npos ||
813 result.suite_name.find(test_filter_) != std::string::npos;
814
815 if (!category_match || !text_match) {
816 continue;
817 }
818
819 ImGui::PushID(&result);
820
821 // Status icon and test name
822 const char* status_icon = ICON_MD_HELP;
823 switch (result.status) {
825 status_icon = ICON_MD_CHECK_CIRCLE;
826 break;
828 status_icon = ICON_MD_ERROR;
829 break;
831 status_icon = ICON_MD_SKIP_NEXT;
832 break;
834 status_icon = ICON_MD_PLAY_CIRCLE_FILLED;
835 break;
836 default:
837 break;
838 }
839
840 ImGui::TextColored(GetTestStatusColor(result.status), "%s %s::%s",
841 status_icon, result.suite_name.c_str(),
842 result.name.c_str());
843
844 // Show duration and timestamp on same line if space allows
845 if (ImGui::GetContentRegionAvail().x > 200) {
846 ImGui::SameLine();
847 ImGui::TextColored(gui::GetDisabledColor(), "(%lld ms)",
848 result.duration.count());
849 }
850
851 // Show detailed information for failed tests
852 if (result.status == TestStatus::kFailed &&
853 !result.error_message.empty()) {
854 ImGui::Indent();
855 {
856 gui::StyleColorGuard err_guard(ImGuiCol_Text,
858 ImGui::TextWrapped("%s %s", ICON_MD_ERROR_OUTLINE,
859 result.error_message.c_str());
860 }
861 ImGui::Unindent();
862 }
863
864 // Show additional info for passed tests if they have messages
865 if (result.status == TestStatus::kPassed &&
866 !result.error_message.empty()) {
867 ImGui::Indent();
868 {
869 gui::StyleColorGuard info_guard(ImGuiCol_Text,
871 ImGui::TextWrapped("%s %s", ICON_MD_INFO,
872 result.error_message.c_str());
873 }
874 ImGui::Unindent();
875 }
876
877 ImGui::PopID();
878 }
879 }
880 }
881 ImGui::EndChild();
882 ImGui::EndTabItem();
883 }
884
885 // Harness Test Results tab (for gRPC GUI automation tests)
886#if defined(YAZE_WITH_GRPC)
887 if (ImGui::BeginTabItem("GUI Automation Tests")) {
888 if (ImGui::BeginChild("HarnessTests", ImVec2(0, 0), true)) {
889 // Display harness test summaries
890 auto summaries = ListHarnessTestSummaries();
891
892 if (summaries.empty()) {
893 ImGui::TextColored(
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.");
898 } else {
899 ImGui::Text("%s GUI Automation Test History", ICON_MD_HISTORY);
900 ImGui::Text("Total Tests: %zu", summaries.size());
901 ImGui::Separator();
902
903 // Table of harness test results
904 if (ImGui::BeginTable("HarnessTestTable", 6,
905 ImGuiTableFlags_Borders |
906 ImGuiTableFlags_RowBg |
907 ImGuiTableFlags_Resizable)) {
908 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed,
909 80);
910 ImGui::TableSetupColumn("Test Name",
911 ImGuiTableColumnFlags_WidthStretch);
912 ImGui::TableSetupColumn("Category",
913 ImGuiTableColumnFlags_WidthFixed, 100);
914 ImGui::TableSetupColumn("Runs", ImGuiTableColumnFlags_WidthFixed,
915 60);
916 ImGui::TableSetupColumn("Pass Rate",
917 ImGuiTableColumnFlags_WidthFixed, 80);
918 ImGui::TableSetupColumn("Duration",
919 ImGuiTableColumnFlags_WidthFixed, 80);
920 ImGui::TableHeadersRow();
921
922 for (const auto& summary : summaries) {
923 const auto& exec = summary.latest_execution;
924
925 ImGui::TableNextRow();
926 ImGui::TableNextColumn();
927
928 // Status indicator
929 ImVec4 status_color;
930 const char* status_icon;
931 const char* status_text;
932
933 switch (exec.status) {
934 case HarnessTestStatus::kPassed:
935 status_color = gui::GetSuccessColor();
936 status_icon = ICON_MD_CHECK_CIRCLE;
937 status_text = "Passed";
938 break;
939 case HarnessTestStatus::kFailed:
940 status_color = gui::GetErrorColor();
941 status_icon = ICON_MD_ERROR;
942 status_text = "Failed";
943 break;
944 case HarnessTestStatus::kTimeout:
945 status_color = gui::GetWarningColor();
946 status_icon = ICON_MD_TIMER_OFF;
947 status_text = "Timeout";
948 break;
949 case HarnessTestStatus::kRunning:
950 status_color = gui::GetWarningColor();
951 status_icon = ICON_MD_PLAY_CIRCLE_FILLED;
952 status_text = "Running";
953 break;
954 case HarnessTestStatus::kQueued:
955 status_color = gui::GetDisabledColor();
956 status_icon = ICON_MD_SCHEDULE;
957 status_text = "Queued";
958 break;
959 default:
960 status_color = gui::GetDisabledColor();
961 status_icon = ICON_MD_HELP;
962 status_text = "Unknown";
963 break;
964 }
965
966 ImGui::TextColored(status_color, "%s %s", status_icon,
967 status_text);
968
969 ImGui::TableNextColumn();
970 ImGui::Text("%s", exec.name.c_str());
971
972 // Show error message if failed
973 if (exec.status == HarnessTestStatus::kFailed &&
974 !exec.error_message.empty()) {
975 ImGui::SameLine();
976 ImGui::TextColored(gui::GetErrorColor(), "(%s)",
977 exec.error_message.c_str());
978 }
979
980 ImGui::TableNextColumn();
981 ImGui::Text("%s", exec.category.c_str());
982
983 ImGui::TableNextColumn();
984 ImGui::Text("%d", summary.total_runs);
985
986 ImGui::TableNextColumn();
987 if (summary.total_runs > 0) {
988 float pass_rate =
989 static_cast<float>(summary.pass_count) / summary.total_runs;
990 ImVec4 rate_color = pass_rate >= 0.9f ? gui::GetSuccessColor()
991 : pass_rate >= 0.7f ? gui::GetWarningColor()
993 ImGui::TextColored(rate_color, "%.0f%%", pass_rate * 100.0f);
994 } else {
995 ImGui::Text("-");
996 }
997
998 ImGui::TableNextColumn();
999 double duration_ms =
1000 absl::ToDoubleMilliseconds(summary.total_duration);
1001 if (summary.total_runs > 0) {
1002 ImGui::Text("%.0f ms", duration_ms / summary.total_runs);
1003 } else {
1004 ImGui::Text("-");
1005 }
1006
1007 // Expandable details
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);
1013
1014 if (!exec.logs.empty()) {
1015 ImGui::Separator();
1016 ImGui::Text("%s Logs:", ICON_MD_DESCRIPTION);
1017 for (const auto& log : exec.logs) {
1018 ImGui::BulletText("%s", log.c_str());
1019 }
1020 }
1021
1022 if (!exec.assertion_failures.empty()) {
1023 ImGui::Separator();
1024 ImGui::TextColored(gui::GetErrorColor(),
1025 "%s Assertion Failures:", ICON_MD_ERROR);
1026 for (const auto& failure : exec.assertion_failures) {
1027 ImGui::BulletText("%s", failure.c_str());
1028 }
1029 }
1030
1031 if (!exec.screenshot_path.empty()) {
1032 ImGui::Separator();
1033 ImGui::Text("%s Screenshot: %s", ICON_MD_CAMERA_ALT,
1034 exec.screenshot_path.c_str());
1035 ImGui::Text("Size: %.2f KB",
1036 exec.screenshot_size_bytes / 1024.0);
1037 }
1038
1039 ImGui::TreePop();
1040 }
1041 }
1042
1043 ImGui::EndTable();
1044 }
1045 }
1046 }
1047 ImGui::EndChild();
1048 ImGui::EndTabItem();
1049 }
1050#endif // defined(YAZE_WITH_GRPC)
1051
1052 ImGui::EndTabBar();
1053 }
1054
1055 ImGui::End();
1056
1057 // Resource monitor window
1059 ImGui::Begin(absl::StrCat(ICON_MD_MONITOR, " Resource Monitor").c_str(),
1061
1062 if (!resource_history_.empty()) {
1063 const auto& latest = resource_history_.back();
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);
1068
1069 // Simple plot of resource usage over time
1070 if (resource_history_.size() > 1) {
1071 std::vector<float> texture_counts;
1072 std::vector<float> surface_counts;
1073 texture_counts.reserve(resource_history_.size());
1074 surface_counts.reserve(resource_history_.size());
1075
1076 for (const auto& stats : resource_history_) {
1077 texture_counts.push_back(static_cast<float>(stats.texture_count));
1078 surface_counts.push_back(static_cast<float>(stats.surface_count));
1079 }
1080
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));
1087 }
1088 }
1089
1090 ImGui::End();
1091 }
1092
1093 // Google Tests window
1094 if (show_google_tests_) {
1095 ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver);
1096 if (ImGui::Begin("Google Tests", &show_google_tests_)) {
1097 ImGui::Text("%s Google Test Integration", ICON_MD_SCIENCE);
1098 ImGui::Separator();
1099
1100#ifdef YAZE_ENABLE_GTEST
1101 ImGui::Text("Google Test framework is available");
1102
1103 if (ImGui::Button("Run All Google Tests")) {
1104 // Run Google tests - this would integrate with gtest
1105 LOG_INFO("TestManager", "Running Google Tests...");
1106 }
1107
1108 ImGui::SameLine();
1109 if (ImGui::Button("Run Specific Test Suite")) {
1110 // Show test suite selector
1111 }
1112
1113 ImGui::Separator();
1114 ImGui::Text("Available Test Suites:");
1115 ImGui::BulletText("Unit Tests");
1116 ImGui::BulletText("Integration Tests");
1117 ImGui::BulletText("Performance Tests");
1118#else
1119 ImGui::TextColored(gui::GetWarningColor(),
1120 "%s Google Test framework not available",
1122 ImGui::Text("Enable YAZE_ENABLE_GTEST to use Google Test integration");
1123#endif
1124 }
1125 ImGui::End();
1126 }
1127
1128 // ROM Test Results window
1130 ImGui::SetNextWindowSize(ImVec2(700, 500), ImGuiCond_FirstUseEver);
1131 if (ImGui::Begin("ROM Test Results", &show_rom_test_results_)) {
1132 ImGui::Text("%s ROM Analysis Results", ICON_MD_ANALYTICS);
1133
1135 ImGui::Text("Testing ROM: %s", current_rom_->title().c_str());
1136 ImGui::Separator();
1137
1138 // Show ROM-specific test results
1139 if (ImGui::CollapsingHeader("ROM Data Integrity",
1140 ImGuiTreeNodeFlags_DefaultOpen)) {
1141 ImGui::Text("ROM Size: %.2f MB", current_rom_->size() / 1048576.0f);
1142 ImGui::Text("Modified: %s", current_rom_->dirty() ? "Yes" : "No");
1143
1144 if (ImGui::Button("Run Data Integrity Check")) {
1145 [[maybe_unused]] auto status = TestRomDataIntegrity(current_rom_);
1146 [[maybe_unused]] auto suite_status =
1148 }
1149 }
1150
1151 if (ImGui::CollapsingHeader("Save/Load Testing")) {
1152 ImGui::Text("Test ROM save and load operations");
1153
1154 if (ImGui::Button("Test Save Operations")) {
1155 [[maybe_unused]] auto status = TestRomSaveLoad(current_rom_);
1156 }
1157
1158 ImGui::SameLine();
1159 if (ImGui::Button("Test Load Operations")) {
1160 [[maybe_unused]] auto status = TestRomSaveLoad(current_rom_);
1161 }
1162 }
1163
1164 if (ImGui::CollapsingHeader("Editor Integration")) {
1165 ImGui::Text("Test editor components with current ROM");
1166
1167 if (ImGui::Button("Test Overworld Editor")) {
1168 // Test overworld editor with current ROM
1169 }
1170
1171 ImGui::SameLine();
1172 if (ImGui::Button("Test Tile16 Editor")) {
1173 // Test tile16 editor with current ROM
1174 }
1175 }
1176
1177 } else {
1178 ImGui::TextColored(gui::GetWarningColor(),
1179 "%s No ROM loaded for analysis", ICON_MD_WARNING);
1180 }
1181 }
1182 ImGui::End();
1183 }
1184
1185 // ROM File Dialog
1187 ImGui::SetNextWindowSize(ImVec2(400, 200), ImGuiCond_Appearing);
1188 if (ImGui::Begin("Load ROM for Testing", &show_rom_file_dialog_,
1189 ImGuiWindowFlags_NoResize)) {
1190 ImGui::Text("%s Load ROM for Testing", ICON_MD_FOLDER_OPEN);
1191 ImGui::Separator();
1192
1193 ImGui::Text("Select a ROM file to run tests on:");
1194
1195 if (ImGui::Button("Browse ROM File...", ImVec2(-1, 0))) {
1196 // TODO: Implement file dialog to load ROM specifically for testing
1197 // This would be separate from the main editor ROM
1198 show_rom_file_dialog_ = false;
1199 }
1200
1201 ImGui::Separator();
1202 if (ImGui::Button("Cancel", ImVec2(-1, 0))) {
1203 show_rom_file_dialog_ = false;
1204 }
1205 }
1206 ImGui::End();
1207 }
1208
1209 // Test Configuration Window
1211 ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
1212 if (ImGui::Begin("Test Configuration", &show_test_configuration_)) {
1213 ImGui::Text("%s Test Configuration", ICON_MD_SETTINGS);
1214 ImGui::Separator();
1215
1216 // File Dialog Configuration
1217 if (ImGui::CollapsingHeader("File Dialog Settings",
1218 ImGuiTreeNodeFlags_DefaultOpen)) {
1219 ImGui::Text("File Dialog Implementation:");
1220
1222 if (ImGui::RadioButton("NFD (Native File Dialog)", nfd_mode)) {
1224 LOG_INFO("TestManager", "Global file dialog mode set to: NFD");
1225 }
1226 if (ImGui::IsItemHovered()) {
1227 ImGui::SetTooltip(
1228 "Use NFD library for native OS file dialogs (global setting)");
1229 }
1230
1231 if (ImGui::RadioButton("Bespoke Implementation", !nfd_mode)) {
1233 LOG_INFO("TestManager", "Global file dialog mode set to: Bespoke");
1234 }
1235 if (ImGui::IsItemHovered()) {
1236 ImGui::SetTooltip(
1237 "Use custom file dialog implementation (global setting)");
1238 }
1239
1240 ImGui::Separator();
1241 ImGui::Text(
1242 "Current Mode: %s",
1243 core::FeatureFlags::get().kUseNativeFileDialog ? "NFD" : "Bespoke");
1244 ImGui::TextColored(
1246 "Note: This setting affects ALL file dialogs in the application");
1247
1248 if (ImGui::Button("Test Current File Dialog")) {
1249 // Test the current file dialog implementation
1250 LOG_INFO("TestManager", "Testing global file dialog mode: %s",
1251 core::FeatureFlags::get().kUseNativeFileDialog ? "NFD"
1252 : "Bespoke");
1253
1254 // Actually test the file dialog
1256 if (!result.empty()) {
1257 LOG_INFO("TestManager", "File dialog test successful: %s",
1258 result.c_str());
1259 } else {
1260 LOG_INFO("TestManager",
1261 "File dialog test: No file selected or dialog canceled");
1262 }
1263 }
1264
1265 ImGui::SameLine();
1266 if (ImGui::Button("Test NFD Directly")) {
1268 if (!result.empty()) {
1269 LOG_INFO("TestManager", "NFD test successful: %s", result.c_str());
1270 } else {
1271 LOG_INFO("TestManager",
1272 "NFD test: No file selected, canceled, or error occurred");
1273 }
1274 }
1275
1276 ImGui::SameLine();
1277 if (ImGui::Button("Test Bespoke Directly")) {
1279 if (!result.empty()) {
1280 LOG_INFO("TestManager", "Bespoke test successful: %s",
1281 result.c_str());
1282 } else {
1283 LOG_INFO("TestManager",
1284 "Bespoke test: No file selected or not implemented");
1285 }
1286 }
1287 }
1288
1289 // Test Selection Configuration
1290 if (ImGui::CollapsingHeader("Test Selection",
1291 ImGuiTreeNodeFlags_DefaultOpen)) {
1292 ImGui::Text("Enable/Disable Individual Tests:");
1293 ImGui::Separator();
1294
1295 // List of known tests with their risk levels
1296 static const std::vector<std::pair<std::string, std::string>>
1297 known_tests = {
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"}};
1312
1313 // Initialize problematic tests as disabled by default
1314 static bool initialized_defaults = false;
1315 if (!initialized_defaults) {
1316 DisableTest("Comprehensive_Save_Test"); // Disable crash-prone test
1317 // by default
1318 initialized_defaults = true;
1319 }
1320
1321 if (ImGui::BeginTable(
1322 "TestSelection", 4,
1323 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
1324 ImGui::TableSetupColumn("Test Name", ImGuiTableColumnFlags_WidthFixed,
1325 200);
1326 ImGui::TableSetupColumn("Risk Level",
1327 ImGuiTableColumnFlags_WidthStretch);
1328 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed,
1329 80);
1330 ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthFixed,
1331 100);
1332 ImGui::TableHeadersRow();
1333
1334 for (const auto& [test_name, description] : known_tests) {
1335 bool enabled = IsTestEnabled(test_name);
1336
1337 ImGui::TableNextRow();
1338 ImGui::TableNextColumn();
1339 ImGui::Text("%s", test_name.c_str());
1340
1341 ImGui::TableNextColumn();
1342 // Color-code the risk level
1343 if (description.find("DANGEROUS") != std::string::npos) {
1344 ImGui::TextColored(gui::GetErrorColor(), "%s",
1345 description.c_str());
1346 } else if (description.find("Moderate") != std::string::npos) {
1347 ImGui::TextColored(gui::GetWarningColor(), "%s",
1348 description.c_str());
1349 } else {
1350 ImGui::TextColored(gui::GetSuccessColor(), "%s",
1351 description.c_str());
1352 }
1353
1354 ImGui::TableNextColumn();
1355 if (enabled) {
1356 ImGui::TextColored(gui::GetSuccessColor(), "%s ON",
1358 } else {
1359 ImGui::TextColored(gui::GetWarningColor(), "%s OFF",
1361 }
1362
1363 ImGui::TableNextColumn();
1364 ImGui::PushID(test_name.c_str());
1365 if (enabled) {
1366 if (ImGui::Button("Disable")) {
1367 DisableTest(test_name);
1368 LOG_INFO("TestManager", "Disabled test: %s", test_name.c_str());
1369 }
1370 } else {
1371 if (ImGui::Button("Enable")) {
1372 EnableTest(test_name);
1373 LOG_INFO("TestManager", "Enabled test: %s", test_name.c_str());
1374 }
1375 }
1376 ImGui::PopID();
1377 }
1378
1379 ImGui::EndTable();
1380 }
1381
1382 ImGui::Separator();
1383 ImGui::Text("Quick Actions:");
1384
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) {
1388 EnableTest(test_name);
1389 } else {
1390 DisableTest(test_name);
1391 }
1392 }
1393 LOG_INFO("TestManager", "Enabled only safe tests");
1394 }
1395 ImGui::SameLine();
1396
1397 if (ImGui::Button("Enable All Tests")) {
1398 for (const auto& [test_name, description] : known_tests) {
1399 EnableTest(test_name);
1400 }
1401 LOG_INFO("TestManager",
1402 "Enabled all tests (including dangerous ones)");
1403 }
1404 ImGui::SameLine();
1405
1406 if (ImGui::Button("Disable All Tests")) {
1407 for (const auto& [test_name, description] : known_tests) {
1408 DisableTest(test_name);
1409 }
1410 LOG_INFO("TestManager", "Disabled all tests");
1411 }
1412
1413 ImGui::Separator();
1414 ImGui::TextColored(
1416 " Recommendation: Use 'Enable Safe Tests Only' to avoid crashes");
1417 }
1418
1419 // Platform-specific settings
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");
1424 ImGui::BulletText(
1425 "Global setting affects File → Open, Project dialogs, etc.");
1426
1427 ImGui::Separator();
1428 ImGui::Text("Test Both Implementations:");
1429
1430 if (ImGui::Button("Quick Test NFD")) {
1432 LOG_INFO("TestManager", "NFD test result: %s",
1433 result.empty() ? "Failed/Canceled" : result.c_str());
1434 }
1435 ImGui::SameLine();
1436 if (ImGui::Button("Quick Test Bespoke")) {
1438 LOG_INFO("TestManager", "Bespoke test result: %s",
1439 result.empty() ? "Failed/Not Implemented" : result.c_str());
1440 }
1441
1442 ImGui::Separator();
1443 ImGui::TextColored(gui::GetDisabledColor(),
1444 "Note: These tests don't change the global setting");
1445 }
1446 }
1447 ImGui::End();
1448 }
1449
1450 // Test Session Creation Dialog
1452 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1453 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1454 ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_Appearing);
1455
1456 if (ImGui::Begin("Test ROM Session", &show_test_session_dialog_,
1457 ImGuiWindowFlags_NoResize)) {
1458 ImGui::Text("%s Test ROM Created Successfully", ICON_MD_CHECK_CIRCLE);
1459 ImGui::Separator();
1460
1461 ImGui::Text("A test ROM has been created with your modifications:");
1462 ImGui::Text("File: %s", test_rom_path_for_session_.c_str());
1463
1464 // Extract just the filename for display
1465 std::string display_filename = test_rom_path_for_session_;
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);
1469 }
1470
1471 ImGui::Separator();
1472 ImGui::Text("Would you like to open this test ROM in a new session?");
1473
1474 if (ImGui::Button(
1475 absl::StrFormat("%s Open in New Session", ICON_MD_TAB).c_str(),
1476 ImVec2(200, 0))) {
1477 // TODO: This would need access to EditorManager to create a new session
1478 // For now, just show a message
1479 LOG_INFO("TestManager",
1480 "User requested to open test ROM in new session: %s",
1483 }
1484
1485 ImGui::SameLine();
1486 if (ImGui::Button(
1487 absl::StrFormat("%s Keep Current Session", ICON_MD_CLOSE).c_str(),
1488 ImVec2(200, 0))) {
1490 }
1491
1492 ImGui::Separator();
1493 ImGui::TextColored(
1495 "Note: Test ROM contains your modifications and can be");
1496 ImGui::TextColored(gui::GetDisabledColor(),
1497 "opened later using File → Open");
1498 }
1499 ImGui::End();
1500 }
1501}
1502
1504 LOG_INFO("TestManager", "=== TestManager ROM Refresh ===");
1505
1506 // Log current TestManager ROM state for debugging
1507 if (current_rom_) {
1508 LOG_INFO("TestManager", "TestManager ROM pointer: %p", (void*)current_rom_);
1509 LOG_INFO("TestManager", "ROM is_loaded(): %s",
1510 current_rom_->is_loaded() ? "true" : "false");
1511 if (current_rom_->is_loaded()) {
1512 LOG_INFO("TestManager", "ROM title: '%s'", current_rom_->title().c_str());
1513 LOG_INFO("TestManager", "ROM size: %.2f MB",
1514 current_rom_->size() / 1048576.0f);
1515 LOG_INFO("TestManager", "ROM dirty: %s",
1516 current_rom_->dirty() ? "true" : "false");
1517 }
1518 } else {
1519 LOG_INFO("TestManager", "TestManager ROM pointer is null");
1520 LOG_INFO("TestManager",
1521 "Note: ROM should be set by EditorManager when ROM is loaded");
1522 }
1523 LOG_INFO("TestManager", "===============================");
1524}
1525
1526absl::Status TestManager::CreateTestRomCopy(Rom* source_rom,
1527 std::unique_ptr<Rom>& test_rom) {
1528 if (!source_rom || !source_rom->is_loaded()) {
1529 return absl::FailedPreconditionError("Source ROM not loaded");
1530 }
1531
1532 LOG_INFO("TestManager", "Creating test ROM copy from: %s",
1533 source_rom->title().c_str());
1534
1535 // Create a new ROM instance
1536 test_rom = std::make_unique<Rom>();
1537
1538 // Copy the ROM data
1539 auto rom_data = source_rom->vector();
1540 auto load_status = test_rom->LoadFromData(rom_data);
1541 if (!load_status.ok()) {
1542 return load_status;
1543 }
1544
1545 LOG_INFO("TestManager", "Test ROM copy created successfully (size: %.2f MB)",
1546 test_rom->size() / 1048576.0f);
1547 return absl::OkStatus();
1548}
1549
1550std::string TestManager::GenerateTestRomFilename(const std::string& base_name) {
1551 // Generate filename with timestamp
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);
1555
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);
1560
1561 std::string base_filename = base_name;
1562 // Remove any path and extension
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);
1566 }
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);
1570 }
1571
1572 return absl::StrFormat("%s_test_%s.sfc", base_filename.c_str(),
1573 timestamp.c_str());
1574}
1575
1576void TestManager::OfferTestSessionCreation(const std::string& test_rom_path) {
1577 // Store the test ROM path for the dialog
1578 test_rom_path_for_session_ = test_rom_path;
1580}
1581
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");
1586 }
1587
1588 // Create a copy of the ROM for testing
1589 std::unique_ptr<Rom> test_rom;
1590 RETURN_IF_ERROR(CreateTestRomCopy(source_rom, test_rom));
1591
1592 LOG_INFO("TestManager", "Executing test function on ROM copy");
1593
1594 // Run the test function on the copy
1595 auto test_result = test_function(test_rom.get());
1596
1597 LOG_INFO("TestManager", "Test function completed with status: %s",
1598 test_result.ToString().c_str());
1599
1600 return test_result;
1601}
1602
1603absl::Status TestManager::LoadRomForTesting(const std::string& filename) {
1604 // This would load a ROM specifically for testing purposes
1605 // For now, just log the request
1606 LOG_INFO("TestManager", "Request to load ROM for testing: %s",
1607 filename.c_str());
1608 return absl::UnimplementedError(
1609 "ROM loading for testing not yet implemented");
1610}
1611
1613 const Rom& after) {
1614 if (ImGui::Begin("ROM Comparison Results")) {
1615 ImGui::Text("%s ROM Before/After Comparison", ICON_MD_COMPARE);
1616 ImGui::Separator();
1617
1618 if (ImGui::BeginTable("RomComparison", 3, ImGuiTableFlags_Borders)) {
1619 ImGui::TableSetupColumn("Property");
1620 ImGui::TableSetupColumn("Before");
1621 ImGui::TableSetupColumn("After");
1622 ImGui::TableHeadersRow();
1623
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);
1631
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");
1639
1640 ImGui::EndTable();
1641 }
1642 }
1643 ImGui::End();
1644}
1645
1647 if (!rom || !rom->is_loaded()) {
1648 return absl::FailedPreconditionError("No ROM loaded for testing");
1649 }
1650
1651 // Use TestRomWithCopy to avoid affecting the original ROM
1652 return TestRomWithCopy(rom, [this](Rom* test_rom) -> absl::Status {
1653 LOG_INFO("TestManager", "Testing ROM save/load operations on copy: %s",
1654 test_rom->title().c_str());
1655
1656 // Perform test modifications on the copy
1657 // Test save operations
1658 Rom::SaveSettings settings;
1659 settings.backup = false;
1660 settings.save_new = true;
1661 settings.filename = GenerateTestRomFilename(test_rom->title());
1662
1663 auto save_status = test_rom->SaveToFile(settings);
1664 if (!save_status.ok()) {
1665 return save_status;
1666 }
1667
1668 LOG_INFO("TestManager", "Test ROM saved successfully to: %s",
1669 settings.filename.c_str());
1670
1671 // Offer to open test ROM in new session
1673
1674 return absl::OkStatus();
1675 });
1676}
1677
1679 if (!rom || !rom->is_loaded()) {
1680 return absl::FailedPreconditionError("No ROM loaded for testing");
1681 }
1682
1683 // Use TestRomWithCopy for integrity testing (read-only but uses copy for
1684 // safety)
1685 return TestRomWithCopy(rom, [](Rom* test_rom) -> absl::Status {
1686 LOG_INFO("TestManager", "Testing ROM data integrity on copy: %s",
1687 test_rom->title().c_str());
1688
1689 // Perform data integrity checks on the copy
1690 // This validates ROM structure, checksums, etc. without affecting original
1691
1692 // Basic ROM structure validation
1693 if (test_rom->size() < 0x100000) { // 1MB minimum for ALTTP
1694 return absl::FailedPreconditionError(
1695 "ROM file too small for A Link to the Past");
1696 }
1697
1698 // Check ROM header
1699 auto header_status = test_rom->ReadByteVector(0x7FC0, 32);
1700 if (!header_status.ok()) {
1701 return header_status.status();
1702 }
1703
1704 LOG_INFO("TestManager", "ROM integrity check passed for: %s",
1705 test_rom->title().c_str());
1706 return absl::OkStatus();
1707 });
1708}
1709
1710#if defined(YAZE_WITH_GRPC)
1711std::string TestManager::RegisterHarnessTest(const std::string& name,
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);
1729 return test_id;
1730}
1731#else
1732std::string TestManager::RegisterHarnessTest(const std::string& name,
1733 const std::string& category) {
1734 (void)name;
1735 (void)category;
1736 return {};
1737}
1738#endif
1739
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()) {
1745 return;
1746 }
1747 HarnessTestExecution& execution = it->second;
1748 execution.status = HarnessTestStatus::kRunning;
1749 execution.started_at = absl::Now();
1750}
1751#endif
1752
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()) {
1763 return;
1764 }
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());
1773
1774 bool capture_failure_context = status == HarnessTestStatus::kFailed ||
1775 status == HarnessTestStatus::kTimeout;
1776
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;
1784 }
1785 harness_aggregates_[execution.name].total_duration += execution.duration;
1786 harness_aggregates_[execution.name].last_run = execution.completed_at;
1787
1788 if (capture_failure_context) {
1789 CaptureFailureContext(test_id);
1790 }
1791
1792 if (harness_listener_) {
1793 harness_listener_->OnHarnessTestUpdated(execution);
1794 }
1795}
1796#endif
1797
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()) {
1804 return;
1805 }
1806 HarnessTestExecution& execution = it->second;
1807 execution.logs.push_back(log_entry);
1808 harness_aggregates_[execution.name].latest_execution.logs = execution.logs;
1809}
1810#endif
1811
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));
1820 }
1821 return it->second;
1822}
1823#endif
1824
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) {
1833 continue;
1834 }
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);
1842 }
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;
1848 }
1849 absl::Time time_b = b.latest_execution.completed_at;
1850 if (time_b == absl::InfinitePast()) {
1851 time_b = b.latest_execution.queued_at;
1852 }
1853 return time_a > time_b;
1854 });
1855 return summaries;
1856}
1857#endif
1858
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()) {
1864 return;
1865 }
1866 HarnessTestExecution& execution = it->second;
1867 // absl::MutexLock does not support Unlock/Lock; scope the lock instead
1868 {
1869 absl::MutexLock unlock_guard(&harness_history_mutex_);
1870 // This block is just to clarify lock scope, but the lock is already held
1871 // so we do nothing here.
1872 }
1873
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";
1878 } else {
1879 failure_context = "Harness failure context capture unavailable";
1880 }
1881
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;
1886 }
1887
1888 if (harness_listener_) {
1889 harness_listener_->OnHarnessTestUpdated(execution);
1890 }
1891}
1892#else
1893void TestManager::CaptureFailureContext(const std::string& test_id) {
1894 (void)test_id;
1895}
1896#endif
1897
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);
1904 }
1905}
1906
1907absl::Status TestManager::ReplayLastPlan() {
1908 return absl::FailedPreconditionError("Harness plan replay not available");
1909}
1910
1911void TestManager::SetHarnessListener(HarnessListener* listener) {
1912 absl::MutexLock lock(&mutex_);
1913 harness_listener_ = listener;
1914}
1915#else
1916absl::Status TestManager::ReplayLastPlan() {
1917 return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
1918}
1919#endif
1920
1921absl::Status TestManager::ShowHarnessDashboard() {
1922 // These methods are always available, but may return unimplemented without
1923 // GRPC
1924#if defined(YAZE_WITH_GRPC)
1925 return absl::OkStatus();
1926#else
1927 return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
1928#endif
1929}
1930
1931absl::Status TestManager::ShowHarnessActiveTests() {
1932#if defined(YAZE_WITH_GRPC)
1933 return absl::OkStatus();
1934#else
1935 return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
1936#endif
1937}
1938
1939void TestManager::RecordPlanSummary(const std::string& summary) {
1940 (void)summary;
1941}
1942
1943} // namespace test
1944} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
Definition rom.cc:431
auto filename() const
Definition rom.h:145
const auto & vector() const
Definition rom.h:143
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:291
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
bool dirty() const
Definition rom.h:133
bool is_loaded() const
Definition rom.h:132
auto title() const
Definition rom.h:137
static Flags & get()
Definition features.h:118
static Arena & Get()
Definition arena.cc:21
RAII guard for ImGui style colors.
Definition style_guard.h:27
std::string GenerateTestRomFilename(const std::string &base_name)
void CaptureFailureContext(const std::string &test_id)
std::string current_test_name_
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)
std::string test_rom_path_for_session_
absl::Status TestRomDataIntegrity(Rom *rom)
static constexpr size_t kMaxResourceHistorySize
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)
void EnableTest(const std::string &test_name)
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 ShowRomComparisonResults(const Rom &before, const Rom &after)
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_BLOCK
Definition icons.h:269
#define ICON_MD_TEXTURE
Definition icons.h:1965
#define ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_ASSESSMENT
Definition icons.h:193
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_MONITOR
Definition icons.h:1233
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_MEMORY
Definition icons.h:1195
#define ICON_MD_STORAGE
Definition icons.h:1865
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_CAMERA_ALT
Definition icons.h:355
#define ICON_MD_CHECKLIST
Definition icons.h:402
#define ICON_MD_PLAY_ARROW
Definition icons.h:1479
#define ICON_MD_CHECK
Definition icons.h:397
#define ICON_MD_SCHEDULE
Definition icons.h:1652
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_PLAY_CIRCLE_FILLED
Definition icons.h:1482
#define ICON_MD_SPEED
Definition icons.h:1817
#define ICON_MD_TIMER_OFF
Definition icons.h:1987
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_ERROR_OUTLINE
Definition icons.h:687
#define ICON_MD_LAYERS
Definition icons.h:1068
#define ICON_MD_TIMER
Definition icons.h:1982
#define ICON_MD_SCIENCE
Definition icons.h:1656
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_SKIP_NEXT
Definition icons.h:1773
#define ICON_MD_CLEAR
Definition icons.h:416
#define ICON_MD_DESCRIPTION
Definition icons.h:539
#define ICON_MD_FILTER_LIST
Definition icons.h:768
#define ICON_MD_TAB
Definition icons.h:1930
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_ANALYTICS
Definition icons.h:154
#define ICON_MD_HELP
Definition icons.h:933
#define ICON_MD_COMPARE
Definition icons.h:447
#define ICON_MD_HISTORY
Definition icons.h:946
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
ImVec4 GetSuccessColor()
Definition ui_helpers.cc:48
ButtonColorSet GetSuccessButtonColors()
ImVec4 GetDisabledColor()
Definition ui_helpers.cc:73
ImVec4 GetErrorColor()
Definition ui_helpers.cc:58
ImVec4 GetWarningColor()
Definition ui_helpers.cc:53
ImVec4 GetInfoColor()
Definition ui_helpers.cc:63
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)
Definition snes.cc:22
std::string filename
Definition rom.h:33
std::chrono::time_point< std::chrono::steady_clock > timestamp
std::vector< TestResult > individual_results
std::chrono::milliseconds total_duration
float GetPassRate() const