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
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 UpdateRemoteReplayFocus();
209 ImGuiTestEngine_PostSwap(ui_test_engine_);
210 // A harness terminal record is published inside TestFunc, before the
211 // engine unwinds and applies its end-of-queue focus policy. Restore only
212 // here, on the UI thread after that lifecycle boundary.
213 UpdateRemoteReplayFocus();
214 }
215}
216
217void TestManager::RegisterRemoteReplayTest(ImGuiTest* test,
218 const std::string& harness_id) {
219 absl::MutexLock lock(&mutex_);
220 remote_replay_tests_[test] = harness_id;
221}
222
223absl::Status TestManager::BeginRemoteReplayFocus(ImGuiTest* test) {
224 ImVector<ImGuiTestRunTask> queue;
225 ImGuiTestEngine_GetTestQueue(ui_test_engine_, &queue);
226 {
227 absl::MutexLock lock(&mutex_);
228 // Registered dynamic tests remain manually re-runnable. An untagged
229 // later run is ordinary work and must not inherit the original RPC scope.
230 if (!remote_replay_tests_.contains(test))
231 return absl::OkStatus();
232 for (const auto& task : queue) {
233 if (!remote_replay_tests_.contains(task.Test)) {
234 RestoreRemoteReplayFocus();
235 return absl::FailedPreconditionError(
236 "Remote replay cannot share a queue with ordinary UI tests");
237 }
238 }
239 }
240 auto& io = ImGuiTestEngine_GetIO(ui_test_engine_);
241 if (!remote_replay_restore_focus_.has_value())
242 remote_replay_restore_focus_ = io.ConfigRestoreFocusAfterTests;
243 io.ConfigRestoreFocusAfterTests = false;
244 return absl::OkStatus();
245}
246
247void TestManager::RestoreRemoteReplayFocus() {
248 if (remote_replay_restore_focus_.has_value()) {
249 ImGuiTestEngine_GetIO(ui_test_engine_).ConfigRestoreFocusAfterTests =
252 }
253}
254
255void TestManager::UpdateRemoteReplayFocus() {
256 ImVector<ImGuiTestRunTask> queue;
257 ImGuiTestEngine_GetTestQueue(ui_test_engine_, &queue);
258 absl::MutexLock lock(&mutex_);
259 bool ordinary_job = false;
260 for (const auto& task : queue)
261 ordinary_job |= !remote_replay_tests_.contains(task.Test);
262 // Ordinary/manual work keeps its existing focus policy. A remote job may
263 // not acquire an override in a mixed queue.
264 if (queue.empty() || ordinary_job)
265 RestoreRemoteReplayFocus();
266 if (queue.empty())
267 remote_replay_tests_.clear();
268}
269
271 if (ui_test_engine_ && ImGui::GetCurrentContext() != nullptr) {
272 ImGuiTestEngine_AbortCurrentTest(ui_test_engine_);
273 ImGuiTestEngine_Stop(ui_test_engine_);
274 RestoreRemoteReplayFocus();
275 std::vector<std::string> cancelled;
276 {
277 absl::MutexLock lock(&mutex_);
278 for (const auto& entry : remote_replay_tests_)
279 cancelled.push_back(entry.second);
280 remote_replay_tests_.clear();
281 }
282#ifdef YAZE_WITH_GRPC
283 for (const auto& id : cancelled) {
284 auto execution = GetHarnessTestExecution(id);
285 if (execution.ok() && (execution->status == HarnessTestStatus::kQueued ||
286 execution->status == HarnessTestStatus::kRunning))
287 MarkHarnessTestCompleted(id, HarnessTestStatus::kFailed,
288 "Remote replay cancelled with UI test engine");
289 }
290#endif
291 }
292}
293
295 if (ui_test_engine_) {
296 RestoreRemoteReplayFocus();
297 ImGuiTestEngine_DestroyContext(ui_test_engine_);
298 ui_test_engine_ = nullptr;
299 }
300}
301
303 // Complete shutdown - calls both phases
306}
307#endif
308
310 if (is_running_) {
311 return absl::FailedPreconditionError("Tests are already running");
312 }
313
314 is_running_ = true;
315 progress_ = 0.0f;
317
318 // Execute all test suites
319 for (auto& suite : test_suites_) {
320 if (suite->IsEnabled()) {
321 current_test_name_ = suite->GetName();
322 auto status = ExecuteTestSuite(suite.get());
323 if (!status.ok()) {
324 is_running_ = false;
325 return status;
326 }
328 }
329 }
330
331 is_running_ = false;
332 current_test_name_.clear();
333 progress_ = 1.0f;
334
335 return absl::OkStatus();
336}
337
339 if (is_running_) {
340 return absl::FailedPreconditionError("Tests are already running");
341 }
342
343 is_running_ = true;
344 progress_ = 0.0f;
346
347 // Filter and execute test suites by category
348 std::vector<TestSuite*> filtered_suites;
349 for (auto& suite : test_suites_) {
350 if (suite->IsEnabled() && suite->GetCategory() == category) {
351 filtered_suites.push_back(suite.get());
352 }
353 }
354
355 for (auto* suite : filtered_suites) {
356 current_test_name_ = suite->GetName();
357 auto status = ExecuteTestSuite(suite);
358 if (!status.ok()) {
359 is_running_ = false;
360 return status;
361 }
363 }
364
365 is_running_ = false;
366 current_test_name_.clear();
367 progress_ = 1.0f;
368
369 return absl::OkStatus();
370}
371
372absl::Status TestManager::RunTestSuite(const std::string& suite_name) {
373 if (is_running_) {
374 return absl::FailedPreconditionError("Tests are already running");
375 }
376
377 auto it = suite_lookup_.find(suite_name);
378 if (it == suite_lookup_.end()) {
379 return absl::NotFoundError("Test suite not found: " + suite_name);
380 }
381
382 is_running_ = true;
383 progress_ = 0.0f;
385 current_test_name_ = suite_name;
386
387 auto status = ExecuteTestSuite(it->second);
388
389 is_running_ = false;
390 current_test_name_.clear();
391 progress_ = 1.0f;
392
393 return status;
394}
395
396void TestManager::RegisterTestSuite(std::unique_ptr<TestSuite> suite) {
397 if (suite) {
398 std::string name = suite->GetName();
399 suite_lookup_[name] = suite.get();
400 test_suites_.push_back(std::move(suite));
401 }
402}
403
404std::vector<std::string> TestManager::GetTestSuiteNames() const {
405 std::vector<std::string> names;
406 names.reserve(test_suites_.size());
407 for (const auto& suite : test_suites_) {
408 names.push_back(suite->GetName());
409 }
410 return names;
411}
412
413TestSuite* TestManager::GetTestSuite(const std::string& name) {
414 auto it = suite_lookup_.find(name);
415 return it != suite_lookup_.end() ? it->second : nullptr;
416}
417
422
424 if (!suite) {
425 return absl::InvalidArgumentError("Test suite is null");
426 }
427
428 // Collect resource stats before test
430
431 // Execute the test suite
432 auto status = suite->RunTests(last_results_);
433
434 // Collect resource stats after test
436
437 return status;
438}
439
441 if (test_suites_.empty()) {
442 progress_ = 1.0f;
443 return;
444 }
445
446 size_t completed = 0;
447 for (const auto& suite : test_suites_) {
448 if (suite->IsEnabled()) {
449 completed++;
450 }
451 }
452
453 progress_ = static_cast<float>(completed) / test_suites_.size();
454}
455
457 ResourceStats stats;
458 stats.timestamp = std::chrono::steady_clock::now();
459
460 // Get Arena statistics
461 auto& arena = gfx::Arena::Get();
462 stats.texture_count = arena.GetTextureCount();
463 stats.surface_count = arena.GetSurfaceCount();
464
465 // Get frame rate from ImGui
466 stats.frame_rate = ImGui::GetIO().Framerate;
467
468 // Estimate memory usage (simplified)
469 stats.memory_usage_mb =
470 (stats.texture_count + stats.surface_count) / 1024; // Rough estimate
471
472 resource_history_.push_back(stats);
473}
474
483
484void TestManager::DrawTestDashboard(bool* show_dashboard) {
485 bool* dashboard_flag = show_dashboard ? show_dashboard : &show_dashboard_;
486
487 // Set a larger default window size
488 ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
489
490 if (!ImGui::Begin("Test Dashboard", dashboard_flag,
491 ImGuiWindowFlags_MenuBar)) {
492 ImGui::End();
493 return;
494 }
495
496 // ROM status indicator with detailed information
497 bool has_rom = current_rom_ && current_rom_->is_loaded();
498
499 // Add real-time ROM status checking
500 static int frame_counter = 0;
501 frame_counter++;
502 if (frame_counter % 60 == 0) { // Check every 60 frames
503 // Log ROM status periodically for debugging
504 LOG_INFO("TestManager",
505 "TestManager ROM status check - Frame %d: ROM %p, loaded: %s",
506 frame_counter, (void*)current_rom_, has_rom ? "true" : "false");
507 }
508
509 if (ImGui::BeginTable("ROM_Status_Table", 2, ImGuiTableFlags_BordersInner)) {
510 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 120);
511 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
512
513 ImGui::TableNextRow();
514 ImGui::TableNextColumn();
515 ImGui::Text("ROM Status:");
516 ImGui::TableNextColumn();
517 if (has_rom) {
518 ImGui::TextColored(gui::GetSuccessColor(), "%s Loaded",
520
521 ImGui::TableNextRow();
522 ImGui::TableNextColumn();
523 ImGui::Text("ROM Title:");
524 ImGui::TableNextColumn();
525 ImGui::Text("%s", current_rom_->title().c_str());
526
527 ImGui::TableNextRow();
528 ImGui::TableNextColumn();
529 ImGui::Text("File Name:");
530 ImGui::TableNextColumn();
531 ImGui::Text("%s", current_rom_->filename().c_str());
532
533 ImGui::TableNextRow();
534 ImGui::TableNextColumn();
535 ImGui::Text("Size:");
536 ImGui::TableNextColumn();
537 ImGui::Text("%.2f MB (%zu bytes)", current_rom_->size() / 1048576.0f,
538 current_rom_->size());
539
540 ImGui::TableNextRow();
541 ImGui::TableNextColumn();
542 ImGui::Text("Modified:");
543 ImGui::TableNextColumn();
544 if (current_rom_->dirty()) {
545 ImGui::TextColored(gui::GetWarningColor(), "%s Yes", ICON_MD_EDIT);
546 } else {
547 ImGui::TextColored(gui::GetSuccessColor(), "%s No", ICON_MD_CHECK);
548 }
549
550 ImGui::TableNextRow();
551 ImGui::TableNextColumn();
552 ImGui::Text("ROM Pointer:");
553 ImGui::TableNextColumn();
554 ImGui::Text("%p", (void*)current_rom_);
555
556 ImGui::TableNextRow();
557 ImGui::TableNextColumn();
558 ImGui::Text("Actions:");
559 ImGui::TableNextColumn();
560 if (ImGui::Button("Refresh ROM Reference")) {
562 }
563
564 } else {
565 ImGui::TextColored(gui::GetWarningColor(), "%s Not Loaded",
567 ImGui::TableNextRow();
568 ImGui::TableNextColumn();
569 ImGui::Text("ROM Pointer:");
570 ImGui::TableNextColumn();
571 ImGui::Text("%p", (void*)current_rom_);
572 ImGui::TableNextRow();
573 ImGui::TableNextColumn();
574 ImGui::Text("Status:");
575 ImGui::TableNextColumn();
576 ImGui::Text("ROM-dependent tests will be skipped");
577
578 ImGui::TableNextRow();
579 ImGui::TableNextColumn();
580 ImGui::Text("Actions:");
581 ImGui::TableNextColumn();
582 if (ImGui::Button("Refresh ROM Reference")) {
584 }
585 ImGui::SameLine();
586 if (ImGui::Button("Debug ROM State")) {
587 LOG_INFO("TestManager", "=== ROM DEBUG INFO ===");
588 LOG_INFO("TestManager", "current_rom_ pointer: %p",
589 (void*)current_rom_);
590 if (current_rom_) {
591 LOG_INFO("TestManager", "ROM title: '%s'",
592 current_rom_->title().c_str());
593 LOG_INFO("TestManager", "ROM size: %zu", current_rom_->size());
594 LOG_INFO("TestManager", "ROM is_loaded(): %s",
595 current_rom_->is_loaded() ? "true" : "false");
596 LOG_INFO("TestManager", "ROM data pointer: %p",
597 (void*)current_rom_->data());
598 }
599 LOG_INFO("TestManager", "======================");
600 }
601 }
602
603 ImGui::EndTable();
604 }
605 ImGui::Separator();
606
607 // Menu bar
608 if (ImGui::BeginMenuBar()) {
609 if (ImGui::BeginMenu("Run")) {
610 if (ImGui::MenuItem("All Tests", "Ctrl+T", false, !is_running_)) {
611 [[maybe_unused]] auto status = RunAllTests();
612 }
613 ImGui::Separator();
614 if (ImGui::MenuItem("Unit Tests", nullptr, false, !is_running_)) {
615 [[maybe_unused]] auto status = RunTestsByCategory(TestCategory::kUnit);
616 }
617 if (ImGui::MenuItem("Integration Tests", nullptr, false, !is_running_)) {
618 [[maybe_unused]] auto status =
620 }
621 if (ImGui::MenuItem("UI Tests", nullptr, false, !is_running_)) {
622 [[maybe_unused]] auto status = RunTestsByCategory(TestCategory::kUI);
623 }
624 if (ImGui::MenuItem("Performance Tests", nullptr, false, !is_running_)) {
625 [[maybe_unused]] auto status =
627 }
628 if (ImGui::MenuItem("Memory Tests", nullptr, false, !is_running_)) {
629 [[maybe_unused]] auto status =
631 }
632 ImGui::EndMenu();
633 }
634
635 if (ImGui::BeginMenu("View")) {
636 ImGui::MenuItem("Resource Monitor", nullptr, &show_resource_monitor_);
637 ImGui::MenuItem("Google Tests", nullptr, &show_google_tests_);
638 ImGui::MenuItem("ROM Test Results", nullptr, &show_rom_test_results_);
639 ImGui::Separator();
640 if (ImGui::MenuItem("Export Results", nullptr, false,
642 // TODO: Implement result export
643 }
644 ImGui::EndMenu();
645 }
646
647 if (ImGui::BeginMenu("ROM")) {
648 if (ImGui::MenuItem("Test Current ROM", nullptr, false,
650 [[maybe_unused]] auto status =
652 }
653 if (ImGui::MenuItem("Load ROM for Testing...")) {
655 }
656 ImGui::Separator();
657 if (ImGui::MenuItem("Refresh ROM Reference")) {
659 }
660 ImGui::EndMenu();
661 }
662
663 if (ImGui::BeginMenu("Configure")) {
664 if (ImGui::MenuItem("Test Configuration")) {
666 }
667 ImGui::Separator();
669 if (ImGui::MenuItem("Use NFD File Dialog", nullptr, &nfd_mode)) {
671 LOG_INFO("TestManager", "Global file dialog mode changed to: %s",
672 nfd_mode ? "NFD" : "Bespoke");
673 }
674 ImGui::EndMenu();
675 }
676
677 ImGui::EndMenuBar();
678 }
679
680 // Show test configuration status
681 int enabled_count = 0;
682 int total_count = 0;
683 static const std::vector<std::string> all_test_names = {
684 "ROM_Header_Validation_Test", "ROM_Data_Access_Test",
685 "ROM_Graphics_Extraction_Test", "ROM_Overworld_Loading_Test",
686 "Tile16_Editor_Test", "Comprehensive_Save_Test",
687 "ROM_Sprite_Data_Test", "ROM_Music_Data_Test"};
688
689 for (const auto& test_name : all_test_names) {
690 total_count++;
691 if (IsTestEnabled(test_name)) {
692 enabled_count++;
693 }
694 }
695
696 ImGui::Text("%s Test Status: %d/%d enabled", ICON_MD_CHECKLIST, enabled_count,
697 total_count);
698 if (enabled_count < total_count) {
699 ImGui::SameLine();
700 ImGui::TextColored(gui::GetWarningColor(),
701 "(Some tests disabled - check Configuration)");
702 }
703
704 // Enhanced test execution status
705 if (is_running_) {
706 {
707 gui::StyleColorGuard running_guard(
709 ImGui::Text("%s Running: %s", ICON_MD_PLAY_CIRCLE_FILLED,
710 current_test_name_.c_str());
711 }
712 ImGui::ProgressBar(progress_, ImVec2(-1, 0),
713 absl::StrFormat("%.0f%%", progress_ * 100.0f).c_str());
714 } else {
715 // Enhanced control buttons
716 {
717 gui::StyleColorGuard run_btn_guard(ImGuiCol_Button,
719 if (ImGui::Button(
720 absl::StrCat(ICON_MD_PLAY_ARROW, " Run All Tests").c_str(),
721 ImVec2(140, 0))) {
722 [[maybe_unused]] auto status = RunAllTests();
723 }
724 }
725
726 ImGui::SameLine();
727 if (ImGui::Button(absl::StrCat(ICON_MD_SPEED, " Quick Test").c_str(),
728 ImVec2(100, 0))) {
729 [[maybe_unused]] auto status = RunTestsByCategory(TestCategory::kMemory);
730 }
731
732 ImGui::SameLine();
733 bool has_rom = current_rom_ && current_rom_->is_loaded();
734 {
735 std::optional<gui::StyleColorGuard> rom_btn_guard;
736 if (has_rom) {
737 rom_btn_guard.emplace(ImGuiCol_Button, gui::GetInfoColor());
738 }
739 if (ImGui::Button(absl::StrCat(ICON_MD_STORAGE, " ROM Tests").c_str(),
740 ImVec2(100, 0))) {
741 if (has_rom) {
742 [[maybe_unused]] auto status =
744 }
745 }
746 }
747 if (ImGui::IsItemHovered()) {
748 if (has_rom) {
749 ImGui::SetTooltip("Run tests on current ROM: %s",
750 current_rom_->title().c_str());
751 } else {
752 ImGui::SetTooltip("Load a ROM to enable ROM-dependent tests");
753 }
754 }
755
756 ImGui::SameLine();
757 if (ImGui::Button(absl::StrCat(ICON_MD_CLEAR, " Clear").c_str(),
758 ImVec2(80, 0))) {
759 ClearResults();
760 }
761
762 ImGui::SameLine();
763 if (ImGui::Button(absl::StrCat(ICON_MD_SETTINGS, " Config").c_str(),
764 ImVec2(80, 0))) {
766 }
767 }
768
769 ImGui::Separator();
770
771 // Enhanced test results summary with better visuals
772 if (last_results_.total_tests > 0) {
773 // Test summary header
774 ImGui::Text("%s Test Results Summary", ICON_MD_ASSESSMENT);
775
776 // Progress bar showing pass rate
777 float pass_rate = last_results_.GetPassRate();
778 ImVec4 progress_color = pass_rate >= 0.9f ? gui::GetSuccessColor()
779 : pass_rate >= 0.7f ? gui::GetWarningColor()
781
782 {
783 gui::StyleColorGuard hist_guard(ImGuiCol_PlotHistogram, progress_color);
784 ImGui::ProgressBar(
785 pass_rate, ImVec2(-1, 0),
786 absl::StrFormat("Pass Rate: %.1f%%", pass_rate * 100.0f).c_str());
787 }
788
789 // Test counts with icons
790 ImGui::Text("%s Total: %zu", ICON_MD_ANALYTICS, last_results_.total_tests);
791 ImGui::SameLine();
792 ImGui::TextColored(GetTestStatusColor(TestStatus::kPassed), "%s %zu",
794 ImGui::SameLine();
795 ImGui::TextColored(GetTestStatusColor(TestStatus::kFailed), "%s %zu",
797 ImGui::SameLine();
798 ImGui::TextColored(GetTestStatusColor(TestStatus::kSkipped), "%s %zu",
800
801 ImGui::Text("%s Duration: %lld ms", ICON_MD_TIMER,
803
804 // Test suite breakdown
805 if (ImGui::CollapsingHeader("Test Suite Breakdown")) {
806 std::unordered_map<std::string, std::pair<size_t, size_t>>
807 suite_stats; // passed, total
808 for (const auto& result : last_results_.individual_results) {
809 suite_stats[result.suite_name].second++; // total
810 if (result.status == TestStatus::kPassed) {
811 suite_stats[result.suite_name].first++; // passed
812 }
813 }
814
815 for (const auto& [suite_name, stats] : suite_stats) {
816 float suite_pass_rate =
817 stats.second > 0 ? static_cast<float>(stats.first) / stats.second
818 : 0.0f;
819 ImGui::Text("%s: %zu/%zu (%.0f%%)", suite_name.c_str(), stats.first,
820 stats.second, suite_pass_rate * 100.0f);
821 }
822 }
823 }
824
825 ImGui::Separator();
826
827 // Enhanced test filter with category selection
828 ImGui::Text("%s Filter & View Options", ICON_MD_FILTER_LIST);
829
830 // Category filter
831 const char* categories[] = {"All", "Unit", "Integration",
832 "UI", "Performance", "Memory"};
833 static int selected_category = 0;
834 if (ImGui::Combo("Category", &selected_category, categories,
835 IM_ARRAYSIZE(categories))) {
836 switch (selected_category) {
837 case 0:
839 break; // All - use Unit as default
840 case 1:
842 break;
843 case 2:
845 break;
846 case 3:
848 break;
849 case 4:
851 break;
852 case 5:
854 break;
855 }
856 }
857
858 // Text filter
859 static char filter_buffer[256] = "";
860 ImGui::SetNextItemWidth(-80);
861 if (ImGui::InputTextWithHint("##filter", "Search tests...", filter_buffer,
862 sizeof(filter_buffer))) {
863 test_filter_ = std::string(filter_buffer);
864 }
865 ImGui::SameLine();
866 if (ImGui::Button("Clear")) {
867 filter_buffer[0] = '\0';
868 test_filter_.clear();
869 }
870
871 ImGui::Separator();
872
873 // Tabs for different test result views
874 if (ImGui::BeginTabBar("TestResultsTabs", ImGuiTabBarFlags_None)) {
875 // Standard test results tab
876 if (ImGui::BeginTabItem("Test Results")) {
877 if (ImGui::BeginChild("TestResults", ImVec2(0, 0), true)) {
878 if (last_results_.individual_results.empty()) {
879 ImGui::TextColored(gui::GetDisabledColor(),
880 "No test results to display. Run some tests to "
881 "see results here.");
882 } else {
883 for (const auto& result : last_results_.individual_results) {
884 // Apply filters
885 bool category_match = (selected_category == 0) ||
886 (result.category == category_filter_);
887 bool text_match =
888 test_filter_.empty() ||
889 result.name.find(test_filter_) != std::string::npos ||
890 result.suite_name.find(test_filter_) != std::string::npos;
891
892 if (!category_match || !text_match) {
893 continue;
894 }
895
896 ImGui::PushID(&result);
897
898 // Status icon and test name
899 const char* status_icon = ICON_MD_HELP;
900 switch (result.status) {
902 status_icon = ICON_MD_CHECK_CIRCLE;
903 break;
905 status_icon = ICON_MD_ERROR;
906 break;
908 status_icon = ICON_MD_SKIP_NEXT;
909 break;
911 status_icon = ICON_MD_PLAY_CIRCLE_FILLED;
912 break;
913 default:
914 break;
915 }
916
917 ImGui::TextColored(GetTestStatusColor(result.status), "%s %s::%s",
918 status_icon, result.suite_name.c_str(),
919 result.name.c_str());
920
921 // Show duration and timestamp on same line if space allows
922 if (ImGui::GetContentRegionAvail().x > 200) {
923 ImGui::SameLine();
924 ImGui::TextColored(gui::GetDisabledColor(), "(%lld ms)",
925 result.duration.count());
926 }
927
928 // Show detailed information for failed tests
929 if (result.status == TestStatus::kFailed &&
930 !result.error_message.empty()) {
931 ImGui::Indent();
932 {
933 gui::StyleColorGuard err_guard(ImGuiCol_Text,
935 ImGui::TextWrapped("%s %s", ICON_MD_ERROR_OUTLINE,
936 result.error_message.c_str());
937 }
938 ImGui::Unindent();
939 }
940
941 // Show additional info for passed tests if they have messages
942 if (result.status == TestStatus::kPassed &&
943 !result.error_message.empty()) {
944 ImGui::Indent();
945 {
946 gui::StyleColorGuard info_guard(ImGuiCol_Text,
948 ImGui::TextWrapped("%s %s", ICON_MD_INFO,
949 result.error_message.c_str());
950 }
951 ImGui::Unindent();
952 }
953
954 ImGui::PopID();
955 }
956 }
957 }
958 ImGui::EndChild();
959 ImGui::EndTabItem();
960 }
961
962 // Harness Test Results tab (for gRPC GUI automation tests)
963#if defined(YAZE_WITH_GRPC)
964 if (ImGui::BeginTabItem("GUI Automation Tests")) {
965 if (ImGui::BeginChild("HarnessTests", ImVec2(0, 0), true)) {
966 // Display harness test summaries
967 auto summaries = ListHarnessTestSummaries();
968
969 if (summaries.empty()) {
970 ImGui::TextColored(
972 "No GUI automation test results yet.\n\n"
973 "These tests are run via the ImGuiTestHarness gRPC service.\n"
974 "Results will appear here after running GUI automation tests.");
975 } else {
976 ImGui::Text("%s GUI Automation Test History", ICON_MD_HISTORY);
977 ImGui::Text("Total Tests: %zu", summaries.size());
978 ImGui::Separator();
979
980 // Table of harness test results
981 if (ImGui::BeginTable("HarnessTestTable", 6,
982 ImGuiTableFlags_Borders |
983 ImGuiTableFlags_RowBg |
984 ImGuiTableFlags_Resizable)) {
985 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed,
986 80);
987 ImGui::TableSetupColumn("Test Name",
988 ImGuiTableColumnFlags_WidthStretch);
989 ImGui::TableSetupColumn("Category",
990 ImGuiTableColumnFlags_WidthFixed, 100);
991 ImGui::TableSetupColumn("Runs", ImGuiTableColumnFlags_WidthFixed,
992 60);
993 ImGui::TableSetupColumn("Pass Rate",
994 ImGuiTableColumnFlags_WidthFixed, 80);
995 ImGui::TableSetupColumn("Duration",
996 ImGuiTableColumnFlags_WidthFixed, 80);
997 ImGui::TableHeadersRow();
998
999 for (const auto& summary : summaries) {
1000 const auto& exec = summary.latest_execution;
1001
1002 ImGui::TableNextRow();
1003 ImGui::TableNextColumn();
1004
1005 // Status indicator
1006 ImVec4 status_color;
1007 const char* status_icon;
1008 const char* status_text;
1009
1010 switch (exec.status) {
1011 case HarnessTestStatus::kPassed:
1012 status_color = gui::GetSuccessColor();
1013 status_icon = ICON_MD_CHECK_CIRCLE;
1014 status_text = "Passed";
1015 break;
1016 case HarnessTestStatus::kFailed:
1017 status_color = gui::GetErrorColor();
1018 status_icon = ICON_MD_ERROR;
1019 status_text = "Failed";
1020 break;
1021 case HarnessTestStatus::kTimeout:
1022 status_color = gui::GetWarningColor();
1023 status_icon = ICON_MD_TIMER_OFF;
1024 status_text = "Timeout";
1025 break;
1026 case HarnessTestStatus::kRunning:
1027 status_color = gui::GetWarningColor();
1028 status_icon = ICON_MD_PLAY_CIRCLE_FILLED;
1029 status_text = "Running";
1030 break;
1031 case HarnessTestStatus::kQueued:
1032 status_color = gui::GetDisabledColor();
1033 status_icon = ICON_MD_SCHEDULE;
1034 status_text = "Queued";
1035 break;
1036 default:
1037 status_color = gui::GetDisabledColor();
1038 status_icon = ICON_MD_HELP;
1039 status_text = "Unknown";
1040 break;
1041 }
1042
1043 ImGui::TextColored(status_color, "%s %s", status_icon,
1044 status_text);
1045
1046 ImGui::TableNextColumn();
1047 ImGui::Text("%s", exec.name.c_str());
1048
1049 // Show error message if failed
1050 if (exec.status == HarnessTestStatus::kFailed &&
1051 !exec.error_message.empty()) {
1052 ImGui::SameLine();
1053 ImGui::TextColored(gui::GetErrorColor(), "(%s)",
1054 exec.error_message.c_str());
1055 }
1056
1057 ImGui::TableNextColumn();
1058 ImGui::Text("%s", exec.category.c_str());
1059
1060 ImGui::TableNextColumn();
1061 ImGui::Text("%d", summary.total_runs);
1062
1063 ImGui::TableNextColumn();
1064 if (summary.total_runs > 0) {
1065 float pass_rate =
1066 static_cast<float>(summary.pass_count) / summary.total_runs;
1067 ImVec4 rate_color = pass_rate >= 0.9f ? gui::GetSuccessColor()
1068 : pass_rate >= 0.7f ? gui::GetWarningColor()
1070 ImGui::TextColored(rate_color, "%.0f%%", pass_rate * 100.0f);
1071 } else {
1072 ImGui::Text("-");
1073 }
1074
1075 ImGui::TableNextColumn();
1076 double duration_ms =
1077 absl::ToDoubleMilliseconds(summary.total_duration);
1078 if (summary.total_runs > 0) {
1079 ImGui::Text("%.0f ms", duration_ms / summary.total_runs);
1080 } else {
1081 ImGui::Text("-");
1082 }
1083
1084 // Expandable details
1085 if (ImGui::TreeNode(("Details##" + exec.test_id).c_str())) {
1086 ImGui::Text("Test ID: %s", exec.test_id.c_str());
1087 ImGui::Text("Total Runs: %d (Pass: %d, Fail: %d)",
1088 summary.total_runs, summary.pass_count,
1089 summary.fail_count);
1090
1091 if (!exec.logs.empty()) {
1092 ImGui::Separator();
1093 ImGui::Text("%s Logs:", ICON_MD_DESCRIPTION);
1094 for (const auto& log : exec.logs) {
1095 ImGui::BulletText("%s", log.c_str());
1096 }
1097 }
1098
1099 if (!exec.assertion_failures.empty()) {
1100 ImGui::Separator();
1101 ImGui::TextColored(gui::GetErrorColor(),
1102 "%s Assertion Failures:", ICON_MD_ERROR);
1103 for (const auto& failure : exec.assertion_failures) {
1104 ImGui::BulletText("%s", failure.c_str());
1105 }
1106 }
1107
1108 if (!exec.screenshot_path.empty()) {
1109 ImGui::Separator();
1110 ImGui::Text("%s Screenshot: %s", ICON_MD_CAMERA_ALT,
1111 exec.screenshot_path.c_str());
1112 ImGui::Text("Size: %.2f KB",
1113 exec.screenshot_size_bytes / 1024.0);
1114 }
1115
1116 ImGui::TreePop();
1117 }
1118 }
1119
1120 ImGui::EndTable();
1121 }
1122 }
1123 }
1124 ImGui::EndChild();
1125 ImGui::EndTabItem();
1126 }
1127#endif // defined(YAZE_WITH_GRPC)
1128
1129 ImGui::EndTabBar();
1130 }
1131
1132 ImGui::End();
1133
1134 // Resource monitor window
1136 ImGui::Begin(absl::StrCat(ICON_MD_MONITOR, " Resource Monitor").c_str(),
1138
1139 if (!resource_history_.empty()) {
1140 const auto& latest = resource_history_.back();
1141 ImGui::Text("%s Textures: %zu", ICON_MD_TEXTURE, latest.texture_count);
1142 ImGui::Text("%s Surfaces: %zu", ICON_MD_LAYERS, latest.surface_count);
1143 ImGui::Text("%s Memory: %zu MB", ICON_MD_MEMORY, latest.memory_usage_mb);
1144 ImGui::Text("%s FPS: %.1f", ICON_MD_SPEED, latest.frame_rate);
1145
1146 // Simple plot of resource usage over time
1147 if (resource_history_.size() > 1) {
1148 std::vector<float> texture_counts;
1149 std::vector<float> surface_counts;
1150 texture_counts.reserve(resource_history_.size());
1151 surface_counts.reserve(resource_history_.size());
1152
1153 for (const auto& stats : resource_history_) {
1154 texture_counts.push_back(static_cast<float>(stats.texture_count));
1155 surface_counts.push_back(static_cast<float>(stats.surface_count));
1156 }
1157
1158 ImGui::PlotLines("Textures", texture_counts.data(),
1159 static_cast<int>(texture_counts.size()), 0, nullptr,
1160 0.0f, FLT_MAX, ImVec2(0, 80));
1161 ImGui::PlotLines("Surfaces", surface_counts.data(),
1162 static_cast<int>(surface_counts.size()), 0, nullptr,
1163 0.0f, FLT_MAX, ImVec2(0, 80));
1164 }
1165 }
1166
1167 ImGui::End();
1168 }
1169
1170 // Google Tests window
1171 if (show_google_tests_) {
1172 ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver);
1173 if (ImGui::Begin("Google Tests", &show_google_tests_)) {
1174 ImGui::Text("%s Google Test Integration", ICON_MD_SCIENCE);
1175 ImGui::Separator();
1176
1177#ifdef YAZE_ENABLE_GTEST
1178 ImGui::Text("Google Test framework is available");
1179
1180 if (ImGui::Button("Run All Google Tests")) {
1181 // Run Google tests - this would integrate with gtest
1182 LOG_INFO("TestManager", "Running Google Tests...");
1183 }
1184
1185 ImGui::SameLine();
1186 if (ImGui::Button("Run Specific Test Suite")) {
1187 // Show test suite selector
1188 }
1189
1190 ImGui::Separator();
1191 ImGui::Text("Available Test Suites:");
1192 ImGui::BulletText("Unit Tests");
1193 ImGui::BulletText("Integration Tests");
1194 ImGui::BulletText("Performance Tests");
1195#else
1196 ImGui::TextColored(gui::GetWarningColor(),
1197 "%s Google Test framework not available",
1199 ImGui::Text("Enable YAZE_ENABLE_GTEST to use Google Test integration");
1200#endif
1201 }
1202 ImGui::End();
1203 }
1204
1205 // ROM Test Results window
1207 ImGui::SetNextWindowSize(ImVec2(700, 500), ImGuiCond_FirstUseEver);
1208 if (ImGui::Begin("ROM Test Results", &show_rom_test_results_)) {
1209 ImGui::Text("%s ROM Analysis Results", ICON_MD_ANALYTICS);
1210
1212 ImGui::Text("Testing ROM: %s", current_rom_->title().c_str());
1213 ImGui::Separator();
1214
1215 // Show ROM-specific test results
1216 if (ImGui::CollapsingHeader("ROM Data Integrity",
1217 ImGuiTreeNodeFlags_DefaultOpen)) {
1218 ImGui::Text("ROM Size: %.2f MB", current_rom_->size() / 1048576.0f);
1219 ImGui::Text("Modified: %s", current_rom_->dirty() ? "Yes" : "No");
1220
1221 if (ImGui::Button("Run Data Integrity Check")) {
1222 [[maybe_unused]] auto status = TestRomDataIntegrity(current_rom_);
1223 [[maybe_unused]] auto suite_status =
1225 }
1226 }
1227
1228 if (ImGui::CollapsingHeader("Save/Load Testing")) {
1229 ImGui::Text("Test ROM save and load operations");
1230
1231 if (ImGui::Button("Test Save Operations")) {
1232 [[maybe_unused]] auto status = TestRomSaveLoad(current_rom_);
1233 }
1234
1235 ImGui::SameLine();
1236 if (ImGui::Button("Test Load Operations")) {
1237 [[maybe_unused]] auto status = TestRomSaveLoad(current_rom_);
1238 }
1239 }
1240
1241 if (ImGui::CollapsingHeader("Editor Integration")) {
1242 ImGui::Text("Test editor components with current ROM");
1243
1244 if (ImGui::Button("Test Overworld Editor")) {
1245 // Test overworld editor with current ROM
1246 }
1247
1248 ImGui::SameLine();
1249 if (ImGui::Button("Test Tile16 Editor")) {
1250 // Test tile16 editor with current ROM
1251 }
1252 }
1253
1254 } else {
1255 ImGui::TextColored(gui::GetWarningColor(),
1256 "%s No ROM loaded for analysis", ICON_MD_WARNING);
1257 }
1258 }
1259 ImGui::End();
1260 }
1261
1262 // ROM File Dialog
1264 ImGui::SetNextWindowSize(ImVec2(400, 200), ImGuiCond_Appearing);
1265 if (ImGui::Begin("Load ROM for Testing", &show_rom_file_dialog_,
1266 ImGuiWindowFlags_NoResize)) {
1267 ImGui::Text("%s Load ROM for Testing", ICON_MD_FOLDER_OPEN);
1268 ImGui::Separator();
1269
1270 ImGui::Text("Select a ROM file to run tests on:");
1271
1272 if (ImGui::Button("Browse ROM File...", ImVec2(-1, 0))) {
1273 // TODO: Implement file dialog to load ROM specifically for testing
1274 // This would be separate from the main editor ROM
1275 show_rom_file_dialog_ = false;
1276 }
1277
1278 ImGui::Separator();
1279 if (ImGui::Button("Cancel", ImVec2(-1, 0))) {
1280 show_rom_file_dialog_ = false;
1281 }
1282 }
1283 ImGui::End();
1284 }
1285
1286 // Test Configuration Window
1288 ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
1289 if (ImGui::Begin("Test Configuration", &show_test_configuration_)) {
1290 ImGui::Text("%s Test Configuration", ICON_MD_SETTINGS);
1291 ImGui::Separator();
1292
1293 // File Dialog Configuration
1294 if (ImGui::CollapsingHeader("File Dialog Settings",
1295 ImGuiTreeNodeFlags_DefaultOpen)) {
1296 ImGui::Text("File Dialog Implementation:");
1297
1299 if (ImGui::RadioButton("NFD (Native File Dialog)", nfd_mode)) {
1301 LOG_INFO("TestManager", "Global file dialog mode set to: NFD");
1302 }
1303 if (ImGui::IsItemHovered()) {
1304 ImGui::SetTooltip(
1305 "Use NFD library for native OS file dialogs (global setting)");
1306 }
1307
1308 if (ImGui::RadioButton("Bespoke Implementation", !nfd_mode)) {
1310 LOG_INFO("TestManager", "Global file dialog mode set to: Bespoke");
1311 }
1312 if (ImGui::IsItemHovered()) {
1313 ImGui::SetTooltip(
1314 "Use custom file dialog implementation (global setting)");
1315 }
1316
1317 ImGui::Separator();
1318 ImGui::Text(
1319 "Current Mode: %s",
1320 core::FeatureFlags::get().kUseNativeFileDialog ? "NFD" : "Bespoke");
1321 ImGui::TextColored(
1323 "Note: This setting affects ALL file dialogs in the application");
1324
1325 if (ImGui::Button("Test Current File Dialog")) {
1326 // Test the current file dialog implementation
1327 LOG_INFO("TestManager", "Testing global file dialog mode: %s",
1328 core::FeatureFlags::get().kUseNativeFileDialog ? "NFD"
1329 : "Bespoke");
1330
1331 // Actually test the file dialog
1333 if (!result.empty()) {
1334 LOG_INFO("TestManager", "File dialog test successful: %s",
1335 result.c_str());
1336 } else {
1337 LOG_INFO("TestManager",
1338 "File dialog test: No file selected or dialog canceled");
1339 }
1340 }
1341
1342 ImGui::SameLine();
1343 if (ImGui::Button("Test NFD Directly")) {
1345 if (!result.empty()) {
1346 LOG_INFO("TestManager", "NFD test successful: %s", result.c_str());
1347 } else {
1348 LOG_INFO("TestManager",
1349 "NFD test: No file selected, canceled, or error occurred");
1350 }
1351 }
1352
1353 ImGui::SameLine();
1354 if (ImGui::Button("Test Bespoke Directly")) {
1356 if (!result.empty()) {
1357 LOG_INFO("TestManager", "Bespoke test successful: %s",
1358 result.c_str());
1359 } else {
1360 LOG_INFO("TestManager",
1361 "Bespoke test: No file selected or not implemented");
1362 }
1363 }
1364 }
1365
1366 // Test Selection Configuration
1367 if (ImGui::CollapsingHeader("Test Selection",
1368 ImGuiTreeNodeFlags_DefaultOpen)) {
1369 ImGui::Text("Enable/Disable Individual Tests:");
1370 ImGui::Separator();
1371
1372 // List of known tests with their risk levels
1373 static const std::vector<std::pair<std::string, std::string>>
1374 known_tests = {
1375 {"ROM_Header_Validation_Test",
1376 "Safe - Read-only ROM header validation"},
1377 {"ROM_Data_Access_Test",
1378 "Safe - Basic ROM data access testing"},
1379 {"ROM_Graphics_Extraction_Test",
1380 "Safe - Graphics data extraction testing"},
1381 {"ROM_Overworld_Loading_Test",
1382 "Safe - Overworld data loading testing"},
1383 {"Tile16_Editor_Test",
1384 "Moderate - Tile16 editor initialization"},
1385 {"Comprehensive_Save_Test",
1386 "DANGEROUS - Known to crash, uses ROM copies"},
1387 {"ROM_Sprite_Data_Test", "Safe - Sprite data validation"},
1388 {"ROM_Music_Data_Test", "Safe - Music data validation"}};
1389
1390 // Initialize problematic tests as disabled by default
1391 static bool initialized_defaults = false;
1392 if (!initialized_defaults) {
1393 DisableTest("Comprehensive_Save_Test"); // Disable crash-prone test
1394 // by default
1395 initialized_defaults = true;
1396 }
1397
1398 if (ImGui::BeginTable(
1399 "TestSelection", 4,
1400 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
1401 ImGui::TableSetupColumn("Test Name", ImGuiTableColumnFlags_WidthFixed,
1402 200);
1403 ImGui::TableSetupColumn("Risk Level",
1404 ImGuiTableColumnFlags_WidthStretch);
1405 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed,
1406 80);
1407 ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthFixed,
1408 100);
1409 ImGui::TableHeadersRow();
1410
1411 for (const auto& [test_name, description] : known_tests) {
1412 bool enabled = IsTestEnabled(test_name);
1413
1414 ImGui::TableNextRow();
1415 ImGui::TableNextColumn();
1416 ImGui::Text("%s", test_name.c_str());
1417
1418 ImGui::TableNextColumn();
1419 // Color-code the risk level
1420 if (description.find("DANGEROUS") != std::string::npos) {
1421 ImGui::TextColored(gui::GetErrorColor(), "%s",
1422 description.c_str());
1423 } else if (description.find("Moderate") != std::string::npos) {
1424 ImGui::TextColored(gui::GetWarningColor(), "%s",
1425 description.c_str());
1426 } else {
1427 ImGui::TextColored(gui::GetSuccessColor(), "%s",
1428 description.c_str());
1429 }
1430
1431 ImGui::TableNextColumn();
1432 if (enabled) {
1433 ImGui::TextColored(gui::GetSuccessColor(), "%s ON",
1435 } else {
1436 ImGui::TextColored(gui::GetWarningColor(), "%s OFF",
1438 }
1439
1440 ImGui::TableNextColumn();
1441 ImGui::PushID(test_name.c_str());
1442 if (enabled) {
1443 if (ImGui::Button("Disable")) {
1444 DisableTest(test_name);
1445 LOG_INFO("TestManager", "Disabled test: %s", test_name.c_str());
1446 }
1447 } else {
1448 if (ImGui::Button("Enable")) {
1449 EnableTest(test_name);
1450 LOG_INFO("TestManager", "Enabled test: %s", test_name.c_str());
1451 }
1452 }
1453 ImGui::PopID();
1454 }
1455
1456 ImGui::EndTable();
1457 }
1458
1459 ImGui::Separator();
1460 ImGui::Text("Quick Actions:");
1461
1462 if (ImGui::Button("Enable Safe Tests Only")) {
1463 for (const auto& [test_name, description] : known_tests) {
1464 if (description.find("Safe") != std::string::npos) {
1465 EnableTest(test_name);
1466 } else {
1467 DisableTest(test_name);
1468 }
1469 }
1470 LOG_INFO("TestManager", "Enabled only safe tests");
1471 }
1472 ImGui::SameLine();
1473
1474 if (ImGui::Button("Enable All Tests")) {
1475 for (const auto& [test_name, description] : known_tests) {
1476 EnableTest(test_name);
1477 }
1478 LOG_INFO("TestManager",
1479 "Enabled all tests (including dangerous ones)");
1480 }
1481 ImGui::SameLine();
1482
1483 if (ImGui::Button("Disable All Tests")) {
1484 for (const auto& [test_name, description] : known_tests) {
1485 DisableTest(test_name);
1486 }
1487 LOG_INFO("TestManager", "Disabled all tests");
1488 }
1489
1490 ImGui::Separator();
1491 ImGui::TextColored(
1493 " Recommendation: Use 'Enable Safe Tests Only' to avoid crashes");
1494 }
1495
1496 // Platform-specific settings
1497 if (ImGui::CollapsingHeader("Platform Settings")) {
1498 ImGui::Text("macOS Tahoe Compatibility:");
1499 ImGui::BulletText("NFD may have issues on macOS Sequoia+");
1500 ImGui::BulletText("Bespoke dialog provides fallback option");
1501 ImGui::BulletText(
1502 "Global setting affects File → Open, Project dialogs, etc.");
1503
1504 ImGui::Separator();
1505 ImGui::Text("Test Both Implementations:");
1506
1507 if (ImGui::Button("Quick Test NFD")) {
1509 LOG_INFO("TestManager", "NFD test result: %s",
1510 result.empty() ? "Failed/Canceled" : result.c_str());
1511 }
1512 ImGui::SameLine();
1513 if (ImGui::Button("Quick Test Bespoke")) {
1515 LOG_INFO("TestManager", "Bespoke test result: %s",
1516 result.empty() ? "Failed/Not Implemented" : result.c_str());
1517 }
1518
1519 ImGui::Separator();
1520 ImGui::TextColored(gui::GetDisabledColor(),
1521 "Note: These tests don't change the global setting");
1522 }
1523 }
1524 ImGui::End();
1525 }
1526
1527 // Test Session Creation Dialog
1529 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1530 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1531 ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_Appearing);
1532
1533 if (ImGui::Begin("Test ROM Session", &show_test_session_dialog_,
1534 ImGuiWindowFlags_NoResize)) {
1535 ImGui::Text("%s Test ROM Created Successfully", ICON_MD_CHECK_CIRCLE);
1536 ImGui::Separator();
1537
1538 ImGui::Text("A test ROM has been created with your modifications:");
1539 ImGui::Text("File: %s", test_rom_path_for_session_.c_str());
1540
1541 // Extract just the filename for display
1542 std::string display_filename = test_rom_path_for_session_;
1543 auto last_slash = display_filename.find_last_of("/\\");
1544 if (last_slash != std::string::npos) {
1545 display_filename = display_filename.substr(last_slash + 1);
1546 }
1547
1548 ImGui::Separator();
1549 ImGui::Text("Would you like to open this test ROM in a new session?");
1550
1551 if (ImGui::Button(
1552 absl::StrFormat("%s Open in New Session", ICON_MD_TAB).c_str(),
1553 ImVec2(200, 0))) {
1554 // TODO: This would need access to EditorManager to create a new session
1555 // For now, just show a message
1556 LOG_INFO("TestManager",
1557 "User requested to open test ROM in new session: %s",
1560 }
1561
1562 ImGui::SameLine();
1563 if (ImGui::Button(
1564 absl::StrFormat("%s Keep Current Session", ICON_MD_CLOSE).c_str(),
1565 ImVec2(200, 0))) {
1567 }
1568
1569 ImGui::Separator();
1570 ImGui::TextColored(
1572 "Note: Test ROM contains your modifications and can be");
1573 ImGui::TextColored(gui::GetDisabledColor(),
1574 "opened later using File → Open");
1575 }
1576 ImGui::End();
1577 }
1578}
1579
1581 LOG_INFO("TestManager", "=== TestManager ROM Refresh ===");
1582
1583 // Log current TestManager ROM state for debugging
1584 if (current_rom_) {
1585 LOG_INFO("TestManager", "TestManager ROM pointer: %p", (void*)current_rom_);
1586 LOG_INFO("TestManager", "ROM is_loaded(): %s",
1587 current_rom_->is_loaded() ? "true" : "false");
1588 if (current_rom_->is_loaded()) {
1589 LOG_INFO("TestManager", "ROM title: '%s'", current_rom_->title().c_str());
1590 LOG_INFO("TestManager", "ROM size: %.2f MB",
1591 current_rom_->size() / 1048576.0f);
1592 LOG_INFO("TestManager", "ROM dirty: %s",
1593 current_rom_->dirty() ? "true" : "false");
1594 }
1595 } else {
1596 LOG_INFO("TestManager", "TestManager ROM pointer is null");
1597 LOG_INFO("TestManager",
1598 "Note: ROM should be set by EditorManager when ROM is loaded");
1599 }
1600 LOG_INFO("TestManager", "===============================");
1601}
1602
1603absl::Status TestManager::CreateTestRomCopy(Rom* source_rom,
1604 std::unique_ptr<Rom>& test_rom) {
1605 if (!source_rom || !source_rom->is_loaded()) {
1606 return absl::FailedPreconditionError("Source ROM not loaded");
1607 }
1608
1609 LOG_INFO("TestManager", "Creating test ROM copy from: %s",
1610 source_rom->title().c_str());
1611
1612 // Create a new ROM instance
1613 test_rom = std::make_unique<Rom>();
1614
1615 // Copy the ROM data
1616 auto rom_data = source_rom->vector();
1617 auto load_status = test_rom->LoadFromData(rom_data);
1618 if (!load_status.ok()) {
1619 return load_status;
1620 }
1621
1622 LOG_INFO("TestManager", "Test ROM copy created successfully (size: %.2f MB)",
1623 test_rom->size() / 1048576.0f);
1624 return absl::OkStatus();
1625}
1626
1627std::string TestManager::GenerateTestRomFilename(const std::string& base_name) {
1628 // Generate filename with timestamp
1629 auto now = std::chrono::system_clock::now();
1630 auto time_t = std::chrono::system_clock::to_time_t(now);
1631 auto local_time = *std::localtime(&time_t);
1632
1633 std::string timestamp =
1634 absl::StrFormat("%04d%02d%02d_%02d%02d%02d", local_time.tm_year + 1900,
1635 local_time.tm_mon + 1, local_time.tm_mday,
1636 local_time.tm_hour, local_time.tm_min, local_time.tm_sec);
1637
1638 std::string base_filename = base_name;
1639 // Remove any path and extension
1640 auto last_slash = base_filename.find_last_of("/\\");
1641 if (last_slash != std::string::npos) {
1642 base_filename = base_filename.substr(last_slash + 1);
1643 }
1644 auto last_dot = base_filename.find_last_of('.');
1645 if (last_dot != std::string::npos) {
1646 base_filename = base_filename.substr(0, last_dot);
1647 }
1648
1649 return absl::StrFormat("%s_test_%s.sfc", base_filename.c_str(),
1650 timestamp.c_str());
1651}
1652
1653void TestManager::OfferTestSessionCreation(const std::string& test_rom_path) {
1654 // Store the test ROM path for the dialog
1655 test_rom_path_for_session_ = test_rom_path;
1657}
1658
1660 Rom* source_rom, std::function<absl::Status(Rom*)> test_function) {
1661 if (!source_rom || !source_rom->is_loaded()) {
1662 return absl::FailedPreconditionError("Source ROM not loaded");
1663 }
1664
1665 // Create a copy of the ROM for testing
1666 std::unique_ptr<Rom> test_rom;
1667 RETURN_IF_ERROR(CreateTestRomCopy(source_rom, test_rom));
1668
1669 LOG_INFO("TestManager", "Executing test function on ROM copy");
1670
1671 // Run the test function on the copy
1672 auto test_result = test_function(test_rom.get());
1673
1674 LOG_INFO("TestManager", "Test function completed with status: %s",
1675 test_result.ToString().c_str());
1676
1677 return test_result;
1678}
1679
1680absl::Status TestManager::LoadRomForTesting(const std::string& filename) {
1681 // This would load a ROM specifically for testing purposes
1682 // For now, just log the request
1683 LOG_INFO("TestManager", "Request to load ROM for testing: %s",
1684 filename.c_str());
1685 return absl::UnimplementedError(
1686 "ROM loading for testing not yet implemented");
1687}
1688
1690 const Rom& after) {
1691 if (ImGui::Begin("ROM Comparison Results")) {
1692 ImGui::Text("%s ROM Before/After Comparison", ICON_MD_COMPARE);
1693 ImGui::Separator();
1694
1695 if (ImGui::BeginTable("RomComparison", 3, ImGuiTableFlags_Borders)) {
1696 ImGui::TableSetupColumn("Property");
1697 ImGui::TableSetupColumn("Before");
1698 ImGui::TableSetupColumn("After");
1699 ImGui::TableHeadersRow();
1700
1701 ImGui::TableNextRow();
1702 ImGui::TableNextColumn();
1703 ImGui::Text("Size");
1704 ImGui::TableNextColumn();
1705 ImGui::Text("%.2f MB", before.size() / 1048576.0f);
1706 ImGui::TableNextColumn();
1707 ImGui::Text("%.2f MB", after.size() / 1048576.0f);
1708
1709 ImGui::TableNextRow();
1710 ImGui::TableNextColumn();
1711 ImGui::Text("Modified");
1712 ImGui::TableNextColumn();
1713 ImGui::Text("%s", before.dirty() ? "Yes" : "No");
1714 ImGui::TableNextColumn();
1715 ImGui::Text("%s", after.dirty() ? "Yes" : "No");
1716
1717 ImGui::EndTable();
1718 }
1719 }
1720 ImGui::End();
1721}
1722
1724 if (!rom || !rom->is_loaded()) {
1725 return absl::FailedPreconditionError("No ROM loaded for testing");
1726 }
1727
1728 // Use TestRomWithCopy to avoid affecting the original ROM
1729 return TestRomWithCopy(rom, [this](Rom* test_rom) -> absl::Status {
1730 LOG_INFO("TestManager", "Testing ROM save/load operations on copy: %s",
1731 test_rom->title().c_str());
1732
1733 // Perform test modifications on the copy
1734 // Test save operations
1735 Rom::SaveSettings settings;
1736 settings.backup = false;
1737 settings.save_new = true;
1738 settings.filename = GenerateTestRomFilename(test_rom->title());
1739
1740 auto save_status = test_rom->SaveToFile(settings);
1741 if (!save_status.ok()) {
1742 return save_status;
1743 }
1744
1745 LOG_INFO("TestManager", "Test ROM saved successfully to: %s",
1746 settings.filename.c_str());
1747
1748 // Offer to open test ROM in new session
1750
1751 return absl::OkStatus();
1752 });
1753}
1754
1756 if (!rom || !rom->is_loaded()) {
1757 return absl::FailedPreconditionError("No ROM loaded for testing");
1758 }
1759
1760 // Use TestRomWithCopy for integrity testing (read-only but uses copy for
1761 // safety)
1762 return TestRomWithCopy(rom, [](Rom* test_rom) -> absl::Status {
1763 LOG_INFO("TestManager", "Testing ROM data integrity on copy: %s",
1764 test_rom->title().c_str());
1765
1766 // Perform data integrity checks on the copy
1767 // This validates ROM structure, checksums, etc. without affecting original
1768
1769 // Basic ROM structure validation
1770 if (test_rom->size() < 0x100000) { // 1MB minimum for ALTTP
1771 return absl::FailedPreconditionError(
1772 "ROM file too small for A Link to the Past");
1773 }
1774
1775 // Check ROM header
1776 auto header_status = test_rom->ReadByteVector(0x7FC0, 32);
1777 if (!header_status.ok()) {
1778 return header_status.status();
1779 }
1780
1781 LOG_INFO("TestManager", "ROM integrity check passed for: %s",
1782 test_rom->title().c_str());
1783 return absl::OkStatus();
1784 });
1785}
1786
1787#if defined(YAZE_WITH_GRPC)
1788std::string TestManager::RegisterHarnessTest(const std::string& name,
1789 const std::string& category) {
1790 absl::MutexLock lock(&harness_history_mutex_);
1791 std::string test_id =
1792 absl::StrCat("harness_", absl::ToUnixMicros(absl::Now()), "_",
1793 harness_history_.size());
1794 HarnessTestExecution execution;
1795 execution.test_id = test_id;
1796 execution.name = name;
1797 execution.category = category;
1798 execution.status = HarnessTestStatus::kQueued;
1799 execution.queued_at = absl::Now();
1800 execution.logs.reserve(32);
1801 harness_history_[test_id] = execution;
1802 TrimHarnessHistoryLocked();
1803 HarnessAggregate& aggregate = harness_aggregates_[name];
1804 aggregate.category = category;
1805 aggregate.latest_execution = execution;
1806 harness_history_order_.push_back(test_id);
1807 return test_id;
1808}
1809#else
1810std::string TestManager::RegisterHarnessTest(const std::string& name,
1811 const std::string& category) {
1812 (void)name;
1813 (void)category;
1814 return {};
1815}
1816#endif
1817
1818#if defined(YAZE_WITH_GRPC)
1819void TestManager::MarkHarnessTestRunning(const std::string& test_id) {
1820 absl::MutexLock lock(&harness_history_mutex_);
1821 auto it = harness_history_.find(test_id);
1822 if (it == harness_history_.end()) {
1823 return;
1824 }
1825 HarnessTestExecution& execution = it->second;
1826 execution.status = HarnessTestStatus::kRunning;
1827 execution.started_at = absl::Now();
1828}
1829#endif
1830
1831#if defined(YAZE_WITH_GRPC)
1832void TestManager::MarkHarnessTestCompleted(
1833 const std::string& test_id, HarnessTestStatus status,
1834 const std::string& message,
1835 const std::vector<std::string>& assertion_failures,
1836 const std::vector<std::string>& logs,
1837 const std::map<std::string, int32_t>& metrics) {
1838 HarnessTestExecution execution_snapshot;
1839 {
1840 absl::MutexLock lock(&harness_history_mutex_);
1841 auto it = harness_history_.find(test_id);
1842 if (it == harness_history_.end()) {
1843 return;
1844 }
1845 HarnessTestExecution& execution = it->second;
1846 execution.status = status;
1847 execution.completed_at = absl::Now();
1848 execution.duration = execution.completed_at - execution.started_at;
1849 execution.error_message = message;
1850 execution.metrics = metrics;
1851 execution.assertion_failures = assertion_failures;
1852 execution.logs.insert(execution.logs.end(), logs.begin(), logs.end());
1853
1854 HarnessAggregate& aggregate = harness_aggregates_[execution.name];
1855 aggregate.latest_execution = execution;
1856 aggregate.total_runs += 1;
1857 if (status == HarnessTestStatus::kPassed) {
1858 aggregate.pass_count += 1;
1859 } else if (status == HarnessTestStatus::kFailed ||
1860 status == HarnessTestStatus::kTimeout) {
1861 aggregate.fail_count += 1;
1862 }
1863 aggregate.total_duration += execution.duration;
1864 aggregate.last_run = execution.completed_at;
1865 execution_snapshot = execution;
1866 }
1867
1868 NotifyHarnessListener(execution_snapshot);
1869 if (status == HarnessTestStatus::kFailed ||
1870 status == HarnessTestStatus::kTimeout) {
1871 CaptureFailureContext(test_id);
1872 }
1873}
1874#endif
1875
1876#if defined(YAZE_WITH_GRPC)
1877void TestManager::AppendHarnessTestLog(const std::string& test_id,
1878 const std::string& log_entry) {
1879 absl::MutexLock lock(&harness_history_mutex_);
1880 auto it = harness_history_.find(test_id);
1881 if (it == harness_history_.end()) {
1882 return;
1883 }
1884 HarnessTestExecution& execution = it->second;
1885 execution.logs.push_back(log_entry);
1886 harness_aggregates_[execution.name].latest_execution.logs = execution.logs;
1887}
1888#endif
1889
1890#if defined(YAZE_WITH_GRPC)
1891absl::StatusOr<HarnessTestExecution> TestManager::GetHarnessTestExecution(
1892 const std::string& test_id) const {
1893 absl::MutexLock lock(&harness_history_mutex_);
1894 auto it = harness_history_.find(test_id);
1895 if (it == harness_history_.end()) {
1896 return absl::NotFoundError(
1897 absl::StrFormat("Test ID '%s' not found", test_id));
1898 }
1899 return it->second;
1900}
1901#endif
1902
1903#if defined(YAZE_WITH_GRPC)
1904std::vector<HarnessTestSummary> TestManager::ListHarnessTestSummaries(
1905 const std::string& category_filter) const {
1906 absl::MutexLock lock(&harness_history_mutex_);
1907 std::vector<HarnessTestSummary> summaries;
1908 summaries.reserve(harness_aggregates_.size());
1909 for (const auto& [name, aggregate] : harness_aggregates_) {
1910 if (!category_filter.empty() && aggregate.category != category_filter) {
1911 continue;
1912 }
1913 HarnessTestSummary summary;
1914 summary.latest_execution = aggregate.latest_execution;
1915 summary.total_runs = aggregate.total_runs;
1916 summary.pass_count = aggregate.pass_count;
1917 summary.fail_count = aggregate.fail_count;
1918 summary.total_duration = aggregate.total_duration;
1919 summaries.push_back(summary);
1920 }
1921 std::sort(summaries.begin(), summaries.end(),
1922 [](const HarnessTestSummary& a, const HarnessTestSummary& b) {
1923 absl::Time time_a = a.latest_execution.completed_at;
1924 if (time_a == absl::InfinitePast()) {
1925 time_a = a.latest_execution.queued_at;
1926 }
1927 absl::Time time_b = b.latest_execution.completed_at;
1928 if (time_b == absl::InfinitePast()) {
1929 time_b = b.latest_execution.queued_at;
1930 }
1931 return time_a > time_b;
1932 });
1933 return summaries;
1934}
1935#endif
1936
1937#if defined(YAZE_WITH_GRPC)
1938void TestManager::CaptureFailureContext(const std::string& test_id) {
1939 HarnessTestStatus expected_status = HarnessTestStatus::kUnspecified;
1940 absl::Time expected_completed_at = absl::InfinitePast();
1941 {
1942 absl::MutexLock lock(&harness_history_mutex_);
1943 auto it = harness_history_.find(test_id);
1944 if (it == harness_history_.end()) {
1945 return;
1946 }
1947 expected_status = it->second.status;
1948 expected_completed_at = it->second.completed_at;
1949 }
1950
1951 auto apply_capture =
1952 [this, test_id, expected_status, expected_completed_at](
1953 absl::StatusOr<ScreenshotArtifact> screenshot_artifact) {
1954 absl::MutexLock lock(&harness_history_mutex_);
1955 auto it = harness_history_.find(test_id);
1956 if (it == harness_history_.end()) {
1957 return;
1958 }
1959 HarnessTestExecution& execution = it->second;
1960 if (execution.status != expected_status ||
1961 execution.completed_at != expected_completed_at) {
1962 return;
1963 }
1964 execution.failure_context =
1965 screenshot_artifact.ok()
1966 ? "Harness failure context captured successfully"
1967 : "Harness failure context capture unavailable";
1968 if (screenshot_artifact.ok()) {
1969 execution.screenshot_path = screenshot_artifact->file_path;
1970 execution.screenshot_size_bytes =
1971 screenshot_artifact->file_size_bytes;
1972 }
1973 HarnessAggregate& aggregate = harness_aggregates_[execution.name];
1974 if (aggregate.latest_execution.test_id == test_id) {
1975 harness_aggregates_[execution.name].latest_execution = execution;
1976 }
1977 };
1978
1979 FailureScreenshotRequester screenshot_requester;
1980 {
1981 absl::MutexLock lock(&mutex_);
1982 screenshot_requester = failure_screenshot_requester_;
1983 }
1984 if (!screenshot_requester) {
1985 apply_capture(absl::FailedPreconditionError("Controller unavailable"));
1986 return;
1987 }
1988
1989 screenshot_requester(GenerateFailureScreenshotPath(test_id),
1990 std::move(apply_capture));
1991}
1992#else
1993void TestManager::CaptureFailureContext(const std::string& test_id) {
1994 (void)test_id;
1995}
1996#endif
1997
1998#if defined(YAZE_WITH_GRPC)
1999void TestManager::TrimHarnessHistoryLocked() {
2000 while (harness_history_order_.size() > harness_history_limit_) {
2001 const std::string& oldest_test = harness_history_order_.front();
2002 harness_history_order_.pop_front();
2003 harness_history_.erase(oldest_test);
2004 }
2005}
2006
2007absl::Status TestManager::ReplayLastPlan() {
2008 return absl::FailedPreconditionError("Harness plan replay not available");
2009}
2010
2011void TestManager::SetHarnessListener(
2012 std::shared_ptr<HarnessListener> listener) {
2013 absl::MutexLock lock(&mutex_);
2014 harness_listener_ = std::move(listener);
2015}
2016
2017void TestManager::ClearHarnessListener(const HarnessListener* listener) {
2018 absl::MutexLock lock(&mutex_);
2019 if (harness_listener_.get() == listener) {
2020 harness_listener_.reset();
2021 }
2022}
2023
2024void TestManager::SetFailureScreenshotRequester(
2025 FailureScreenshotRequester requester) {
2026 absl::MutexLock lock(&mutex_);
2027 failure_screenshot_requester_ = std::move(requester);
2028}
2029
2030void TestManager::NotifyHarnessListener(const HarnessTestExecution& execution) {
2031 std::shared_ptr<HarnessListener> listener;
2032 {
2033 absl::MutexLock lock(&mutex_);
2034 listener = harness_listener_;
2035 }
2036 if (listener) {
2037 listener->OnHarnessTestUpdated(execution);
2038 }
2039}
2040#else
2041absl::Status TestManager::ReplayLastPlan() {
2042 return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
2043}
2044#endif
2045
2046absl::Status TestManager::ShowHarnessDashboard() {
2047 // These methods are always available, but may return unimplemented without
2048 // GRPC
2049#if defined(YAZE_WITH_GRPC)
2050 return absl::OkStatus();
2051#else
2052 return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
2053#endif
2054}
2055
2056absl::Status TestManager::ShowHarnessActiveTests() {
2057#if defined(YAZE_WITH_GRPC)
2058 return absl::OkStatus();
2059#else
2060 return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
2061#endif
2062}
2063
2064void TestManager::RecordPlanSummary(const std::string& summary) {
2065 (void)summary;
2066}
2067
2068} // namespace test
2069} // 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:52
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
Definition rom.cc:1059
auto filename() const
Definition rom.h:219
const auto & vector() const
Definition rom.h:217
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:893
auto data() const
Definition rom.h:213
auto size() const
Definition rom.h:212
bool dirty() const
Definition rom.h:200
bool is_loaded() const
Definition rom.h:199
auto title() const
Definition rom.h:211
static Flags & get()
Definition features.h:122
static Arena & Get()
Definition arena.cc:24
RAII guard for ImGui style colors.
Definition style_guard.h:29
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::optional< bool > remote_replay_restore_focus_
std::string test_rom_path_for_session_
absl::Status TestRomDataIntegrity(Rom *rom)
static constexpr size_t kMaxResourceHistorySize
ImGuiTestEngine * ui_test_engine_
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:138
#define LOG_INFO(category, format,...)
Definition log.h:136
ImVec4 GetSuccessColor()
Definition ui_helpers.cc:49
ButtonColorSet GetSuccessButtonColors()
ImVec4 GetDisabledColor()
Definition ui_helpers.cc:74
ImVec4 GetErrorColor()
Definition ui_helpers.cc:59
ImVec4 GetWarningColor()
Definition ui_helpers.cc:54
ImVec4 GetInfoColor()
Definition ui_helpers.cc:64
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:57
std::chrono::time_point< std::chrono::steady_clock > timestamp
std::vector< TestResult > individual_results
std::chrono::milliseconds total_duration
float GetPassRate() const