Add Overworld accessors and enhance testing framework

- Introduced new methods in `Controller`, `EditorManager`, and `OverworldEditor` to access the `Overworld` instance, improving modularity and interaction with the overworld data.
- Added `GetTile` method in `Overworld` to retrieve tile data based on coordinates, enhancing functionality for tile manipulation.
- Implemented a new testing framework with automated GUI tests, including `CanvasSelectionTest` and `FrameworkSmokeTest`, to validate user interactions and ensure robustness.
- Updated `CMakeLists.txt` to include new test files and dependencies, streamlining the build process for testing.
- Refactored logging behavior to ensure proper file handling during logging operations, improving reliability in logging outputs.
This commit is contained in:
scawful
2025-09-30 19:32:34 -04:00
parent dcfdcf71d3
commit 5cc5c08122
14 changed files with 374 additions and 9 deletions

View File

@@ -74,6 +74,7 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF")
test_editor.h
testing.h
test_utils.h
test_utils.cc
# Unit Tests
unit/core/asar_wrapper_test.cc
@@ -101,6 +102,8 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF")
integration/editor/editor_integration_test.h
# E2E Tests (included in development builds)
e2e/canvas_selection_test.cc
e2e/framework_smoke_test.cc
e2e/rom_dependent/e2e_rom_test.cc
e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc
@@ -212,7 +215,6 @@ endif()
${PNG_LIBRARIES}
${OPENGL_LIBRARIES}
${CMAKE_DL_LIBS}
ImGui
gmock_main
gmock
gtest_main

View File

@@ -0,0 +1,76 @@
#define IMGUI_DEFINE_MATH_OPERATORS
#include "e2e/canvas_selection_test.h"
#include "app/core/controller.h"
#include "test_utils.h"
void E2ETest_CanvasSelectionTest(ImGuiTestContext* ctx)
{
yaze::test::gui::LoadRomInTest(ctx, "zelda3.sfc");
yaze::core::Controller* controller = (yaze::core::Controller*)ctx->Test->UserData;
yaze::zelda3::Overworld* overworld = controller->overworld();
// 1. Open the Overworld Editor
yaze::test::gui::OpenEditorInTest(ctx, "Overworld Editor");
// 2. Find the canvas
ctx->WindowFocus("Overworld Editor");
ctx->ItemClick("##Canvas");
// 3. Get the original tile data
// We'll check the 2x2 tile area at the paste location (600, 300)
// The tile at (600, 300) is at (75, 37) in tile coordinates.
// The overworld map is 128x128 tiles.
uint16_t orig_tile1 = overworld->GetTile(75, 37);
uint16_t orig_tile2 = overworld->GetTile(76, 37);
uint16_t orig_tile3 = overworld->GetTile(75, 38);
uint16_t orig_tile4 = overworld->GetTile(76, 38);
// 4. Perform a rectangle selection that crosses a 512px boundary
// The canvas is 1024x1024, with the top-left at (0,0).
// We'll select a 2x2 tile area from (510, 256) to (514, 258).
// This will cross the 512px boundary.
ctx->MouseMoveToPos(ImVec2(510, 256));
ctx->MouseDown(0);
ctx->MouseMoveToPos(ImVec2(514, 258));
ctx->MouseUp(0);
// 5. Copy the selection
ctx->KeyDown(ImGuiKey_LeftCtrl);
ctx->KeyPress(ImGuiKey_C);
ctx->KeyUp(ImGuiKey_LeftCtrl);
// 6. Paste the selection
ctx->MouseMoveToPos(ImVec2(600, 300));
ctx->KeyDown(ImGuiKey_LeftCtrl);
ctx->KeyPress(ImGuiKey_V);
ctx->KeyUp(ImGuiKey_LeftCtrl);
// 7. Verify that the pasted tiles are correct
uint16_t new_tile1 = overworld->GetTile(75, 37);
uint16_t new_tile2 = overworld->GetTile(76, 37);
uint16_t new_tile3 = overworld->GetTile(75, 38);
uint16_t new_tile4 = overworld->GetTile(76, 38);
// The bug is that the selection wraps around, so the pasted tiles are incorrect.
// We expect the new tiles to be different from the original tiles.
IM_CHECK_NE(orig_tile1, new_tile1);
IM_CHECK_NE(orig_tile2, new_tile2);
IM_CHECK_NE(orig_tile3, new_tile3);
IM_CHECK_NE(orig_tile4, new_tile4);
// We also expect the pasted tiles to be the same as the selected tiles.
// The selected tiles are at (63, 32) and (64, 32), (63, 33) and (64, 33).
uint16_t selected_tile1 = overworld->GetTile(63, 32);
uint16_t selected_tile2 = overworld->GetTile(64, 32);
uint16_t selected_tile3 = overworld->GetTile(63, 33);
uint16_t selected_tile4 = overworld->GetTile(64, 33);
IM_CHECK_EQ(new_tile1, selected_tile1);
IM_CHECK_EQ(new_tile2, selected_tile2);
IM_CHECK_EQ(new_tile3, selected_tile3);
IM_CHECK_EQ(new_tile4, selected_tile4);
ctx->LogInfo("Original tiles: %d, %d, %d, %d", orig_tile1, orig_tile2, orig_tile3, orig_tile4);
ctx->LogInfo("Selected tiles: %d, %d, %d, %d", selected_tile1, selected_tile2, selected_tile3, selected_tile4);
ctx->LogInfo("New tiles: %d, %d, %d, %d", new_tile1, new_tile2, new_tile3, new_tile4);
}

