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"
20#include "core/features.h"
21#include "util/file_util.h"
22#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
23#include "imgui.h"
24#include "imgui_internal.h"
25#else
26#include "imgui.h"
27#endif
28#include "util/log.h"
29
30// Forward declaration to avoid circular dependency
31namespace yaze {
32namespace editor {
33class EditorManager;
34}
35} // namespace yaze
36
37#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
38#include "imgui_test_engine/imgui_te_engine.h"
39#endif
40
41namespace yaze {
42namespace test {
43
44namespace {
45
46std::string GenerateFailureScreenshotPath(const std::string& test_id) {
47 std::filesystem::path base_dir = std::filesystem::temp_directory_path() /
48 "yaze" / "test-results" / test_id;
49 std::error_code ec;
50 std::filesystem::create_directories(base_dir, ec);
51
52 const int64_t timestamp_ms = absl::ToUnixMillis(absl::Now());
53 std::filesystem::path file_path =
54 base_dir / std::filesystem::path(absl::StrFormat(
55 "failure_%lld.bmp", static_cast<long long>(timestamp_ms)));
56 return file_path.string();
57}
58
59} // namespace
60
61// Utility function implementations
62const char* TestStatusToString(TestStatus status) {
63 switch (status) {
65 return "Not Run";
67 return "Running";
69 return "Passed";
71 return "Failed";
73 return "Skipped";
74 }
75 return "Unknown";
76}
77
78const char* TestCategoryToString(TestCategory category) {
79 switch (category) {
81 return "Unit";
83 return "Integration";
85 return "UI";
87 return "Performance";
89 return "Memory";
90 }
91 return "Unknown";
92}
93
95 switch (status) {
97 return ImVec4(0.6f, 0.6f, 0.6f, 1.0f); // Gray
99 return ImVec4(1.0f, 1.0f, 0.0f, 1.0f); // Yellow
101 return ImVec4(0.0f, 1.0f, 0.0f, 1.0f); // Green
103 return ImVec4(1.0f, 0.0f, 0.0f, 1.0f); // Red
105 return ImVec4(1.0f, 0.5f, 0.0f, 1.0f); // Orange
106 }
107 return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
108}
109
110#if defined(YAZE_WITH_GRPC)
111const char* HarnessStatusToString(HarnessTestStatus status) {
112 switch (status) {
113 case HarnessTestStatus::kPassed:
114 return "passed";
115 case HarnessTestStatus::kFailed:
116 return "failed";
117 case HarnessTestStatus::kTimeout:
118 return "timeout";
119 case HarnessTestStatus::kRunning:
120 return "running";
121 case HarnessTestStatus::kQueued:
122 return "queued";
123 case HarnessTestStatus::kUnspecified:
124 default:
125 return "unspecified";
126 }
127}
128
129HarnessTestStatus HarnessStatusFromString(absl::string_view status) {
130 if (absl::EqualsIgnoreCase(status, "passed")) {
131 return HarnessTestStatus::kPassed;
132 }
133 if (absl::EqualsIgnoreCase(status, "failed")) {
134 return HarnessTestStatus::kFailed;
135 }
136 if (absl::EqualsIgnoreCase(status, "timeout")) {
137 return HarnessTestStatus::kTimeout;
138 }
139 if (absl::EqualsIgnoreCase(status, "running")) {
140 return HarnessTestStatus::kRunning;
141 }
142 if (absl::EqualsIgnoreCase(status, "queued")) {
143 return HarnessTestStatus::kQueued;
144 }
145 return HarnessTestStatus::kUnspecified;
146}
147#endif // defined(YAZE_WITH_GRPC)
148
149// TestManager implementation
151 static TestManager instance;
152 return instance;
153}
154
156 // Note: UI test engine initialization is deferred until ImGui context is
157 // ready Call InitializeUITesting() explicitly after ImGui::CreateContext()
158}
159
161#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
163#endif
164}
165
166#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
168 if (!ui_test_engine_) {
169 // Check if ImGui context is ready
170 if (ImGui::GetCurrentContext() == nullptr) {
171 LOG_WARN("TestManager",
172 "ImGui context not ready, deferring test engine initialization");
173 return;
174 }
175
176 ui_test_engine_ = ImGuiTestEngine_CreateContext();
177 if (ui_test_engine_) {
178 ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(ui_test_engine_);
179 test_io.ConfigVerboseLevel = ImGuiTestVerboseLevel_Info;
180 test_io.ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Debug;
181 test_io.ConfigRunSpeed = ImGuiTestRunSpeed_Fast;
182
183 // Start the test engine
184 ImGuiTestEngine_Start(ui_test_engine_, ImGui::GetCurrentContext());
185 LOG_INFO("TestManager", "ImGuiTestEngine initialized successfully");
186 }
187 }
188}
189
191 if (ui_test_engine_ && ImGui::GetCurrentContext() != nullptr) {
192 ImGuiTestEngine_Stop(ui_test_engine_);
193 }
194}
195
197 if (ui_test_engine_) {
198 ImGuiTestEngine_DestroyContext(ui_test_engine_);
199 ui_test_engine_ = nullptr;
200 }
201}
202
204 // Complete shutdown - calls both phases
207}
208#endif
209
211 if (is_running_) {
212 return absl::FailedPreconditionError("Tests are already running");
213 }
214
215 is_running_ = true;
216 progress_ = 0.0f;
218
219 // Execute all test suites
220 for (auto& suite : test_suites_) {
221 if (suite->IsEnabled()) {
222 current_test_name_ = suite->GetName();
223 auto status = ExecuteTestSuite(suite.get());
224 if (!status.ok()) {
225 is_running_ = false;
226 return status;
227 }
229 }
230 }
231
232 is_running_ = false;
233 current_test_name_.clear();
234 progress_ = 1.0f;
235
236 return absl::OkStatus();
237}
238
240 if (is_running_) {
241 return absl::FailedPreconditionError("Tests are already running");
242 }
243
244 is_running_ = true;
245 progress_ = 0.0f;
247
248 // Filter and execute test suites by category
249 std::vector<TestSuite*> filtered_suites;
250 for (auto& suite : test_suites_) {
251 if (suite->IsEnabled() && suite->GetCategory() == category) {
252 filtered_suites.push_back(suite.get());
253 }
254 }
255
256 for (auto* suite : filtered_suites) {
257 current_test_name_ = suite->GetName();
258 auto status = ExecuteTestSuite(suite);
259 if (!status.ok()) {
260 is_running_ = false;
261 return status;
262 }
264 }
265
266 is_running_ = false;
267 current_test_name_.clear();
268 progress_ = 1.0f;
269
270 return absl::OkStatus();
271}
272
273absl::Status TestManager::RunTestSuite(const std::string& suite_name) {
274 if (is_running_) {
275 return absl::FailedPreconditionError("Tests are already running");
276 }
277
278 auto it = suite_lookup_.find(suite_name);
279 if (it == suite_lookup_.end()) {
280 return absl::NotFoundError("Test suite not found: " + suite_name);
281 }
282
283 is_running_ = true;
284 progress_ = 0.0f;
286 current_test_name_ = suite_name;
287
288 auto status = ExecuteTestSuite(it->second);
289
290 is_running_ = false;
291 current_test_name_.clear();
292 progress_ = 1.0f;
293
294 return status;
295}
296
297void TestManager::RegisterTestSuite(std::unique_ptr<TestSuite> suite) {
298 if (suite) {
299 std::string name = suite->GetName();
300 suite_lookup_[name] = suite.get();
301 test_suites_.push_back(std::move(suite));
302 }
303}
304
305std::vector<std::string> TestManager::GetTestSuiteNames() const {
306 std::vector<std::string> names;
307 names.reserve(test_suites_.size());
308 for (const auto& suite : test_suites_) {
309 names.push_back(suite->GetName());
310 }
311 return names;
312}
313
314TestSuite* TestManager::GetTestSuite(const std::string& name) {
315 auto it = suite_lookup_.find(name);
316 return it != suite_lookup_.end() ? it->second : nullptr;
317}
318
323
325 if (!suite) {
326 return absl::InvalidArgumentError("Test suite is null");
327 }
328
329 // Collect resource stats before test
331
332 // Execute the test suite
333 auto status = suite->RunTests(last_results_);
334
335 // Collect resource stats after test
337
338 return status;
339}
340
342 if (test_suites_.empty()) {
343 progress_ = 1.0f;
344 return;
345 }
346
347 size_t completed = 0;
348 for (const auto& suite : test_suites_) {
349 if (suite->IsEnabled()) {
350 completed++;
351 }
352 }
353
354 progress_ = static_cast<float>(completed) / test_suites_.size();
355}
356
358 ResourceStats stats;
359 stats.timestamp = std::chrono::steady_clock::now();
360
361 // Get Arena statistics
362 auto& arena = gfx::Arena::Get();
363 stats.texture_count = arena.GetTextureCount();
364 stats.surface_count = arena.GetSurfaceCount();
365
366 // Get frame rate from ImGui
367 stats.frame_rate = ImGui::GetIO().Framerate;
368
369 // Estimate memory usage (simplified)
370 stats.memory_usage_mb =
371 (stats.texture_count + stats.surface_count) / 1024; // Rough estimate
372
373 resource_history_.push_back(stats);
374}
375
384
385void TestManager::DrawTestDashboard(bool* show_dashboard) {
386 bool* dashboard_flag = show_dashboard ? show_dashboard : &show_dashboard_;
387
388 // Set a larger default window size
389 ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
390
391 if (!ImGui::Begin("Test Dashboard", dashboard_flag,
392 ImGuiWindowFlags_MenuBar)) {
393 ImGui::End();
394 return;
395 }
396
397 // ROM status indicator with detailed information
398 bool has_rom = current_rom_ && current_rom_->is_loaded();
399
400 // Add real-time ROM status checking
401 static int frame_counter = 0;
402 frame_counter++;
403 if (frame_counter % 60 == 0) { // Check every 60 frames
404 // Log ROM status periodically for debugging
405 LOG_INFO("TestManager",
406 "TestManager ROM status check - Frame %d: ROM %p, loaded: %s",
407 frame_counter, (void*)current_rom_, has_rom ? "true" : "false");
408 }
409
410 if (ImGui::BeginTable("ROM_Status_Table", 2, ImGuiTableFlags_BordersInner)) {
411 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 120);
412 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
413
414 ImGui::TableNextRow();
415 ImGui::TableNextColumn();
416 ImGui::Text("ROM Status:");
417 ImGui::TableNextColumn();
418 if (has_rom) {
419 ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s Loaded",
421
422 ImGui::TableNextRow();
423 ImGui::TableNextColumn();
424 ImGui::Text("ROM Title:");
425 ImGui::TableNextColumn();
426 ImGui::Text("%s", current_rom_->title().c_str());
427
428 ImGui::TableNextRow();
429 ImGui::TableNextColumn();
430 ImGui::Text("File Name:");
431 ImGui::TableNextColumn();
432 ImGui::Text("%s", current_rom_->filename().c_str());
433
434 ImGui::TableNextRow();
435 ImGui::TableNextColumn();
436 ImGui::Text("Size:");
437 ImGui::TableNextColumn();
438 ImGui::Text("%.2f MB (%zu bytes)", current_rom_->size() / 1048576.0f,
439 current_rom_->size());
440
441 ImGui::TableNextRow();
442 ImGui::TableNextColumn();
443 ImGui::Text("Modified:");
444 ImGui::TableNextColumn();
445 if (current_rom_->dirty()) {
446 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "%s Yes",
448 } else {
449 ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s No",
451 }
452
453 ImGui::TableNextRow();
454 ImGui::TableNextColumn();
455 ImGui::Text("ROM Pointer:");
456 ImGui::TableNextColumn();
457 ImGui::Text("%p", (void*)current_rom_);
458
459 ImGui::TableNextRow();
460 ImGui::TableNextColumn();
461 ImGui::Text("Actions:");
462 ImGui::TableNextColumn();
463 if (ImGui::Button("Refresh ROM Reference")) {
465 }
466
467 } else {
468 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "%s Not Loaded",
470 ImGui::TableNextRow();
471 ImGui::TableNextColumn();
472 ImGui::Text("ROM Pointer:");
473 ImGui::TableNextColumn();
474 ImGui::Text("%p", (void*)current_rom_);
475 ImGui::TableNextRow();
476 ImGui::TableNextColumn();
477 ImGui::Text("Status:");
478 ImGui::TableNextColumn();
479 ImGui::Text("ROM-dependent tests will be skipped");
480
481 ImGui::TableNextRow();
482 ImGui::TableNextColumn();
483 ImGui::Text("Actions:");
484 ImGui::TableNextColumn();
485 if (ImGui::Button("Refresh ROM Reference")) {
487 }
488 ImGui::SameLine();
489 if (ImGui::Button("Debug ROM State")) {
490 LOG_INFO("TestManager", "=== ROM DEBUG INFO ===");
491 LOG_INFO("TestManager", "current_rom_ pointer: %p",
492 (void*)current_rom_);
493 if (current_rom_) {
494 LOG_INFO("TestManager", "ROM title: '%s'",
495 current_rom_->title().c_str());
496 LOG_INFO("TestManager", "ROM size: %zu", current_rom_->size());
497 LOG_INFO("TestManager", "ROM is_loaded(): %s",
498 current_rom_->is_loaded() ? "true" : "false");
499 LOG_INFO("TestManager", "ROM data pointer: %p",
500 (void*)current_rom_->data());
501 }
502 LOG_INFO("TestManager", "======================");
503 }
504 }
505
506 ImGui::EndTable();
507 }
508 ImGui::Separator();
509
510 // Menu bar
511 if (ImGui::BeginMenuBar()) {
512 if (ImGui::BeginMenu("Run")) {
513 if (ImGui::MenuItem("All Tests", "Ctrl+T", false, !is_running_)) {
514 [[maybe_unused]] auto status = RunAllTests();
515 }
516 ImGui::Separator();
517 if (ImGui::MenuItem("Unit Tests", nullptr, false, !is_running_)) {
518 [[maybe_unused]] auto status = RunTestsByCategory(TestCategory::kUnit);
519 }
520 if (ImGui::MenuItem("Integration Tests", nullptr, false, !is_running_)) {
521 [[maybe_unused]] auto status =
523 }
524 if (ImGui::MenuItem("UI Tests", nullptr, false, !is_running_)) {
525 [[maybe_unused]] auto status = RunTestsByCategory(TestCategory::kUI);
526 }
527 if (ImGui::MenuItem("Performance Tests", nullptr, false, !is_running_)) {
528 [[maybe_unused]] auto status =
530 }
531 if (ImGui::MenuItem("Memory Tests", nullptr, false, !is_running_)) {
532 [[maybe_unused]] auto status =
534 }
535 ImGui::EndMenu();
536 }
537
538 if (ImGui::BeginMenu("View")) {
539 ImGui::MenuItem("Resource Monitor", nullptr, &show_resource_monitor_);
540 ImGui::MenuItem("Google Tests", nullptr, &show_google_tests_);
541 ImGui::MenuItem("ROM Test Results", nullptr, &show_rom_test_results_);
542 ImGui::Separator();
543 if (ImGui::MenuItem("Export Results", nullptr, false,
545 // TODO: Implement result export
546 }
547 ImGui::EndMenu();
548 }
549
550 if (ImGui::BeginMenu("ROM")) {
551 if (ImGui::MenuItem("Test Current ROM", nullptr, false,
553 [[maybe_unused]] auto status =
555 }
556 if (ImGui::MenuItem("Load ROM for Testing...")) {
558 }
559 ImGui::Separator();
560 if (ImGui::MenuItem("Refresh ROM Reference")) {
562 }
563 ImGui::EndMenu();
564 }
565
566 if (ImGui::BeginMenu("Configure")) {
567 if (ImGui::MenuItem("Test Configuration")) {
569 }
570 ImGui::Separator();
572 if (ImGui::MenuItem("Use NFD File Dialog", nullptr, &nfd_mode)) {
574 LOG_INFO("TestManager", "Global file dialog mode changed to: %s",
575 nfd_mode ? "NFD" : "Bespoke");
576 }
577 ImGui::EndMenu();
578 }
579
580 ImGui::EndMenuBar();
581 }
582
583 // Show test configuration status
584 int enabled_count = 0;
585 int total_count = 0;
586 static const std::vector<std::string> all_test_names = {
587 "ROM_Header_Validation_Test", "ROM_Data_Access_Test",
588 "ROM_Graphics_Extraction_Test", "ROM_Overworld_Loading_Test",
589 "Tile16_Editor_Test", "Comprehensive_Save_Test",
590 "ROM_Sprite_Data_Test", "ROM_Music_Data_Test"};
591
592 for (const auto& test_name : all_test_names) {
593 total_count++;
594 if (IsTestEnabled(test_name)) {
595 enabled_count++;
596 }
597 }
598
599 ImGui::Text("%s Test Status: %d/%d enabled", ICON_MD_CHECKLIST, enabled_count,
600 total_count);
601 if (enabled_count < total_count) {
602 ImGui::SameLine();
603 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f),
604 "(Some tests disabled - check Configuration)");
605 }
606
607 // Enhanced test execution status
608 if (is_running_) {
609 ImGui::PushStyleColor(ImGuiCol_Text,
611 ImGui::Text("%s Running: %s", ICON_MD_PLAY_CIRCLE_FILLED,
612 current_test_name_.c_str());
613 ImGui::PopStyleColor();
614 ImGui::ProgressBar(progress_, ImVec2(-1, 0),
615 absl::StrFormat("%.0f%%", progress_ * 100.0f).c_str());
616 } else {
617 // Enhanced control buttons
618 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.7f, 0.2f, 1.0f));
619 if (ImGui::Button(
620 absl::StrCat(ICON_MD_PLAY_ARROW, " Run All Tests").c_str(),
621 ImVec2(140, 0))) {
622 [[maybe_unused]] auto status = RunAllTests();
623 }
624 ImGui::PopStyleColor();
625
626 ImGui::SameLine();
627 if (ImGui::Button(absl::StrCat(ICON_MD_SPEED, " Quick Test").c_str(),
628 ImVec2(100, 0))) {
629 [[maybe_unused]] auto status = RunTestsByCategory(TestCategory::kMemory);
630 }
631
632 ImGui::SameLine();
633 bool has_rom = current_rom_ && current_rom_->is_loaded();
634 if (has_rom) {
635 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.8f, 1.0f));
636 }
637 if (ImGui::Button(absl::StrCat(ICON_MD_STORAGE, " ROM Tests").c_str(),
638 ImVec2(100, 0))) {
639 if (has_rom) {
640 [[maybe_unused]] auto status =
642 }
643 }
644 if (has_rom) {
645 ImGui::PopStyleColor();
646 }
647 if (ImGui::IsItemHovered()) {
648 if (has_rom) {
649 ImGui::SetTooltip("Run tests on current ROM: %s",
650 current_rom_->title().c_str());
651 } else {
652 ImGui::SetTooltip("Load a ROM to enable ROM-dependent tests");
653 }
654 }
655
656 ImGui::SameLine();
657 if (ImGui::Button(absl::StrCat(ICON_MD_CLEAR, " Clear").c_str(),
658 ImVec2(80, 0))) {
659 ClearResults();
660 }
661
662 ImGui::SameLine();
663 if (ImGui::Button(absl::StrCat(ICON_MD_SETTINGS, " Config").c_str(),
664 ImVec2(80, 0))) {
666 }
667 }
668
669 ImGui::Separator();
670
671 // Enhanced test results summary with better visuals
672 if (last_results_.total_tests > 0) {
673 // Test summary header
674 ImGui::Text("%s Test Results Summary", ICON_MD_ASSESSMENT);
675
676 // Progress bar showing pass rate
677 float pass_rate = last_results_.GetPassRate();
678 ImVec4 progress_color = pass_rate >= 0.9f ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f)
679 : pass_rate >= 0.7f
680 ? ImVec4(1.0f, 1.0f, 0.0f, 1.0f)
681 : ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
682
683 ImGui::PushStyleColor(ImGuiCol_PlotHistogram, progress_color);
684 ImGui::ProgressBar(
685 pass_rate, ImVec2(-1, 0),
686 absl::StrFormat("Pass Rate: %.1f%%", pass_rate * 100.0f).c_str());
687 ImGui::PopStyleColor();
688
689 // Test counts with icons
690 ImGui::Text("%s Total: %zu", ICON_MD_ANALYTICS, last_results_.total_tests);
691 ImGui::SameLine();
692 ImGui::TextColored(GetTestStatusColor(TestStatus::kPassed), "%s %zu",
694 ImGui::SameLine();
695 ImGui::TextColored(GetTestStatusColor(TestStatus::kFailed), "%s %zu",
697 ImGui::SameLine();
698 ImGui::TextColored(GetTestStatusColor(TestStatus::kSkipped), "%s %zu",
700
701 ImGui::Text("%s Duration: %lld ms", ICON_MD_TIMER,
703
704 // Test suite breakdown
705 if (ImGui::CollapsingHeader("Test Suite Breakdown")) {
706 std::unordered_map<std::string, std::pair<size_t, size_t>>
707 suite_stats; // passed, total
708 for (const auto& result : last_results_.individual_results) {
709 suite_stats[result.suite_name].second++; // total
710 if (result.status == TestStatus::kPassed) {
711 suite_stats[result.suite_name].first++; // passed
712 }
713 }
714
715 for (const auto& [suite_name, stats] : suite_stats) {
716 float suite_pass_rate =
717 stats.second > 0 ? static_cast<float>(stats.first) / stats.second
718 : 0.0f;
719 ImGui::Text("%s: %zu/%zu (%.0f%%)", suite_name.c_str(), stats.first,
720 stats.second, suite_pass_rate * 100.0f);
721 }
722 }
723 }
724
725 ImGui::Separator();
726
727 // Enhanced test filter with category selection
728 ImGui::Text("%s Filter & View Options", ICON_MD_FILTER_LIST);
729
730 // Category filter
731 const char* categories[] = {"All", "Unit", "Integration",
732 "UI", "Performance", "Memory"};
733 static int selected_category = 0;
734 if (ImGui::Combo("Category", &selected_category, categories,
735 IM_ARRAYSIZE(categories))) {
736 switch (selected_category) {
737 case 0:
739 break; // All - use Unit as default
740 case 1:
742 break;
743 case 2:
745 break;
746 case 3:
748 break;
749 case 4:
751 break;
752 case 5:
754 break;
755 }
756 }
757
758 // Text filter
759 static char filter_buffer[256] = "";
760 ImGui::SetNextItemWidth(-80);
761 if (ImGui::InputTextWithHint("##filter", "Search tests...", filter_buffer,
762 sizeof(filter_buffer))) {
763 test_filter_ = std::string(filter_buffer);
764 }
765 ImGui::SameLine();
766 if (ImGui::Button("Clear")) {
767 filter_buffer[0] = '\0';
768 test_filter_.clear();
769 }
770
771 ImGui::Separator();
772
773 // Tabs for different test result views
774 if (ImGui::BeginTabBar("TestResultsTabs", ImGuiTabBarFlags_None)) {
775 // Standard test results tab
776 if (ImGui::BeginTabItem("Test Results")) {
777 if (ImGui::BeginChild("TestResults", ImVec2(0, 0), true)) {
778 if (last_results_.individual_results.empty()) {
779 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
780 "No test results to display. Run some tests to "
781 "see results here.");
782 } else {
783 for (const auto& result : last_results_.individual_results) {
784 // Apply filters
785 bool category_match = (selected_category == 0) ||
786 (result.category == category_filter_);
787 bool text_match =
788 test_filter_.empty() ||
789 result.name.find(test_filter_) != std::string::npos ||
790 result.suite_name.find(test_filter_) != std::string::npos;
791
792 if (!category_match || !text_match) {
793 continue;
794 }
795
796 ImGui::PushID(&result);
797
798 // Status icon and test name
799 const char* status_icon = ICON_MD_HELP;
800 switch (result.status) {
802 status_icon = ICON_MD_CHECK_CIRCLE;
803 break;
805 status_icon = ICON_MD_ERROR;
806 break;
808 status_icon = ICON_MD_SKIP_NEXT;
809 break;
811 status_icon = ICON_MD_PLAY_CIRCLE_FILLED;
812 break;
813 default:
814 break;
815 }
816
817 ImGui::TextColored(GetTestStatusColor(result.status), "%s %s::%s",
818 status_icon, result.suite_name.c_str(),
819 result.name.c_str());
820
821 // Show duration and timestamp on same line if space allows
822 if (ImGui::GetContentRegionAvail().x > 200) {
823 ImGui::SameLine();
824 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "(%lld ms)",
825 result.duration.count());
826 }
827
828 // Show detailed information for failed tests
829 if (result.status == TestStatus::kFailed &&
830 !result.error_message.empty()) {
831 ImGui::Indent();
832 ImGui::PushStyleColor(ImGuiCol_Text,
833 ImVec4(1.0f, 0.8f, 0.8f, 1.0f));
834 ImGui::TextWrapped("%s %s", ICON_MD_ERROR_OUTLINE,
835 result.error_message.c_str());
836 ImGui::PopStyleColor();
837 ImGui::Unindent();
838 }
839
840 // Show additional info for passed tests if they have messages
841 if (result.status == TestStatus::kPassed &&
842 !result.error_message.empty()) {
843 ImGui::Indent();
844 ImGui::PushStyleColor(ImGuiCol_Text,
845 ImVec4(0.8f, 1.0f, 0.8f, 1.0f));
846 ImGui::TextWrapped("%s %s", ICON_MD_INFO,
847 result.error_message.c_str());
848 ImGui::PopStyleColor();
849 ImGui::Unindent();
850 }
851
852 ImGui::PopID();
853 }
854 }
855 }
856 ImGui::EndChild();
857 ImGui::EndTabItem();
858 }
859
860 // Harness Test Results tab (for gRPC GUI automation tests)
861#if defined(YAZE_WITH_GRPC)
862 if (ImGui::BeginTabItem("GUI Automation Tests")) {
863 if (ImGui::BeginChild("HarnessTests", ImVec2(0, 0), true)) {
864 // Display harness test summaries
865 auto summaries = ListHarnessTestSummaries();
866
867 if (summaries.empty()) {
868 ImGui::TextColored(
869 ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
870 "No GUI automation test results yet.\n\n"
871 "These tests are run via the ImGuiTestHarness gRPC service.\n"
872 "Results will appear here after running GUI automation tests.");
873 } else {
874 ImGui::Text("%s GUI Automation Test History", ICON_MD_HISTORY);
875 ImGui::Text("Total Tests: %zu", summaries.size());
876 ImGui::Separator();
877
878 // Table of harness test results
879 if (ImGui::BeginTable("HarnessTestTable", 6,
880 ImGuiTableFlags_Borders |
881 ImGuiTableFlags_RowBg |
882 ImGuiTableFlags_Resizable)) {
883 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed,
884 80);
885 ImGui::TableSetupColumn("Test Name",
886 ImGuiTableColumnFlags_WidthStretch);
887 ImGui::TableSetupColumn("Category",
888 ImGuiTableColumnFlags_WidthFixed, 100);
889 ImGui::TableSetupColumn("Runs", ImGuiTableColumnFlags_WidthFixed,
890 60);
891 ImGui::TableSetupColumn("Pass Rate",
892 ImGuiTableColumnFlags_WidthFixed, 80);
893 ImGui::TableSetupColumn("Duration",
894 ImGuiTableColumnFlags_WidthFixed, 80);
895 ImGui::TableHeadersRow();
896
897 for (const auto& summary : summaries) {
898 const auto& exec = summary.latest_execution;
899
900 ImGui::TableNextRow();
901 ImGui::TableNextColumn();
902
903 // Status indicator
904 ImVec4 status_color;
905 const char* status_icon;
906 const char* status_text;
907
908 switch (exec.status) {
909 case HarnessTestStatus::kPassed:
910 status_color = ImVec4(0.0f, 1.0f, 0.0f, 1.0f);
911 status_icon = ICON_MD_CHECK_CIRCLE;
912 status_text = "Passed";
913 break;
914 case HarnessTestStatus::kFailed:
915 status_color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
916 status_icon = ICON_MD_ERROR;
917 status_text = "Failed";
918 break;
919 case HarnessTestStatus::kTimeout:
920 status_color = ImVec4(1.0f, 0.5f, 0.0f, 1.0f);
921 status_icon = ICON_MD_TIMER_OFF;
922 status_text = "Timeout";
923 break;
924 case HarnessTestStatus::kRunning:
925 status_color = ImVec4(1.0f, 1.0f, 0.0f, 1.0f);
926 status_icon = ICON_MD_PLAY_CIRCLE_FILLED;
927 status_text = "Running";
928 break;
929 case HarnessTestStatus::kQueued:
930 status_color = ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
931 status_icon = ICON_MD_SCHEDULE;
932 status_text = "Queued";
933 break;
934 default:
935 status_color = ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
936 status_icon = ICON_MD_HELP;
937 status_text = "Unknown";
938 break;
939 }
940
941 ImGui::TextColored(status_color, "%s %s", status_icon,
942 status_text);
943
944 ImGui::TableNextColumn();
945 ImGui::Text("%s", exec.name.c_str());
946
947 // Show error message if failed
948 if (exec.status == HarnessTestStatus::kFailed &&
949 !exec.error_message.empty()) {
950 ImGui::SameLine();
951 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.5f, 1.0f), "(%s)",
952 exec.error_message.c_str());
953 }
954
955 ImGui::TableNextColumn();
956 ImGui::Text("%s", exec.category.c_str());
957
958 ImGui::TableNextColumn();
959 ImGui::Text("%d", summary.total_runs);
960
961 ImGui::TableNextColumn();
962 if (summary.total_runs > 0) {
963 float pass_rate =
964 static_cast<float>(summary.pass_count) / summary.total_runs;
965 ImVec4 rate_color =
966 pass_rate >= 0.9f ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f)
967 : pass_rate >= 0.7f ? ImVec4(1.0f, 1.0f, 0.0f, 1.0f)
968 : ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
969 ImGui::TextColored(rate_color, "%.0f%%", pass_rate * 100.0f);
970 } else {
971 ImGui::Text("-");
972 }
973
974 ImGui::TableNextColumn();
975 double duration_ms =
976 absl::ToDoubleMilliseconds(summary.total_duration);
977 if (summary.total_runs > 0) {
978 ImGui::Text("%.0f ms", duration_ms / summary.total_runs);
979 } else {
980 ImGui::Text("-");
981 }
982
983 // Expandable details
984 if (ImGui::TreeNode(("Details##" + exec.test_id).c_str())) {
985 ImGui::Text("Test ID: %s", exec.test_id.c_str());
986 ImGui::Text("Total Runs: %d (Pass: %d, Fail: %d)",
987 summary.total_runs, summary.pass_count,
988 summary.fail_count);
989
990 if (!exec.logs.empty()) {
991 ImGui::Separator();
992 ImGui::Text("%s Logs:", ICON_MD_DESCRIPTION);
993 for (const auto& log : exec.logs) {
994 ImGui::BulletText("%s", log.c_str());
995 }
996 }
997
998 if (!exec.assertion_failures.empty()) {
999 ImGui::Separator();
1000 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
1001 "%s Assertion Failures:", ICON_MD_ERROR);
1002 for (const auto& failure : exec.assertion_failures) {
1003 ImGui::BulletText("%s", failure.c_str());
1004 }
1005 }
1006
1007 if (!exec.screenshot_path.empty()) {
1008 ImGui::Separator();
1009 ImGui::Text("%s Screenshot: %s", ICON_MD_CAMERA_ALT,
1010 exec.screenshot_path.c_str());
1011 ImGui::Text("Size: %.2f KB",
1012 exec.screenshot_size_bytes / 1024.0);
1013 }
1014
1015 ImGui::TreePop();
1016 }
1017 }
1018
1019 ImGui::EndTable();
1020 }
1021 }
1022 }
1023 ImGui::EndChild();
1024 ImGui::EndTabItem();
1025 }
1026#endif // defined(YAZE_WITH_GRPC)
1027
1028 ImGui::EndTabBar();
1029 }
1030
1031 ImGui::End();
1032
1033 // Resource monitor window
1035 ImGui::Begin(absl::StrCat(ICON_MD_MONITOR, " Resource Monitor").c_str(),
1037
1038 if (!resource_history_.empty()) {
1039 const auto& latest = resource_history_.back();
1040 ImGui::Text("%s Textures: %zu", ICON_MD_TEXTURE, latest.texture_count);
1041 ImGui::Text("%s Surfaces: %zu", ICON_MD_LAYERS, latest.surface_count);
1042 ImGui::Text("%s Memory: %zu MB", ICON_MD_MEMORY, latest.memory_usage_mb);
1043 ImGui::Text("%s FPS: %.1f", ICON_MD_SPEED, latest.frame_rate);
1044
1045 // Simple plot of resource usage over time
1046 if (resource_history_.size() > 1) {
1047 std::vector<float> texture_counts;
1048 std::vector<float> surface_counts;
1049 texture_counts.reserve(resource_history_.size());
1050 surface_counts.reserve(resource_history_.size());
1051
1052 for (const auto& stats : resource_history_) {
1053 texture_counts.push_back(static_cast<float>(stats.texture_count));
1054 surface_counts.push_back(static_cast<float>(stats.surface_count));
1055 }
1056
1057 ImGui::PlotLines("Textures", texture_counts.data(),
1058 static_cast<int>(texture_counts.size()), 0, nullptr,
1059 0.0f, FLT_MAX, ImVec2(0, 80));
1060 ImGui::PlotLines("Surfaces", surface_counts.data(),
1061 static_cast<int>(surface_counts.size()), 0, nullptr,
1062 0.0f, FLT_MAX, ImVec2(0, 80));
1063 }
1064 }
1065
1066 ImGui::End();
1067 }
1068
1069 // Google Tests window
1070 if (show_google_tests_) {
1071 ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver);
1072 if (ImGui::Begin("Google Tests", &show_google_tests_)) {
1073 ImGui::Text("%s Google Test Integration", ICON_MD_SCIENCE);
1074 ImGui::Separator();
1075
1076#ifdef YAZE_ENABLE_GTEST
1077 ImGui::Text("Google Test framework is available");
1078
1079 if (ImGui::Button("Run All Google Tests")) {
1080 // Run Google tests - this would integrate with gtest
1081 LOG_INFO("TestManager", "Running Google Tests...");
1082 }
1083
1084 ImGui::SameLine();
1085 if (ImGui::Button("Run Specific Test Suite")) {
1086 // Show test suite selector
1087 }
1088
1089 ImGui::Separator();
1090 ImGui::Text("Available Test Suites:");
1091 ImGui::BulletText("Unit Tests");
1092 ImGui::BulletText("Integration Tests");
1093 ImGui::BulletText("Performance Tests");
1094#else
1095 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f),
1096 "%s Google Test framework not available",
1098 ImGui::Text("Enable YAZE_ENABLE_GTEST to use Google Test integration");
1099#endif
1100 }
1101 ImGui::End();
1102 }
1103
1104 // ROM Test Results window
1106 ImGui::SetNextWindowSize(ImVec2(700, 500), ImGuiCond_FirstUseEver);
1107 if (ImGui::Begin("ROM Test Results", &show_rom_test_results_)) {
1108 ImGui::Text("%s ROM Analysis Results", ICON_MD_ANALYTICS);
1109
1111 ImGui::Text("Testing ROM: %s", current_rom_->title().c_str());
1112 ImGui::Separator();
1113
1114 // Show ROM-specific test results
1115 if (ImGui::CollapsingHeader("ROM Data Integrity",
1116 ImGuiTreeNodeFlags_DefaultOpen)) {
1117 ImGui::Text("ROM Size: %.2f MB", current_rom_->size() / 1048576.0f);
1118 ImGui::Text("Modified: %s", current_rom_->dirty() ? "Yes" : "No");
1119
1120 if (ImGui::Button("Run Data Integrity Check")) {
1121 [[maybe_unused]] auto status = TestRomDataIntegrity(current_rom_);
1122 [[maybe_unused]] auto suite_status =
1124 }
1125 }
1126
1127 if (ImGui::CollapsingHeader("Save/Load Testing")) {
1128 ImGui::Text("Test ROM save and load operations");
1129
1130 if (ImGui::Button("Test Save Operations")) {
1131 [[maybe_unused]] auto status = TestRomSaveLoad(current_rom_);
1132 }
1133
1134 ImGui::SameLine();
1135 if (ImGui::Button("Test Load Operations")) {
1136 [[maybe_unused]] auto status = TestRomSaveLoad(current_rom_);
1137 }
1138 }
1139
1140 if (ImGui::CollapsingHeader("Editor Integration")) {
1141 ImGui::Text("Test editor components with current ROM");
1142
1143 if (ImGui::Button("Test Overworld Editor")) {
1144 // Test overworld editor with current ROM
1145 }
1146
1147 ImGui::SameLine();
1148 if (ImGui::Button("Test Tile16 Editor")) {
1149 // Test tile16 editor with current ROM
1150 }
1151 }
1152
1153 } else {
1154 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f),
1155 "%s No ROM loaded for analysis", ICON_MD_WARNING);
1156 }
1157 }
1158 ImGui::End();
1159 }
1160
1161 // ROM File Dialog
1163 ImGui::SetNextWindowSize(ImVec2(400, 200), ImGuiCond_Appearing);
1164 if (ImGui::Begin("Load ROM for Testing", &show_rom_file_dialog_,
1165 ImGuiWindowFlags_NoResize)) {
1166 ImGui::Text("%s Load ROM for Testing", ICON_MD_FOLDER_OPEN);
1167 ImGui::Separator();
1168
1169 ImGui::Text("Select a ROM file to run tests on:");
1170
1171 if (ImGui::Button("Browse ROM File...", ImVec2(-1, 0))) {
1172 // TODO: Implement file dialog to load ROM specifically for testing
1173 // This would be separate from the main editor ROM
1174 show_rom_file_dialog_ = false;
1175 }
1176
1177 ImGui::Separator();
1178 if (ImGui::Button("Cancel", ImVec2(-1, 0))) {
1179 show_rom_file_dialog_ = false;
1180 }
1181 }
1182 ImGui::End();
1183 }
1184
1185 // Test Configuration Window
1187 ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
1188 if (ImGui::Begin("Test Configuration", &show_test_configuration_)) {
1189 ImGui::Text("%s Test Configuration", ICON_MD_SETTINGS);
1190 ImGui::Separator();
1191
1192 // File Dialog Configuration
1193 if (ImGui::CollapsingHeader("File Dialog Settings",
1194 ImGuiTreeNodeFlags_DefaultOpen)) {
1195 ImGui::Text("File Dialog Implementation:");
1196
1198 if (ImGui::RadioButton("NFD (Native File Dialog)", nfd_mode)) {
1200 LOG_INFO("TestManager", "Global file dialog mode set to: NFD");
1201 }
1202 if (ImGui::IsItemHovered()) {
1203 ImGui::SetTooltip(
1204 "Use NFD library for native OS file dialogs (global setting)");
1205 }
1206
1207 if (ImGui::RadioButton("Bespoke Implementation", !nfd_mode)) {
1209 LOG_INFO("TestManager", "Global file dialog mode set to: Bespoke");
1210 }
1211 if (ImGui::IsItemHovered()) {
1212 ImGui::SetTooltip(
1213 "Use custom file dialog implementation (global setting)");
1214 }
1215
1216 ImGui::Separator();
1217 ImGui::Text(
1218 "Current Mode: %s",
1219 core::FeatureFlags::get().kUseNativeFileDialog ? "NFD" : "Bespoke");
1220 ImGui::TextColored(
1221 ImVec4(1.0f, 1.0f, 0.0f, 1.0f),
1222 "Note: This setting affects ALL file dialogs in the application");
1223
1224 if (ImGui::Button("Test Current File Dialog")) {
1225 // Test the current file dialog implementation
1226 LOG_INFO("TestManager", "Testing global file dialog mode: %s",
1227 core::FeatureFlags::get().kUseNativeFileDialog ? "NFD"
1228 : "Bespoke");
1229
1230 // Actually test the file dialog
1232 if (!result.empty()) {
1233 LOG_INFO("TestManager", "File dialog test successful: %s",
1234 result.c_str());
1235 } else {
1236 LOG_INFO("TestManager",
1237 "File dialog test: No file selected or dialog canceled");
1238 }
1239 }
1240
1241 ImGui::SameLine();
1242 if (ImGui::Button("Test NFD Directly")) {
1244 if (!result.empty()) {
1245 LOG_INFO("TestManager", "NFD test successful: %s", result.c_str());
1246 } else {
1247 LOG_INFO("TestManager",
1248 "NFD test: No file selected, canceled, or error occurred");
1249 }
1250 }
1251
1252 ImGui::SameLine();
1253 if (ImGui::Button("Test Bespoke Directly")) {
1255 if (!result.empty()) {
1256 LOG_INFO("TestManager", "Bespoke test successful: %s",
1257 result.c_str());
1258 } else {
1259 LOG_INFO("TestManager",
1260 "Bespoke test: No file selected or not implemented");
1261 }
1262 }
1263 }
1264
1265 // Test Selection Configuration
1266 if (ImGui::CollapsingHeader("Test Selection",
1267 ImGuiTreeNodeFlags_DefaultOpen)) {
1268 ImGui::Text("Enable/Disable Individual Tests:");
1269 ImGui::Separator();
1270
1271 // List of known tests with their risk levels
1272 static const std::vector<std::pair<std::string, std::string>>
1273 known_tests = {
1274 {"ROM_Header_Validation_Test",
1275 "Safe - Read-only ROM header validation"},
1276 {"ROM_Data_Access_Test",
1277 "Safe - Basic ROM data access testing"},
1278 {"ROM_Graphics_Extraction_Test",
1279 "Safe - Graphics data extraction testing"},
1280 {"ROM_Overworld_Loading_Test",
1281 "Safe - Overworld data loading testing"},
1282 {"Tile16_Editor_Test",
1283 "Moderate - Tile16 editor initialization"},
1284 {"Comprehensive_Save_Test",
1285 "DANGEROUS - Known to crash, uses ROM copies"},
1286 {"ROM_Sprite_Data_Test", "Safe - Sprite data validation"},
1287 {"ROM_Music_Data_Test", "Safe - Music data validation"}};
1288
1289 // Initialize problematic tests as disabled by default
1290 static bool initialized_defaults = false;
1291 if (!initialized_defaults) {
1292 DisableTest("Comprehensive_Save_Test"); // Disable crash-prone test
1293 // by default
1294 initialized_defaults = true;
1295 }
1296
1297 if (ImGui::BeginTable(
1298 "TestSelection", 4,
1299 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
1300 ImGui::TableSetupColumn("Test Name", ImGuiTableColumnFlags_WidthFixed,
1301 200);
1302 ImGui::TableSetupColumn("Risk Level",
1303 ImGuiTableColumnFlags_WidthStretch);
1304 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed,
1305 80);
1306 ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthFixed,
1307 100);
1308 ImGui::TableHeadersRow();
1309
1310 for (const auto& [test_name, description] : known_tests) {
1311 bool enabled = IsTestEnabled(test_name);
1312
1313 ImGui::TableNextRow();
1314 ImGui::TableNextColumn();
1315 ImGui::Text("%s", test_name.c_str());
1316
1317 ImGui::TableNextColumn();
1318 // Color-code the risk level
1319 if (description.find("DANGEROUS") != std::string::npos) {
1320 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s",
1321 description.c_str());
1322 } else if (description.find("Moderate") != std::string::npos) {
1323 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s",
1324 description.c_str());
1325 } else {
1326 ImGui::TextColored(ImVec4(0.0f, 0.8f, 0.0f, 1.0f), "%s",
1327 description.c_str());
1328 }
1329
1330 ImGui::TableNextColumn();
1331 if (enabled) {
1332 ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s ON",
1334 } else {
1335 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "%s OFF",
1337 }
1338
1339 ImGui::TableNextColumn();
1340 ImGui::PushID(test_name.c_str());
1341 if (enabled) {
1342 if (ImGui::Button("Disable")) {
1343 DisableTest(test_name);
1344 LOG_INFO("TestManager", "Disabled test: %s", test_name.c_str());
1345 }
1346 } else {
1347 if (ImGui::Button("Enable")) {
1348 EnableTest(test_name);
1349 LOG_INFO("TestManager", "Enabled test: %s", test_name.c_str());
1350 }
1351 }
1352 ImGui::PopID();
1353 }
1354
1355 ImGui::EndTable();
1356 }
1357
1358 ImGui::Separator();
1359 ImGui::Text("Quick Actions:");
1360
1361 if (ImGui::Button("Enable Safe Tests Only")) {
1362 for (const auto& [test_name, description] : known_tests) {
1363 if (description.find("Safe") != std::string::npos) {
1364 EnableTest(test_name);
1365 } else {
1366 DisableTest(test_name);
1367 }
1368 }
1369 LOG_INFO("TestManager", "Enabled only safe tests");
1370 }
1371 ImGui::SameLine();
1372
1373 if (ImGui::Button("Enable All Tests")) {
1374 for (const auto& [test_name, description] : known_tests) {
1375 EnableTest(test_name);
1376 }
1377 LOG_INFO("TestManager",
1378 "Enabled all tests (including dangerous ones)");
1379 }
1380 ImGui::SameLine();
1381
1382 if (ImGui::Button("Disable All Tests")) {
1383 for (const auto& [test_name, description] : known_tests) {
1384 DisableTest(test_name);
1385 }
1386 LOG_INFO("TestManager", "Disabled all tests");
1387 }
1388
1389 ImGui::Separator();
1390 ImGui::TextColored(
1391 ImVec4(1.0f, 0.5f, 0.0f, 1.0f),
1392 "⚠️ Recommendation: Use 'Enable Safe Tests Only' to avoid crashes");
1393 }
1394
1395 // Platform-specific settings
1396 if (ImGui::CollapsingHeader("Platform Settings")) {
1397 ImGui::Text("macOS Tahoe Compatibility:");
1398 ImGui::BulletText("NFD may have issues on macOS Sequoia+");
1399 ImGui::BulletText("Bespoke dialog provides fallback option");
1400 ImGui::BulletText(
1401 "Global setting affects File → Open, Project dialogs, etc.");
1402
1403 ImGui::Separator();
1404 ImGui::Text("Test Both Implementations:");
1405
1406 if (ImGui::Button("Quick Test NFD")) {
1408 LOG_INFO("TestManager", "NFD test result: %s",
1409 result.empty() ? "Failed/Canceled" : result.c_str());
1410 }
1411 ImGui::SameLine();
1412 if (ImGui::Button("Quick Test Bespoke")) {
1414 LOG_INFO("TestManager", "Bespoke test result: %s",
1415 result.empty() ? "Failed/Not Implemented" : result.c_str());
1416 }
1417
1418 ImGui::Separator();
1419 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
1420 "Note: These tests don't change the global setting");
1421 }
1422 }
1423 ImGui::End();
1424 }
1425
1426 // Test Session Creation Dialog
1428 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1429 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1430 ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_Appearing);
1431
1432 if (ImGui::Begin("Test ROM Session", &show_test_session_dialog_,
1433 ImGuiWindowFlags_NoResize)) {
1434 ImGui::Text("%s Test ROM Created Successfully", ICON_MD_CHECK_CIRCLE);
1435 ImGui::Separator();
1436
1437 ImGui::Text("A test ROM has been created with your modifications:");
1438 ImGui::Text("File: %s", test_rom_path_for_session_.c_str());
1439
1440 // Extract just the filename for display
1441 std::string display_filename = test_rom_path_for_session_;
1442 auto last_slash = display_filename.find_last_of("/\\");
1443 if (last_slash != std::string::npos) {
1444 display_filename = display_filename.substr(last_slash + 1);
1445 }
1446
1447 ImGui::Separator();
1448 ImGui::Text("Would you like to open this test ROM in a new session?");
1449
1450 if (ImGui::Button(
1451 absl::StrFormat("%s Open in New Session", ICON_MD_TAB).c_str(),
1452 ImVec2(200, 0))) {
1453 // TODO: This would need access to EditorManager to create a new session
1454 // For now, just show a message
1455 LOG_INFO("TestManager",
1456 "User requested to open test ROM in new session: %s",
1459 }
1460
1461 ImGui::SameLine();
1462 if (ImGui::Button(
1463 absl::StrFormat("%s Keep Current Session", ICON_MD_CLOSE).c_str(),
1464 ImVec2(200, 0))) {
1466 }
1467
1468 ImGui::Separator();
1469 ImGui::TextColored(
1470 ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
1471 "Note: Test ROM contains your modifications and can be");
1472 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
1473 "opened later using File → Open");
1474 }
1475 ImGui::End();
1476 }
1477}
1478
1480 LOG_INFO("TestManager", "=== TestManager ROM Refresh ===");
1481
1482 // Log current TestManager ROM state for debugging
1483 if (current_rom_) {
1484 LOG_INFO("TestManager", "TestManager ROM pointer: %p", (void*)current_rom_);
1485 LOG_INFO("TestManager", "ROM is_loaded(): %s",
1486 current_rom_->is_loaded() ? "true" : "false");
1487 if (current_rom_->is_loaded()) {
1488 LOG_INFO("TestManager", "ROM title: '%s'", current_rom_->title().c_str());
1489 LOG_INFO("TestManager", "ROM size: %.2f MB",
1490 current_rom_->size() / 1048576.0f);
1491 LOG_INFO("TestManager", "ROM dirty: %s",
1492 current_rom_->dirty() ? "true" : "false");
1493 }
1494 } else {
1495 LOG_INFO("TestManager", "TestManager ROM pointer is null");
1496 LOG_INFO("TestManager",
1497 "Note: ROM should be set by EditorManager when ROM is loaded");
1498 }
1499 LOG_INFO("TestManager", "===============================");
1500}
1501
1502absl::Status TestManager::CreateTestRomCopy(Rom* source_rom,
1503 std::unique_ptr<Rom>& test_rom) {
1504 if (!source_rom || !source_rom->is_loaded()) {
1505 return absl::FailedPreconditionError("Source ROM not loaded");
1506 }
1507
1508 LOG_INFO("TestManager", "Creating test ROM copy from: %s",
1509 source_rom->title().c_str());
1510
1511 // Create a new ROM instance
1512 test_rom = std::make_unique<Rom>();
1513
1514 // Copy the ROM data
1515 auto rom_data = source_rom->vector();
1516 auto load_status = test_rom->LoadFromData(rom_data);
1517 if (!load_status.ok()) {
1518 return load_status;
1519 }
1520
1521 LOG_INFO("TestManager", "Test ROM copy created successfully (size: %.2f MB)",
1522 test_rom->size() / 1048576.0f);
1523 return absl::OkStatus();
1524}
1525
1526std::string TestManager::GenerateTestRomFilename(const std::string& base_name) {
1527 // Generate filename with timestamp
1528 auto now = std::chrono::system_clock::now();
1529 auto time_t = std::chrono::system_clock::to_time_t(now);
1530 auto local_time = *std::localtime(&time_t);
1531
1532 std::string timestamp =
1533 absl::StrFormat("%04d%02d%02d_%02d%02d%02d", local_time.tm_year + 1900,
1534 local_time.tm_mon + 1, local_time.tm_mday,
1535 local_time.tm_hour, local_time.tm_min, local_time.tm_sec);
1536
1537 std::string base_filename = base_name;
1538 // Remove any path and extension
1539 auto last_slash = base_filename.find_last_of("/\\");
1540 if (last_slash != std::string::npos) {
1541 base_filename = base_filename.substr(last_slash + 1);
1542 }
1543 auto last_dot = base_filename.find_last_of('.');
1544 if (last_dot != std::string::npos) {
1545 base_filename = base_filename.substr(0, last_dot);
1546 }
1547
1548 return absl::StrFormat("%s_test_%s.sfc", base_filename.c_str(),
1549 timestamp.c_str());
1550}
1551
1552void TestManager::OfferTestSessionCreation(const std::string& test_rom_path) {
1553 // Store the test ROM path for the dialog
1554 test_rom_path_for_session_ = test_rom_path;
1556}
1557
1559 Rom* source_rom, std::function<absl::Status(Rom*)> test_function) {
1560 if (!source_rom || !source_rom->is_loaded()) {
1561 return absl::FailedPreconditionError("Source ROM not loaded");
1562 }
1563
1564 // Create a copy of the ROM for testing
1565 std::unique_ptr<Rom> test_rom;
1566 RETURN_IF_ERROR(CreateTestRomCopy(source_rom, test_rom));
1567
1568 LOG_INFO("TestManager", "Executing test function on ROM copy");
1569
1570 // Run the test function on the copy
1571 auto test_result = test_function(test_rom.get());
1572
1573 LOG_INFO("TestManager", "Test function completed with status: %s",
1574 test_result.ToString().c_str());
1575
1576 return test_result;
1577}
1578
1579absl::Status TestManager::LoadRomForTesting(const std::string& filename) {
1580 // This would load a ROM specifically for testing purposes
1581 // For now, just log the request
1582 LOG_INFO("TestManager", "Request to load ROM for testing: %s",
1583 filename.c_str());
1584 return absl::UnimplementedError(
1585 "ROM loading for testing not yet implemented");
1586}
1587
1589 const Rom& after) {
1590 if (ImGui::Begin("ROM Comparison Results")) {
1591 ImGui::Text("%s ROM Before/After Comparison", ICON_MD_COMPARE);
1592 ImGui::Separator();
1593
1594 if (ImGui::BeginTable("RomComparison", 3, ImGuiTableFlags_Borders)) {
1595 ImGui::TableSetupColumn("Property");
1596 ImGui::TableSetupColumn("Before");
1597 ImGui::TableSetupColumn("After");
1598 ImGui::TableHeadersRow();
1599
1600 ImGui::TableNextRow();
1601 ImGui::TableNextColumn();
1602 ImGui::Text("Size");
1603 ImGui::TableNextColumn();
1604 ImGui::Text("%.2f MB", before.size() / 1048576.0f);
1605 ImGui::TableNextColumn();
1606 ImGui::Text("%.2f MB", after.size() / 1048576.0f);
1607
1608 ImGui::TableNextRow();
1609 ImGui::TableNextColumn();
1610 ImGui::Text("Modified");
1611 ImGui::TableNextColumn();
1612 ImGui::Text("%s", before.dirty() ? "Yes" : "No");
1613 ImGui::TableNextColumn();
1614 ImGui::Text("%s", after.dirty() ? "Yes" : "No");
1615
1616 ImGui::EndTable();
1617 }
1618 }
1619 ImGui::End();
1620}
1621
1623 if (!rom || !rom->is_loaded()) {
1624 return absl::FailedPreconditionError("No ROM loaded for testing");
1625 }
1626
1627 // Use TestRomWithCopy to avoid affecting the original ROM
1628 return TestRomWithCopy(rom, [this](Rom* test_rom) -> absl::Status {
1629 LOG_INFO("TestManager", "Testing ROM save/load operations on copy: %s",
1630 test_rom->title().c_str());
1631
1632 // Perform test modifications on the copy
1633 // Test save operations
1634 Rom::SaveSettings settings;
1635 settings.backup = false;
1636 settings.save_new = true;
1637 settings.filename = GenerateTestRomFilename(test_rom->title());
1638
1639 auto save_status = test_rom->SaveToFile(settings);
1640 if (!save_status.ok()) {
1641 return save_status;
1642 }
1643
1644 LOG_INFO("TestManager", "Test ROM saved successfully to: %s",
1645 settings.filename.c_str());
1646
1647 // Offer to open test ROM in new session
1649
1650 return absl::OkStatus();
1651 });
1652}
1653
1655 if (!rom || !rom->is_loaded()) {
1656 return absl::FailedPreconditionError("No ROM loaded for testing");
1657 }
1658
1659 // Use TestRomWithCopy for integrity testing (read-only but uses copy for
1660 // safety)
1661 return TestRomWithCopy(rom, [](Rom* test_rom) -> absl::Status {
1662 LOG_INFO("TestManager", "Testing ROM data integrity on copy: %s",
1663 test_rom->title().c_str());
1664
1665 // Perform data integrity checks on the copy
1666 // This validates ROM structure, checksums, etc. without affecting original
1667
1668 // Basic ROM structure validation
1669 if (test_rom->size() < 0x100000) { // 1MB minimum for ALTTP
1670 return absl::FailedPreconditionError(
1671 "ROM file too small for A Link to the Past");
1672 }
1673
1674 // Check ROM header
1675 auto header_status = test_rom->ReadByteVector(0x7FC0, 32);
1676 if (!header_status.ok()) {
1677 return header_status.status();
1678 }
1679
1680 LOG_INFO("TestManager", "ROM integrity check passed for: %s",
1681 test_rom->title().c_str());
1682 return absl::OkStatus();
1683 });
1684}
1685
1686#if defined(YAZE_WITH_GRPC)
1687std::string TestManager::RegisterHarnessTest(const std::string& name,
1688 const std::string& category) {
1689 absl::MutexLock lock(&harness_history_mutex_);
1690 std::string test_id =
1691 absl::StrCat("harness_", absl::ToUnixMicros(absl::Now()), "_",
1692 harness_history_.size());
1693 HarnessTestExecution execution;
1694 execution.test_id = test_id;
1695 execution.name = name;
1696 execution.category = category;
1697 execution.status = HarnessTestStatus::kQueued;
1698 execution.queued_at = absl::Now();
1699 execution.logs.reserve(32);
1700 harness_history_[test_id] = execution;
1701 TrimHarnessHistoryLocked();
1702 HarnessAggregate& aggregate = harness_aggregates_[name];
1703 aggregate.latest_execution = execution;
1704 harness_history_order_.push_back(test_id);
1705 return test_id;
1706}
1707#else
1708std::string TestManager::RegisterHarnessTest(const std::string& name,
1709 const std::string& category) {
1710 (void)name;
1711 (void)category;
1712 return {};
1713}
1714#endif
1715
1716#if defined(YAZE_WITH_GRPC)
1717void TestManager::MarkHarnessTestRunning(const std::string& test_id) {
1718 absl::MutexLock lock(&harness_history_mutex_);
1719 auto it = harness_history_.find(test_id);
1720 if (it == harness_history_.end()) {
1721 return;
1722 }
1723 HarnessTestExecution& execution = it->second;
1724 execution.status = HarnessTestStatus::kRunning;
1725 execution.started_at = absl::Now();
1726}
1727#endif
1728
1729#if defined(YAZE_WITH_GRPC)
1730void TestManager::MarkHarnessTestCompleted(
1731 const std::string& test_id, HarnessTestStatus status,
1732 const std::string& message,
1733 const std::vector<std::string>& assertion_failures,
1734 const std::vector<std::string>& logs,
1735 const std::map<std::string, int32_t>& metrics) {
1736 absl::MutexLock lock(&harness_history_mutex_);
1737 auto it = harness_history_.find(test_id);
1738 if (it == harness_history_.end()) {
1739 return;
1740 }
1741 HarnessTestExecution& execution = it->second;
1742 execution.status = status;
1743 execution.completed_at = absl::Now();
1744 execution.duration = execution.completed_at - execution.started_at;
1745 execution.error_message = message;
1746 execution.metrics = metrics;
1747 execution.assertion_failures = assertion_failures;
1748 execution.logs.insert(execution.logs.end(), logs.begin(), logs.end());
1749
1750 bool capture_failure_context = status == HarnessTestStatus::kFailed ||
1751 status == HarnessTestStatus::kTimeout;
1752
1753 harness_aggregates_[execution.name].latest_execution = execution;
1754 harness_aggregates_[execution.name].total_runs += 1;
1755 if (status == HarnessTestStatus::kPassed) {
1756 harness_aggregates_[execution.name].pass_count += 1;
1757 } else if (status == HarnessTestStatus::kFailed ||
1758 status == HarnessTestStatus::kTimeout) {
1759 harness_aggregates_[execution.name].fail_count += 1;
1760 }
1761 harness_aggregates_[execution.name].total_duration += execution.duration;
1762 harness_aggregates_[execution.name].last_run = execution.completed_at;
1763
1764 if (capture_failure_context) {
1765 CaptureFailureContext(test_id);
1766 }
1767
1768 if (harness_listener_) {
1769 harness_listener_->OnHarnessTestUpdated(execution);
1770 }
1771}
1772#endif
1773
1774#if defined(YAZE_WITH_GRPC)
1775void TestManager::AppendHarnessTestLog(const std::string& test_id,
1776 const std::string& log_entry) {
1777 absl::MutexLock lock(&harness_history_mutex_);
1778 auto it = harness_history_.find(test_id);
1779 if (it == harness_history_.end()) {
1780 return;
1781 }
1782 HarnessTestExecution& execution = it->second;
1783 execution.logs.push_back(log_entry);
1784 harness_aggregates_[execution.name].latest_execution.logs = execution.logs;
1785}
1786#endif
1787
1788#if defined(YAZE_WITH_GRPC)
1789absl::StatusOr<HarnessTestExecution> TestManager::GetHarnessTestExecution(
1790 const std::string& test_id) const {
1791 absl::MutexLock lock(&harness_history_mutex_);
1792 auto it = harness_history_.find(test_id);
1793 if (it == harness_history_.end()) {
1794 return absl::NotFoundError(
1795 absl::StrFormat("Test ID '%s' not found", test_id));
1796 }
1797 return it->second;
1798}
1799#endif
1800
1801#if defined(YAZE_WITH_GRPC)
1802std::vector<HarnessTestSummary> TestManager::ListHarnessTestSummaries(
1803 const std::string& category_filter) const {
1804 absl::MutexLock lock(&harness_history_mutex_);
1805 std::vector<HarnessTestSummary> summaries;
1806 summaries.reserve(harness_aggregates_.size());
1807 for (const auto& [name, aggregate] : harness_aggregates_) {
1808 if (!category_filter.empty() && aggregate.category != category_filter) {
1809 continue;
1810 }
1811 HarnessTestSummary summary;
1812 summary.latest_execution = aggregate.latest_execution;
1813 summary.total_runs = aggregate.total_runs;
1814 summary.pass_count = aggregate.pass_count;
1815 summary.fail_count = aggregate.fail_count;
1816 summary.total_duration = aggregate.total_duration;
1817 summaries.push_back(summary);
1818 }
1819 std::sort(summaries.begin(), summaries.end(),
1820 [](const HarnessTestSummary& a, const HarnessTestSummary& b) {
1821 absl::Time time_a = a.latest_execution.completed_at;
1822 if (time_a == absl::InfinitePast()) {
1823 time_a = a.latest_execution.queued_at;
1824 }
1825 absl::Time time_b = b.latest_execution.completed_at;
1826 if (time_b == absl::InfinitePast()) {
1827 time_b = b.latest_execution.queued_at;
1828 }
1829 return time_a > time_b;
1830 });
1831 return summaries;
1832}
1833#endif
1834
1835#if defined(YAZE_WITH_GRPC)
1836void TestManager::CaptureFailureContext(const std::string& test_id) {
1837 absl::MutexLock lock(&harness_history_mutex_);
1838 auto it = harness_history_.find(test_id);
1839 if (it == harness_history_.end()) {
1840 return;
1841 }
1842 HarnessTestExecution& execution = it->second;
1843 // absl::MutexLock does not support Unlock/Lock; scope the lock instead
1844 {
1845 absl::MutexLock unlock_guard(&harness_history_mutex_);
1846 // This block is just to clarify lock scope, but the lock is already held
1847 // so we do nothing here.
1848 }
1849
1850 auto screenshot_artifact = test::CaptureHarnessScreenshot("harness_failures");
1851 std::string failure_context;
1852 if (screenshot_artifact.ok()) {
1853 failure_context = "Harness failure context captured successfully";
1854 } else {
1855 failure_context = "Harness failure context capture unavailable";
1856 }
1857
1858 execution.failure_context = failure_context;
1859 if (screenshot_artifact.ok()) {
1860 execution.screenshot_path = screenshot_artifact->file_path;
1861 execution.screenshot_size_bytes = screenshot_artifact->file_size_bytes;
1862 }
1863
1864 if (harness_listener_) {
1865 harness_listener_->OnHarnessTestUpdated(execution);
1866 }
1867}
1868#else
1869void TestManager::CaptureFailureContext(const std::string& test_id) {
1870 (void)test_id;
1871}
1872#endif
1873
1874#if defined(YAZE_WITH_GRPC)
1875void TestManager::TrimHarnessHistoryLocked() {
1876 while (harness_history_order_.size() > harness_history_limit_) {
1877 const std::string& oldest_test = harness_history_order_.front();
1878 harness_history_order_.pop_front();
1879 harness_history_.erase(oldest_test);
1880 }
1881}
1882
1883absl::Status TestManager::ReplayLastPlan() {
1884 return absl::FailedPreconditionError("Harness plan replay not available");
1885}
1886
1887void TestManager::SetHarnessListener(HarnessListener* listener) {
1888 absl::MutexLock lock(&mutex_);
1889 harness_listener_ = listener;
1890}
1891#else
1892absl::Status TestManager::ReplayLastPlan() {
1893 return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
1894}
1895#endif
1896
1897absl::Status TestManager::ShowHarnessDashboard() {
1898 // These methods are always available, but may return unimplemented without
1899 // GRPC
1900#if defined(YAZE_WITH_GRPC)
1901 return absl::OkStatus();
1902#else
1903 return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
1904#endif
1905}
1906
1907absl::Status TestManager::ShowHarnessActiveTests() {
1908#if defined(YAZE_WITH_GRPC)
1909 return absl::OkStatus();
1910#else
1911 return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
1912#endif
1913}
1914
1915void TestManager::RecordPlanSummary(const std::string& summary) {
1916 (void)summary;
1917}
1918
1919} // namespace test
1920} // 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:24
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
Definition rom.cc:243
auto filename() const
Definition rom.h:141
const auto & vector() const
Definition rom.h:139
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:164
auto data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
bool dirty() const
Definition rom.h:129
bool is_loaded() const
Definition rom.h:128
auto title() const
Definition rom.h:133
static Flags & get()
Definition features.h:92
static Arena & Get()
Definition arena.cc:19
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
std::string GenerateFailureScreenshotPath(const std::string &test_id)
const char * TestStatusToString(TestStatus status)
const char * TestCategoryToString(TestCategory category)
ImVec4 GetTestStatusColor(TestStatus status)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::string filename
Definition rom.h:29
std::chrono::time_point< std::chrono::steady_clock > timestamp
std::vector< TestResult > individual_results
std::chrono::milliseconds total_duration
float GetPassRate() const