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