View File

@@ -0,0 +1,8 @@
#ifndef YAZE_TEST_E2E_CANVAS_SELECTION_TEST_H
#define YAZE_TEST_E2E_CANVAS_SELECTION_TEST_H
#include "imgui_test_engine/imgui_te_context.h"
void E2ETest_CanvasSelectionTest(ImGuiTestContext* ctx);
#endif // YAZE_TEST_E2E_CANVAS_SELECTION_TEST_H

View File

@@ -0,0 +1,16 @@
#include "e2e/framework_smoke_test.h"
#include "test_utils.h"
#include "imgui.h"
#include "imgui_test_engine/imgui_te_context.h"
// Smoke test for the E2E testing framework.
// This test is run by the `test-gui` command.
// It opens a window, clicks a button, and verifies that the button was clicked.
// The GUI for this test is rendered in `test/yaze_test.cc`.
void E2ETest_FrameworkSmokeTest(ImGuiTestContext* ctx)
{
yaze::test::gui::LoadRomInTest(ctx, "zelda3.sfc");
ctx->SetRef("Hello World Window");
ctx->ItemClick("Button");
ctx->ItemCheck("Clicked 1 times");
}

View File

@@ -0,0 +1,8 @@
#ifndef YAZE_TEST_E2E_FRAMEWORK_SMOKE_TEST_H
#define YAZE_TEST_E2E_FRAMEWORK_SMOKE_TEST_H
#include "imgui_test_engine/imgui_te_context.h"
void E2ETest_FrameworkSmokeTest(ImGuiTestContext* ctx);
#endif // YAZE_TEST_E2E_FRAMEWORK_SMOKE_TEST_H

19
test/test_utils.cc Normal file
View File

@@ -0,0 +1,19 @@
#include "test_utils.h"
#include "app/core/controller.h"
namespace yaze {
namespace test {
namespace gui {
void LoadRomInTest(ImGuiTestContext* ctx, const std::string& rom_path) {
yaze::core::Controller* controller = (yaze::core::Controller*)ctx->Test->UserData;
controller->OnEntry(rom_path);
}
void OpenEditorInTest(ImGuiTestContext* ctx, const std::string& editor_name) {
ctx->MenuClick(absl::StrFormat("Editors/%s", editor_name).c_str());
}
} // namespace gui
} // namespace test
} // namespace yaze

View File

@@ -10,6 +10,9 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "absl/strings/str_format.h"
#include "imgui_test_engine/imgui_te_context.h"
namespace yaze {
namespace test {
@@ -150,6 +153,13 @@ class RomDependentTest : public ::testing::Test {
std::vector<uint8_t> test_rom_;
};
namespace gui {
void LoadRomInTest(ImGuiTestContext* ctx, const std::string& rom_path);
void OpenEditorInTest(ImGuiTestContext* ctx, const std::string& editor_name);
} // namespace gui
} // namespace test
} // namespace yaze

View File

@@ -9,6 +9,17 @@
#include "absl/debugging/failure_signal_handler.h"
#include "absl/debugging/symbolize.h"
#include "imgui/imgui.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui_test_engine/imgui_te_context.h"
#include "imgui_test_engine/imgui_te_engine.h"
#include "imgui_test_engine/imgui_te_ui.h"
#include "app/core/window.h"
#include "app/core/controller.h"
#include "e2e/canvas_selection_test.h"
#include "e2e/framework_smoke_test.h"
// #include "test_editor.h" // Not used in main
namespace yaze {
@@ -36,6 +47,7 @@ struct TestConfig {
bool verbose = false;
bool skip_rom_tests = false;
bool enable_ui_tests = false;
bool show_gui = false;
};
// Parse command line arguments for better AI agent testing support
@@ -98,6 +110,8 @@ TestConfig ParseArguments(int argc, char* argv[]) {
config.enable_ui_tests = true;
} else if (arg == "--verbose") {
config.verbose = true;
} else if (arg == "--show-gui") {
config.show_gui = true;
} else if (arg.find("--") != 0) {
// Test pattern (not a flag)
config.mode = TestMode::kSpecific;
@@ -224,11 +238,121 @@ int main(int argc, char* argv[]) {
// Initialize Google Test
::testing::InitGoogleTest(&argc, argv);
// Run tests
int result = RUN_ALL_TESTS();
// Cleanup SDL
SDL_Quit();
return result;
if (config.enable_ui_tests) {
// Create a window
yaze::core::Window window;
yaze::core::CreateWindow(window, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
// Create a renderer
yaze::core::Renderer::Get().CreateRenderer(window.window_.get());
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
// Setup Dear ImGui style
ImGui::StyleColorsDark();
// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
ImGuiStyle& style = ImGui::GetStyle();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}
// Setup Platform/Renderer backends
ImGui_ImplSDL2_InitForSDLRenderer(window.window_.get(), yaze::core::Renderer::Get().renderer());
ImGui_ImplSDLRenderer2_Init(yaze::core::Renderer::Get().renderer());
// Setup test engine
ImGuiTestEngine* engine = ImGuiTestEngine_CreateContext();
ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(engine);
test_io.ConfigRunSpeed = ImGuiTestRunSpeed_Fast;
test_io.ConfigVerboseLevel = ImGuiTestVerboseLevel_Info;
test_io.ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Debug;
yaze::core::Controller controller;
// Register smoke test
ImGuiTest* smoke_test = IM_REGISTER_TEST(engine, "E2ETest", "FrameworkSmokeTest");
smoke_test->TestFunc = E2ETest_FrameworkSmokeTest;
// Register canvas selection test
ImGuiTest* canvas_test = IM_REGISTER_TEST(engine, "E2ETest", "CanvasSelectionTest");
canvas_test->TestFunc = E2ETest_CanvasSelectionTest;
canvas_test->UserData = &controller;
// Main loop
bool done = false;
while (!done) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
ImGui_ImplSDL2_ProcessEvent(&event);
if (event.type == SDL_QUIT) {
done = true;
}
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window.window_.get())) {
done = true;
}
}
// Start the Dear ImGui frame
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
// Render the UI
if (config.show_gui) {
ImGuiTestEngine_ShowTestEngineWindows(engine, &config.show_gui);
}
controller.DoRender();
// End the Dear ImGui frame
ImGui::Render();
yaze::core::Renderer::Get().Clear();
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), yaze::core::Renderer::Get().renderer());
yaze::core::Renderer::Get().Present();
// Update and Render additional Platform Windows
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
SDL_Window* backup_current_window = SDL_GL_GetCurrentWindow();
SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
SDL_GL_MakeCurrent(backup_current_window, backup_current_context);
}
// Run test engine
ImGuiTestEngine_PostSwap(engine);
}
// Get test result
ImGuiTestEngineResultSummary summary;
ImGuiTestEngine_GetResultSummary(engine, &summary);
int result = (summary.CountSuccess == summary.CountTested) ? 0 : 1;
// Cleanup
controller.OnExit();
ImGuiTestEngine_DestroyContext(engine);
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
yaze::core::ShutdownWindow(window);
SDL_Quit();
return result;
} else {
// Run tests
int result = RUN_ALL_TESTS();
// Cleanup SDL
SDL_Quit();
return result;
}
}