backend-infra-engineer: Release v0.3.3 snapshot
This commit is contained in:
@@ -13,13 +13,13 @@ if(YAZE_BUILD_TESTS)
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/incl
|
||||
${CMAKE_SOURCE_DIR}/test
|
||||
${CMAKE_SOURCE_DIR}/src/lib
|
||||
${CMAKE_SOURCE_DIR}/src/lib/imgui
|
||||
${CMAKE_SOURCE_DIR}/src/lib/imgui/backends
|
||||
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
|
||||
${CMAKE_SOURCE_DIR}/src/lib/SDL/include
|
||||
${CMAKE_SOURCE_DIR}/third_party/json/include
|
||||
${CMAKE_BINARY_DIR}/src/lib/SDL/include
|
||||
${CMAKE_SOURCE_DIR}/ext
|
||||
${CMAKE_SOURCE_DIR}/ext/imgui
|
||||
${CMAKE_SOURCE_DIR}/ext/imgui/backends
|
||||
${CMAKE_SOURCE_DIR}/ext/imgui_test_engine
|
||||
${CMAKE_SOURCE_DIR}/ext/SDL/include
|
||||
${CMAKE_SOURCE_DIR}/ext/json/include
|
||||
${CMAKE_BINARY_DIR}/ext/SDL/include
|
||||
${PROJECT_BINARY_DIR}
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ if(YAZE_BUILD_TESTS)
|
||||
absl::flags
|
||||
absl::flags_parse
|
||||
ImGui
|
||||
${SDL_TARGETS}
|
||||
${YAZE_SDL2_TARGETS}
|
||||
)
|
||||
|
||||
# Link ImGui Test Engine for GUI tests (always available when tests are built)
|
||||
@@ -43,16 +43,22 @@ if(YAZE_BUILD_TESTS)
|
||||
YAZE_GUI_TEST_TARGET=1) # Mark this as a GUI test target
|
||||
endif()
|
||||
|
||||
# Link protobuf with /WHOLEARCHIVE on Windows (instead of via libraries)
|
||||
if(WIN32 AND MSVC AND YAZE_WITH_GRPC AND YAZE_PROTOBUF_WHOLEARCHIVE_TARGETS)
|
||||
foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_WHOLEARCHIVE_TARGETS)
|
||||
if(TARGET ${_yaze_proto_target})
|
||||
get_target_property(_target_type ${_yaze_proto_target} TYPE)
|
||||
if(_target_type MATCHES ".*_LIBRARY")
|
||||
target_link_options(${suite_name} PRIVATE /WHOLEARCHIVE:$<TARGET_FILE:${_yaze_proto_target}>)
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
message(STATUS "Configuring Windows stack size for ${suite_name} to 16MB")
|
||||
if(MSVC)
|
||||
target_link_options(${suite_name} PRIVATE /STACK:16777216)
|
||||
# Force whole-archive linking for protobuf to ensure all symbols are included
|
||||
if(YAZE_WITH_GRPC AND MSVC AND YAZE_PROTOBUF_WHOLEARCHIVE_TARGETS)
|
||||
foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_WHOLEARCHIVE_TARGETS)
|
||||
target_link_options(${suite_name} PRIVATE /WHOLEARCHIVE:$<TARGET_FILE:${_yaze_proto_target}>)
|
||||
endforeach()
|
||||
endif()
|
||||
else()
|
||||
target_link_options(${suite_name} PRIVATE -Wl,--stack,16777216)
|
||||
endif()
|
||||
@@ -135,8 +141,14 @@ if(YAZE_BUILD_TESTS)
|
||||
e2e/framework_smoke_test.cc
|
||||
e2e/dungeon_editor_smoke_test.cc
|
||||
e2e/canvas_selection_test.cc
|
||||
integration/ai/ai_gui_controller_test.cc
|
||||
)
|
||||
|
||||
if(YAZE_ENABLE_AI_RUNTIME)
|
||||
list(APPEND GUI_TEST_SOURCES integration/ai/ai_gui_controller_test.cc)
|
||||
else()
|
||||
message(STATUS "Skipping AI GUI controller tests (YAZE_ENABLE_AI_RUNTIME=OFF)")
|
||||
endif()
|
||||
|
||||
yaze_add_test_suite(yaze_test_gui "gui;experimental" ON ${GUI_TEST_SOURCES})
|
||||
|
||||
# Add a single test entry to run the entire GUI suite headlessly
|
||||
@@ -147,12 +159,16 @@ if(YAZE_BUILD_TESTS)
|
||||
)
|
||||
set_tests_properties(headless_gui_suite PROPERTIES LABELS "headless_gui;experimental")
|
||||
|
||||
set(EXPERIMENTAL_TEST_SOURCES
|
||||
test_utils.cc
|
||||
integration/ai/test_ai_tile_placement.cc
|
||||
integration/ai/test_gemini_vision.cc
|
||||
)
|
||||
yaze_add_test_suite(yaze_test_experimental "experimental" OFF ${EXPERIMENTAL_TEST_SOURCES})
|
||||
if(YAZE_ENABLE_AI_RUNTIME)
|
||||
set(EXPERIMENTAL_TEST_SOURCES
|
||||
test_utils.cc
|
||||
integration/ai/test_ai_tile_placement.cc
|
||||
integration/ai/test_gemini_vision.cc
|
||||
)
|
||||
yaze_add_test_suite(yaze_test_experimental "experimental" OFF ${EXPERIMENTAL_TEST_SOURCES})
|
||||
else()
|
||||
message(STATUS "Skipping experimental AI suites (YAZE_ENABLE_AI_RUNTIME=OFF)")
|
||||
endif()
|
||||
|
||||
# --- Benchmark Test Suite ---
|
||||
set(BENCHMARK_TEST_SOURCES
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/debug/performance/performance_dashboard.h"
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
#include "app/gfx/render/atlas_renderer.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gfx/resource/memory_pool.h"
|
||||
#include "app/gfx/render/atlas_renderer.h"
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
#include "app/gfx/debug/performance/performance_dashboard.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gfx {
|
||||
@@ -32,8 +33,8 @@ class GraphicsOptimizationBenchmarks : public ::testing::Test {
|
||||
std::vector<uint8_t> data(width * height);
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_int_distribution<> dis(0, 15); // 4-bit color indices
|
||||
|
||||
std::uniform_int_distribution<> dis(0, 15); // 4-bit color indices
|
||||
|
||||
for (auto& pixel : data) {
|
||||
pixel = static_cast<uint8_t>(dis(gen));
|
||||
}
|
||||
@@ -53,157 +54,172 @@ class GraphicsOptimizationBenchmarks : public ::testing::Test {
|
||||
TEST_F(GraphicsOptimizationBenchmarks, PaletteLookupPerformance) {
|
||||
const int kIterations = 10000;
|
||||
const int kBitmapSize = 128;
|
||||
|
||||
|
||||
auto test_data = CreateTestBitmapData(kBitmapSize, kBitmapSize);
|
||||
auto test_palette = CreateTestPalette();
|
||||
|
||||
|
||||
Bitmap bitmap(kBitmapSize, kBitmapSize, 8, test_data, test_palette);
|
||||
|
||||
|
||||
// Benchmark palette lookup
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (int i = 0; i < kIterations; ++i) {
|
||||
SnesColor test_color(i % 16, (i + 1) % 16, (i + 2) % 16);
|
||||
uint8_t index = bitmap.FindColorIndex(test_color);
|
||||
(void)index; // Prevent optimization
|
||||
(void)index; // Prevent optimization
|
||||
}
|
||||
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
double avg_time_us = static_cast<double>(duration.count()) / kIterations;
|
||||
|
||||
|
||||
// Verify optimization is working (should be < 1μs per lookup)
|
||||
EXPECT_LT(avg_time_us, 1.0) << "Palette lookup should be optimized to < 1μs";
|
||||
|
||||
std::cout << "Palette lookup average time: " << avg_time_us << " μs" << std::endl;
|
||||
|
||||
std::cout << "Palette lookup average time: " << avg_time_us << " μs"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// Benchmark dirty region tracking
|
||||
TEST_F(GraphicsOptimizationBenchmarks, DirtyRegionTrackingPerformance) {
|
||||
const int kBitmapSize = 256;
|
||||
const int kPixelUpdates = 1000;
|
||||
|
||||
|
||||
auto test_data = CreateTestBitmapData(kBitmapSize, kBitmapSize);
|
||||
auto test_palette = CreateTestPalette();
|
||||
|
||||
|
||||
Bitmap bitmap(kBitmapSize, kBitmapSize, 8, test_data, test_palette);
|
||||
|
||||
|
||||
// Benchmark pixel updates with dirty region tracking
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (int i = 0; i < kPixelUpdates; ++i) {
|
||||
int x = i % kBitmapSize;
|
||||
int y = (i * 7) % kBitmapSize; // Spread updates across bitmap
|
||||
int y = (i * 7) % kBitmapSize; // Spread updates across bitmap
|
||||
SnesColor color(i % 16, (i + 1) % 16, (i + 2) % 16);
|
||||
bitmap.SetPixel(x, y, color);
|
||||
}
|
||||
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
double avg_time_us = static_cast<double>(duration.count()) / kPixelUpdates;
|
||||
|
||||
|
||||
// Verify dirty region tracking is efficient
|
||||
EXPECT_LT(avg_time_us, 10.0) << "Pixel updates should be < 10μs with dirty region tracking";
|
||||
|
||||
std::cout << "Pixel update average time: " << avg_time_us << " μs" << std::endl;
|
||||
EXPECT_LT(avg_time_us, 10.0)
|
||||
<< "Pixel updates should be < 10μs with dirty region tracking";
|
||||
|
||||
std::cout << "Pixel update average time: " << avg_time_us << " μs"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// Benchmark memory pool allocation
|
||||
TEST_F(GraphicsOptimizationBenchmarks, MemoryPoolAllocationPerformance) {
|
||||
const int kAllocations = 10000;
|
||||
const size_t kAllocationSize = 1024; // 1KB blocks
|
||||
|
||||
const size_t kAllocationSize = 1024; // 1KB blocks
|
||||
|
||||
auto& memory_pool = MemoryPool::Get();
|
||||
|
||||
|
||||
std::vector<void*> allocations;
|
||||
allocations.reserve(kAllocations);
|
||||
|
||||
|
||||
// Benchmark allocations
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (int i = 0; i < kAllocations; ++i) {
|
||||
void* ptr = memory_pool.Allocate(kAllocationSize);
|
||||
allocations.push_back(ptr);
|
||||
}
|
||||
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
double avg_time_us = static_cast<double>(duration.count()) / kAllocations;
|
||||
|
||||
|
||||
// Verify memory pool is faster than system malloc
|
||||
EXPECT_LT(avg_time_us, 1.0) << "Memory pool allocation should be < 1μs";
|
||||
|
||||
std::cout << "Memory pool allocation average time: " << avg_time_us << " μs" << std::endl;
|
||||
|
||||
|
||||
std::cout << "Memory pool allocation average time: " << avg_time_us << " μs"
|
||||
<< std::endl;
|
||||
|
||||
// Benchmark deallocations
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (void* ptr : allocations) {
|
||||
memory_pool.Deallocate(ptr);
|
||||
}
|
||||
|
||||
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
|
||||
avg_time_us = static_cast<double>(duration.count()) / kAllocations;
|
||||
|
||||
|
||||
EXPECT_LT(avg_time_us, 1.0) << "Memory pool deallocation should be < 1μs";
|
||||
|
||||
std::cout << "Memory pool deallocation average time: " << avg_time_us << " μs" << std::endl;
|
||||
|
||||
std::cout << "Memory pool deallocation average time: " << avg_time_us << " μs"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// Benchmark batch texture updates
|
||||
TEST_F(GraphicsOptimizationBenchmarks, BatchTextureUpdatePerformance) {
|
||||
const int kTextureUpdates = 100;
|
||||
const int kBitmapSize = 64;
|
||||
|
||||
|
||||
auto test_data = CreateTestBitmapData(kBitmapSize, kBitmapSize);
|
||||
auto test_palette = CreateTestPalette();
|
||||
|
||||
|
||||
std::vector<Bitmap> bitmaps;
|
||||
bitmaps.reserve(kTextureUpdates);
|
||||
|
||||
|
||||
// Create test bitmaps
|
||||
for (int i = 0; i < kTextureUpdates; ++i) {
|
||||
bitmaps.emplace_back(kBitmapSize, kBitmapSize, 8, test_data, test_palette);
|
||||
}
|
||||
|
||||
|
||||
auto& arena = Arena::Get();
|
||||
|
||||
|
||||
// Benchmark individual texture updates
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (auto& bitmap : bitmaps) {
|
||||
gfx::Arena::Get().QueueTextureCommand(
|
||||
gfx::Arena::TextureCommandType::UPDATE, &bitmap);
|
||||
}
|
||||
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto individual_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto individual_duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
// Benchmark batch texture updates
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (auto& bitmap : bitmaps) {
|
||||
gfx::Arena::Get().QueueTextureCommand(
|
||||
gfx::Arena::TextureCommandType::UPDATE, &bitmap);
|
||||
}
|
||||
gfx::Arena::Get().ProcessTextureQueue(nullptr); // Process all at once
|
||||
|
||||
gfx::Arena::Get().ProcessTextureQueue(nullptr); // Process all at once
|
||||
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
auto batch_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto batch_duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
// Verify batch updates are faster
|
||||
double individual_avg = static_cast<double>(individual_duration.count()) / kTextureUpdates;
|
||||
double batch_avg = static_cast<double>(batch_duration.count()) / kTextureUpdates;
|
||||
|
||||
EXPECT_LT(batch_avg, individual_avg) << "Batch updates should be faster than individual updates";
|
||||
|
||||
std::cout << "Individual texture update average: " << individual_avg << " μs" << std::endl;
|
||||
std::cout << "Batch texture update average: " << batch_avg << " μs" << std::endl;
|
||||
double individual_avg =
|
||||
static_cast<double>(individual_duration.count()) / kTextureUpdates;
|
||||
double batch_avg =
|
||||
static_cast<double>(batch_duration.count()) / kTextureUpdates;
|
||||
|
||||
EXPECT_LT(batch_avg, individual_avg)
|
||||
<< "Batch updates should be faster than individual updates";
|
||||
|
||||
std::cout << "Individual texture update average: " << individual_avg << " μs"
|
||||
<< std::endl;
|
||||
std::cout << "Batch texture update average: " << batch_avg << " μs"
|
||||
<< std::endl;
|
||||
std::cout << "Speedup: " << (individual_avg / batch_avg) << "x" << std::endl;
|
||||
}
|
||||
|
||||
@@ -211,21 +227,21 @@ TEST_F(GraphicsOptimizationBenchmarks, BatchTextureUpdatePerformance) {
|
||||
TEST_F(GraphicsOptimizationBenchmarks, AtlasRenderingPerformance) {
|
||||
const int kBitmaps = 50;
|
||||
const int kBitmapSize = 32;
|
||||
|
||||
|
||||
auto test_data = CreateTestBitmapData(kBitmapSize, kBitmapSize);
|
||||
auto test_palette = CreateTestPalette();
|
||||
|
||||
|
||||
std::vector<Bitmap> bitmaps;
|
||||
bitmaps.reserve(kBitmaps);
|
||||
|
||||
|
||||
// Create test bitmaps
|
||||
for (int i = 0; i < kBitmaps; ++i) {
|
||||
bitmaps.emplace_back(kBitmapSize, kBitmapSize, 8, test_data, test_palette);
|
||||
}
|
||||
|
||||
|
||||
auto& atlas_renderer = AtlasRenderer::Get();
|
||||
atlas_renderer.Initialize(nullptr, 512); // Initialize with 512x512 atlas
|
||||
|
||||
atlas_renderer.Initialize(nullptr, 512); // Initialize with 512x512 atlas
|
||||
|
||||
// Add bitmaps to atlas
|
||||
std::vector<int> atlas_ids;
|
||||
for (auto& bitmap : bitmaps) {
|
||||
@@ -234,56 +250,61 @@ TEST_F(GraphicsOptimizationBenchmarks, AtlasRenderingPerformance) {
|
||||
atlas_ids.push_back(atlas_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create render commands
|
||||
std::vector<RenderCommand> render_commands;
|
||||
for (size_t i = 0; i < atlas_ids.size(); ++i) {
|
||||
render_commands.emplace_back(atlas_ids[i], i * 10.0f, i * 10.0f);
|
||||
}
|
||||
|
||||
|
||||
// Benchmark atlas rendering
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
atlas_renderer.RenderBatch(render_commands);
|
||||
}
|
||||
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
double avg_time_us = static_cast<double>(duration.count()) / 1000.0;
|
||||
|
||||
|
||||
// Verify atlas rendering is efficient
|
||||
EXPECT_LT(avg_time_us, 100.0) << "Atlas rendering should be < 100μs per batch";
|
||||
|
||||
std::cout << "Atlas rendering average time: " << avg_time_us << " μs per batch" << std::endl;
|
||||
|
||||
EXPECT_LT(avg_time_us, 100.0)
|
||||
<< "Atlas rendering should be < 100μs per batch";
|
||||
|
||||
std::cout << "Atlas rendering average time: " << avg_time_us
|
||||
<< " μs per batch" << std::endl;
|
||||
|
||||
// Get atlas statistics
|
||||
auto stats = atlas_renderer.GetStats();
|
||||
std::cout << "Atlas utilization: " << stats.utilization_percent << "%" << std::endl;
|
||||
std::cout << "Atlas utilization: " << stats.utilization_percent << "%"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// Benchmark performance profiler overhead
|
||||
TEST_F(GraphicsOptimizationBenchmarks, PerformanceProfilerOverhead) {
|
||||
const int kOperations = 100000;
|
||||
|
||||
|
||||
auto& profiler = PerformanceProfiler::Get();
|
||||
|
||||
|
||||
// Benchmark operations without profiling
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (int i = 0; i < kOperations; ++i) {
|
||||
// Simulate some work
|
||||
volatile int result = i * i;
|
||||
(void)result;
|
||||
}
|
||||
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto no_profiling_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto no_profiling_duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
// Benchmark operations with profiling
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (int i = 0; i < kOperations; ++i) {
|
||||
profiler.StartTimer("test_operation");
|
||||
// Simulate some work
|
||||
@@ -291,20 +312,26 @@ TEST_F(GraphicsOptimizationBenchmarks, PerformanceProfilerOverhead) {
|
||||
(void)result;
|
||||
profiler.EndTimer("test_operation");
|
||||
}
|
||||
|
||||
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
auto with_profiling_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto with_profiling_duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
// Calculate profiling overhead
|
||||
double no_profiling_avg = static_cast<double>(no_profiling_duration.count()) / kOperations;
|
||||
double with_profiling_avg = static_cast<double>(with_profiling_duration.count()) / kOperations;
|
||||
double no_profiling_avg =
|
||||
static_cast<double>(no_profiling_duration.count()) / kOperations;
|
||||
double with_profiling_avg =
|
||||
static_cast<double>(with_profiling_duration.count()) / kOperations;
|
||||
double overhead = with_profiling_avg - no_profiling_avg;
|
||||
|
||||
|
||||
// Verify profiling overhead is minimal
|
||||
EXPECT_LT(overhead, 1.0) << "Profiling overhead should be < 1μs per operation";
|
||||
|
||||
std::cout << "No profiling average: " << no_profiling_avg << " μs" << std::endl;
|
||||
std::cout << "With profiling average: " << with_profiling_avg << " μs" << std::endl;
|
||||
EXPECT_LT(overhead, 1.0)
|
||||
<< "Profiling overhead should be < 1μs per operation";
|
||||
|
||||
std::cout << "No profiling average: " << no_profiling_avg << " μs"
|
||||
<< std::endl;
|
||||
std::cout << "With profiling average: " << with_profiling_avg << " μs"
|
||||
<< std::endl;
|
||||
std::cout << "Profiling overhead: " << overhead << " μs" << std::endl;
|
||||
}
|
||||
|
||||
@@ -312,63 +339,69 @@ TEST_F(GraphicsOptimizationBenchmarks, PerformanceProfilerOverhead) {
|
||||
TEST_F(GraphicsOptimizationBenchmarks, AtlasRenderingPerformance2) {
|
||||
const int kNumTiles = 100;
|
||||
const int kTileSize = 16;
|
||||
|
||||
|
||||
auto& atlas_renderer = AtlasRenderer::Get();
|
||||
auto& profiler = PerformanceProfiler::Get();
|
||||
|
||||
|
||||
// Create test tiles
|
||||
std::vector<Bitmap> test_tiles;
|
||||
std::vector<int> atlas_ids;
|
||||
|
||||
|
||||
for (int i = 0; i < kNumTiles; ++i) {
|
||||
auto tile_data = CreateTestBitmapData(kTileSize, kTileSize);
|
||||
auto tile_palette = CreateTestPalette();
|
||||
|
||||
|
||||
test_tiles.emplace_back(kTileSize, kTileSize, 8, tile_data, tile_palette);
|
||||
|
||||
|
||||
// Add to atlas
|
||||
int atlas_id = atlas_renderer.AddBitmap(test_tiles.back());
|
||||
if (atlas_id >= 0) {
|
||||
atlas_ids.push_back(atlas_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Benchmark individual tile rendering
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (int i = 0; i < kNumTiles; ++i) {
|
||||
if (i < atlas_ids.size()) {
|
||||
atlas_renderer.RenderBitmap(atlas_ids[i], i * 20.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto individual_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto individual_duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
// Benchmark batch rendering
|
||||
std::vector<RenderCommand> render_commands;
|
||||
for (size_t i = 0; i < atlas_ids.size(); ++i) {
|
||||
render_commands.emplace_back(atlas_ids[i], i * 20.0f, 100.0f);
|
||||
}
|
||||
|
||||
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
atlas_renderer.RenderBatch(render_commands);
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
auto batch_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto batch_duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
// Verify batch rendering is faster
|
||||
EXPECT_LT(batch_duration.count(), individual_duration.count())
|
||||
EXPECT_LT(batch_duration.count(), individual_duration.count())
|
||||
<< "Batch rendering should be faster than individual rendering";
|
||||
|
||||
|
||||
// Get atlas statistics
|
||||
auto stats = atlas_renderer.GetStats();
|
||||
EXPECT_GT(stats.total_entries, 0) << "Atlas should contain entries";
|
||||
EXPECT_GT(stats.used_entries, 0) << "Atlas should have used entries";
|
||||
|
||||
std::cout << "Individual rendering: " << individual_duration.count() << " μs" << std::endl;
|
||||
std::cout << "Batch rendering: " << batch_duration.count() << " μs" << std::endl;
|
||||
std::cout << "Atlas entries: " << stats.used_entries << "/" << stats.total_entries << std::endl;
|
||||
std::cout << "Atlas utilization: " << stats.utilization_percent << "%" << std::endl;
|
||||
|
||||
std::cout << "Individual rendering: " << individual_duration.count() << " μs"
|
||||
<< std::endl;
|
||||
std::cout << "Batch rendering: " << batch_duration.count() << " μs"
|
||||
<< std::endl;
|
||||
std::cout << "Atlas entries: " << stats.used_entries << "/"
|
||||
<< stats.total_entries << std::endl;
|
||||
std::cout << "Atlas utilization: " << stats.utilization_percent << "%"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// Integration test for overall performance
|
||||
@@ -376,68 +409,73 @@ TEST_F(GraphicsOptimizationBenchmarks, OverallPerformanceIntegration) {
|
||||
const int kGraphicsSheets = 10;
|
||||
const int kTilesPerSheet = 100;
|
||||
const int kTileSize = 16;
|
||||
|
||||
|
||||
auto& memory_pool = MemoryPool::Get();
|
||||
auto& arena = Arena::Get();
|
||||
auto& profiler = PerformanceProfiler::Get();
|
||||
|
||||
|
||||
// Simulate loading graphics sheets
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
std::vector<Bitmap> graphics_sheets;
|
||||
for (int sheet = 0; sheet < kGraphicsSheets; ++sheet) {
|
||||
auto sheet_data = CreateTestBitmapData(kTileSize * 10, kTileSize * 10);
|
||||
auto sheet_palette = CreateTestPalette();
|
||||
|
||||
graphics_sheets.emplace_back(kTileSize * 10, kTileSize * 10, 8, sheet_data, sheet_palette);
|
||||
|
||||
graphics_sheets.emplace_back(kTileSize * 10, kTileSize * 10, 8, sheet_data,
|
||||
sheet_palette);
|
||||
}
|
||||
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto load_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto load_duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
// Simulate tile operations
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (int sheet = 0; sheet < kGraphicsSheets; ++sheet) {
|
||||
for (int tile = 0; tile < kTilesPerSheet; ++tile) {
|
||||
int x = (tile % 10) * kTileSize;
|
||||
int y = (tile / 10) * kTileSize;
|
||||
|
||||
|
||||
SnesColor color(tile % 16, (tile + 1) % 16, (tile + 2) % 16);
|
||||
graphics_sheets[sheet].SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
auto tile_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto tile_duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
// Simulate batch texture updates
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
for (auto& sheet : graphics_sheets) {
|
||||
arena.QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, &sheet);
|
||||
}
|
||||
arena.ProcessTextureQueue(nullptr);
|
||||
|
||||
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
auto batch_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
auto batch_duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
// Verify overall performance
|
||||
double load_time_ms = static_cast<double>(load_duration.count()) / 1000.0;
|
||||
double tile_time_ms = static_cast<double>(tile_duration.count()) / 1000.0;
|
||||
double batch_time_ms = static_cast<double>(batch_duration.count()) / 1000.0;
|
||||
|
||||
|
||||
EXPECT_LT(load_time_ms, 100.0) << "Graphics sheet loading should be < 100ms";
|
||||
EXPECT_LT(tile_time_ms, 50.0) << "Tile operations should be < 50ms";
|
||||
EXPECT_LT(batch_time_ms, 10.0) << "Batch updates should be < 10ms";
|
||||
|
||||
|
||||
std::cout << "Graphics sheet loading: " << load_time_ms << " ms" << std::endl;
|
||||
std::cout << "Tile operations: " << tile_time_ms << " ms" << std::endl;
|
||||
std::cout << "Batch updates: " << batch_time_ms << " ms" << std::endl;
|
||||
|
||||
|
||||
// Get performance summary
|
||||
auto summary = PerformanceDashboard::Get().GetSummary();
|
||||
std::cout << "Optimization score: " << summary.optimization_score << "/100" << std::endl;
|
||||
std::cout << "Optimization score: " << summary.optimization_score << "/100"
|
||||
<< std::endl;
|
||||
std::cout << "Status: " << summary.status_message << std::endl;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "cli/service/resources/command_context.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
@@ -16,7 +16,7 @@ class CommandContextTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Initialize mock ROM for testing
|
||||
std::vector<uint8_t> test_data(1024, 0); // 1KB of empty data
|
||||
std::vector<uint8_t> test_data(1024, 0); // 1KB of empty data
|
||||
auto status = mock_rom_.SetTestData(test_data);
|
||||
ASSERT_TRUE(status.ok());
|
||||
}
|
||||
@@ -27,9 +27,9 @@ class CommandContextTest : public ::testing::Test {
|
||||
TEST_F(CommandContextTest, LoadsRomFromConfig) {
|
||||
CommandContext::Config config;
|
||||
config.use_mock_rom = true;
|
||||
|
||||
|
||||
CommandContext context(config);
|
||||
|
||||
|
||||
auto rom_or = context.GetRom();
|
||||
ASSERT_TRUE(rom_or.ok());
|
||||
EXPECT_TRUE(rom_or.value()->is_loaded());
|
||||
@@ -38,9 +38,9 @@ TEST_F(CommandContextTest, LoadsRomFromConfig) {
|
||||
TEST_F(CommandContextTest, UsesExternalRomContext) {
|
||||
CommandContext::Config config;
|
||||
config.external_rom_context = &mock_rom_;
|
||||
|
||||
|
||||
CommandContext context(config);
|
||||
|
||||
|
||||
auto rom_or = context.GetRom();
|
||||
ASSERT_TRUE(rom_or.ok());
|
||||
EXPECT_EQ(rom_or.value(), &mock_rom_);
|
||||
@@ -49,9 +49,9 @@ TEST_F(CommandContextTest, UsesExternalRomContext) {
|
||||
TEST_F(CommandContextTest, LoadsRomFromPath) {
|
||||
CommandContext::Config config;
|
||||
config.rom_path = "test_rom.sfc"; // This would need a real ROM file
|
||||
|
||||
|
||||
CommandContext context(config);
|
||||
|
||||
|
||||
// This test would need a real ROM file to pass
|
||||
// For now, we expect it to fail gracefully
|
||||
auto rom_or = context.GetRom();
|
||||
@@ -61,12 +61,12 @@ TEST_F(CommandContextTest, LoadsRomFromPath) {
|
||||
TEST_F(CommandContextTest, EnsuresLabelsLoaded) {
|
||||
CommandContext::Config config;
|
||||
config.use_mock_rom = true;
|
||||
|
||||
|
||||
CommandContext context(config);
|
||||
|
||||
|
||||
auto rom_or = context.GetRom();
|
||||
ASSERT_TRUE(rom_or.ok());
|
||||
|
||||
|
||||
auto status = context.EnsureLabelsLoaded(rom_or.value());
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
@@ -74,18 +74,18 @@ TEST_F(CommandContextTest, EnsuresLabelsLoaded) {
|
||||
TEST_F(CommandContextTest, GetFormatReturnsConfigFormat) {
|
||||
CommandContext::Config config;
|
||||
config.format = "text";
|
||||
|
||||
|
||||
CommandContext context(config);
|
||||
|
||||
|
||||
EXPECT_EQ(context.GetFormat(), "text");
|
||||
}
|
||||
|
||||
TEST_F(CommandContextTest, IsVerboseReturnsConfigVerbose) {
|
||||
CommandContext::Config config;
|
||||
config.verbose = true;
|
||||
|
||||
|
||||
CommandContext context(config);
|
||||
|
||||
|
||||
EXPECT_TRUE(context.IsVerbose());
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ class ArgumentParserTest : public ::testing::Test {
|
||||
TEST_F(ArgumentParserTest, ParsesStringArguments) {
|
||||
std::vector<std::string> args = {"--type=dungeon", "--format", "json"};
|
||||
ArgumentParser parser(args);
|
||||
|
||||
|
||||
EXPECT_EQ(parser.GetString("type").value(), "dungeon");
|
||||
EXPECT_EQ(parser.GetString("format").value(), "json");
|
||||
}
|
||||
@@ -106,11 +106,11 @@ TEST_F(ArgumentParserTest, ParsesStringArguments) {
|
||||
TEST_F(ArgumentParserTest, ParsesIntArguments) {
|
||||
std::vector<std::string> args = {"--room=0x12", "--count", "42"};
|
||||
ArgumentParser parser(args);
|
||||
|
||||
|
||||
auto room_or = parser.GetInt("room");
|
||||
ASSERT_TRUE(room_or.ok());
|
||||
EXPECT_EQ(room_or.value(), 0x12);
|
||||
|
||||
|
||||
auto count_or = parser.GetInt("count");
|
||||
ASSERT_TRUE(count_or.ok());
|
||||
EXPECT_EQ(count_or.value(), 42);
|
||||
@@ -119,11 +119,11 @@ TEST_F(ArgumentParserTest, ParsesIntArguments) {
|
||||
TEST_F(ArgumentParserTest, ParsesHexArguments) {
|
||||
std::vector<std::string> args = {"--address=0x1234", "--value", "0xFF"};
|
||||
ArgumentParser parser(args);
|
||||
|
||||
|
||||
auto addr_or = parser.GetHex("address");
|
||||
ASSERT_TRUE(addr_or.ok());
|
||||
EXPECT_EQ(addr_or.value(), 0x1234);
|
||||
|
||||
|
||||
auto value_or = parser.GetHex("value");
|
||||
ASSERT_TRUE(value_or.ok());
|
||||
EXPECT_EQ(value_or.value(), 0xFF);
|
||||
@@ -132,27 +132,29 @@ TEST_F(ArgumentParserTest, ParsesHexArguments) {
|
||||
TEST_F(ArgumentParserTest, DetectsFlags) {
|
||||
std::vector<std::string> args = {"--verbose", "--debug", "--format=json"};
|
||||
ArgumentParser parser(args);
|
||||
|
||||
|
||||
EXPECT_TRUE(parser.HasFlag("verbose"));
|
||||
EXPECT_TRUE(parser.HasFlag("debug"));
|
||||
EXPECT_FALSE(parser.HasFlag("format")); // format is a value, not a flag
|
||||
}
|
||||
|
||||
TEST_F(ArgumentParserTest, GetsPositionalArguments) {
|
||||
std::vector<std::string> args = {"command", "--flag", "value", "positional1", "positional2"};
|
||||
std::vector<std::string> args = {"command", "--flag", "value", "positional1",
|
||||
"positional2"};
|
||||
ArgumentParser parser(args);
|
||||
|
||||
|
||||
auto positional = parser.GetPositional();
|
||||
EXPECT_THAT(positional, ::testing::ElementsAre("command", "positional1", "positional2"));
|
||||
EXPECT_THAT(positional,
|
||||
::testing::ElementsAre("command", "positional1", "positional2"));
|
||||
}
|
||||
|
||||
TEST_F(ArgumentParserTest, ValidatesRequiredArguments) {
|
||||
std::vector<std::string> args = {"--type=dungeon"};
|
||||
ArgumentParser parser(args);
|
||||
|
||||
|
||||
auto status = parser.RequireArgs({"type"});
|
||||
EXPECT_TRUE(status.ok());
|
||||
|
||||
|
||||
status = parser.RequireArgs({"type", "missing"});
|
||||
EXPECT_FALSE(status.ok());
|
||||
}
|
||||
@@ -160,10 +162,10 @@ TEST_F(ArgumentParserTest, ValidatesRequiredArguments) {
|
||||
TEST_F(ArgumentParserTest, HandlesMissingArguments) {
|
||||
std::vector<std::string> args = {"--type=dungeon"};
|
||||
ArgumentParser parser(args);
|
||||
|
||||
|
||||
auto missing = parser.GetString("missing");
|
||||
EXPECT_FALSE(missing.has_value());
|
||||
|
||||
|
||||
auto int_missing = parser.GetInt("missing");
|
||||
EXPECT_FALSE(int_missing.ok());
|
||||
}
|
||||
@@ -178,11 +180,11 @@ TEST_F(OutputFormatterTest, CreatesFromString) {
|
||||
auto json_formatter = OutputFormatter::FromString("json");
|
||||
ASSERT_TRUE(json_formatter.ok());
|
||||
EXPECT_TRUE(json_formatter.value().IsJson());
|
||||
|
||||
|
||||
auto text_formatter = OutputFormatter::FromString("text");
|
||||
ASSERT_TRUE(text_formatter.ok());
|
||||
EXPECT_TRUE(text_formatter.value().IsText());
|
||||
|
||||
|
||||
auto invalid_formatter = OutputFormatter::FromString("invalid");
|
||||
EXPECT_FALSE(invalid_formatter.ok());
|
||||
}
|
||||
@@ -191,22 +193,22 @@ TEST_F(OutputFormatterTest, GeneratesValidJson) {
|
||||
auto formatter_or = OutputFormatter::FromString("json");
|
||||
ASSERT_TRUE(formatter_or.ok());
|
||||
auto formatter = std::move(formatter_or.value());
|
||||
|
||||
|
||||
formatter.BeginObject("Test");
|
||||
formatter.AddField("string_field", "value");
|
||||
formatter.AddField("int_field", 42);
|
||||
formatter.AddField("bool_field", true);
|
||||
formatter.AddHexField("hex_field", 0x1234, 4);
|
||||
|
||||
|
||||
formatter.BeginArray("array_field");
|
||||
formatter.AddArrayItem("item1");
|
||||
formatter.AddArrayItem("item2");
|
||||
formatter.EndArray();
|
||||
|
||||
|
||||
formatter.EndObject();
|
||||
|
||||
|
||||
std::string output = formatter.GetOutput();
|
||||
|
||||
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("\"string_field\": \"value\""));
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("\"int_field\": 42"));
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("\"bool_field\": true"));
|
||||
@@ -220,22 +222,22 @@ TEST_F(OutputFormatterTest, GeneratesValidText) {
|
||||
auto formatter_or = OutputFormatter::FromString("text");
|
||||
ASSERT_TRUE(formatter_or.ok());
|
||||
auto formatter = std::move(formatter_or.value());
|
||||
|
||||
|
||||
formatter.BeginObject("Test Object");
|
||||
formatter.AddField("string_field", "value");
|
||||
formatter.AddField("int_field", 42);
|
||||
formatter.AddField("bool_field", true);
|
||||
formatter.AddHexField("hex_field", 0x1234, 4);
|
||||
|
||||
|
||||
formatter.BeginArray("array_field");
|
||||
formatter.AddArrayItem("item1");
|
||||
formatter.AddArrayItem("item2");
|
||||
formatter.EndArray();
|
||||
|
||||
|
||||
formatter.EndObject();
|
||||
|
||||
|
||||
std::string output = formatter.GetOutput();
|
||||
|
||||
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("=== Test Object ==="));
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("string_field : value"));
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("int_field : 42"));
|
||||
@@ -250,15 +252,15 @@ TEST_F(OutputFormatterTest, EscapesJsonStrings) {
|
||||
auto formatter_or = OutputFormatter::FromString("json");
|
||||
ASSERT_TRUE(formatter_or.ok());
|
||||
auto formatter = std::move(formatter_or.value());
|
||||
|
||||
|
||||
formatter.BeginObject("Test");
|
||||
formatter.AddField("quotes", "He said \"Hello\"");
|
||||
formatter.AddField("newlines", "Line1\nLine2");
|
||||
formatter.AddField("backslashes", "Path\\to\\file");
|
||||
formatter.EndObject();
|
||||
|
||||
|
||||
std::string output = formatter.GetOutput();
|
||||
|
||||
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("\\\""));
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("\\n"));
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("\\\\"));
|
||||
@@ -268,10 +270,10 @@ TEST_F(OutputFormatterTest, HandlesEmptyObjects) {
|
||||
auto formatter_or = OutputFormatter::FromString("json");
|
||||
ASSERT_TRUE(formatter_or.ok());
|
||||
auto formatter = std::move(formatter_or.value());
|
||||
|
||||
|
||||
formatter.BeginObject("Empty");
|
||||
formatter.EndObject();
|
||||
|
||||
|
||||
std::string output = formatter.GetOutput();
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("{}"));
|
||||
}
|
||||
@@ -280,12 +282,12 @@ TEST_F(OutputFormatterTest, HandlesEmptyArrays) {
|
||||
auto formatter_or = OutputFormatter::FromString("json");
|
||||
ASSERT_TRUE(formatter_or.ok());
|
||||
auto formatter = std::move(formatter_or.value());
|
||||
|
||||
|
||||
formatter.BeginObject("Test");
|
||||
formatter.BeginArray("empty_array");
|
||||
formatter.EndArray();
|
||||
formatter.EndObject();
|
||||
|
||||
|
||||
std::string output = formatter.GetOutput();
|
||||
EXPECT_THAT(output, ::testing::HasSubstr("\"empty_array\": []"));
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ Key methods available:
|
||||
- `ctx->LogError("message", ...)` - Log error
|
||||
- `ctx->Yield()` - Yield to allow UI to update
|
||||
|
||||
Full API: `src/lib/imgui_test_engine/imgui_te_engine.h`
|
||||
Full API: `ext/imgui_test_engine/imgui_te_engine.h`
|
||||
|
||||
## Test Logging
|
||||
|
||||
@@ -163,7 +163,7 @@ Potential tests to add:
|
||||
|
||||
## References
|
||||
|
||||
- **ImGui Test Engine**: `src/lib/imgui_test_engine/`
|
||||
- **ImGui Test Engine**: `ext/imgui_test_engine/`
|
||||
- **Test Registration**: `test/yaze_test.cc`
|
||||
- **Test Utilities**: `test/test_utils.h`
|
||||
- **Working Examples**: See existing tests in this directory
|
||||
|
||||
@@ -1,76 +1,79 @@
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include "e2e/canvas_selection_test.h"
|
||||
|
||||
#include "app/controller.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
void E2ETest_CanvasSelectionTest(ImGuiTestContext* ctx)
|
||||
{
|
||||
yaze::test::gui::LoadRomInTest(ctx, "zelda3.sfc");
|
||||
yaze::Controller* controller = (yaze::Controller*)ctx->Test->UserData;
|
||||
yaze::zelda3::Overworld* overworld = controller->overworld();
|
||||
void E2ETest_CanvasSelectionTest(ImGuiTestContext* ctx) {
|
||||
yaze::test::gui::LoadRomInTest(ctx, "zelda3.sfc");
|
||||
yaze::Controller* controller = (yaze::Controller*)ctx->Test->UserData;
|
||||
yaze::zelda3::Overworld* overworld = controller->overworld();
|
||||
|
||||
// 1. Open the Overworld Editor
|
||||
yaze::test::gui::OpenEditorInTest(ctx, "Overworld Editor");
|
||||
// 1. Open the Overworld Editor
|
||||
yaze::test::gui::OpenEditorInTest(ctx, "Overworld Editor");
|
||||
|
||||
// 2. Find the canvas
|
||||
ctx->WindowFocus("Overworld Editor");
|
||||
ctx->ItemClick("##Canvas");
|
||||
// 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);
|
||||
// 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);
|
||||
// 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);
|
||||
// 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);
|
||||
// 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);
|
||||
// 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);
|
||||
// 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);
|
||||
// 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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,124 +1,124 @@
|
||||
#include "e2e/dungeon_editor_smoke_test.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#include "app/controller.h"
|
||||
#include "imgui_test_engine/imgui_te_context.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
/**
|
||||
* @brief Quick smoke test for DungeonEditorV2
|
||||
*
|
||||
*
|
||||
* Tests the card-based architecture:
|
||||
* - Independent windows (cards) can be opened/closed
|
||||
* - Room cards function correctly
|
||||
* - Basic navigation works
|
||||
*/
|
||||
void E2ETest_DungeonEditorV2SmokeTest(ImGuiTestContext* ctx)
|
||||
{
|
||||
ctx->LogInfo("=== Starting DungeonEditorV2 Smoke Test ===");
|
||||
|
||||
// Load ROM first
|
||||
ctx->LogInfo("Loading ROM...");
|
||||
yaze::test::gui::LoadRomInTest(ctx, "zelda3.sfc");
|
||||
ctx->LogInfo("ROM loaded successfully");
|
||||
|
||||
// Open the Dungeon Editor
|
||||
ctx->LogInfo("Opening Dungeon Editor...");
|
||||
yaze::test::gui::OpenEditorInTest(ctx, "Dungeon");
|
||||
ctx->LogInfo("Dungeon Editor opened");
|
||||
|
||||
// Test 1: Control Panel Access
|
||||
ctx->LogInfo("--- Test 1: Control Panel ---");
|
||||
if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) {
|
||||
ctx->WindowFocus("Dungeon Controls");
|
||||
ctx->LogInfo("Dungeon Controls panel is visible");
|
||||
} else {
|
||||
ctx->LogWarning("Dungeon Controls panel not visible - may be minimized");
|
||||
}
|
||||
|
||||
// Test 2: Open Room Selector Card
|
||||
ctx->LogInfo("--- Test 2: Room Selector Card ---");
|
||||
if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) {
|
||||
ctx->SetRef("Dungeon Controls");
|
||||
ctx->ItemClick("Rooms"); // Toggle checkbox
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Toggled Room Selector visibility");
|
||||
}
|
||||
|
||||
// Test 3: Open Room Matrix Card
|
||||
ctx->LogInfo("--- Test 3: Room Matrix Card ---");
|
||||
if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) {
|
||||
ctx->SetRef("Dungeon Controls");
|
||||
ctx->ItemClick("Matrix"); // Toggle checkbox
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Toggled Room Matrix visibility");
|
||||
}
|
||||
|
||||
// Test 4: Open a Room Card
|
||||
ctx->LogInfo("--- Test 4: Room Card ---");
|
||||
// Try to open room 0 by clicking in room selector
|
||||
if (ctx->WindowInfo("Room Selector").Window != nullptr) {
|
||||
ctx->SetRef("Room Selector");
|
||||
// Look for selectable room items
|
||||
if (ctx->ItemExists("Room 0x00")) {
|
||||
ctx->ItemDoubleClick("Room 0x00");
|
||||
ctx->Yield(2);
|
||||
ctx->LogInfo("Opened Room 0x00 card");
|
||||
|
||||
// Verify room card exists
|
||||
if (ctx->WindowInfo("Room 0x00").Window != nullptr) {
|
||||
ctx->LogInfo("Room 0x00 card successfully opened");
|
||||
ctx->SetRef("Room 0x00");
|
||||
|
||||
// Test 5: Per-Room Layer Controls
|
||||
ctx->LogInfo("--- Test 5: Per-Room Layer Controls ---");
|
||||
if (ctx->ItemExists("Show BG1")) {
|
||||
ctx->LogInfo("Found per-room BG1 control");
|
||||
// Toggle it
|
||||
ctx->ItemClick("Show BG1");
|
||||
ctx->Yield();
|
||||
ctx->ItemClick("Show BG1"); // Toggle back
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Per-room layer controls functional");
|
||||
}
|
||||
} else {
|
||||
ctx->LogWarning("Room card did not open");
|
||||
}
|
||||
} else {
|
||||
ctx->LogWarning("Room 0x00 not found in selector");
|
||||
void E2ETest_DungeonEditorV2SmokeTest(ImGuiTestContext* ctx) {
|
||||
ctx->LogInfo("=== Starting DungeonEditorV2 Smoke Test ===");
|
||||
|
||||
// Load ROM first
|
||||
ctx->LogInfo("Loading ROM...");
|
||||
yaze::test::gui::LoadRomInTest(ctx, "zelda3.sfc");
|
||||
ctx->LogInfo("ROM loaded successfully");
|
||||
|
||||
// Open the Dungeon Editor
|
||||
ctx->LogInfo("Opening Dungeon Editor...");
|
||||
yaze::test::gui::OpenEditorInTest(ctx, "Dungeon");
|
||||
ctx->LogInfo("Dungeon Editor opened");
|
||||
|
||||
// Test 1: Control Panel Access
|
||||
ctx->LogInfo("--- Test 1: Control Panel ---");
|
||||
if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) {
|
||||
ctx->WindowFocus("Dungeon Controls");
|
||||
ctx->LogInfo("Dungeon Controls panel is visible");
|
||||
} else {
|
||||
ctx->LogWarning("Dungeon Controls panel not visible - may be minimized");
|
||||
}
|
||||
|
||||
// Test 2: Open Room Selector Card
|
||||
ctx->LogInfo("--- Test 2: Room Selector Card ---");
|
||||
if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) {
|
||||
ctx->SetRef("Dungeon Controls");
|
||||
ctx->ItemClick("Rooms"); // Toggle checkbox
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Toggled Room Selector visibility");
|
||||
}
|
||||
|
||||
// Test 3: Open Room Matrix Card
|
||||
ctx->LogInfo("--- Test 3: Room Matrix Card ---");
|
||||
if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) {
|
||||
ctx->SetRef("Dungeon Controls");
|
||||
ctx->ItemClick("Matrix"); // Toggle checkbox
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Toggled Room Matrix visibility");
|
||||
}
|
||||
|
||||
// Test 4: Open a Room Card
|
||||
ctx->LogInfo("--- Test 4: Room Card ---");
|
||||
// Try to open room 0 by clicking in room selector
|
||||
if (ctx->WindowInfo("Room Selector").Window != nullptr) {
|
||||
ctx->SetRef("Room Selector");
|
||||
// Look for selectable room items
|
||||
if (ctx->ItemExists("Room 0x00")) {
|
||||
ctx->ItemDoubleClick("Room 0x00");
|
||||
ctx->Yield(2);
|
||||
ctx->LogInfo("Opened Room 0x00 card");
|
||||
|
||||
// Verify room card exists
|
||||
if (ctx->WindowInfo("Room 0x00").Window != nullptr) {
|
||||
ctx->LogInfo("Room 0x00 card successfully opened");
|
||||
ctx->SetRef("Room 0x00");
|
||||
|
||||
// Test 5: Per-Room Layer Controls
|
||||
ctx->LogInfo("--- Test 5: Per-Room Layer Controls ---");
|
||||
if (ctx->ItemExists("Show BG1")) {
|
||||
ctx->LogInfo("Found per-room BG1 control");
|
||||
// Toggle it
|
||||
ctx->ItemClick("Show BG1");
|
||||
ctx->Yield();
|
||||
ctx->ItemClick("Show BG1"); // Toggle back
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Per-room layer controls functional");
|
||||
}
|
||||
} else {
|
||||
ctx->LogWarning("Room card did not open");
|
||||
}
|
||||
} else {
|
||||
ctx->LogWarning("Room Selector card not visible");
|
||||
ctx->LogWarning("Room 0x00 not found in selector");
|
||||
}
|
||||
|
||||
// Test 6: Object Editor Card
|
||||
ctx->LogInfo("--- Test 6: Object Editor Card ---");
|
||||
if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) {
|
||||
ctx->SetRef("Dungeon Controls");
|
||||
ctx->ItemClick("Objects"); // Toggle checkbox
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Toggled Object Editor visibility");
|
||||
}
|
||||
|
||||
// Test 7: Palette Editor Card
|
||||
ctx->LogInfo("--- Test 7: Palette Editor Card ---");
|
||||
if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) {
|
||||
ctx->SetRef("Dungeon Controls");
|
||||
ctx->ItemClick("Palette"); // Toggle checkbox
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Toggled Palette Editor visibility");
|
||||
}
|
||||
|
||||
// Test 8: Independent Cards can be closed
|
||||
ctx->LogInfo("--- Test 8: Close Independent Cards ---");
|
||||
// Close room card if it's open
|
||||
if (ctx->WindowInfo("Room 0x00").Window != nullptr) {
|
||||
ctx->WindowClose("Room 0x00");
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Closed Room 0x00 card");
|
||||
}
|
||||
|
||||
// Final verification
|
||||
ctx->LogInfo("=== DungeonEditorV2 Smoke Test Completed Successfully ===");
|
||||
ctx->LogInfo("Card-based architecture is functional");
|
||||
ctx->LogInfo("Independent windows can be opened and closed");
|
||||
ctx->LogInfo("Per-room settings are accessible");
|
||||
} else {
|
||||
ctx->LogWarning("Room Selector card not visible");
|
||||
}
|
||||
|
||||
// Test 6: Object Editor Card
|
||||
ctx->LogInfo("--- Test 6: Object Editor Card ---");
|
||||
if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) {
|
||||
ctx->SetRef("Dungeon Controls");
|
||||
ctx->ItemClick("Objects"); // Toggle checkbox
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Toggled Object Editor visibility");
|
||||
}
|
||||
|
||||
// Test 7: Palette Editor Card
|
||||
ctx->LogInfo("--- Test 7: Palette Editor Card ---");
|
||||
if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) {
|
||||
ctx->SetRef("Dungeon Controls");
|
||||
ctx->ItemClick("Palette"); // Toggle checkbox
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Toggled Palette Editor visibility");
|
||||
}
|
||||
|
||||
// Test 8: Independent Cards can be closed
|
||||
ctx->LogInfo("--- Test 8: Close Independent Cards ---");
|
||||
// Close room card if it's open
|
||||
if (ctx->WindowInfo("Room 0x00").Window != nullptr) {
|
||||
ctx->WindowClose("Room 0x00");
|
||||
ctx->Yield();
|
||||
ctx->LogInfo("Closed Room 0x00 card");
|
||||
}
|
||||
|
||||
// Final verification
|
||||
ctx->LogInfo("=== DungeonEditorV2 Smoke Test Completed Successfully ===");
|
||||
ctx->LogInfo("Card-based architecture is functional");
|
||||
ctx->LogInfo("Independent windows can be opened and closed");
|
||||
ctx->LogInfo("Per-room settings are accessible");
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/**
|
||||
* @brief Quick smoke test for DungeonEditorV2 card-based UI
|
||||
*
|
||||
*
|
||||
* Tests basic functionality:
|
||||
* - Opening dungeon editor
|
||||
* - Opening independent cards (Rooms, Matrix, Objects, etc.)
|
||||
@@ -15,4 +15,3 @@
|
||||
void E2ETest_DungeonEditorV2SmokeTest(ImGuiTestContext* ctx);
|
||||
|
||||
#endif // YAZE_TEST_E2E_DUNGEON_EDITOR_SMOKE_TEST_H
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,16 @@
|
||||
#include "e2e/framework_smoke_test.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_test_engine/imgui_te_context.h"
|
||||
#include "test_utils.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");
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "testing.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
#include "zelda3/overworld/overworld_map.h"
|
||||
#include "testing.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* @brief Comprehensive End-to-End Overworld Test Suite
|
||||
*
|
||||
*
|
||||
* This test suite validates the complete overworld editing workflow:
|
||||
* 1. Load vanilla ROM and extract golden data
|
||||
* 2. Apply ZSCustomOverworld ASM patches
|
||||
@@ -34,7 +35,7 @@ class OverworldE2ETest : public ::testing::Test {
|
||||
// Get ROM path from environment or use default
|
||||
const char* rom_path_env = getenv("YAZE_TEST_ROM_PATH");
|
||||
vanilla_rom_path_ = rom_path_env ? rom_path_env : "zelda3.sfc";
|
||||
|
||||
|
||||
if (!std::filesystem::exists(vanilla_rom_path_)) {
|
||||
GTEST_SKIP() << "Test ROM not found: " << vanilla_rom_path_;
|
||||
}
|
||||
@@ -43,7 +44,7 @@ class OverworldE2ETest : public ::testing::Test {
|
||||
vanilla_test_path_ = "test_vanilla_e2e.sfc";
|
||||
edited_test_path_ = "test_edited_e2e.sfc";
|
||||
golden_data_path_ = "golden_data_e2e.h";
|
||||
|
||||
|
||||
// Copy vanilla ROM for testing
|
||||
std::filesystem::copy_file(vanilla_rom_path_, vanilla_test_path_);
|
||||
}
|
||||
@@ -51,9 +52,8 @@ class OverworldE2ETest : public ::testing::Test {
|
||||
void TearDown() override {
|
||||
// Clean up test files
|
||||
std::vector<std::string> test_files = {
|
||||
vanilla_test_path_, edited_test_path_, golden_data_path_
|
||||
};
|
||||
|
||||
vanilla_test_path_, edited_test_path_, golden_data_path_};
|
||||
|
||||
for (const auto& file : test_files) {
|
||||
if (std::filesystem::exists(file)) {
|
||||
std::filesystem::remove(file);
|
||||
@@ -62,32 +62,37 @@ class OverworldE2ETest : public ::testing::Test {
|
||||
}
|
||||
|
||||
// Helper to extract golden data from ROM
|
||||
absl::Status ExtractGoldenData(const std::string& rom_path,
|
||||
absl::Status ExtractGoldenData(const std::string& rom_path,
|
||||
const std::string& output_path) {
|
||||
// Run the golden data extractor
|
||||
std::string command = "./overworld_golden_data_extractor " + rom_path + " " + output_path;
|
||||
std::string command =
|
||||
"./overworld_golden_data_extractor " + rom_path + " " + output_path;
|
||||
int result = system(command.c_str());
|
||||
|
||||
|
||||
if (result != 0) {
|
||||
return absl::InternalError("Failed to extract golden data");
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Helper to validate ROM against golden data
|
||||
bool ValidateROMAgainstGoldenData(Rom& rom, const std::string& /* golden_data_path */) {
|
||||
bool ValidateROMAgainstGoldenData(Rom& rom,
|
||||
const std::string& /* golden_data_path */) {
|
||||
// This would load the generated golden data header and compare values
|
||||
// For now, we'll do basic validation
|
||||
|
||||
|
||||
// Check basic ROM properties
|
||||
if (rom.title().empty()) return false;
|
||||
if (rom.size() < 1024*1024) return false; // At least 1MB
|
||||
|
||||
if (rom.title().empty())
|
||||
return false;
|
||||
if (rom.size() < 1024 * 1024)
|
||||
return false; // At least 1MB
|
||||
|
||||
// Check ASM version
|
||||
auto asm_version = rom.ReadByte(0x140145);
|
||||
if (!asm_version.ok()) return false;
|
||||
|
||||
if (!asm_version.ok())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -101,13 +106,13 @@ class OverworldE2ETest : public ::testing::Test {
|
||||
TEST_F(OverworldE2ETest, ExtractVanillaGoldenData) {
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Extract golden data
|
||||
ASSERT_OK(ExtractGoldenData(vanilla_test_path_, golden_data_path_));
|
||||
|
||||
|
||||
// Verify golden data file was created
|
||||
EXPECT_TRUE(std::filesystem::exists(golden_data_path_));
|
||||
|
||||
|
||||
// Validate ROM against golden data
|
||||
EXPECT_TRUE(ValidateROMAgainstGoldenData(*rom, golden_data_path_));
|
||||
}
|
||||
@@ -116,37 +121,37 @@ TEST_F(OverworldE2ETest, ExtractVanillaGoldenData) {
|
||||
TEST_F(OverworldE2ETest, LoadVanillaOverworldData) {
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
zelda3::Overworld overworld(rom.get());
|
||||
auto status = overworld.Load(rom.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Validate basic overworld structure
|
||||
EXPECT_TRUE(overworld.is_loaded());
|
||||
|
||||
|
||||
const auto& maps = overworld.overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
|
||||
// Validate that we have a vanilla ROM (ASM version 0xFF)
|
||||
auto asm_version = rom->ReadByte(0x140145);
|
||||
ASSERT_TRUE(asm_version.ok());
|
||||
EXPECT_EQ(*asm_version, 0xFF);
|
||||
|
||||
|
||||
// Validate expansion flags for vanilla
|
||||
EXPECT_FALSE(overworld.expanded_tile16());
|
||||
EXPECT_FALSE(overworld.expanded_tile32());
|
||||
|
||||
|
||||
// Validate data structures
|
||||
const auto& entrances = overworld.entrances();
|
||||
const auto& exits = overworld.exits();
|
||||
const auto& holes = overworld.holes();
|
||||
const auto& items = overworld.all_items();
|
||||
|
||||
|
||||
EXPECT_EQ(entrances.size(), 129);
|
||||
EXPECT_EQ(exits->size(), 0x4F);
|
||||
EXPECT_EQ(holes.size(), 0x13);
|
||||
EXPECT_GE(items.size(), 0);
|
||||
|
||||
|
||||
// Validate sprite data (3 game states)
|
||||
const auto& sprites = overworld.all_sprites();
|
||||
EXPECT_EQ(sprites.size(), 3);
|
||||
@@ -156,31 +161,31 @@ TEST_F(OverworldE2ETest, LoadVanillaOverworldData) {
|
||||
TEST_F(OverworldE2ETest, ApplyZSCustomOverworldV3) {
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Apply ZSCustomOverworld v3 ASM
|
||||
// This would typically be done through the editor, but we can simulate it
|
||||
ASSERT_OK(rom->WriteByte(0x140145, 0x03)); // Set ASM version to v3
|
||||
|
||||
ASSERT_OK(rom->WriteByte(0x140145, 0x03)); // Set ASM version to v3
|
||||
|
||||
// Enable v3 features
|
||||
ASSERT_OK(rom->WriteByte(0x140146, 0x01)); // Enable main palettes
|
||||
ASSERT_OK(rom->WriteByte(0x140147, 0x01)); // Enable area-specific BG
|
||||
ASSERT_OK(rom->WriteByte(0x140148, 0x01)); // Enable subscreen overlay
|
||||
ASSERT_OK(rom->WriteByte(0x140149, 0x01)); // Enable animated GFX
|
||||
ASSERT_OK(rom->WriteByte(0x14014A, 0x01)); // Enable custom tile GFX groups
|
||||
ASSERT_OK(rom->WriteByte(0x14014B, 0x01)); // Enable mosaic
|
||||
|
||||
ASSERT_OK(rom->WriteByte(0x140146, 0x01)); // Enable main palettes
|
||||
ASSERT_OK(rom->WriteByte(0x140147, 0x01)); // Enable area-specific BG
|
||||
ASSERT_OK(rom->WriteByte(0x140148, 0x01)); // Enable subscreen overlay
|
||||
ASSERT_OK(rom->WriteByte(0x140149, 0x01)); // Enable animated GFX
|
||||
ASSERT_OK(rom->WriteByte(0x14014A, 0x01)); // Enable custom tile GFX groups
|
||||
ASSERT_OK(rom->WriteByte(0x14014B, 0x01)); // Enable mosaic
|
||||
|
||||
// Save the modified ROM
|
||||
ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = edited_test_path_}));
|
||||
|
||||
|
||||
// Reload and validate
|
||||
std::unique_ptr<Rom> reloaded_rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(reloaded_rom->LoadFromFile(edited_test_path_));
|
||||
|
||||
|
||||
// Validate ASM version was applied
|
||||
auto asm_version = reloaded_rom->ReadByte(0x140145);
|
||||
ASSERT_TRUE(asm_version.ok());
|
||||
EXPECT_EQ(*asm_version, 0x03);
|
||||
|
||||
|
||||
// Validate feature flags
|
||||
auto main_palettes = reloaded_rom->ReadByte(0x140146);
|
||||
auto area_bg = reloaded_rom->ReadByte(0x140147);
|
||||
@@ -188,26 +193,26 @@ TEST_F(OverworldE2ETest, ApplyZSCustomOverworldV3) {
|
||||
auto animated_gfx = reloaded_rom->ReadByte(0x140149);
|
||||
auto custom_tiles = reloaded_rom->ReadByte(0x14014A);
|
||||
auto mosaic = reloaded_rom->ReadByte(0x14014B);
|
||||
|
||||
|
||||
ASSERT_TRUE(main_palettes.ok());
|
||||
ASSERT_TRUE(area_bg.ok());
|
||||
ASSERT_TRUE(subscreen_overlay.ok());
|
||||
ASSERT_TRUE(animated_gfx.ok());
|
||||
ASSERT_TRUE(custom_tiles.ok());
|
||||
ASSERT_TRUE(mosaic.ok());
|
||||
|
||||
|
||||
EXPECT_EQ(*main_palettes, 0x01);
|
||||
EXPECT_EQ(*area_bg, 0x01);
|
||||
EXPECT_EQ(*subscreen_overlay, 0x01);
|
||||
EXPECT_EQ(*animated_gfx, 0x01);
|
||||
EXPECT_EQ(*custom_tiles, 0x01);
|
||||
EXPECT_EQ(*mosaic, 0x01);
|
||||
|
||||
|
||||
// Load overworld and validate v3 features are detected
|
||||
zelda3::Overworld overworld(reloaded_rom.get());
|
||||
auto status = overworld.Load(reloaded_rom.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// v3 should have expanded features available
|
||||
EXPECT_TRUE(overworld.expanded_tile16());
|
||||
EXPECT_TRUE(overworld.expanded_tile32());
|
||||
@@ -217,37 +222,37 @@ TEST_F(OverworldE2ETest, ApplyZSCustomOverworldV3) {
|
||||
TEST_F(OverworldE2ETest, OverworldEditPersistence) {
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Load overworld
|
||||
zelda3::Overworld overworld(rom.get());
|
||||
auto status = overworld.Load(rom.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Make some edits to overworld maps
|
||||
auto* map0 = overworld.mutable_overworld_map(0);
|
||||
uint8_t original_gfx = map0->area_graphics();
|
||||
uint8_t original_palette = map0->main_palette();
|
||||
|
||||
|
||||
// Change graphics and palette
|
||||
map0->set_area_graphics(0x01);
|
||||
map0->set_main_palette(0x02);
|
||||
|
||||
|
||||
// Save the changes
|
||||
auto save_maps_status = overworld.SaveOverworldMaps();
|
||||
ASSERT_TRUE(save_maps_status.ok());
|
||||
auto save_props_status = overworld.SaveMapProperties();
|
||||
ASSERT_TRUE(save_props_status.ok());
|
||||
|
||||
|
||||
// Save ROM
|
||||
ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = edited_test_path_}));
|
||||
|
||||
|
||||
// Reload ROM and validate changes persisted
|
||||
std::unique_ptr<Rom> reloaded_rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(reloaded_rom->LoadFromFile(edited_test_path_));
|
||||
|
||||
|
||||
zelda3::Overworld reloaded_overworld(reloaded_rom.get());
|
||||
ASSERT_OK(reloaded_overworld.Load(reloaded_rom.get()));
|
||||
|
||||
|
||||
const auto& reloaded_map0 = reloaded_overworld.overworld_map(0);
|
||||
EXPECT_EQ(reloaded_map0->area_graphics(), 0x01);
|
||||
EXPECT_EQ(reloaded_map0->main_palette(), 0x02);
|
||||
@@ -257,48 +262,52 @@ TEST_F(OverworldE2ETest, OverworldEditPersistence) {
|
||||
TEST_F(OverworldE2ETest, CoordinateCalculationValidation) {
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
zelda3::Overworld overworld(rom.get());
|
||||
ASSERT_OK(overworld.Load(rom.get()));
|
||||
|
||||
|
||||
const auto& entrances = overworld.entrances();
|
||||
EXPECT_EQ(entrances.size(), 129);
|
||||
|
||||
|
||||
// Test coordinate calculation for first 10 entrances
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
const auto& entrance = entrances[i];
|
||||
|
||||
|
||||
// ZScream coordinate calculation logic
|
||||
uint16_t map_pos = entrance.map_pos_;
|
||||
uint16_t map_id = entrance.map_id_;
|
||||
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_x =
|
||||
(x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
EXPECT_EQ(entrance.x_, expected_x) << "Entrance " << i << " X coordinate mismatch";
|
||||
EXPECT_EQ(entrance.y_, expected_y) << "Entrance " << i << " Y coordinate mismatch";
|
||||
|
||||
EXPECT_EQ(entrance.x_, expected_x)
|
||||
<< "Entrance " << i << " X coordinate mismatch";
|
||||
EXPECT_EQ(entrance.y_, expected_y)
|
||||
<< "Entrance " << i << " Y coordinate mismatch";
|
||||
}
|
||||
|
||||
|
||||
// Test hole coordinate calculation with 0x400 offset
|
||||
const auto& holes = overworld.holes();
|
||||
EXPECT_EQ(holes.size(), 0x13);
|
||||
|
||||
|
||||
for (int i = 0; i < std::min(5, static_cast<int>(holes.size())); i++) {
|
||||
const auto& hole = holes[i];
|
||||
|
||||
|
||||
// ZScream hole coordinate calculation with 0x400 offset
|
||||
uint16_t map_pos = hole.map_pos_;
|
||||
uint16_t map_id = hole.map_id_;
|
||||
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_x =
|
||||
(x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
|
||||
EXPECT_EQ(hole.x_, expected_x) << "Hole " << i << " X coordinate mismatch";
|
||||
EXPECT_EQ(hole.y_, expected_y) << "Hole " << i << " Y coordinate mismatch";
|
||||
EXPECT_TRUE(hole.is_hole_) << "Hole " << i << " should be marked as hole";
|
||||
@@ -309,48 +318,52 @@ TEST_F(OverworldE2ETest, CoordinateCalculationValidation) {
|
||||
TEST_F(OverworldE2ETest, BeforeAfterValidation) {
|
||||
// Extract golden data from vanilla ROM
|
||||
ASSERT_OK(ExtractGoldenData(vanilla_test_path_, golden_data_path_));
|
||||
|
||||
|
||||
// Load vanilla ROM and make some changes
|
||||
std::unique_ptr<Rom> vanilla_rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(vanilla_rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Store some original values for comparison
|
||||
auto original_asm_version = vanilla_rom->ReadByte(0x140145);
|
||||
auto original_graphics_0 = vanilla_rom->ReadByte(0x7C9C); // First map graphics
|
||||
auto original_palette_0 = vanilla_rom->ReadByte(0x7D1C); // First map palette
|
||||
|
||||
auto original_graphics_0 =
|
||||
vanilla_rom->ReadByte(0x7C9C); // First map graphics
|
||||
auto original_palette_0 = vanilla_rom->ReadByte(0x7D1C); // First map palette
|
||||
|
||||
ASSERT_TRUE(original_asm_version.ok());
|
||||
ASSERT_TRUE(original_graphics_0.ok());
|
||||
ASSERT_TRUE(original_palette_0.ok());
|
||||
|
||||
|
||||
// Make changes
|
||||
auto write1 = vanilla_rom->WriteByte(0x140145, 0x03); // Apply v3 ASM
|
||||
auto write1 = vanilla_rom->WriteByte(0x140145, 0x03); // Apply v3 ASM
|
||||
ASSERT_TRUE(write1.ok());
|
||||
auto write2 = vanilla_rom->WriteByte(0x7C9C, 0x01); // Change first map graphics
|
||||
auto write2 =
|
||||
vanilla_rom->WriteByte(0x7C9C, 0x01); // Change first map graphics
|
||||
ASSERT_TRUE(write2.ok());
|
||||
auto write3 = vanilla_rom->WriteByte(0x7D1C, 0x02); // Change first map palette
|
||||
auto write3 =
|
||||
vanilla_rom->WriteByte(0x7D1C, 0x02); // Change first map palette
|
||||
ASSERT_TRUE(write3.ok());
|
||||
|
||||
|
||||
// Save modified ROM
|
||||
ASSERT_OK(vanilla_rom->SaveToFile(Rom::SaveSettings{.filename = edited_test_path_}));
|
||||
|
||||
ASSERT_OK(vanilla_rom->SaveToFile(
|
||||
Rom::SaveSettings{.filename = edited_test_path_}));
|
||||
|
||||
// Reload and validate changes
|
||||
std::unique_ptr<Rom> modified_rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(modified_rom->LoadFromFile(edited_test_path_));
|
||||
|
||||
|
||||
auto modified_asm_version = modified_rom->ReadByte(0x140145);
|
||||
auto modified_graphics_0 = modified_rom->ReadByte(0x7C9C);
|
||||
auto modified_palette_0 = modified_rom->ReadByte(0x7D1C);
|
||||
|
||||
|
||||
ASSERT_TRUE(modified_asm_version.ok());
|
||||
ASSERT_TRUE(modified_graphics_0.ok());
|
||||
ASSERT_TRUE(modified_palette_0.ok());
|
||||
|
||||
|
||||
// Validate changes were applied
|
||||
EXPECT_EQ(*modified_asm_version, 0x03);
|
||||
EXPECT_EQ(*modified_graphics_0, 0x01);
|
||||
EXPECT_EQ(*modified_palette_0, 0x02);
|
||||
|
||||
|
||||
// Validate original values were different
|
||||
EXPECT_NE(*original_asm_version, *modified_asm_version);
|
||||
EXPECT_NE(*original_graphics_0, *modified_graphics_0);
|
||||
@@ -361,37 +374,38 @@ TEST_F(OverworldE2ETest, BeforeAfterValidation) {
|
||||
TEST_F(OverworldE2ETest, RomDependentTestSuiteIntegration) {
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Test that our overworld loading works with RomDependentTestSuite patterns
|
||||
zelda3::Overworld overworld(rom.get());
|
||||
auto status = overworld.Load(rom.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Validate ROM-dependent features work correctly
|
||||
EXPECT_TRUE(overworld.is_loaded());
|
||||
|
||||
|
||||
const auto& maps = overworld.overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
|
||||
// Test that we can access the same data structures as RomDependentTestSuite
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(maps.size())); i++) {
|
||||
const auto& map = maps[i];
|
||||
|
||||
|
||||
// Verify map properties are accessible
|
||||
EXPECT_GE(map.area_graphics(), 0);
|
||||
EXPECT_GE(map.main_palette(), 0);
|
||||
EXPECT_GE(map.area_size(), zelda3::AreaSizeEnum::SmallArea);
|
||||
EXPECT_LE(map.area_size(), zelda3::AreaSizeEnum::TallArea);
|
||||
}
|
||||
|
||||
// Test that sprite data is accessible (matches RomDependentTestSuite expectations)
|
||||
|
||||
// Test that sprite data is accessible (matches RomDependentTestSuite
|
||||
// expectations)
|
||||
const auto& sprites = overworld.all_sprites();
|
||||
EXPECT_EQ(sprites.size(), 3); // Three game states
|
||||
|
||||
EXPECT_EQ(sprites.size(), 3); // Three game states
|
||||
|
||||
// Test that item data is accessible
|
||||
const auto& items = overworld.all_items();
|
||||
EXPECT_GE(items.size(), 0);
|
||||
|
||||
|
||||
// Test that entrance/exit data is accessible
|
||||
const auto& entrances = overworld.entrances();
|
||||
const auto& exits = overworld.exits();
|
||||
@@ -403,20 +417,21 @@ TEST_F(OverworldE2ETest, RomDependentTestSuiteIntegration) {
|
||||
TEST_F(OverworldE2ETest, PerformanceAndStability) {
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Test multiple load/unload cycles
|
||||
for (int cycle = 0; cycle < 5; cycle++) {
|
||||
zelda3::Overworld overworld(rom.get());
|
||||
auto status = overworld.Load(rom.get());
|
||||
ASSERT_TRUE(status.ok()) << "Load failed on cycle " << cycle;
|
||||
|
||||
|
||||
// Validate basic structure
|
||||
const auto& maps = overworld.overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160) << "Map count mismatch on cycle " << cycle;
|
||||
|
||||
|
||||
const auto& entrances = overworld.entrances();
|
||||
EXPECT_EQ(entrances.size(), 129) << "Entrance count mismatch on cycle " << cycle;
|
||||
|
||||
EXPECT_EQ(entrances.size(), 129)
|
||||
<< "Entrance count mismatch on cycle " << cycle;
|
||||
|
||||
const auto& exits = overworld.exits();
|
||||
EXPECT_EQ(exits->size(), 0x4F) << "Exit count mismatch on cycle " << cycle;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/transaction.h"
|
||||
@@ -14,7 +15,7 @@ namespace test {
|
||||
|
||||
/**
|
||||
* @brief Comprehensive End-to-End ROM testing suite
|
||||
*
|
||||
*
|
||||
* This test suite validates the complete ROM editing workflow:
|
||||
* 1. Load vanilla ROM
|
||||
* 2. Apply various edits (ROM data, graphics, etc.)
|
||||
@@ -33,7 +34,7 @@ class E2ERomDependentTest : public ::testing::Test {
|
||||
// Get ROM path from environment or use default
|
||||
const char* rom_path_env = getenv("YAZE_TEST_ROM_PATH");
|
||||
vanilla_rom_path_ = rom_path_env ? rom_path_env : "zelda3.sfc";
|
||||
|
||||
|
||||
if (!std::filesystem::exists(vanilla_rom_path_)) {
|
||||
GTEST_SKIP() << "Test ROM not found: " << vanilla_rom_path_;
|
||||
}
|
||||
@@ -41,7 +42,7 @@ class E2ERomDependentTest : public ::testing::Test {
|
||||
// Create test ROM copies
|
||||
test_rom_path_ = "test_rom_edit.sfc";
|
||||
backup_rom_path_ = "test_rom_backup.sfc";
|
||||
|
||||
|
||||
// Copy vanilla ROM for testing
|
||||
std::filesystem::copy_file(vanilla_rom_path_, test_rom_path_);
|
||||
std::filesystem::copy_file(vanilla_rom_path_, backup_rom_path_);
|
||||
@@ -58,51 +59,53 @@ class E2ERomDependentTest : public ::testing::Test {
|
||||
}
|
||||
|
||||
// Helper to load ROM and verify basic integrity
|
||||
static absl::Status LoadAndVerifyROM(const std::string& path, std::unique_ptr<Rom>& rom) {
|
||||
static absl::Status LoadAndVerifyROM(const std::string& path,
|
||||
std::unique_ptr<Rom>& rom) {
|
||||
rom = std::make_unique<Rom>();
|
||||
RETURN_IF_ERROR(rom->LoadFromFile(path));
|
||||
|
||||
|
||||
// Basic ROM integrity checks
|
||||
EXPECT_EQ(rom->size(), 0x200000) << "ROM size should be 2MB";
|
||||
EXPECT_NE(rom->data(), nullptr) << "ROM data should not be null";
|
||||
|
||||
|
||||
// Check ROM header
|
||||
auto header_byte = rom->ReadByte(0x7FC0);
|
||||
RETURN_IF_ERROR(header_byte.status());
|
||||
EXPECT_EQ(*header_byte, 0x21) << "ROM should be LoROM format";
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Helper to verify ROM data integrity by comparing checksums
|
||||
static bool VerifyROMIntegrity(const std::string& path1, const std::string& path2,
|
||||
const std::vector<uint32_t>& exclude_ranges = {}) {
|
||||
static bool VerifyROMIntegrity(
|
||||
const std::string& path1, const std::string& path2,
|
||||
const std::vector<uint32_t>& exclude_ranges = {}) {
|
||||
std::ifstream file1(path1, std::ios::binary);
|
||||
std::ifstream file2(path2, std::ios::binary);
|
||||
|
||||
|
||||
if (!file1.is_open() || !file2.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
file1.seekg(0, std::ios::end);
|
||||
file2.seekg(0, std::ios::end);
|
||||
|
||||
|
||||
size_t size1 = file1.tellg();
|
||||
size_t size2 = file2.tellg();
|
||||
|
||||
|
||||
if (size1 != size2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
file1.seekg(0);
|
||||
file2.seekg(0);
|
||||
|
||||
|
||||
std::vector<char> buffer1(size1);
|
||||
std::vector<char> buffer2(size2);
|
||||
|
||||
|
||||
file1.read(buffer1.data(), size1);
|
||||
file2.read(buffer2.data(), size2);
|
||||
|
||||
|
||||
// Compare byte by byte, excluding specified ranges
|
||||
for (size_t i = 0; i < size1; i++) {
|
||||
bool in_exclude_range = false;
|
||||
@@ -112,12 +115,12 @@ class E2ERomDependentTest : public ::testing::Test {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!in_exclude_range && buffer1[i] != buffer2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -130,10 +133,10 @@ class E2ERomDependentTest : public ::testing::Test {
|
||||
TEST_F(E2ERomDependentTest, BasicROMLoadSave) {
|
||||
std::unique_ptr<Rom> rom;
|
||||
ASSERT_OK(LoadAndVerifyROM(vanilla_rom_path_, rom));
|
||||
|
||||
|
||||
// Save ROM to test path
|
||||
ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = test_rom_path_}));
|
||||
|
||||
|
||||
// Verify saved ROM matches original
|
||||
EXPECT_TRUE(VerifyROMIntegrity(vanilla_rom_path_, test_rom_path_));
|
||||
}
|
||||
@@ -142,35 +145,35 @@ TEST_F(E2ERomDependentTest, BasicROMLoadSave) {
|
||||
TEST_F(E2ERomDependentTest, ROMDataEditWorkflow) {
|
||||
std::unique_ptr<Rom> rom;
|
||||
ASSERT_OK(LoadAndVerifyROM(vanilla_rom_path_, rom));
|
||||
|
||||
|
||||
// Get initial state
|
||||
auto initial_byte = rom->ReadByte(0x1000);
|
||||
ASSERT_TRUE(initial_byte.ok());
|
||||
|
||||
|
||||
// Make edits
|
||||
ASSERT_OK(rom->WriteByte(0x1000, 0xAA));
|
||||
ASSERT_OK(rom->WriteByte(0x2000, 0xBB));
|
||||
ASSERT_OK(rom->WriteWord(0x3000, 0xCCDD));
|
||||
|
||||
|
||||
// Save changes
|
||||
ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = test_rom_path_}));
|
||||
|
||||
|
||||
// Reload and verify
|
||||
std::unique_ptr<Rom> reloaded_rom;
|
||||
ASSERT_OK(LoadAndVerifyROM(test_rom_path_, reloaded_rom));
|
||||
|
||||
|
||||
auto byte1 = reloaded_rom->ReadByte(0x1000);
|
||||
ASSERT_OK(byte1.status());
|
||||
EXPECT_EQ(*byte1, 0xAA);
|
||||
|
||||
|
||||
auto byte2 = reloaded_rom->ReadByte(0x2000);
|
||||
ASSERT_OK(byte2.status());
|
||||
EXPECT_EQ(*byte2, 0xBB);
|
||||
|
||||
|
||||
auto word1 = reloaded_rom->ReadWord(0x3000);
|
||||
ASSERT_OK(word1.status());
|
||||
EXPECT_EQ(*word1, 0xCCDD);
|
||||
|
||||
|
||||
// Verify other data wasn't corrupted
|
||||
EXPECT_NE(*byte1, *initial_byte);
|
||||
}
|
||||
@@ -179,36 +182,36 @@ TEST_F(E2ERomDependentTest, ROMDataEditWorkflow) {
|
||||
TEST_F(E2ERomDependentTest, TransactionSystem) {
|
||||
std::unique_ptr<Rom> rom;
|
||||
ASSERT_OK(LoadAndVerifyROM(vanilla_rom_path_, rom));
|
||||
|
||||
|
||||
// Create transaction
|
||||
auto transaction = std::make_unique<yaze::Transaction>(*rom);
|
||||
|
||||
|
||||
// Make multiple edits in transaction
|
||||
transaction->WriteByte(0x1000, 0xAA);
|
||||
transaction->WriteByte(0x2000, 0xBB);
|
||||
transaction->WriteWord(0x3000, 0xCCDD);
|
||||
|
||||
|
||||
// Commit the transaction
|
||||
ASSERT_OK(transaction->Commit());
|
||||
|
||||
|
||||
// Commit transaction
|
||||
ASSERT_OK(transaction->Commit());
|
||||
|
||||
|
||||
// Save ROM
|
||||
ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = test_rom_path_}));
|
||||
|
||||
|
||||
// Reload and verify all changes
|
||||
std::unique_ptr<Rom> reloaded_rom;
|
||||
ASSERT_OK(LoadAndVerifyROM(test_rom_path_, reloaded_rom));
|
||||
|
||||
|
||||
auto byte1 = reloaded_rom->ReadByte(0x1000);
|
||||
ASSERT_OK(byte1.status());
|
||||
EXPECT_EQ(*byte1, 0xAA);
|
||||
|
||||
|
||||
auto byte2 = reloaded_rom->ReadByte(0x2000);
|
||||
ASSERT_OK(byte2.status());
|
||||
EXPECT_EQ(*byte2, 0xBB);
|
||||
|
||||
|
||||
auto word1 = reloaded_rom->ReadWord(0x3000);
|
||||
ASSERT_OK(word1.status());
|
||||
EXPECT_EQ(*word1, 0xCCDD);
|
||||
@@ -218,22 +221,22 @@ TEST_F(E2ERomDependentTest, TransactionSystem) {
|
||||
TEST_F(E2ERomDependentTest, CorruptionDetection) {
|
||||
std::unique_ptr<Rom> rom;
|
||||
ASSERT_OK(LoadAndVerifyROM(vanilla_rom_path_, rom));
|
||||
|
||||
|
||||
// Corrupt some data
|
||||
ASSERT_OK(rom->WriteByte(0x1000, 0xFF)); // Corrupt some data
|
||||
ASSERT_OK(rom->WriteByte(0x2000, 0xAA)); // Corrupt more data
|
||||
|
||||
|
||||
// Save corrupted ROM
|
||||
ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = test_rom_path_}));
|
||||
|
||||
|
||||
// Verify corruption is detected
|
||||
std::unique_ptr<Rom> reloaded_rom;
|
||||
ASSERT_OK(LoadAndVerifyROM(test_rom_path_, reloaded_rom));
|
||||
|
||||
|
||||
auto corrupt_byte1 = reloaded_rom->ReadByte(0x1000);
|
||||
ASSERT_OK(corrupt_byte1.status());
|
||||
EXPECT_EQ(*corrupt_byte1, 0xFF);
|
||||
|
||||
|
||||
auto corrupt_byte2 = reloaded_rom->ReadByte(0x2000);
|
||||
ASSERT_OK(corrupt_byte2.status());
|
||||
EXPECT_EQ(*corrupt_byte2, 0xAA);
|
||||
@@ -243,25 +246,25 @@ TEST_F(E2ERomDependentTest, CorruptionDetection) {
|
||||
TEST_F(E2ERomDependentTest, LargeScaleEditing) {
|
||||
std::unique_ptr<Rom> rom;
|
||||
ASSERT_OK(LoadAndVerifyROM(vanilla_rom_path_, rom));
|
||||
|
||||
|
||||
// Edit multiple areas
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(rom->WriteByte(0x1000 + i, i % 16));
|
||||
ASSERT_OK(rom->WriteByte(0x2000 + i, (i + 1) % 16));
|
||||
}
|
||||
|
||||
|
||||
// Save and reload
|
||||
ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = test_rom_path_}));
|
||||
|
||||
|
||||
std::unique_ptr<Rom> reloaded_rom;
|
||||
ASSERT_OK(LoadAndVerifyROM(test_rom_path_, reloaded_rom));
|
||||
|
||||
|
||||
// Verify all changes
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto byte1 = reloaded_rom->ReadByte(0x1000 + i);
|
||||
ASSERT_OK(byte1.status());
|
||||
EXPECT_EQ(*byte1, i % 16);
|
||||
|
||||
|
||||
auto byte2 = reloaded_rom->ReadByte(0x2000 + i);
|
||||
ASSERT_OK(byte2.status());
|
||||
EXPECT_EQ(*byte2, (i + 1) % 16);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "testing.h"
|
||||
@@ -13,7 +14,7 @@ namespace test {
|
||||
|
||||
/**
|
||||
* @brief ZSCustomOverworld upgrade testing suite
|
||||
*
|
||||
*
|
||||
* This test suite validates ZSCustomOverworld version upgrades:
|
||||
* 1. Vanilla -> v2 upgrade with proper address changes
|
||||
* 2. v2 -> v3 upgrade with expanded features
|
||||
@@ -32,7 +33,7 @@ class ZSCustomOverworldUpgradeTest : public ::testing::Test {
|
||||
// Get ROM path from environment or use default
|
||||
const char* rom_path_env = getenv("YAZE_TEST_ROM_PATH");
|
||||
vanilla_rom_path_ = rom_path_env ? rom_path_env : "zelda3.sfc";
|
||||
|
||||
|
||||
if (!std::filesystem::exists(vanilla_rom_path_)) {
|
||||
GTEST_SKIP() << "Test ROM not found: " << vanilla_rom_path_;
|
||||
}
|
||||
@@ -41,20 +42,19 @@ class ZSCustomOverworldUpgradeTest : public ::testing::Test {
|
||||
vanilla_test_path_ = "test_vanilla.sfc";
|
||||
v2_test_path_ = "test_v2.sfc";
|
||||
v3_test_path_ = "test_v3.sfc";
|
||||
|
||||
|
||||
// Copy vanilla ROM for testing
|
||||
std::filesystem::copy_file(vanilla_rom_path_, vanilla_test_path_);
|
||||
|
||||
|
||||
// Define version-specific addresses and features
|
||||
InitializeVersionData();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// Clean up test files
|
||||
std::vector<std::string> test_files = {
|
||||
vanilla_test_path_, v2_test_path_, v3_test_path_
|
||||
};
|
||||
|
||||
std::vector<std::string> test_files = {vanilla_test_path_, v2_test_path_,
|
||||
v3_test_path_};
|
||||
|
||||
for (const auto& file : test_files) {
|
||||
if (std::filesystem::exists(file)) {
|
||||
std::filesystem::remove(file);
|
||||
@@ -65,41 +65,41 @@ class ZSCustomOverworldUpgradeTest : public ::testing::Test {
|
||||
void InitializeVersionData() {
|
||||
// Vanilla ROM addresses and values
|
||||
vanilla_data_ = {
|
||||
{"version_flag", {0x140145, 0xFF}}, // OverworldCustomASMHasBeenApplied
|
||||
{"message_ids", {0x3F51D, 0x00}}, // Message ID table start
|
||||
{"area_graphics", {0x7C9C, 0x00}}, // Area graphics table
|
||||
{"area_palettes", {0x7D1C, 0x00}}, // Area palettes table
|
||||
{"screen_sizes", {0x1788D, 0x01}}, // Screen sizes table
|
||||
{"sprite_sets", {0x7A41, 0x00}}, // Sprite sets table
|
||||
{"sprite_palettes", {0x7B41, 0x00}}, // Sprite palettes table
|
||||
{"version_flag", {0x140145, 0xFF}}, // OverworldCustomASMHasBeenApplied
|
||||
{"message_ids", {0x3F51D, 0x00}}, // Message ID table start
|
||||
{"area_graphics", {0x7C9C, 0x00}}, // Area graphics table
|
||||
{"area_palettes", {0x7D1C, 0x00}}, // Area palettes table
|
||||
{"screen_sizes", {0x1788D, 0x01}}, // Screen sizes table
|
||||
{"sprite_sets", {0x7A41, 0x00}}, // Sprite sets table
|
||||
{"sprite_palettes", {0x7B41, 0x00}}, // Sprite palettes table
|
||||
};
|
||||
|
||||
// v2 ROM addresses and values
|
||||
v2_data_ = {
|
||||
{"version_flag", {0x140145, 0x02}}, // v2 version
|
||||
{"message_ids", {0x1417F8, 0x00}}, // Expanded message ID table
|
||||
{"area_graphics", {0x7C9C, 0x00}}, // Same as vanilla
|
||||
{"area_palettes", {0x7D1C, 0x00}}, // Same as vanilla
|
||||
{"screen_sizes", {0x1788D, 0x01}}, // Same as vanilla
|
||||
{"sprite_sets", {0x7A41, 0x00}}, // Same as vanilla
|
||||
{"sprite_palettes", {0x7B41, 0x00}}, // Same as vanilla
|
||||
{"main_palettes", {0x140160, 0x00}}, // New v2 feature
|
||||
{"version_flag", {0x140145, 0x02}}, // v2 version
|
||||
{"message_ids", {0x1417F8, 0x00}}, // Expanded message ID table
|
||||
{"area_graphics", {0x7C9C, 0x00}}, // Same as vanilla
|
||||
{"area_palettes", {0x7D1C, 0x00}}, // Same as vanilla
|
||||
{"screen_sizes", {0x1788D, 0x01}}, // Same as vanilla
|
||||
{"sprite_sets", {0x7A41, 0x00}}, // Same as vanilla
|
||||
{"sprite_palettes", {0x7B41, 0x00}}, // Same as vanilla
|
||||
{"main_palettes", {0x140160, 0x00}}, // New v2 feature
|
||||
};
|
||||
|
||||
// v3 ROM addresses and values
|
||||
v3_data_ = {
|
||||
{"version_flag", {0x140145, 0x03}}, // v3 version
|
||||
{"message_ids", {0x1417F8, 0x00}}, // Same as v2
|
||||
{"area_graphics", {0x7C9C, 0x00}}, // Same as vanilla
|
||||
{"area_palettes", {0x7D1C, 0x00}}, // Same as vanilla
|
||||
{"screen_sizes", {0x1788D, 0x01}}, // Same as vanilla
|
||||
{"sprite_sets", {0x7A41, 0x00}}, // Same as vanilla
|
||||
{"sprite_palettes", {0x7B41, 0x00}}, // Same as vanilla
|
||||
{"main_palettes", {0x140160, 0x00}}, // Same as v2
|
||||
{"bg_colors", {0x140000, 0x00}}, // New v3 feature
|
||||
{"subscreen_overlays", {0x140340, 0x00}}, // New v3 feature
|
||||
{"animated_gfx", {0x1402A0, 0x00}}, // New v3 feature
|
||||
{"custom_tiles", {0x140480, 0x00}}, // New v3 feature
|
||||
{"version_flag", {0x140145, 0x03}}, // v3 version
|
||||
{"message_ids", {0x1417F8, 0x00}}, // Same as v2
|
||||
{"area_graphics", {0x7C9C, 0x00}}, // Same as vanilla
|
||||
{"area_palettes", {0x7D1C, 0x00}}, // Same as vanilla
|
||||
{"screen_sizes", {0x1788D, 0x01}}, // Same as vanilla
|
||||
{"sprite_sets", {0x7A41, 0x00}}, // Same as vanilla
|
||||
{"sprite_palettes", {0x7B41, 0x00}}, // Same as vanilla
|
||||
{"main_palettes", {0x140160, 0x00}}, // Same as v2
|
||||
{"bg_colors", {0x140000, 0x00}}, // New v3 feature
|
||||
{"subscreen_overlays", {0x140340, 0x00}}, // New v3 feature
|
||||
{"animated_gfx", {0x1402A0, 0x00}}, // New v3 feature
|
||||
{"custom_tiles", {0x140480, 0x00}}, // New v3 feature
|
||||
};
|
||||
}
|
||||
|
||||
@@ -120,15 +120,18 @@ class ZSCustomOverworldUpgradeTest : public ::testing::Test {
|
||||
// Apply version-specific features
|
||||
if (version == "v2") {
|
||||
// Enable v2 features
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x140146, 0x01)); // Enable main palettes
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x140146, 0x01)); // Enable main palettes
|
||||
} else if (version == "v3") {
|
||||
// Enable v3 features
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x140146, 0x01)); // Enable main palettes
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x140147, 0x01)); // Enable area-specific BG
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x140148, 0x01)); // Enable subscreen overlay
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x140149, 0x01)); // Enable animated GFX
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x14014A, 0x01)); // Enable custom tile GFX groups
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x14014B, 0x01)); // Enable mosaic
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x140146, 0x01)); // Enable main palettes
|
||||
RETURN_IF_ERROR(
|
||||
rom.WriteByte(0x140147, 0x01)); // Enable area-specific BG
|
||||
RETURN_IF_ERROR(
|
||||
rom.WriteByte(0x140148, 0x01)); // Enable subscreen overlay
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x140149, 0x01)); // Enable animated GFX
|
||||
RETURN_IF_ERROR(
|
||||
rom.WriteByte(0x14014A, 0x01)); // Enable custom tile GFX groups
|
||||
RETURN_IF_ERROR(rom.WriteByte(0x14014B, 0x01)); // Enable mosaic
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
@@ -157,7 +160,7 @@ class ZSCustomOverworldUpgradeTest : public ::testing::Test {
|
||||
std::string vanilla_test_path_;
|
||||
std::string v2_test_path_;
|
||||
std::string v3_test_path_;
|
||||
|
||||
|
||||
std::map<std::string, std::pair<uint32_t, uint8_t>> vanilla_data_;
|
||||
std::map<std::string, std::pair<uint32_t, uint8_t>> v2_data_;
|
||||
std::map<std::string, std::pair<uint32_t, uint8_t>> v3_data_;
|
||||
@@ -167,10 +170,10 @@ class ZSCustomOverworldUpgradeTest : public ::testing::Test {
|
||||
TEST_F(ZSCustomOverworldUpgradeTest, VanillaBaseline) {
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Validate vanilla addresses
|
||||
EXPECT_TRUE(ValidateVersionAddresses(*rom, "vanilla"));
|
||||
|
||||
|
||||
// Verify version flag
|
||||
auto version_byte = rom->ReadByte(0x140145);
|
||||
ASSERT_TRUE(version_byte.ok());
|
||||
@@ -182,20 +185,20 @@ TEST_F(ZSCustomOverworldUpgradeTest, VanillaToV2Upgrade) {
|
||||
// Load vanilla ROM
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Apply v2 patch
|
||||
ASSERT_OK(ApplyVersionPatch(*rom, "v2"));
|
||||
|
||||
|
||||
// Validate v2 addresses
|
||||
EXPECT_TRUE(ValidateVersionAddresses(*rom, "v2"));
|
||||
|
||||
|
||||
// Save v2 ROM
|
||||
ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = v2_test_path_}));
|
||||
|
||||
|
||||
// Reload and verify
|
||||
std::unique_ptr<Rom> reloaded_rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(reloaded_rom->LoadFromFile(v2_test_path_));
|
||||
|
||||
|
||||
EXPECT_TRUE(ValidateVersionAddresses(*reloaded_rom, "v2"));
|
||||
auto version_byte = reloaded_rom->ReadByte(0x140145);
|
||||
ASSERT_TRUE(version_byte.ok());
|
||||
@@ -207,23 +210,23 @@ TEST_F(ZSCustomOverworldUpgradeTest, V2ToV3Upgrade) {
|
||||
// Load vanilla ROM
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Apply v2 patch first
|
||||
ASSERT_OK(ApplyVersionPatch(*rom, "v2"));
|
||||
|
||||
|
||||
// Apply v3 patch
|
||||
ASSERT_OK(ApplyVersionPatch(*rom, "v3"));
|
||||
|
||||
|
||||
// Validate v3 addresses
|
||||
EXPECT_TRUE(ValidateVersionAddresses(*rom, "v3"));
|
||||
|
||||
|
||||
// Save v3 ROM
|
||||
ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = v3_test_path_}));
|
||||
|
||||
|
||||
// Reload and verify
|
||||
std::unique_ptr<Rom> reloaded_rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(reloaded_rom->LoadFromFile(v3_test_path_));
|
||||
|
||||
|
||||
EXPECT_TRUE(ValidateVersionAddresses(*reloaded_rom, "v3"));
|
||||
auto version_byte = reloaded_rom->ReadByte(0x140145);
|
||||
ASSERT_TRUE(version_byte.ok());
|
||||
@@ -235,20 +238,20 @@ TEST_F(ZSCustomOverworldUpgradeTest, VanillaToV3Upgrade) {
|
||||
// Load vanilla ROM
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Apply v3 patch directly
|
||||
ASSERT_OK(ApplyVersionPatch(*rom, "v3"));
|
||||
|
||||
|
||||
// Validate v3 addresses
|
||||
EXPECT_TRUE(ValidateVersionAddresses(*rom, "v3"));
|
||||
|
||||
|
||||
// Save v3 ROM
|
||||
ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = v3_test_path_}));
|
||||
|
||||
|
||||
// Reload and verify
|
||||
std::unique_ptr<Rom> reloaded_rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(reloaded_rom->LoadFromFile(v3_test_path_));
|
||||
|
||||
|
||||
EXPECT_TRUE(ValidateVersionAddresses(*reloaded_rom, "v3"));
|
||||
auto version_byte = reloaded_rom->ReadByte(0x140145);
|
||||
ASSERT_TRUE(version_byte.ok());
|
||||
@@ -261,11 +264,11 @@ TEST_F(ZSCustomOverworldUpgradeTest, AddressValidation) {
|
||||
std::unique_ptr<Rom> vanilla_rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(vanilla_rom->LoadFromFile(vanilla_test_path_));
|
||||
EXPECT_TRUE(ValidateVersionAddresses(*vanilla_rom, "vanilla"));
|
||||
|
||||
|
||||
// Test v2 addresses
|
||||
ASSERT_OK(ApplyVersionPatch(*vanilla_rom, "v2"));
|
||||
EXPECT_TRUE(ValidateVersionAddresses(*vanilla_rom, "v2"));
|
||||
|
||||
|
||||
// Test v3 addresses
|
||||
ASSERT_OK(ApplyVersionPatch(*vanilla_rom, "v3"));
|
||||
EXPECT_TRUE(ValidateVersionAddresses(*vanilla_rom, "v3"));
|
||||
@@ -276,7 +279,7 @@ TEST_F(ZSCustomOverworldUpgradeTest, FeatureToggle) {
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
ASSERT_OK(ApplyVersionPatch(*rom, "v3"));
|
||||
|
||||
|
||||
// Test feature flags
|
||||
auto main_palettes = rom->ReadByte(0x140146);
|
||||
auto area_bg = rom->ReadByte(0x140147);
|
||||
@@ -284,44 +287,44 @@ TEST_F(ZSCustomOverworldUpgradeTest, FeatureToggle) {
|
||||
auto animated_gfx = rom->ReadByte(0x140149);
|
||||
auto custom_tiles = rom->ReadByte(0x14014A);
|
||||
auto mosaic = rom->ReadByte(0x14014B);
|
||||
|
||||
|
||||
ASSERT_TRUE(main_palettes.ok());
|
||||
ASSERT_TRUE(area_bg.ok());
|
||||
ASSERT_TRUE(subscreen_overlay.ok());
|
||||
ASSERT_TRUE(animated_gfx.ok());
|
||||
ASSERT_TRUE(custom_tiles.ok());
|
||||
ASSERT_TRUE(mosaic.ok());
|
||||
|
||||
EXPECT_EQ(*main_palettes, 0x01); // Main palettes enabled
|
||||
EXPECT_EQ(*area_bg, 0x01); // Area-specific BG enabled
|
||||
EXPECT_EQ(*subscreen_overlay, 0x01); // Subscreen overlay enabled
|
||||
EXPECT_EQ(*animated_gfx, 0x01); // Animated GFX enabled
|
||||
EXPECT_EQ(*custom_tiles, 0x01); // Custom tile GFX groups enabled
|
||||
EXPECT_EQ(*mosaic, 0x01); // Mosaic enabled
|
||||
|
||||
|
||||
EXPECT_EQ(*main_palettes, 0x01); // Main palettes enabled
|
||||
EXPECT_EQ(*area_bg, 0x01); // Area-specific BG enabled
|
||||
EXPECT_EQ(*subscreen_overlay, 0x01); // Subscreen overlay enabled
|
||||
EXPECT_EQ(*animated_gfx, 0x01); // Animated GFX enabled
|
||||
EXPECT_EQ(*custom_tiles, 0x01); // Custom tile GFX groups enabled
|
||||
EXPECT_EQ(*mosaic, 0x01); // Mosaic enabled
|
||||
|
||||
// Disable some features
|
||||
ASSERT_OK(rom->WriteByte(0x140147, 0x00)); // Disable area-specific BG
|
||||
ASSERT_OK(rom->WriteByte(0x140149, 0x00)); // Disable animated GFX
|
||||
|
||||
ASSERT_OK(rom->WriteByte(0x140147, 0x00)); // Disable area-specific BG
|
||||
ASSERT_OK(rom->WriteByte(0x140149, 0x00)); // Disable animated GFX
|
||||
|
||||
// Verify features are disabled
|
||||
auto disabled_area_bg = rom->ReadByte(0x140147);
|
||||
auto disabled_animated_gfx = rom->ReadByte(0x140149);
|
||||
ASSERT_TRUE(disabled_area_bg.ok());
|
||||
ASSERT_TRUE(disabled_animated_gfx.ok());
|
||||
|
||||
|
||||
EXPECT_EQ(*disabled_area_bg, 0x00);
|
||||
EXPECT_EQ(*disabled_animated_gfx, 0x00);
|
||||
|
||||
|
||||
// Re-enable features
|
||||
ASSERT_OK(rom->WriteByte(0x140147, 0x01));
|
||||
ASSERT_OK(rom->WriteByte(0x140149, 0x01));
|
||||
|
||||
|
||||
// Verify features are re-enabled
|
||||
auto reenabled_area_bg = rom->ReadByte(0x140147);
|
||||
auto reenabled_animated_gfx = rom->ReadByte(0x140149);
|
||||
ASSERT_TRUE(reenabled_area_bg.ok());
|
||||
ASSERT_TRUE(reenabled_animated_gfx.ok());
|
||||
|
||||
|
||||
EXPECT_EQ(*reenabled_area_bg, 0x01);
|
||||
EXPECT_EQ(*reenabled_animated_gfx, 0x01);
|
||||
}
|
||||
@@ -330,47 +333,47 @@ TEST_F(ZSCustomOverworldUpgradeTest, FeatureToggle) {
|
||||
TEST_F(ZSCustomOverworldUpgradeTest, DataIntegrity) {
|
||||
std::unique_ptr<Rom> rom = std::make_unique<Rom>();
|
||||
ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
|
||||
|
||||
|
||||
// Store some original data
|
||||
auto original_graphics = rom->ReadByte(0x7C9C);
|
||||
auto original_palette = rom->ReadByte(0x7D1C);
|
||||
auto original_sprite_set = rom->ReadByte(0x7A41);
|
||||
|
||||
|
||||
ASSERT_TRUE(original_graphics.ok());
|
||||
ASSERT_TRUE(original_palette.ok());
|
||||
ASSERT_TRUE(original_sprite_set.ok());
|
||||
|
||||
|
||||
// Upgrade to v3
|
||||
ASSERT_OK(ApplyVersionPatch(*rom, "v3"));
|
||||
|
||||
|
||||
// Verify original data is preserved
|
||||
auto preserved_graphics = rom->ReadByte(0x7C9C);
|
||||
auto preserved_palette = rom->ReadByte(0x7D1C);
|
||||
auto preserved_sprite_set = rom->ReadByte(0x7A41);
|
||||
|
||||
|
||||
ASSERT_TRUE(preserved_graphics.ok());
|
||||
ASSERT_TRUE(preserved_palette.ok());
|
||||
ASSERT_TRUE(preserved_sprite_set.ok());
|
||||
|
||||
|
||||
EXPECT_EQ(*preserved_graphics, *original_graphics);
|
||||
EXPECT_EQ(*preserved_palette, *original_palette);
|
||||
EXPECT_EQ(*preserved_sprite_set, *original_sprite_set);
|
||||
|
||||
|
||||
// Verify new v3 data is initialized
|
||||
auto bg_colors = rom->ReadByte(0x140000);
|
||||
auto subscreen_overlays = rom->ReadByte(0x140340);
|
||||
auto animated_gfx = rom->ReadByte(0x1402A0);
|
||||
auto custom_tiles = rom->ReadByte(0x140480);
|
||||
|
||||
|
||||
ASSERT_TRUE(bg_colors.ok());
|
||||
ASSERT_TRUE(subscreen_overlays.ok());
|
||||
ASSERT_TRUE(animated_gfx.ok());
|
||||
ASSERT_TRUE(custom_tiles.ok());
|
||||
|
||||
EXPECT_EQ(*bg_colors, 0x00); // BG colors
|
||||
EXPECT_EQ(*subscreen_overlays, 0x00); // Subscreen overlays
|
||||
EXPECT_EQ(*animated_gfx, 0x00); // Animated GFX
|
||||
EXPECT_EQ(*custom_tiles, 0x00); // Custom tiles
|
||||
|
||||
EXPECT_EQ(*bg_colors, 0x00); // BG colors
|
||||
EXPECT_EQ(*subscreen_overlays, 0x00); // Subscreen overlays
|
||||
EXPECT_EQ(*animated_gfx, 0x00); // Animated GFX
|
||||
EXPECT_EQ(*custom_tiles, 0x00); // Custom tiles
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -21,17 +21,16 @@ using ::testing::Return;
|
||||
class MockGuiAutomationClient : public GuiAutomationClient {
|
||||
public:
|
||||
MockGuiAutomationClient() : GuiAutomationClient("localhost:50052") {}
|
||||
|
||||
|
||||
MOCK_METHOD(absl::Status, Connect, ());
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Ping, (const std::string&));
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Click,
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Click,
|
||||
(const std::string&, ClickType));
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Type,
|
||||
(const std::string&, const std::string&, bool));
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Wait,
|
||||
(const std::string&, int, int));
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Assert,
|
||||
(const std::string&));
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Assert, (const std::string&));
|
||||
};
|
||||
|
||||
class AIGUIControllerTest : public ::testing::Test {
|
||||
@@ -42,12 +41,12 @@ class AIGUIControllerTest : public ::testing::Test {
|
||||
config.api_key = "test_key";
|
||||
config.model = "gemini-2.5-flash";
|
||||
gemini_service_ = std::make_unique<GeminiAIService>(config);
|
||||
|
||||
|
||||
gui_client_ = std::make_unique<MockGuiAutomationClient>();
|
||||
|
||||
controller_ = std::make_unique<AIGUIController>(
|
||||
gemini_service_.get(), gui_client_.get());
|
||||
|
||||
|
||||
controller_ = std::make_unique<AIGUIController>(gemini_service_.get(),
|
||||
gui_client_.get());
|
||||
|
||||
ControlLoopConfig loop_config;
|
||||
loop_config.max_iterations = 5;
|
||||
loop_config.enable_vision_verification = false; // Disable for unit tests
|
||||
@@ -68,16 +67,16 @@ TEST_F(AIGUIControllerTest, ExecuteClickAction_Success) {
|
||||
AIAction action(AIActionType::kClickButton);
|
||||
action.parameters["target"] = "button:Test";
|
||||
action.parameters["click_type"] = "left";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = true;
|
||||
result.message = "Click successful";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("button:Test", ClickType::kLeft))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok()) << status.status().message();
|
||||
EXPECT_TRUE(status->action_successful);
|
||||
}
|
||||
@@ -85,18 +84,18 @@ TEST_F(AIGUIControllerTest, ExecuteClickAction_Success) {
|
||||
TEST_F(AIGUIControllerTest, ExecuteClickAction_Failure) {
|
||||
AIAction action(AIActionType::kClickButton);
|
||||
action.parameters["target"] = "button:NonExistent";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = false;
|
||||
result.message = "Button not found";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("button:NonExistent", ClickType::kLeft))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.status().message(),
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("Click action failed"));
|
||||
}
|
||||
|
||||
@@ -105,20 +104,21 @@ TEST_F(AIGUIControllerTest, ExecuteClickAction_Failure) {
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(AIGUIControllerTest, ExecuteTypeAction_Success) {
|
||||
AIAction action(AIActionType::kSelectTile); // Using SelectTile as a type action
|
||||
AIAction action(
|
||||
AIActionType::kSelectTile); // Using SelectTile as a type action
|
||||
action.parameters["target"] = "input:TileID";
|
||||
action.parameters["text"] = "0x42";
|
||||
action.parameters["clear_first"] = "true";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = true;
|
||||
result.message = "Text entered";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Type("input:TileID", "0x42", true))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok());
|
||||
EXPECT_TRUE(status->action_successful);
|
||||
}
|
||||
@@ -131,16 +131,16 @@ TEST_F(AIGUIControllerTest, ExecuteWaitAction_Success) {
|
||||
AIAction action(AIActionType::kWait);
|
||||
action.parameters["condition"] = "window:OverworldEditor";
|
||||
action.parameters["timeout_ms"] = "2000";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = true;
|
||||
result.message = "Condition met";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Wait("window:OverworldEditor", 2000, 100))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok());
|
||||
EXPECT_TRUE(status->action_successful);
|
||||
}
|
||||
@@ -149,16 +149,16 @@ TEST_F(AIGUIControllerTest, ExecuteWaitAction_Timeout) {
|
||||
AIAction action(AIActionType::kWait);
|
||||
action.parameters["condition"] = "window:NonExistentWindow";
|
||||
action.parameters["timeout_ms"] = "100";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = false;
|
||||
result.message = "Timeout waiting for condition";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Wait("window:NonExistentWindow", 100, 100))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
}
|
||||
|
||||
@@ -169,18 +169,17 @@ TEST_F(AIGUIControllerTest, ExecuteWaitAction_Timeout) {
|
||||
TEST_F(AIGUIControllerTest, ExecuteVerifyAction_Success) {
|
||||
AIAction action(AIActionType::kVerifyTile);
|
||||
action.parameters["condition"] = "tile_placed";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = true;
|
||||
result.message = "Assertion passed";
|
||||
result.expected_value = "0x42";
|
||||
result.actual_value = "0x42";
|
||||
|
||||
EXPECT_CALL(*gui_client_, Assert("tile_placed"))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Assert("tile_placed")).WillOnce(Return(result));
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok());
|
||||
EXPECT_TRUE(status->action_successful);
|
||||
}
|
||||
@@ -188,25 +187,23 @@ TEST_F(AIGUIControllerTest, ExecuteVerifyAction_Success) {
|
||||
TEST_F(AIGUIControllerTest, ExecuteVerifyAction_Failure) {
|
||||
AIAction action(AIActionType::kVerifyTile);
|
||||
action.parameters["condition"] = "tile_placed";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = false;
|
||||
result.message = "Assertion failed";
|
||||
result.expected_value = "0x42";
|
||||
result.actual_value = "0x00";
|
||||
|
||||
EXPECT_CALL(*gui_client_, Assert("tile_placed"))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Assert("tile_placed")).WillOnce(Return(result));
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("Assert action failed"));
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("expected: 0x42"));
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("actual: 0x00"));
|
||||
EXPECT_THAT(status.status().message(), ::testing::HasSubstr("actual: 0x00"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -219,27 +216,27 @@ TEST_F(AIGUIControllerTest, ExecutePlaceTileAction_CompleteFlow) {
|
||||
action.parameters["x"] = "10";
|
||||
action.parameters["y"] = "20";
|
||||
action.parameters["tile"] = "0x42";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = true;
|
||||
|
||||
|
||||
// Expect sequence: open menu, wait for window, set map ID, click position
|
||||
testing::InSequence seq;
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("menu:Overworld", ClickType::kLeft))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Wait("window:Overworld Editor", 2000, 100))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Type("input:Map ID", "5", true))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click(::testing::_, ClickType::kLeft))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok()) << status.status().message();
|
||||
EXPECT_TRUE(status->action_successful);
|
||||
}
|
||||
@@ -250,26 +247,26 @@ TEST_F(AIGUIControllerTest, ExecutePlaceTileAction_CompleteFlow) {
|
||||
|
||||
TEST_F(AIGUIControllerTest, ExecuteActions_MultipleActionsSuccess) {
|
||||
std::vector<AIAction> actions;
|
||||
|
||||
|
||||
AIAction action1(AIActionType::kClickButton);
|
||||
action1.parameters["target"] = "button:Overworld";
|
||||
actions.push_back(action1);
|
||||
|
||||
|
||||
AIAction action2(AIActionType::kWait);
|
||||
action2.parameters["condition"] = "window:OverworldEditor";
|
||||
actions.push_back(action2);
|
||||
|
||||
|
||||
AutomationResult success_result;
|
||||
success_result.success = true;
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("button:Overworld", ClickType::kLeft))
|
||||
.WillOnce(Return(success_result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Wait("window:OverworldEditor", 5000, 100))
|
||||
.WillOnce(Return(success_result));
|
||||
|
||||
|
||||
auto result = controller_->ExecuteActions(actions);
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
EXPECT_TRUE(result->success);
|
||||
EXPECT_EQ(result->actions_executed.size(), 2);
|
||||
@@ -277,28 +274,27 @@ TEST_F(AIGUIControllerTest, ExecuteActions_MultipleActionsSuccess) {
|
||||
|
||||
TEST_F(AIGUIControllerTest, ExecuteActions_StopsOnFirstFailure) {
|
||||
std::vector<AIAction> actions;
|
||||
|
||||
|
||||
AIAction action1(AIActionType::kClickButton);
|
||||
action1.parameters["target"] = "button:Test";
|
||||
actions.push_back(action1);
|
||||
|
||||
|
||||
AIAction action2(AIActionType::kClickButton);
|
||||
action2.parameters["target"] = "button:NeverReached";
|
||||
actions.push_back(action2);
|
||||
|
||||
|
||||
AutomationResult failure_result;
|
||||
failure_result.success = false;
|
||||
failure_result.message = "First action failed";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("button:Test", ClickType::kLeft))
|
||||
.WillOnce(Return(failure_result));
|
||||
|
||||
|
||||
// Second action should never be called
|
||||
EXPECT_CALL(*gui_client_, Click("button:NeverReached", _))
|
||||
.Times(0);
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("button:NeverReached", _)).Times(0);
|
||||
|
||||
auto result = controller_->ExecuteActions(actions);
|
||||
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_EQ(result->actions_executed.size(), 1);
|
||||
}
|
||||
@@ -309,9 +305,9 @@ TEST_F(AIGUIControllerTest, ExecuteActions_StopsOnFirstFailure) {
|
||||
|
||||
TEST_F(AIGUIControllerTest, ExecuteAction_InvalidActionType) {
|
||||
AIAction action(AIActionType::kInvalidAction);
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("Action type not implemented"));
|
||||
@@ -320,9 +316,9 @@ TEST_F(AIGUIControllerTest, ExecuteAction_InvalidActionType) {
|
||||
TEST_F(AIGUIControllerTest, ExecutePlaceTileAction_MissingParameters) {
|
||||
AIAction action(AIActionType::kPlaceTile);
|
||||
// Missing required parameters
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("requires map_id, x, y, and tile"));
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "cli/service/ai/ai_action_parser.h"
|
||||
#include "cli/service/ai/vision_action_refiner.h"
|
||||
#include "cli/service/ai/ai_gui_controller.h"
|
||||
#include "cli/service/ai/vision_action_refiner.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
#include "cli/service/gui/gui_automation_client.h"
|
||||
#include "cli/service/ai/gemini_ai_service.h"
|
||||
#include "cli/service/gui/gui_automation_client.h"
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
@@ -15,7 +14,7 @@ namespace test {
|
||||
|
||||
/**
|
||||
* @brief Integration tests for AI-controlled tile placement
|
||||
*
|
||||
*
|
||||
* These tests verify the complete pipeline:
|
||||
* 1. Parse natural language commands
|
||||
* 2. Execute actions via gRPC
|
||||
@@ -32,33 +31,33 @@ class AITilePlacementTest : public ::testing::Test {
|
||||
|
||||
TEST_F(AITilePlacementTest, ParsePlaceTileCommand) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
// Test basic tile placement command
|
||||
auto result = AIActionParser::ParseCommand(
|
||||
"Place tile 0x42 at overworld position (5, 7)");
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
EXPECT_EQ(result->size(), 3); // Select, Place, Save
|
||||
|
||||
|
||||
// Check first action (Select)
|
||||
EXPECT_EQ(result->at(0).type, AIActionType::kSelectTile);
|
||||
EXPECT_EQ(result->at(0).parameters.at("tile_id"), "66"); // 0x42 = 66
|
||||
|
||||
|
||||
// Check second action (Place)
|
||||
EXPECT_EQ(result->at(1).type, AIActionType::kPlaceTile);
|
||||
EXPECT_EQ(result->at(1).parameters.at("x"), "5");
|
||||
EXPECT_EQ(result->at(1).parameters.at("y"), "7");
|
||||
EXPECT_EQ(result->at(1).parameters.at("map_id"), "0");
|
||||
|
||||
|
||||
// Check third action (Save)
|
||||
EXPECT_EQ(result->at(2).type, AIActionType::kSaveTile);
|
||||
}
|
||||
|
||||
TEST_F(AITilePlacementTest, ParseSelectTileCommand) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
auto result = AIActionParser::ParseCommand("Select tile 100");
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_EQ(result->size(), 1);
|
||||
EXPECT_EQ(result->at(0).type, AIActionType::kSelectTile);
|
||||
@@ -67,9 +66,9 @@ TEST_F(AITilePlacementTest, ParseSelectTileCommand) {
|
||||
|
||||
TEST_F(AITilePlacementTest, ParseOpenEditorCommand) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
auto result = AIActionParser::ParseCommand("Open the overworld editor");
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_EQ(result->size(), 1);
|
||||
EXPECT_EQ(result->at(0).type, AIActionType::kOpenEditor);
|
||||
@@ -78,13 +77,10 @@ TEST_F(AITilePlacementTest, ParseOpenEditorCommand) {
|
||||
|
||||
TEST_F(AITilePlacementTest, ActionToStringRoundtrip) {
|
||||
using namespace cli::ai;
|
||||
|
||||
AIAction action(AIActionType::kPlaceTile, {
|
||||
{"x", "5"},
|
||||
{"y", "7"},
|
||||
{"tile_id", "42"}
|
||||
});
|
||||
|
||||
|
||||
AIAction action(AIActionType::kPlaceTile,
|
||||
{{"x", "5"}, {"y", "7"}, {"tile_id", "42"}});
|
||||
|
||||
std::string str = AIActionParser::ActionToString(action);
|
||||
EXPECT_FALSE(str.empty());
|
||||
EXPECT_TRUE(str.find("5") != std::string::npos);
|
||||
@@ -99,14 +95,14 @@ TEST_F(AITilePlacementTest, DISABLED_VisionAnalysisBasic) {
|
||||
if (!api_key || std::string(api_key).empty()) {
|
||||
GTEST_SKIP() << "GEMINI_API_KEY not set";
|
||||
}
|
||||
|
||||
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key;
|
||||
config.model = "gemini-2.5-flash";
|
||||
|
||||
|
||||
cli::GeminiAIService gemini_service(config);
|
||||
cli::ai::VisionActionRefiner refiner(&gemini_service);
|
||||
|
||||
|
||||
// Would need actual screenshots for real test
|
||||
// This is a structure test
|
||||
EXPECT_TRUE(true);
|
||||
@@ -117,35 +113,35 @@ TEST_F(AITilePlacementTest, DISABLED_FullAIControlLoop) {
|
||||
// 1. YAZE GUI running with gRPC test harness
|
||||
// 2. Gemini API key for vision
|
||||
// 3. Test ROM loaded
|
||||
|
||||
|
||||
const char* api_key = std::getenv("GEMINI_API_KEY");
|
||||
if (!api_key || std::string(api_key).empty()) {
|
||||
GTEST_SKIP() << "GEMINI_API_KEY not set";
|
||||
}
|
||||
|
||||
|
||||
// Initialize services
|
||||
cli::GeminiConfig gemini_config;
|
||||
gemini_config.api_key = api_key;
|
||||
cli::GeminiAIService gemini_service(gemini_config);
|
||||
|
||||
|
||||
cli::GuiAutomationClient gui_client("localhost:50051");
|
||||
auto connect_status = gui_client.Connect();
|
||||
if (!connect_status.ok()) {
|
||||
GTEST_SKIP() << "GUI test harness not available: "
|
||||
GTEST_SKIP() << "GUI test harness not available: "
|
||||
<< connect_status.message();
|
||||
}
|
||||
|
||||
|
||||
// Create AI controller
|
||||
cli::ai::AIGUIController controller(&gemini_service, &gui_client);
|
||||
cli::ai::ControlLoopConfig config;
|
||||
config.max_iterations = 5;
|
||||
config.enable_vision_verification = true;
|
||||
controller.Initialize(config);
|
||||
|
||||
|
||||
// Execute command
|
||||
auto result = controller.ExecuteCommand(
|
||||
"Place tile 0x42 at overworld position (5, 7)");
|
||||
|
||||
auto result =
|
||||
controller.ExecuteCommand("Place tile 0x42 at overworld position (5, 7)");
|
||||
|
||||
if (result.ok()) {
|
||||
EXPECT_TRUE(result->success);
|
||||
EXPECT_GT(result->iterations_performed, 0);
|
||||
@@ -156,16 +152,14 @@ TEST_F(AITilePlacementTest, DISABLED_FullAIControlLoop) {
|
||||
|
||||
TEST_F(AITilePlacementTest, ActionRefinement) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
// Test refinement logic with a failed action
|
||||
VisionAnalysisResult analysis;
|
||||
analysis.action_successful = false;
|
||||
analysis.error_message = "Element not found";
|
||||
|
||||
AIAction original_action(AIActionType::kClickButton, {
|
||||
{"button", "save"}
|
||||
});
|
||||
|
||||
|
||||
AIAction original_action(AIActionType::kClickButton, {{"button", "save"}});
|
||||
|
||||
// Would need VisionActionRefiner for real test
|
||||
// This verifies the structure compiles
|
||||
EXPECT_TRUE(true);
|
||||
@@ -173,15 +167,12 @@ TEST_F(AITilePlacementTest, ActionRefinement) {
|
||||
|
||||
TEST_F(AITilePlacementTest, MultipleCommandsParsing) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
// Test that we can parse multiple commands in sequence
|
||||
std::vector<std::string> commands = {
|
||||
"Open overworld editor",
|
||||
"Select tile 0x42",
|
||||
"Place tile at position (5, 7)",
|
||||
"Save changes"
|
||||
};
|
||||
|
||||
"Open overworld editor", "Select tile 0x42",
|
||||
"Place tile at position (5, 7)", "Save changes"};
|
||||
|
||||
for (const auto& cmd : commands) {
|
||||
auto result = AIActionParser::ParseCommand(cmd);
|
||||
// At least some should parse successfully
|
||||
@@ -193,13 +184,13 @@ TEST_F(AITilePlacementTest, MultipleCommandsParsing) {
|
||||
|
||||
TEST_F(AITilePlacementTest, HexAndDecimalParsing) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
// Test hex notation
|
||||
auto hex_result = AIActionParser::ParseCommand("Select tile 0xFF");
|
||||
if (hex_result.ok() && !hex_result->empty()) {
|
||||
EXPECT_EQ(hex_result->at(0).parameters.at("tile_id"), "255");
|
||||
}
|
||||
|
||||
|
||||
// Test decimal notation
|
||||
auto dec_result = AIActionParser::ParseCommand("Select tile 255");
|
||||
if (dec_result.ok() && !dec_result->empty()) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "cli/service/ai/gemini_ai_service.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
#include "app/service/screenshot_utils.h"
|
||||
@@ -20,51 +20,47 @@ class GeminiVisionTest : public ::testing::Test {
|
||||
if (!api_key || std::string(api_key).empty()) {
|
||||
GTEST_SKIP() << "GEMINI_API_KEY not set. Skipping multimodal tests.";
|
||||
}
|
||||
|
||||
|
||||
api_key_ = api_key;
|
||||
|
||||
|
||||
// Create test data directory
|
||||
test_dir_ = std::filesystem::temp_directory_path() / "yaze_multimodal_test";
|
||||
std::filesystem::create_directories(test_dir_);
|
||||
}
|
||||
|
||||
|
||||
void TearDown() override {
|
||||
// Clean up test directory
|
||||
if (std::filesystem::exists(test_dir_)) {
|
||||
std::filesystem::remove_all(test_dir_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Helper: Create a simple test image (16x16 PNG)
|
||||
std::filesystem::path CreateTestImage() {
|
||||
auto image_path = test_dir_ / "test_image.png";
|
||||
|
||||
|
||||
// Create a minimal PNG file (16x16 red square)
|
||||
// PNG signature + IHDR + IDAT + IEND
|
||||
const unsigned char png_data[] = {
|
||||
// PNG signature
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
|
||||
// IHDR chunk
|
||||
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,
|
||||
0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x91, 0x68,
|
||||
0x36,
|
||||
// IDAT chunk (minimal data)
|
||||
0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54,
|
||||
0x08, 0x99, 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00,
|
||||
0x03, 0x01, 0x01, 0x00, 0x18, 0xDD, 0x8D, 0xB4,
|
||||
// IEND chunk
|
||||
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44,
|
||||
0xAE, 0x42, 0x60, 0x82
|
||||
};
|
||||
|
||||
// PNG signature
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
|
||||
// IHDR chunk
|
||||
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10,
|
||||
0x00, 0x00, 0x00, 0x10, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x91, 0x68,
|
||||
0x36,
|
||||
// IDAT chunk (minimal data)
|
||||
0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0xF8,
|
||||
0xCF, 0xC0, 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0x18, 0xDD, 0x8D, 0xB4,
|
||||
// IEND chunk
|
||||
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82};
|
||||
|
||||
std::ofstream file(image_path, std::ios::binary);
|
||||
file.write(reinterpret_cast<const char*>(png_data), sizeof(png_data));
|
||||
file.close();
|
||||
|
||||
|
||||
return image_path;
|
||||
}
|
||||
|
||||
|
||||
std::string api_key_;
|
||||
std::filesystem::path test_dir_;
|
||||
};
|
||||
@@ -74,22 +70,20 @@ TEST_F(GeminiVisionTest, BasicImageAnalysis) {
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash"; // Vision-capable model
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
// Create test image
|
||||
auto image_path = CreateTestImage();
|
||||
ASSERT_TRUE(std::filesystem::exists(image_path));
|
||||
|
||||
|
||||
// Send multimodal request
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
"Describe this image in one sentence."
|
||||
);
|
||||
|
||||
image_path.string(), "Describe this image in one sentence.");
|
||||
|
||||
ASSERT_TRUE(response.ok()) << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
|
||||
|
||||
std::cout << "Vision API response: " << response->text_response << std::endl;
|
||||
}
|
||||
|
||||
@@ -98,42 +92,41 @@ TEST_F(GeminiVisionTest, ImageWithSpecificPrompt) {
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
auto image_path = CreateTestImage();
|
||||
|
||||
|
||||
// Ask specific question about the image
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
"What color is the dominant color in this image? Answer with just the color name."
|
||||
);
|
||||
|
||||
"What color is the dominant color in this image? Answer with just the "
|
||||
"color name.");
|
||||
|
||||
ASSERT_TRUE(response.ok()) << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
|
||||
|
||||
// Response should mention "red" since we created a red square
|
||||
std::string response_lower = response->text_response;
|
||||
std::transform(response_lower.begin(), response_lower.end(),
|
||||
response_lower.begin(), ::tolower);
|
||||
response_lower.begin(), ::tolower);
|
||||
EXPECT_TRUE(response_lower.find("red") != std::string::npos ||
|
||||
response_lower.find("pink") != std::string::npos)
|
||||
<< "Expected color 'red' or 'pink' in response: " << response->text_response;
|
||||
<< "Expected color 'red' or 'pink' in response: "
|
||||
<< response->text_response;
|
||||
}
|
||||
|
||||
TEST_F(GeminiVisionTest, InvalidImagePath) {
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
// Try with non-existent image
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
"/nonexistent/image.png",
|
||||
"Describe this image."
|
||||
);
|
||||
|
||||
auto response = service.GenerateMultimodalResponse("/nonexistent/image.png",
|
||||
"Describe this image.");
|
||||
|
||||
EXPECT_FALSE(response.ok());
|
||||
EXPECT_TRUE(absl::IsNotFound(response.status()) ||
|
||||
absl::IsInternal(response.status()));
|
||||
@@ -144,32 +137,31 @@ TEST_F(GeminiVisionTest, InvalidImagePath) {
|
||||
TEST_F(GeminiVisionTest, ScreenshotCaptureIntegration) {
|
||||
// Note: This test requires a running YAZE instance with gRPC test harness
|
||||
// Skip if we can't connect
|
||||
|
||||
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
// Attempt to capture a screenshot
|
||||
auto screenshot_result = yaze::test::CaptureHarnessScreenshot(
|
||||
(test_dir_ / "screenshot.png").string());
|
||||
|
||||
|
||||
if (!screenshot_result.ok()) {
|
||||
GTEST_SKIP() << "Screenshot capture failed (YAZE may not be running): "
|
||||
<< screenshot_result.status().message();
|
||||
}
|
||||
|
||||
|
||||
// Analyze the captured screenshot
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
screenshot_result->file_path,
|
||||
"What UI elements are visible in this screenshot? List them."
|
||||
);
|
||||
|
||||
"What UI elements are visible in this screenshot? List them.");
|
||||
|
||||
ASSERT_TRUE(response.ok()) << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
|
||||
|
||||
std::cout << "Screenshot analysis: " << response->text_response << std::endl;
|
||||
}
|
||||
#endif
|
||||
@@ -180,21 +172,20 @@ TEST_F(GeminiVisionTest, MultipleRequestsSequential) {
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
auto image_path = CreateTestImage();
|
||||
|
||||
|
||||
// Make 3 sequential requests
|
||||
const int num_requests = 3;
|
||||
for (int i = 0; i < num_requests; ++i) {
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
absl::StrCat("Request ", i + 1, ": Describe this image briefly.")
|
||||
);
|
||||
|
||||
ASSERT_TRUE(response.ok()) << "Request " << i + 1 << " failed: "
|
||||
<< response.status().message();
|
||||
absl::StrCat("Request ", i + 1, ": Describe this image briefly."));
|
||||
|
||||
ASSERT_TRUE(response.ok())
|
||||
<< "Request " << i + 1 << " failed: " << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
}
|
||||
}
|
||||
@@ -205,21 +196,19 @@ TEST_F(GeminiVisionTest, RateLimitHandling) {
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
auto image_path = CreateTestImage();
|
||||
|
||||
|
||||
// Make many rapid requests (may hit rate limit)
|
||||
int successful = 0;
|
||||
int rate_limited = 0;
|
||||
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
"Describe this image."
|
||||
);
|
||||
|
||||
auto response = service.GenerateMultimodalResponse(image_path.string(),
|
||||
"Describe this image.");
|
||||
|
||||
if (response.ok()) {
|
||||
successful++;
|
||||
} else if (absl::IsResourceExhausted(response.status()) ||
|
||||
@@ -227,13 +216,14 @@ TEST_F(GeminiVisionTest, RateLimitHandling) {
|
||||
rate_limited++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// At least some requests should succeed
|
||||
EXPECT_GT(successful, 0) << "No successful requests out of 10";
|
||||
|
||||
|
||||
// If we hit rate limits, that's expected behavior (not a failure)
|
||||
if (rate_limited > 0) {
|
||||
std::cout << "Note: Hit rate limit on " << rate_limited << " out of 10 requests (expected)" << std::endl;
|
||||
std::cout << "Note: Hit rate limit on " << rate_limited
|
||||
<< " out of 10 requests (expected)" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "core/asar_wrapper.h"
|
||||
#include "app/rom.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "app/rom.h"
|
||||
#include "core/asar_wrapper.h"
|
||||
#include "testing.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
namespace integration {
|
||||
@@ -18,11 +17,12 @@ class AsarIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
wrapper_ = std::make_unique<core::AsarWrapper>();
|
||||
|
||||
|
||||
// Create test directory
|
||||
test_dir_ = std::filesystem::temp_directory_path() / "yaze_asar_integration";
|
||||
test_dir_ =
|
||||
std::filesystem::temp_directory_path() / "yaze_asar_integration";
|
||||
std::filesystem::create_directories(test_dir_);
|
||||
|
||||
|
||||
CreateTestRom();
|
||||
CreateTestAssemblyFiles();
|
||||
}
|
||||
@@ -40,35 +40,35 @@ class AsarIntegrationTest : public ::testing::Test {
|
||||
void CreateTestRom() {
|
||||
// Create a minimal SNES ROM structure
|
||||
test_rom_.resize(1024 * 1024, 0); // 1MB ROM
|
||||
|
||||
|
||||
// Add SNES header at 0x7FC0 (LoROM)
|
||||
const uint32_t header_offset = 0x7FC0;
|
||||
|
||||
|
||||
// ROM title (21 bytes)
|
||||
std::string title = "YAZE TEST ROM ";
|
||||
std::copy(title.begin(), title.end(), test_rom_.begin() + header_offset);
|
||||
|
||||
|
||||
// Map mode (byte 21) - LoROM
|
||||
test_rom_[header_offset + 21] = 0x20;
|
||||
|
||||
|
||||
// Cartridge type (byte 22)
|
||||
test_rom_[header_offset + 22] = 0x00;
|
||||
|
||||
|
||||
// ROM size (byte 23) - 1MB
|
||||
test_rom_[header_offset + 23] = 0x0A;
|
||||
|
||||
|
||||
// SRAM size (byte 24)
|
||||
test_rom_[header_offset + 24] = 0x00;
|
||||
|
||||
|
||||
// Country code (byte 25)
|
||||
test_rom_[header_offset + 25] = 0x01;
|
||||
|
||||
|
||||
// Developer ID (byte 26)
|
||||
test_rom_[header_offset + 26] = 0x00;
|
||||
|
||||
|
||||
// Version (byte 27)
|
||||
test_rom_[header_offset + 27] = 0x00;
|
||||
|
||||
|
||||
// Calculate and set checksum complement and checksum
|
||||
uint16_t checksum = 0;
|
||||
for (size_t i = 0; i < test_rom_.size(); ++i) {
|
||||
@@ -77,13 +77,13 @@ class AsarIntegrationTest : public ::testing::Test {
|
||||
checksum += test_rom_[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint16_t checksum_complement = checksum ^ 0xFFFF;
|
||||
test_rom_[header_offset + 28] = checksum_complement & 0xFF;
|
||||
test_rom_[header_offset + 29] = (checksum_complement >> 8) & 0xFF;
|
||||
test_rom_[header_offset + 30] = checksum & 0xFF;
|
||||
test_rom_[header_offset + 31] = (checksum >> 8) & 0xFF;
|
||||
|
||||
|
||||
// Add some code at the reset vector location
|
||||
const uint32_t reset_vector_offset = 0x8000;
|
||||
test_rom_[reset_vector_offset] = 0x18; // CLC
|
||||
@@ -246,8 +246,9 @@ org $00FFE0
|
||||
|
||||
// Create test graphics binary
|
||||
std::ofstream gfx_file(test_dir_ / "test_graphics.bin", std::ios::binary);
|
||||
std::vector<uint8_t> graphics_data(2048, 0x55); // Test pattern
|
||||
gfx_file.write(reinterpret_cast<char*>(graphics_data.data()), graphics_data.size());
|
||||
std::vector<uint8_t> graphics_data(2048, 0x55); // Test pattern
|
||||
gfx_file.write(reinterpret_cast<char*>(graphics_data.data()),
|
||||
graphics_data.size());
|
||||
gfx_file.close();
|
||||
|
||||
// Create advanced assembly with macros and includes
|
||||
@@ -339,12 +340,13 @@ TEST_F(AsarIntegrationTest, FullWorkflowIntegration) {
|
||||
size_t original_size = rom_copy.size();
|
||||
|
||||
// Apply comprehensive patch
|
||||
auto patch_result = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
|
||||
auto patch_result =
|
||||
wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
|
||||
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
|
||||
|
||||
const auto& result = patch_result.value();
|
||||
EXPECT_TRUE(result.success) << "Patch failed with errors: "
|
||||
<< testing::PrintToString(result.errors);
|
||||
EXPECT_TRUE(result.success)
|
||||
<< "Patch failed with errors: " << testing::PrintToString(result.errors);
|
||||
|
||||
// Verify ROM was modified correctly
|
||||
EXPECT_NE(rom_copy, test_rom_);
|
||||
@@ -352,7 +354,7 @@ TEST_F(AsarIntegrationTest, FullWorkflowIntegration) {
|
||||
|
||||
// Verify symbols were extracted
|
||||
EXPECT_GT(result.symbols.size(), 0);
|
||||
|
||||
|
||||
// Check for specific expected symbols
|
||||
bool found_main_entry = false;
|
||||
bool found_init_graphics = false;
|
||||
@@ -379,13 +381,14 @@ TEST_F(AsarIntegrationTest, AdvancedFeaturesIntegration) {
|
||||
|
||||
// Test advanced assembly features (macros, conditionals, etc.)
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
|
||||
auto patch_result = wrapper_->ApplyPatch(advanced_asm_path_.string(), rom_copy);
|
||||
|
||||
auto patch_result =
|
||||
wrapper_->ApplyPatch(advanced_asm_path_.string(), rom_copy);
|
||||
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
|
||||
|
||||
const auto& result = patch_result.value();
|
||||
EXPECT_TRUE(result.success) << "Advanced patch failed: "
|
||||
<< testing::PrintToString(result.errors);
|
||||
EXPECT_TRUE(result.success)
|
||||
<< "Advanced patch failed: " << testing::PrintToString(result.errors);
|
||||
|
||||
// Verify symbols from advanced assembly
|
||||
bool found_advanced_entry = false;
|
||||
@@ -408,25 +411,25 @@ TEST_F(AsarIntegrationTest, ErrorHandlingIntegration) {
|
||||
|
||||
// Test error handling with intentionally broken assembly
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
|
||||
|
||||
auto patch_result = wrapper_->ApplyPatch(error_asm_path_.string(), rom_copy);
|
||||
|
||||
|
||||
// Should fail due to errors in assembly
|
||||
EXPECT_FALSE(patch_result.ok());
|
||||
|
||||
|
||||
// Verify error message contains useful information
|
||||
EXPECT_THAT(patch_result.status().message(),
|
||||
testing::AnyOf(
|
||||
testing::HasSubstr("invalid"),
|
||||
testing::HasSubstr("unknown"),
|
||||
testing::HasSubstr("error")));
|
||||
EXPECT_THAT(patch_result.status().message(),
|
||||
testing::AnyOf(testing::HasSubstr("invalid"),
|
||||
testing::HasSubstr("unknown"),
|
||||
testing::HasSubstr("error")));
|
||||
}
|
||||
|
||||
TEST_F(AsarIntegrationTest, SymbolExtractionWorkflow) {
|
||||
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||
|
||||
// Extract symbols without applying patch
|
||||
auto symbols_result = wrapper_->ExtractSymbols(comprehensive_asm_path_.string());
|
||||
auto symbols_result =
|
||||
wrapper_->ExtractSymbols(comprehensive_asm_path_.string());
|
||||
ASSERT_TRUE(symbols_result.ok()) << symbols_result.status().message();
|
||||
|
||||
const auto& symbols = symbols_result.value();
|
||||
@@ -434,7 +437,8 @@ TEST_F(AsarIntegrationTest, SymbolExtractionWorkflow) {
|
||||
|
||||
// Test symbol table operations
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
auto patch_result = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
|
||||
auto patch_result =
|
||||
wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
|
||||
ASSERT_TRUE(patch_result.ok());
|
||||
|
||||
// Test symbol lookup by name
|
||||
@@ -465,7 +469,8 @@ TEST_F(AsarIntegrationTest, MultipleOperationsIntegration) {
|
||||
std::vector<uint8_t> rom_copy2 = test_rom_;
|
||||
|
||||
// First patch
|
||||
auto result1 = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy1);
|
||||
auto result1 =
|
||||
wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy1);
|
||||
ASSERT_TRUE(result1.ok());
|
||||
EXPECT_TRUE(result1->success);
|
||||
|
||||
@@ -497,8 +502,9 @@ subroutine_test:
|
||||
)";
|
||||
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
auto result = wrapper_->ApplyPatchFromString(patch_content, rom_copy, test_dir_.string());
|
||||
|
||||
auto result = wrapper_->ApplyPatchFromString(patch_content, rom_copy,
|
||||
test_dir_.string());
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
EXPECT_TRUE(result->success);
|
||||
EXPECT_GT(result->symbols.size(), 0);
|
||||
@@ -524,16 +530,17 @@ TEST_F(AsarIntegrationTest, LargeRomHandling) {
|
||||
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||
|
||||
// Create a larger ROM for testing
|
||||
std::vector<uint8_t> large_rom(4 * 1024 * 1024, 0); // 4MB ROM
|
||||
|
||||
std::vector<uint8_t> large_rom(4 * 1024 * 1024, 0); // 4MB ROM
|
||||
|
||||
// Set up basic SNES header
|
||||
const uint32_t header_offset = 0x7FC0;
|
||||
std::string title = "LARGE ROM TEST ";
|
||||
std::copy(title.begin(), title.end(), large_rom.begin() + header_offset);
|
||||
large_rom[header_offset + 21] = 0x20; // LoROM
|
||||
large_rom[header_offset + 23] = 0x0C; // 4MB
|
||||
large_rom[header_offset + 21] = 0x20; // LoROM
|
||||
large_rom[header_offset + 23] = 0x0C; // 4MB
|
||||
|
||||
auto result = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), large_rom);
|
||||
auto result =
|
||||
wrapper_->ApplyPatch(comprehensive_asm_path_.string(), large_rom);
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_TRUE(result->success);
|
||||
EXPECT_EQ(large_rom.size(), result->rom_size);
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
#endif
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "core/asar_wrapper.h"
|
||||
#include "app/rom.h"
|
||||
#include "core/asar_wrapper.h"
|
||||
#include "test_utils.h"
|
||||
#include "testing.h"
|
||||
|
||||
@@ -330,7 +331,8 @@ TEST_F(AsarRomIntegrationTest, GameplayModificationPatch) {
|
||||
// Check health modification at 0x7EF36C -> ROM offset would need calculation
|
||||
// For a proper test, we'd need to convert SNES addresses to ROM offsets
|
||||
|
||||
// Check if custom routine was inserted at 0xC000 -> ROM offset 0x18000 (in LoROM)
|
||||
// Check if custom routine was inserted at 0xC000 -> ROM offset 0x18000 (in
|
||||
// LoROM)
|
||||
const uint32_t rom_offset = 0x18000; // Bank $00:C000 in LoROM
|
||||
if (rom_offset < rom_copy.size()) {
|
||||
// Check for SEP #$20 instruction (0xE2 0x20)
|
||||
|
||||
@@ -23,7 +23,8 @@ TEST_F(DungeonEditorIntegrationTest, LoadMultipleRooms) {
|
||||
// Test loading several different rooms
|
||||
for (int room_id : {0x00, 0x01, 0x02, 0x10, 0x20}) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), room_id);
|
||||
EXPECT_NE(room.rom(), nullptr) << "Failed to load room " << std::hex << room_id;
|
||||
EXPECT_NE(room.rom(), nullptr)
|
||||
<< "Failed to load room " << std::hex << room_id;
|
||||
room.LoadObjects();
|
||||
// Some rooms may be empty, but loading should not fail
|
||||
}
|
||||
@@ -32,7 +33,7 @@ TEST_F(DungeonEditorIntegrationTest, LoadMultipleRooms) {
|
||||
TEST_F(DungeonEditorIntegrationTest, DungeonEditorInitialization) {
|
||||
// Initialize the editor before loading
|
||||
dungeon_editor_->Initialize();
|
||||
|
||||
|
||||
// Now load should succeed
|
||||
auto status = dungeon_editor_->Load();
|
||||
ASSERT_TRUE(status.ok()) << "Load failed: " << status.message();
|
||||
@@ -45,20 +46,22 @@ TEST_F(DungeonEditorIntegrationTest, DungeonEditorInitialization) {
|
||||
TEST_F(DungeonEditorIntegrationTest, ObjectEncodingRoundTrip) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
|
||||
auto encoded = room.EncodeObjects();
|
||||
EXPECT_FALSE(encoded.empty());
|
||||
EXPECT_EQ(encoded[encoded.size()-1], 0xFF); // Terminator
|
||||
EXPECT_EQ(encoded[encoded.size() - 1], 0xFF); // Terminator
|
||||
}
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, EncodeType1Object) {
|
||||
// Type 1: xxxxxxss yyyyyyss iiiiiiii (ID < 0x100)
|
||||
zelda3::RoomObject obj(0x10, 5, 7, 0x12, 0); // id, x, y, size, layer
|
||||
zelda3::RoomObject obj(0x10, 5, 7, 0x12, 0); // id, x, y, size, layer
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
|
||||
|
||||
// Verify encoding format
|
||||
EXPECT_EQ((bytes.b1 >> 2), 5) << "X coordinate should be in upper 6 bits of b1";
|
||||
EXPECT_EQ((bytes.b2 >> 2), 7) << "Y coordinate should be in upper 6 bits of b2";
|
||||
EXPECT_EQ((bytes.b1 >> 2), 5)
|
||||
<< "X coordinate should be in upper 6 bits of b1";
|
||||
EXPECT_EQ((bytes.b2 >> 2), 7)
|
||||
<< "Y coordinate should be in upper 6 bits of b2";
|
||||
EXPECT_EQ(bytes.b3, 0x10) << "Object ID should be in b3";
|
||||
}
|
||||
|
||||
@@ -66,9 +69,10 @@ TEST_F(DungeonEditorIntegrationTest, EncodeType2Object) {
|
||||
// Type 2: 111111xx xxxxyyyy yyiiiiii (ID >= 0x100 && < 0x200)
|
||||
zelda3::RoomObject obj(0x150, 12, 8, 0, 0);
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
|
||||
|
||||
// Verify Type 2 marker
|
||||
EXPECT_EQ((bytes.b1 & 0xFC), 0xFC) << "Type 2 objects should have 111111 prefix";
|
||||
EXPECT_EQ((bytes.b1 & 0xFC), 0xFC)
|
||||
<< "Type 2 objects should have 111111 prefix";
|
||||
}
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, EncodeType3Object) {
|
||||
@@ -103,13 +107,13 @@ TEST_F(DungeonEditorIntegrationTest, AddObjectToRoom) {
|
||||
TEST_F(DungeonEditorIntegrationTest, RemoveObjectFromRoom) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
|
||||
size_t initial_count = room.GetTileObjects().size();
|
||||
ASSERT_GT(initial_count, 0) << "Room should have at least one object";
|
||||
|
||||
|
||||
// Remove first object
|
||||
auto status = room.RemoveObject(0);
|
||||
|
||||
|
||||
EXPECT_TRUE(status.ok()) << "Failed to remove object: " << status.message();
|
||||
EXPECT_EQ(room.GetTileObjects().size(), initial_count - 1);
|
||||
}
|
||||
@@ -117,16 +121,16 @@ TEST_F(DungeonEditorIntegrationTest, RemoveObjectFromRoom) {
|
||||
TEST_F(DungeonEditorIntegrationTest, UpdateObjectInRoom) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
|
||||
ASSERT_FALSE(room.GetTileObjects().empty());
|
||||
|
||||
|
||||
// Update first object's position
|
||||
zelda3::RoomObject updated_obj = room.GetTileObjects()[0];
|
||||
updated_obj.x_ = 15;
|
||||
updated_obj.y_ = 15;
|
||||
|
||||
|
||||
auto status = room.UpdateObject(0, updated_obj);
|
||||
|
||||
|
||||
EXPECT_TRUE(status.ok()) << "Failed to update object: " << status.message();
|
||||
EXPECT_EQ(room.GetTileObjects()[0].x_, 15);
|
||||
EXPECT_EQ(room.GetTileObjects()[0].y_, 15);
|
||||
@@ -158,23 +162,23 @@ TEST_F(DungeonEditorIntegrationTest, ValidateObjectBounds) {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Save/Load Round-Trip Tests
|
||||
// Save/Load Round-Trip Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, SaveAndReloadRoom) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
|
||||
size_t original_count = room.GetTileObjects().size();
|
||||
|
||||
|
||||
// Encode objects
|
||||
auto encoded = room.EncodeObjects();
|
||||
EXPECT_FALSE(encoded.empty());
|
||||
|
||||
|
||||
// Create a new room and decode
|
||||
auto room2 = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room2.LoadObjects();
|
||||
|
||||
|
||||
// Verify object count matches
|
||||
EXPECT_EQ(room2.GetTileObjects().size(), original_count);
|
||||
}
|
||||
@@ -186,14 +190,14 @@ TEST_F(DungeonEditorIntegrationTest, SaveAndReloadRoom) {
|
||||
TEST_F(DungeonEditorIntegrationTest, RenderObjectWithTiles) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
|
||||
ASSERT_FALSE(room.GetTileObjects().empty());
|
||||
|
||||
|
||||
// Ensure tiles are loaded for first object
|
||||
auto& obj = room.GetTileObjects()[0];
|
||||
const_cast<zelda3::RoomObject&>(obj).set_rom(rom_.get());
|
||||
const_cast<zelda3::RoomObject&>(obj).EnsureTilesLoaded();
|
||||
|
||||
|
||||
EXPECT_FALSE(obj.tiles_.empty()) << "Object should have tiles after loading";
|
||||
}
|
||||
|
||||
@@ -203,27 +207,27 @@ TEST_F(DungeonEditorIntegrationTest, RenderObjectWithTiles) {
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, ObjectsOnDifferentLayers) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
|
||||
|
||||
// Add objects on different layers
|
||||
zelda3::RoomObject obj_bg1(0x10, 5, 5, 0, 0); // Layer 0 (BG2)
|
||||
zelda3::RoomObject obj_bg2(0x11, 6, 6, 0, 1); // Layer 1 (BG1)
|
||||
zelda3::RoomObject obj_bg3(0x12, 7, 7, 0, 2); // Layer 2 (BG3)
|
||||
|
||||
zelda3::RoomObject obj_bg1(0x10, 5, 5, 0, 0); // Layer 0 (BG2)
|
||||
zelda3::RoomObject obj_bg2(0x11, 6, 6, 0, 1); // Layer 1 (BG1)
|
||||
zelda3::RoomObject obj_bg3(0x12, 7, 7, 0, 2); // Layer 2 (BG3)
|
||||
|
||||
room.AddObject(obj_bg1);
|
||||
room.AddObject(obj_bg2);
|
||||
room.AddObject(obj_bg3);
|
||||
|
||||
|
||||
// Encode and verify layer separation
|
||||
auto encoded = room.EncodeObjects();
|
||||
|
||||
|
||||
// Should have layer terminators (0xFF 0xFF between layers)
|
||||
int terminator_count = 0;
|
||||
for (size_t i = 0; i < encoded.size() - 1; i++) {
|
||||
if (encoded[i] == 0xFF && encoded[i+1] == 0xFF) {
|
||||
if (encoded[i] == 0xFF && encoded[i + 1] == 0xFF) {
|
||||
terminator_count++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EXPECT_GE(terminator_count, 2) << "Should have at least 2 layer terminators";
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
|
||||
#include "app/editor/dungeon/dungeon_editor_v2.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* @brief Integration test framework using real ROM data
|
||||
*
|
||||
*
|
||||
* Updated for DungeonEditorV2 with card-based architecture
|
||||
*/
|
||||
class DungeonEditorIntegrationTest : public ::testing::Test {
|
||||
@@ -31,15 +31,15 @@ class DungeonEditorIntegrationTest : public ::testing::Test {
|
||||
}
|
||||
ASSERT_TRUE(status.ok()) << "Could not load zelda3.sfc from any location";
|
||||
ASSERT_TRUE(rom_->InitializeForTesting().ok());
|
||||
|
||||
|
||||
// Initialize DungeonEditorV2 with ROM
|
||||
dungeon_editor_ = std::make_unique<editor::DungeonEditorV2>();
|
||||
dungeon_editor_->set_rom(rom_.get());
|
||||
|
||||
|
||||
// Load editor data
|
||||
auto load_status = dungeon_editor_->Load();
|
||||
ASSERT_TRUE(load_status.ok()) << "Failed to load dungeon editor: "
|
||||
<< load_status.message();
|
||||
ASSERT_TRUE(load_status.ok())
|
||||
<< "Failed to load dungeon editor: " << load_status.message();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
@@ -49,7 +49,7 @@ class DungeonEditorIntegrationTest : public ::testing::Test {
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<editor::DungeonEditorV2> dungeon_editor_;
|
||||
|
||||
|
||||
static constexpr int kTestRoomId = 0x01;
|
||||
};
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ TEST_F(DungeonEditorV2IntegrationTest, LoadWithoutRom) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, LoadSequence) {
|
||||
// Test the full initialization sequence
|
||||
dungeon_editor_v2_->Initialize();
|
||||
|
||||
|
||||
auto load_status = dungeon_editor_v2_->Load();
|
||||
ASSERT_TRUE(load_status.ok());
|
||||
|
||||
|
||||
// After loading, Update() should work
|
||||
(void)dungeon_editor_v2_->Update();
|
||||
}
|
||||
@@ -63,7 +63,7 @@ TEST_F(DungeonEditorV2IntegrationTest, UpdateBeforeLoad) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, UpdateAfterLoad) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
(void)dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Update should delegate to components
|
||||
auto status = dungeon_editor_v2_->Update();
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -85,7 +85,7 @@ TEST_F(DungeonEditorV2IntegrationTest, SaveAfterLoad) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
auto load_status = dungeon_editor_v2_->Load();
|
||||
ASSERT_TRUE(load_status.ok());
|
||||
|
||||
|
||||
// Save should delegate to room objects
|
||||
auto save_status = dungeon_editor_v2_->Save();
|
||||
EXPECT_TRUE(save_status.ok());
|
||||
@@ -98,10 +98,10 @@ TEST_F(DungeonEditorV2IntegrationTest, SaveAfterLoad) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, AddRoomTab) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
(void)dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Add a room tab
|
||||
dungeon_editor_v2_->add_room(kTestRoomId);
|
||||
|
||||
|
||||
// This should not crash or fail
|
||||
auto status = dungeon_editor_v2_->Update();
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -110,12 +110,12 @@ TEST_F(DungeonEditorV2IntegrationTest, AddRoomTab) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, AddMultipleRoomTabs) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
(void)dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Add multiple rooms
|
||||
dungeon_editor_v2_->add_room(0x00);
|
||||
dungeon_editor_v2_->add_room(0x01);
|
||||
dungeon_editor_v2_->add_room(0x02);
|
||||
|
||||
|
||||
auto status = dungeon_editor_v2_->Update();
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
@@ -128,7 +128,7 @@ TEST_F(DungeonEditorV2IntegrationTest, RoomLoaderDelegation) {
|
||||
// Verify that Load() delegates to room_loader_
|
||||
dungeon_editor_v2_->Initialize();
|
||||
auto status = dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// If Load succeeds, room_loader_ must have worked
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
@@ -137,7 +137,7 @@ TEST_F(DungeonEditorV2IntegrationTest, ComponentsInitializedAfterLoad) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
auto status = dungeon_editor_v2_->Load();
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// After Load(), all components should be properly initialized
|
||||
// We can't directly test this, but Update() should work
|
||||
(void)dungeon_editor_v2_->Update();
|
||||
@@ -151,7 +151,7 @@ TEST_F(DungeonEditorV2IntegrationTest, SetRomAfterConstruction) {
|
||||
// Create editor without ROM
|
||||
editor::DungeonEditorV2 editor;
|
||||
EXPECT_EQ(editor.rom(), nullptr);
|
||||
|
||||
|
||||
// Set ROM
|
||||
editor.set_rom(rom_.get());
|
||||
EXPECT_EQ(editor.rom(), rom_.get());
|
||||
@@ -161,12 +161,12 @@ TEST_F(DungeonEditorV2IntegrationTest, SetRomAfterConstruction) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, SetRomAndLoad) {
|
||||
// Create editor without ROM
|
||||
editor::DungeonEditorV2 editor;
|
||||
|
||||
|
||||
// Set ROM and load
|
||||
editor.set_rom(rom_.get());
|
||||
editor.Initialize();
|
||||
auto status = editor.Load();
|
||||
|
||||
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
|
||||
@@ -174,19 +174,20 @@ TEST_F(DungeonEditorV2IntegrationTest, SetRomAndLoad) {
|
||||
// Unimplemented Methods Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonEditorV2IntegrationTest, UnimplementedMethods) {
|
||||
// These should return UnimplementedError
|
||||
EXPECT_EQ(dungeon_editor_v2_->Undo().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Redo().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Cut().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Copy().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Paste().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Find().code(),
|
||||
TEST_F(DungeonEditorV2IntegrationTest, EditingCommandsFallback) {
|
||||
// Undo/Redo should report precondition when history is empty
|
||||
EXPECT_EQ(dungeon_editor_v2_->Undo().code(),
|
||||
absl::StatusCode::kFailedPrecondition);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Redo().code(),
|
||||
absl::StatusCode::kFailedPrecondition);
|
||||
|
||||
// Cut/Copy/Paste should be callable even without a selection
|
||||
EXPECT_EQ(dungeon_editor_v2_->Cut().code(), absl::StatusCode::kOk);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Copy().code(), absl::StatusCode::kOk);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Paste().code(), absl::StatusCode::kOk);
|
||||
|
||||
// Find remains unimplemented
|
||||
EXPECT_EQ(dungeon_editor_v2_->Find().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
}
|
||||
|
||||
@@ -198,7 +199,7 @@ TEST_F(DungeonEditorV2IntegrationTest, MultipleUpdateCycles) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
auto load_status = dungeon_editor_v2_->Load();
|
||||
ASSERT_TRUE(load_status.ok());
|
||||
|
||||
|
||||
// Run multiple update cycles
|
||||
for (int i = 0; i < 10; i++) {
|
||||
(void)dungeon_editor_v2_->Update();
|
||||
@@ -212,10 +213,10 @@ TEST_F(DungeonEditorV2IntegrationTest, MultipleUpdateCycles) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, InvalidRoomId) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
(void)dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Add invalid room ID (beyond 0x128)
|
||||
dungeon_editor_v2_->add_room(0x200);
|
||||
|
||||
|
||||
// Update should handle gracefully
|
||||
auto status = dungeon_editor_v2_->Update();
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -224,10 +225,10 @@ TEST_F(DungeonEditorV2IntegrationTest, InvalidRoomId) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, NegativeRoomId) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
(void)dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Add negative room ID
|
||||
dungeon_editor_v2_->add_room(-1);
|
||||
|
||||
|
||||
// Update should handle gracefully
|
||||
auto status = dungeon_editor_v2_->Update();
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -235,11 +236,11 @@ TEST_F(DungeonEditorV2IntegrationTest, NegativeRoomId) {
|
||||
|
||||
TEST_F(DungeonEditorV2IntegrationTest, LoadTwice) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
|
||||
|
||||
// Load twice
|
||||
auto status1 = dungeon_editor_v2_->Load();
|
||||
auto status2 = dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Both should succeed
|
||||
EXPECT_TRUE(status1.ok());
|
||||
EXPECT_TRUE(status2.ok());
|
||||
@@ -247,4 +248,3 @@ TEST_F(DungeonEditorV2IntegrationTest, LoadTwice) {
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace test {
|
||||
|
||||
/**
|
||||
* @brief Integration test framework for DungeonEditorV2
|
||||
*
|
||||
*
|
||||
* Tests the simplified component delegation architecture
|
||||
*/
|
||||
class DungeonEditorV2IntegrationTest : public ::testing::Test {
|
||||
@@ -29,7 +29,7 @@ class DungeonEditorV2IntegrationTest : public ::testing::Test {
|
||||
status = rom_->LoadFromFile("zelda3.sfc");
|
||||
}
|
||||
ASSERT_TRUE(status.ok()) << "Could not load zelda3.sfc from any location";
|
||||
|
||||
|
||||
// Create V2 editor with ROM
|
||||
dungeon_editor_v2_ = std::make_unique<editor::DungeonEditorV2>(rom_.get());
|
||||
}
|
||||
@@ -41,7 +41,7 @@ class DungeonEditorV2IntegrationTest : public ::testing::Test {
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<editor::DungeonEditorV2> dungeon_editor_v2_;
|
||||
|
||||
|
||||
static constexpr int kTestRoomId = 0x01;
|
||||
};
|
||||
|
||||
@@ -49,4 +49,3 @@ class DungeonEditorV2IntegrationTest : public ::testing::Test {
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_TEST_INTEGRATION_DUNGEON_EDITOR_V2_TEST_H
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "app/platform/window.h"
|
||||
#include "app/gui/core/style.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||
#include "imgui/imgui.h"
|
||||
@@ -20,13 +20,15 @@
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
EditorIntegrationTest::EditorIntegrationTest()
|
||||
EditorIntegrationTest::EditorIntegrationTest()
|
||||
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||
: engine_(nullptr), show_demo_window_(true)
|
||||
: engine_(nullptr),
|
||||
show_demo_window_(true)
|
||||
#else
|
||||
|
||||
|
||||
#endif
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
EditorIntegrationTest::~EditorIntegrationTest() {
|
||||
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||
@@ -40,7 +42,8 @@ EditorIntegrationTest::~EditorIntegrationTest() {
|
||||
absl::Status EditorIntegrationTest::Initialize() {
|
||||
// Create renderer for test
|
||||
test_renderer_ = std::make_unique<gfx::SDL2Renderer>();
|
||||
RETURN_IF_ERROR(core::CreateWindow(window_, test_renderer_.get(), SDL_WINDOW_RESIZABLE));
|
||||
RETURN_IF_ERROR(
|
||||
core::CreateWindow(window_, test_renderer_.get(), SDL_WINDOW_RESIZABLE));
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
@@ -57,7 +60,8 @@ absl::Status EditorIntegrationTest::Initialize() {
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
|
||||
// Initialize ImGui for SDL
|
||||
SDL_Renderer* sdl_renderer = static_cast<SDL_Renderer*>(test_renderer_->GetBackendRenderer());
|
||||
SDL_Renderer* sdl_renderer =
|
||||
static_cast<SDL_Renderer*>(test_renderer_->GetBackendRenderer());
|
||||
ImGui_ImplSDL2_InitForSDLRenderer(controller_.window(), sdl_renderer);
|
||||
ImGui_ImplSDLRenderer2_Init(sdl_renderer);
|
||||
|
||||
@@ -98,16 +102,15 @@ int EditorIntegrationTest::RunTest() {
|
||||
|
||||
absl::Status EditorIntegrationTest::Update() {
|
||||
ImGui::NewFrame();
|
||||
|
||||
|
||||
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||
// Show test engine windows
|
||||
ImGuiTestEngine_ShowTestEngineWindows(engine_, &show_demo_window_);
|
||||
#endif
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
// Helper methods for testing with a ROM
|
||||
absl::Status EditorIntegrationTest::LoadTestRom(const std::string& filename) {
|
||||
test_rom_ = std::make_unique<Rom>();
|
||||
@@ -125,7 +128,8 @@ absl::Status EditorIntegrationTest::SaveTestRom(const std::string& filename) {
|
||||
return test_rom_->SaveToFile(settings);
|
||||
}
|
||||
|
||||
absl::Status EditorIntegrationTest::TestEditorInitialize(editor::Editor* editor) {
|
||||
absl::Status EditorIntegrationTest::TestEditorInitialize(
|
||||
editor::Editor* editor) {
|
||||
if (!editor) {
|
||||
return absl::InternalError("Editor is null");
|
||||
}
|
||||
@@ -204,4 +208,4 @@ absl::Status EditorIntegrationTest::TestEditorClear(editor::Editor* editor) {
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
} // namespace yaze
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
#include "app/editor/editor.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/controller.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "app/editor/editor.h"
|
||||
#include "app/gfx/backend/sdl2_renderer.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "app/rom.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||
#include "imgui_test_engine/imgui_te_context.h"
|
||||
@@ -21,12 +21,14 @@ namespace test {
|
||||
/**
|
||||
* @class EditorIntegrationTest
|
||||
* @brief Base class for editor integration tests
|
||||
*
|
||||
* This class provides common functionality for testing editors in the application.
|
||||
* It sets up the test environment and provides helper methods for ROM operations.
|
||||
*
|
||||
* For UI interaction testing, use the ImGui test engine API directly within your test functions:
|
||||
*
|
||||
*
|
||||
* This class provides common functionality for testing editors in the
|
||||
* application. It sets up the test environment and provides helper methods for
|
||||
* ROM operations.
|
||||
*
|
||||
* For UI interaction testing, use the ImGui test engine API directly within
|
||||
* your test functions:
|
||||
*
|
||||
* ImGuiTest* test = IM_REGISTER_TEST(engine, "test_suite", "test_name");
|
||||
* test->TestFunc = [](ImGuiTestContext* ctx) {
|
||||
* ctx->SetRef("Window Name");
|
||||
@@ -40,10 +42,10 @@ class EditorIntegrationTest {
|
||||
|
||||
// Initialize the test environment
|
||||
absl::Status Initialize();
|
||||
|
||||
|
||||
// Run the test
|
||||
int RunTest();
|
||||
|
||||
|
||||
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||
// Register tests for a specific editor
|
||||
virtual void RegisterTests(ImGuiTestEngine* engine) = 0;
|
||||
@@ -51,16 +53,15 @@ class EditorIntegrationTest {
|
||||
// Default implementation when ImGui Test Engine is disabled
|
||||
virtual void RegisterTests(void* engine) {}
|
||||
#endif
|
||||
|
||||
|
||||
// Update the test environment
|
||||
virtual absl::Status Update();
|
||||
|
||||
protected:
|
||||
|
||||
// Helper methods for testing with a ROM
|
||||
absl::Status LoadTestRom(const std::string& filename);
|
||||
absl::Status SaveTestRom(const std::string& filename);
|
||||
|
||||
|
||||
// Helper methods for testing with a specific editor
|
||||
absl::Status TestEditorInitialize(editor::Editor* editor);
|
||||
absl::Status TestEditorLoad(editor::Editor* editor);
|
||||
@@ -88,4 +89,4 @@ class EditorIntegrationTest {
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_TEST_EDITOR_INTEGRATION_TEST_H
|
||||
#endif // YAZE_TEST_EDITOR_INTEGRATION_TEST_H
|
||||
@@ -1,17 +1,18 @@
|
||||
#include "app/editor/overworld/tile16_editor.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "app/rom.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gfx/backend/sdl2_renderer.h"
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/render/tilemap.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
@@ -28,7 +29,7 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
|
||||
// Clean up SDL
|
||||
if (window_initialized_) {
|
||||
auto shutdown_result = core::ShutdownWindow(test_window_);
|
||||
(void)shutdown_result; // Suppress unused variable warning
|
||||
(void)shutdown_result; // Suppress unused variable warning
|
||||
window_initialized_ = false;
|
||||
}
|
||||
}
|
||||
@@ -42,41 +43,46 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
|
||||
// Load the test ROM
|
||||
rom_ = std::make_unique<Rom>();
|
||||
auto load_result = rom_->LoadFromFile(YAZE_TEST_ROM_PATH);
|
||||
ASSERT_TRUE(load_result.ok()) << "Failed to load test ROM: " << load_result.message();
|
||||
|
||||
ASSERT_TRUE(load_result.ok())
|
||||
<< "Failed to load test ROM: " << load_result.message();
|
||||
|
||||
// Load overworld data
|
||||
overworld_ = std::make_unique<zelda3::Overworld>(rom_.get());
|
||||
auto overworld_load_result = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(overworld_load_result.ok()) << "Failed to load overworld: " << overworld_load_result.message();
|
||||
|
||||
ASSERT_TRUE(overworld_load_result.ok())
|
||||
<< "Failed to load overworld: " << overworld_load_result.message();
|
||||
|
||||
// Create tile16 blockset
|
||||
auto tile16_data = overworld_->tile16_blockset_data();
|
||||
auto palette = overworld_->current_area_palette();
|
||||
|
||||
|
||||
tile16_blockset_ = std::make_unique<gfx::Tilemap>(
|
||||
gfx::CreateTilemap(nullptr, tile16_data, 0x80, 0x2000, 16,
|
||||
zelda3::kNumTile16Individual, palette));
|
||||
|
||||
gfx::CreateTilemap(nullptr, tile16_data, 0x80, 0x2000, 16,
|
||||
zelda3::kNumTile16Individual, palette));
|
||||
|
||||
// Create graphics bitmap
|
||||
current_gfx_bmp_ = std::make_unique<gfx::Bitmap>();
|
||||
current_gfx_bmp_->Create(0x80, 512, 0x40, overworld_->current_graphics());
|
||||
current_gfx_bmp_->SetPalette(palette);
|
||||
gfx::Arena::Get().QueueTextureCommand(
|
||||
gfx::Arena::TextureCommandType::CREATE, current_gfx_bmp_.get());
|
||||
|
||||
|
||||
// Create tile16 blockset bitmap
|
||||
tile16_blockset_bmp_ = std::make_unique<gfx::Bitmap>();
|
||||
tile16_blockset_bmp_->Create(0x80, 0x2000, 0x08, tile16_data);
|
||||
tile16_blockset_bmp_->SetPalette(palette);
|
||||
gfx::Arena::Get().QueueTextureCommand(
|
||||
gfx::Arena::TextureCommandType::CREATE, tile16_blockset_bmp_.get());
|
||||
|
||||
|
||||
// Initialize the tile16 editor
|
||||
editor_ = std::make_unique<Tile16Editor>(rom_.get(), tile16_blockset_.get());
|
||||
auto init_result = editor_->Initialize(*tile16_blockset_bmp_, *current_gfx_bmp_,
|
||||
*overworld_->mutable_all_tiles_types());
|
||||
ASSERT_TRUE(init_result.ok()) << "Failed to initialize editor: " << init_result.message();
|
||||
|
||||
editor_ =
|
||||
std::make_unique<Tile16Editor>(rom_.get(), tile16_blockset_.get());
|
||||
auto init_result =
|
||||
editor_->Initialize(*tile16_blockset_bmp_, *current_gfx_bmp_,
|
||||
*overworld_->mutable_all_tiles_types());
|
||||
ASSERT_TRUE(init_result.ok())
|
||||
<< "Failed to initialize editor: " << init_result.message();
|
||||
|
||||
rom_loaded_ = true;
|
||||
#else
|
||||
// Fallback for non-ROM tests
|
||||
@@ -87,17 +93,19 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
|
||||
#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
protected:
|
||||
static void InitializeTestEnvironment() {
|
||||
// Create renderer for test
|
||||
test_renderer_ = std::make_unique<gfx::SDL2Renderer>();
|
||||
auto window_result = core::CreateWindow(test_window_, test_renderer_.get(), SDL_WINDOW_HIDDEN);
|
||||
auto window_result = core::CreateWindow(test_window_, test_renderer_.get(),
|
||||
SDL_WINDOW_HIDDEN);
|
||||
if (window_result.ok()) {
|
||||
window_initialized_ = true;
|
||||
} else {
|
||||
window_initialized_ = false;
|
||||
// Log the error but don't fail test setup
|
||||
std::cerr << "Failed to initialize test window: " << window_result.message() << std::endl;
|
||||
std::cerr << "Failed to initialize test window: "
|
||||
<< window_result.message() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,16 +133,16 @@ TEST_F(Tile16EditorIntegrationTest, BasicValidation) {
|
||||
// Test with invalid tile ID
|
||||
EXPECT_FALSE(editor_->IsTile16Valid(-1));
|
||||
EXPECT_FALSE(editor_->IsTile16Valid(9999));
|
||||
|
||||
|
||||
// Test scratch space operations with invalid slots
|
||||
auto save_invalid = editor_->SaveTile16ToScratchSpace(-1);
|
||||
EXPECT_FALSE(save_invalid.ok());
|
||||
EXPECT_EQ(save_invalid.code(), absl::StatusCode::kInvalidArgument);
|
||||
|
||||
|
||||
auto load_invalid = editor_->LoadTile16FromScratchSpace(5);
|
||||
EXPECT_FALSE(load_invalid.ok());
|
||||
EXPECT_EQ(load_invalid.code(), absl::StatusCode::kInvalidArgument);
|
||||
|
||||
|
||||
// Test valid scratch space clearing
|
||||
auto clear_valid = editor_->ClearScratchSpace(0);
|
||||
EXPECT_TRUE(clear_valid.ok());
|
||||
@@ -146,7 +154,7 @@ TEST_F(Tile16EditorIntegrationTest, ValidateTile16DataWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Test validation with properly loaded ROM
|
||||
auto status = editor_->ValidateTile16Data();
|
||||
EXPECT_TRUE(status.ok()) << "Validation failed: " << status.message();
|
||||
@@ -160,19 +168,21 @@ TEST_F(Tile16EditorIntegrationTest, SetCurrentTileWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Test setting a valid tile
|
||||
auto valid_tile_result = editor_->SetCurrentTile(0);
|
||||
EXPECT_TRUE(valid_tile_result.ok()) << "Failed to set tile 0: " << valid_tile_result.message();
|
||||
|
||||
EXPECT_TRUE(valid_tile_result.ok())
|
||||
<< "Failed to set tile 0: " << valid_tile_result.message();
|
||||
|
||||
auto valid_tile_result2 = editor_->SetCurrentTile(100);
|
||||
EXPECT_TRUE(valid_tile_result2.ok()) << "Failed to set tile 100: " << valid_tile_result2.message();
|
||||
|
||||
EXPECT_TRUE(valid_tile_result2.ok())
|
||||
<< "Failed to set tile 100: " << valid_tile_result2.message();
|
||||
|
||||
// Test invalid ranges still fail
|
||||
auto invalid_low = editor_->SetCurrentTile(-1);
|
||||
EXPECT_FALSE(invalid_low.ok());
|
||||
EXPECT_EQ(invalid_low.code(), absl::StatusCode::kOutOfRange);
|
||||
|
||||
|
||||
auto invalid_high = editor_->SetCurrentTile(10000);
|
||||
EXPECT_FALSE(invalid_high.ok());
|
||||
EXPECT_EQ(invalid_high.code(), absl::StatusCode::kOutOfRange);
|
||||
@@ -186,20 +196,24 @@ TEST_F(Tile16EditorIntegrationTest, FlipOperationsWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Set a valid tile first
|
||||
auto set_result = editor_->SetCurrentTile(1);
|
||||
ASSERT_TRUE(set_result.ok()) << "Failed to set initial tile: " << set_result.message();
|
||||
|
||||
ASSERT_TRUE(set_result.ok())
|
||||
<< "Failed to set initial tile: " << set_result.message();
|
||||
|
||||
// Test flip operations
|
||||
auto flip_h_result = editor_->FlipTile16Horizontal();
|
||||
EXPECT_TRUE(flip_h_result.ok()) << "Horizontal flip failed: " << flip_h_result.message();
|
||||
|
||||
EXPECT_TRUE(flip_h_result.ok())
|
||||
<< "Horizontal flip failed: " << flip_h_result.message();
|
||||
|
||||
auto flip_v_result = editor_->FlipTile16Vertical();
|
||||
EXPECT_TRUE(flip_v_result.ok()) << "Vertical flip failed: " << flip_v_result.message();
|
||||
|
||||
EXPECT_TRUE(flip_v_result.ok())
|
||||
<< "Vertical flip failed: " << flip_v_result.message();
|
||||
|
||||
auto rotate_result = editor_->RotateTile16();
|
||||
EXPECT_TRUE(rotate_result.ok()) << "Rotation failed: " << rotate_result.message();
|
||||
EXPECT_TRUE(rotate_result.ok())
|
||||
<< "Rotation failed: " << rotate_result.message();
|
||||
#else
|
||||
GTEST_SKIP() << "ROM tests disabled";
|
||||
#endif
|
||||
@@ -210,18 +224,19 @@ TEST_F(Tile16EditorIntegrationTest, UndoRedoWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Set a tile and perform an operation to create undo state
|
||||
auto set_result = editor_->SetCurrentTile(1);
|
||||
ASSERT_TRUE(set_result.ok());
|
||||
|
||||
|
||||
auto clear_result = editor_->ClearTile16();
|
||||
ASSERT_TRUE(clear_result.ok()) << "Clear operation failed: " << clear_result.message();
|
||||
|
||||
ASSERT_TRUE(clear_result.ok())
|
||||
<< "Clear operation failed: " << clear_result.message();
|
||||
|
||||
// Test undo
|
||||
auto undo_result = editor_->Undo();
|
||||
EXPECT_TRUE(undo_result.ok()) << "Undo failed: " << undo_result.message();
|
||||
|
||||
|
||||
// Test redo
|
||||
auto redo_result = editor_->Redo();
|
||||
EXPECT_TRUE(redo_result.ok()) << "Redo failed: " << redo_result.message();
|
||||
@@ -235,18 +250,21 @@ TEST_F(Tile16EditorIntegrationTest, PaletteOperationsWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Test palette cycling
|
||||
auto cycle_forward = editor_->CyclePalette(true);
|
||||
EXPECT_TRUE(cycle_forward.ok()) << "Palette cycle forward failed: " << cycle_forward.message();
|
||||
|
||||
EXPECT_TRUE(cycle_forward.ok())
|
||||
<< "Palette cycle forward failed: " << cycle_forward.message();
|
||||
|
||||
auto cycle_backward = editor_->CyclePalette(false);
|
||||
EXPECT_TRUE(cycle_backward.ok()) << "Palette cycle backward failed: " << cycle_backward.message();
|
||||
|
||||
EXPECT_TRUE(cycle_backward.ok())
|
||||
<< "Palette cycle backward failed: " << cycle_backward.message();
|
||||
|
||||
// Test valid palette preview
|
||||
auto valid_palette = editor_->PreviewPaletteChange(3);
|
||||
EXPECT_TRUE(valid_palette.ok()) << "Palette preview failed: " << valid_palette.message();
|
||||
|
||||
EXPECT_TRUE(valid_palette.ok())
|
||||
<< "Palette preview failed: " << valid_palette.message();
|
||||
|
||||
// Test invalid palette
|
||||
auto invalid_palette = editor_->PreviewPaletteChange(10);
|
||||
EXPECT_FALSE(invalid_palette.ok());
|
||||
@@ -261,15 +279,15 @@ TEST_F(Tile16EditorIntegrationTest, CopyPasteOperationsWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Set a tile first
|
||||
auto set_result = editor_->SetCurrentTile(10);
|
||||
ASSERT_TRUE(set_result.ok());
|
||||
|
||||
|
||||
// Test copy operation
|
||||
auto copy_result = editor_->CopyTile16ToClipboard(10);
|
||||
EXPECT_TRUE(copy_result.ok()) << "Copy failed: " << copy_result.message();
|
||||
|
||||
|
||||
// Test paste operation
|
||||
auto paste_result = editor_->PasteTile16FromClipboard();
|
||||
EXPECT_TRUE(paste_result.ok()) << "Paste failed: " << paste_result.message();
|
||||
@@ -283,22 +301,25 @@ TEST_F(Tile16EditorIntegrationTest, ScratchSpaceWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Set a tile first
|
||||
auto set_result = editor_->SetCurrentTile(15);
|
||||
ASSERT_TRUE(set_result.ok());
|
||||
|
||||
|
||||
// Test scratch space save
|
||||
auto save_result = editor_->SaveTile16ToScratchSpace(0);
|
||||
EXPECT_TRUE(save_result.ok()) << "Scratch save failed: " << save_result.message();
|
||||
|
||||
EXPECT_TRUE(save_result.ok())
|
||||
<< "Scratch save failed: " << save_result.message();
|
||||
|
||||
// Test scratch space load
|
||||
auto load_result = editor_->LoadTile16FromScratchSpace(0);
|
||||
EXPECT_TRUE(load_result.ok()) << "Scratch load failed: " << load_result.message();
|
||||
|
||||
EXPECT_TRUE(load_result.ok())
|
||||
<< "Scratch load failed: " << load_result.message();
|
||||
|
||||
// Test scratch space clear
|
||||
auto clear_result = editor_->ClearScratchSpace(0);
|
||||
EXPECT_TRUE(clear_result.ok()) << "Scratch clear failed: " << clear_result.message();
|
||||
EXPECT_TRUE(clear_result.ok())
|
||||
<< "Scratch clear failed: " << clear_result.message();
|
||||
#else
|
||||
GTEST_SKIP() << "ROM tests disabled";
|
||||
#endif
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <chrono>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "zelda3/dungeon/dungeon_object_editor.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
@@ -19,18 +20,18 @@ class DungeonEditorSystemIntegrationTest : public ::testing::Test {
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
|
||||
|
||||
// Use the real ROM from build directory
|
||||
rom_path_ = "build/bin/zelda3.sfc";
|
||||
|
||||
|
||||
// Load ROM
|
||||
rom_ = std::make_unique<Rom>();
|
||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path_).ok());
|
||||
|
||||
|
||||
// Initialize dungeon editor system
|
||||
dungeon_editor_system_ = std::make_unique<DungeonEditorSystem>(rom_.get());
|
||||
ASSERT_TRUE(dungeon_editor_system_->Initialize().ok());
|
||||
|
||||
|
||||
// Load test room data
|
||||
ASSERT_TRUE(LoadTestRoomData().ok());
|
||||
}
|
||||
@@ -43,22 +44,23 @@ class DungeonEditorSystemIntegrationTest : public ::testing::Test {
|
||||
absl::Status LoadTestRoomData() {
|
||||
// Load representative rooms for testing
|
||||
test_rooms_ = {0x0000, 0x0001, 0x0002, 0x0010, 0x0012, 0x0020};
|
||||
|
||||
|
||||
for (int room_id : test_rooms_) {
|
||||
auto room_result = dungeon_editor_system_->GetRoom(room_id);
|
||||
if (room_result.ok()) {
|
||||
rooms_[room_id] = room_result.value();
|
||||
std::cout << "Loaded room 0x" << std::hex << room_id << std::dec << std::endl;
|
||||
std::cout << "Loaded room 0x" << std::hex << room_id << std::dec
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
std::string rom_path_;
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<DungeonEditorSystem> dungeon_editor_system_;
|
||||
|
||||
|
||||
std::vector<int> test_rooms_;
|
||||
std::map<int, Room> rooms_;
|
||||
};
|
||||
@@ -74,19 +76,21 @@ TEST_F(DungeonEditorSystemIntegrationTest, BasicInitialization) {
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, RoomLoadingAndManagement) {
|
||||
// Test loading a specific room
|
||||
auto room_result = dungeon_editor_system_->GetRoom(0x0000);
|
||||
ASSERT_TRUE(room_result.ok()) << "Failed to load room 0x0000: " << room_result.status().message();
|
||||
|
||||
ASSERT_TRUE(room_result.ok())
|
||||
<< "Failed to load room 0x0000: " << room_result.status().message();
|
||||
|
||||
const auto& room = room_result.value();
|
||||
// Note: room_id_ is private, so we can't directly access it in tests
|
||||
|
||||
|
||||
// Test setting current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
EXPECT_EQ(dungeon_editor_system_->GetCurrentRoom(), 0x0000);
|
||||
|
||||
|
||||
// Test loading another room
|
||||
auto room2_result = dungeon_editor_system_->GetRoom(0x0001);
|
||||
ASSERT_TRUE(room2_result.ok()) << "Failed to load room 0x0001: " << room2_result.status().message();
|
||||
|
||||
ASSERT_TRUE(room2_result.ok())
|
||||
<< "Failed to load room 0x0001: " << room2_result.status().message();
|
||||
|
||||
const auto& room2 = room2_result.value();
|
||||
// Note: room_id_ is private, so we can't directly access it in tests
|
||||
}
|
||||
@@ -96,22 +100,22 @@ TEST_F(DungeonEditorSystemIntegrationTest, ObjectEditorIntegration) {
|
||||
// Get object editor from system
|
||||
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||
ASSERT_NE(object_editor, nullptr);
|
||||
|
||||
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Test object insertion
|
||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
|
||||
// Verify objects were added
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||
|
||||
|
||||
// Test object selection
|
||||
ASSERT_TRUE(object_editor->SelectObject(5 * 16, 5 * 16).ok());
|
||||
auto selection = object_editor->GetSelection();
|
||||
EXPECT_EQ(selection.selected_objects.size(), 1);
|
||||
|
||||
|
||||
// Test object deletion
|
||||
ASSERT_TRUE(object_editor->DeleteSelectedObjects().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 1);
|
||||
@@ -121,7 +125,7 @@ TEST_F(DungeonEditorSystemIntegrationTest, ObjectEditorIntegration) {
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, SpriteManagement) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Create sprite data
|
||||
DungeonEditorSystem::SpriteData sprite_data;
|
||||
sprite_data.sprite_id = 1;
|
||||
@@ -131,31 +135,32 @@ TEST_F(DungeonEditorSystemIntegrationTest, SpriteManagement) {
|
||||
sprite_data.y = 100;
|
||||
sprite_data.layer = 0;
|
||||
sprite_data.is_active = true;
|
||||
|
||||
|
||||
// Add sprite
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddSprite(sprite_data).ok());
|
||||
|
||||
|
||||
// Get sprites for room
|
||||
auto sprites_result = dungeon_editor_system_->GetSpritesByRoom(0x0000);
|
||||
ASSERT_TRUE(sprites_result.ok()) << "Failed to get sprites: " << sprites_result.status().message();
|
||||
|
||||
ASSERT_TRUE(sprites_result.ok())
|
||||
<< "Failed to get sprites: " << sprites_result.status().message();
|
||||
|
||||
const auto& sprites = sprites_result.value();
|
||||
EXPECT_EQ(sprites.size(), 1);
|
||||
EXPECT_EQ(sprites[0].sprite_id, 1);
|
||||
EXPECT_EQ(sprites[0].name, "Test Sprite");
|
||||
|
||||
|
||||
// Update sprite
|
||||
sprite_data.x = 150;
|
||||
ASSERT_TRUE(dungeon_editor_system_->UpdateSprite(1, sprite_data).ok());
|
||||
|
||||
|
||||
// Get updated sprite
|
||||
auto sprite_result = dungeon_editor_system_->GetSprite(1);
|
||||
ASSERT_TRUE(sprite_result.ok());
|
||||
EXPECT_EQ(sprite_result.value().x, 150);
|
||||
|
||||
|
||||
// Remove sprite
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveSprite(1).ok());
|
||||
|
||||
|
||||
// Verify sprite was removed
|
||||
auto sprites_after = dungeon_editor_system_->GetSpritesByRoom(0x0000);
|
||||
ASSERT_TRUE(sprites_after.ok());
|
||||
@@ -166,7 +171,7 @@ TEST_F(DungeonEditorSystemIntegrationTest, SpriteManagement) {
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, ItemManagement) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Create item data
|
||||
DungeonEditorSystem::ItemData item_data;
|
||||
item_data.item_id = 1;
|
||||
@@ -176,31 +181,32 @@ TEST_F(DungeonEditorSystemIntegrationTest, ItemManagement) {
|
||||
item_data.y = 200;
|
||||
item_data.room_id = 0x0000;
|
||||
item_data.is_hidden = false;
|
||||
|
||||
|
||||
// Add item
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddItem(item_data).ok());
|
||||
|
||||
|
||||
// Get items for room
|
||||
auto items_result = dungeon_editor_system_->GetItemsByRoom(0x0000);
|
||||
ASSERT_TRUE(items_result.ok()) << "Failed to get items: " << items_result.status().message();
|
||||
|
||||
ASSERT_TRUE(items_result.ok())
|
||||
<< "Failed to get items: " << items_result.status().message();
|
||||
|
||||
const auto& items = items_result.value();
|
||||
EXPECT_EQ(items.size(), 1);
|
||||
EXPECT_EQ(items[0].item_id, 1);
|
||||
EXPECT_EQ(items[0].name, "Small Key");
|
||||
|
||||
|
||||
// Update item
|
||||
item_data.is_hidden = true;
|
||||
ASSERT_TRUE(dungeon_editor_system_->UpdateItem(1, item_data).ok());
|
||||
|
||||
|
||||
// Get updated item
|
||||
auto item_result = dungeon_editor_system_->GetItem(1);
|
||||
ASSERT_TRUE(item_result.ok());
|
||||
EXPECT_TRUE(item_result.value().is_hidden);
|
||||
|
||||
|
||||
// Remove item
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveItem(1).ok());
|
||||
|
||||
|
||||
// Verify item was removed
|
||||
auto items_after = dungeon_editor_system_->GetItemsByRoom(0x0000);
|
||||
ASSERT_TRUE(items_after.ok());
|
||||
@@ -221,32 +227,35 @@ TEST_F(DungeonEditorSystemIntegrationTest, EntranceManagement) {
|
||||
entrance_data.target_x = 200;
|
||||
entrance_data.target_y = 200;
|
||||
entrance_data.is_bidirectional = true;
|
||||
|
||||
|
||||
// Add entrance
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddEntrance(entrance_data).ok());
|
||||
|
||||
|
||||
// Get entrances for room
|
||||
auto entrances_result = dungeon_editor_system_->GetEntrancesByRoom(0x0000);
|
||||
ASSERT_TRUE(entrances_result.ok()) << "Failed to get entrances: " << entrances_result.status().message();
|
||||
|
||||
ASSERT_TRUE(entrances_result.ok())
|
||||
<< "Failed to get entrances: " << entrances_result.status().message();
|
||||
|
||||
const auto& entrances = entrances_result.value();
|
||||
EXPECT_EQ(entrances.size(), 1);
|
||||
EXPECT_EQ(entrances[0].name, "Test Entrance");
|
||||
|
||||
|
||||
// Store the entrance ID for later removal
|
||||
int entrance_id = entrances[0].entrance_id;
|
||||
|
||||
|
||||
// Test room connection
|
||||
ASSERT_TRUE(dungeon_editor_system_->ConnectRooms(0x0000, 0x0001, 150, 150, 250, 250).ok());
|
||||
|
||||
ASSERT_TRUE(
|
||||
dungeon_editor_system_->ConnectRooms(0x0000, 0x0001, 150, 150, 250, 250)
|
||||
.ok());
|
||||
|
||||
// Get updated entrances
|
||||
auto entrances_after = dungeon_editor_system_->GetEntrancesByRoom(0x0000);
|
||||
ASSERT_TRUE(entrances_after.ok());
|
||||
EXPECT_GE(entrances_after.value().size(), 1);
|
||||
|
||||
|
||||
// Remove entrance using the correct ID
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveEntrance(entrance_id).ok());
|
||||
|
||||
|
||||
// Verify entrance was removed
|
||||
auto entrances_final = dungeon_editor_system_->GetEntrancesByRoom(0x0000);
|
||||
ASSERT_TRUE(entrances_final.ok());
|
||||
@@ -262,47 +271,48 @@ TEST_F(DungeonEditorSystemIntegrationTest, DoorManagement) {
|
||||
door_data.room_id = 0x0000;
|
||||
door_data.x = 100;
|
||||
door_data.y = 100;
|
||||
door_data.direction = 0; // up
|
||||
door_data.direction = 0; // up
|
||||
door_data.target_room_id = 0x0001;
|
||||
door_data.target_x = 200;
|
||||
door_data.target_y = 200;
|
||||
door_data.requires_key = false;
|
||||
door_data.key_type = 0;
|
||||
door_data.is_locked = false;
|
||||
|
||||
|
||||
// Add door
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddDoor(door_data).ok());
|
||||
|
||||
|
||||
// Get doors for room
|
||||
auto doors_result = dungeon_editor_system_->GetDoorsByRoom(0x0000);
|
||||
ASSERT_TRUE(doors_result.ok()) << "Failed to get doors: " << doors_result.status().message();
|
||||
|
||||
ASSERT_TRUE(doors_result.ok())
|
||||
<< "Failed to get doors: " << doors_result.status().message();
|
||||
|
||||
const auto& doors = doors_result.value();
|
||||
EXPECT_EQ(doors.size(), 1);
|
||||
EXPECT_EQ(doors[0].door_id, 1);
|
||||
EXPECT_EQ(doors[0].name, "Test Door");
|
||||
|
||||
|
||||
// Update door
|
||||
door_data.is_locked = true;
|
||||
ASSERT_TRUE(dungeon_editor_system_->UpdateDoor(1, door_data).ok());
|
||||
|
||||
|
||||
// Get updated door
|
||||
auto door_result = dungeon_editor_system_->GetDoor(1);
|
||||
ASSERT_TRUE(door_result.ok());
|
||||
EXPECT_TRUE(door_result.value().is_locked);
|
||||
|
||||
|
||||
// Set door key requirement
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetDoorKeyRequirement(1, true, 1).ok());
|
||||
|
||||
|
||||
// Get door with key requirement
|
||||
auto door_with_key = dungeon_editor_system_->GetDoor(1);
|
||||
ASSERT_TRUE(door_with_key.ok());
|
||||
EXPECT_TRUE(door_with_key.value().requires_key);
|
||||
EXPECT_EQ(door_with_key.value().key_type, 1);
|
||||
|
||||
|
||||
// Remove door
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveDoor(1).ok());
|
||||
|
||||
|
||||
// Verify door was removed
|
||||
auto doors_after = dungeon_editor_system_->GetDoorsByRoom(0x0000);
|
||||
ASSERT_TRUE(doors_after.ok());
|
||||
@@ -321,39 +331,40 @@ TEST_F(DungeonEditorSystemIntegrationTest, ChestManagement) {
|
||||
chest_data.item_id = 10;
|
||||
chest_data.item_quantity = 1;
|
||||
chest_data.is_opened = false;
|
||||
|
||||
|
||||
// Add chest
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddChest(chest_data).ok());
|
||||
|
||||
|
||||
// Get chests for room
|
||||
auto chests_result = dungeon_editor_system_->GetChestsByRoom(0x0000);
|
||||
ASSERT_TRUE(chests_result.ok()) << "Failed to get chests: " << chests_result.status().message();
|
||||
|
||||
ASSERT_TRUE(chests_result.ok())
|
||||
<< "Failed to get chests: " << chests_result.status().message();
|
||||
|
||||
const auto& chests = chests_result.value();
|
||||
EXPECT_EQ(chests.size(), 1);
|
||||
EXPECT_EQ(chests[0].chest_id, 1);
|
||||
EXPECT_EQ(chests[0].item_id, 10);
|
||||
|
||||
|
||||
// Update chest item
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetChestItem(1, 20, 5).ok());
|
||||
|
||||
|
||||
// Get updated chest
|
||||
auto chest_result = dungeon_editor_system_->GetChest(1);
|
||||
ASSERT_TRUE(chest_result.ok());
|
||||
EXPECT_EQ(chest_result.value().item_id, 20);
|
||||
EXPECT_EQ(chest_result.value().item_quantity, 5);
|
||||
|
||||
|
||||
// Set chest as opened
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetChestOpened(1, true).ok());
|
||||
|
||||
|
||||
// Get opened chest
|
||||
auto opened_chest = dungeon_editor_system_->GetChest(1);
|
||||
ASSERT_TRUE(opened_chest.ok());
|
||||
EXPECT_TRUE(opened_chest.value().is_opened);
|
||||
|
||||
|
||||
// Remove chest
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveChest(1).ok());
|
||||
|
||||
|
||||
// Verify chest was removed
|
||||
auto chests_after = dungeon_editor_system_->GetChestsByRoom(0x0000);
|
||||
ASSERT_TRUE(chests_after.ok());
|
||||
@@ -374,25 +385,29 @@ TEST_F(DungeonEditorSystemIntegrationTest, RoomPropertiesManagement) {
|
||||
properties.is_shop_room = false;
|
||||
properties.music_id = 1;
|
||||
properties.ambient_sound_id = 0;
|
||||
|
||||
|
||||
// Set room properties
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetRoomProperties(0x0000, properties).ok());
|
||||
|
||||
ASSERT_TRUE(
|
||||
dungeon_editor_system_->SetRoomProperties(0x0000, properties).ok());
|
||||
|
||||
// Get room properties
|
||||
auto properties_result = dungeon_editor_system_->GetRoomProperties(0x0000);
|
||||
ASSERT_TRUE(properties_result.ok()) << "Failed to get room properties: " << properties_result.status().message();
|
||||
|
||||
ASSERT_TRUE(properties_result.ok()) << "Failed to get room properties: "
|
||||
<< properties_result.status().message();
|
||||
|
||||
const auto& retrieved_properties = properties_result.value();
|
||||
EXPECT_EQ(retrieved_properties.room_id, 0x0000);
|
||||
EXPECT_EQ(retrieved_properties.name, "Test Room");
|
||||
EXPECT_EQ(retrieved_properties.description, "A test room for integration testing");
|
||||
EXPECT_EQ(retrieved_properties.description,
|
||||
"A test room for integration testing");
|
||||
EXPECT_EQ(retrieved_properties.dungeon_id, 1);
|
||||
|
||||
|
||||
// Update properties
|
||||
properties.name = "Updated Test Room";
|
||||
properties.is_boss_room = true;
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetRoomProperties(0x0000, properties).ok());
|
||||
|
||||
ASSERT_TRUE(
|
||||
dungeon_editor_system_->SetRoomProperties(0x0000, properties).ok());
|
||||
|
||||
// Verify update
|
||||
auto updated_properties = dungeon_editor_system_->GetRoomProperties(0x0000);
|
||||
ASSERT_TRUE(updated_properties.ok());
|
||||
@@ -415,14 +430,15 @@ TEST_F(DungeonEditorSystemIntegrationTest, DungeonSettingsManagement) {
|
||||
settings.has_map = true;
|
||||
settings.has_compass = true;
|
||||
settings.has_big_key = true;
|
||||
|
||||
|
||||
// Set dungeon settings
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetDungeonSettings(settings).ok());
|
||||
|
||||
|
||||
// Get dungeon settings
|
||||
auto settings_result = dungeon_editor_system_->GetDungeonSettings();
|
||||
ASSERT_TRUE(settings_result.ok()) << "Failed to get dungeon settings: " << settings_result.status().message();
|
||||
|
||||
ASSERT_TRUE(settings_result.ok()) << "Failed to get dungeon settings: "
|
||||
<< settings_result.status().message();
|
||||
|
||||
const auto& retrieved_settings = settings_result.value();
|
||||
EXPECT_EQ(retrieved_settings.dungeon_id, 1);
|
||||
EXPECT_EQ(retrieved_settings.name, "Test Dungeon");
|
||||
@@ -438,31 +454,31 @@ TEST_F(DungeonEditorSystemIntegrationTest, DungeonSettingsManagement) {
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, UndoRedoFunctionality) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Get object editor
|
||||
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||
ASSERT_NE(object_editor, nullptr);
|
||||
|
||||
|
||||
// Add some objects
|
||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
|
||||
// Verify objects were added
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||
|
||||
|
||||
// Test undo
|
||||
ASSERT_TRUE(dungeon_editor_system_->Undo().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 1);
|
||||
|
||||
|
||||
// Test redo
|
||||
ASSERT_TRUE(dungeon_editor_system_->Redo().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||
|
||||
|
||||
// Test multiple undos
|
||||
ASSERT_TRUE(dungeon_editor_system_->Undo().ok());
|
||||
ASSERT_TRUE(dungeon_editor_system_->Undo().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 0);
|
||||
|
||||
|
||||
// Test multiple redos
|
||||
ASSERT_TRUE(dungeon_editor_system_->Redo().ok());
|
||||
ASSERT_TRUE(dungeon_editor_system_->Redo().ok());
|
||||
@@ -473,37 +489,39 @@ TEST_F(DungeonEditorSystemIntegrationTest, UndoRedoFunctionality) {
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, ValidationFunctionality) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Validate room
|
||||
auto room_validation = dungeon_editor_system_->ValidateRoom(0x0000);
|
||||
ASSERT_TRUE(room_validation.ok()) << "Room validation failed: " << room_validation.message();
|
||||
|
||||
ASSERT_TRUE(room_validation.ok())
|
||||
<< "Room validation failed: " << room_validation.message();
|
||||
|
||||
// Validate dungeon
|
||||
auto dungeon_validation = dungeon_editor_system_->ValidateDungeon();
|
||||
ASSERT_TRUE(dungeon_validation.ok()) << "Dungeon validation failed: " << dungeon_validation.message();
|
||||
ASSERT_TRUE(dungeon_validation.ok())
|
||||
<< "Dungeon validation failed: " << dungeon_validation.message();
|
||||
}
|
||||
|
||||
// Test save/load functionality
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, SaveLoadFunctionality) {
|
||||
// Set current room and add some objects
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||
ASSERT_NE(object_editor, nullptr);
|
||||
|
||||
|
||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
|
||||
// Save room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SaveRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Reload room
|
||||
ASSERT_TRUE(dungeon_editor_system_->ReloadRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Verify objects are still there
|
||||
auto reloaded_objects = object_editor->GetObjects();
|
||||
EXPECT_EQ(reloaded_objects.size(), 2);
|
||||
|
||||
|
||||
// Save entire dungeon
|
||||
ASSERT_TRUE(dungeon_editor_system_->SaveDungeon().ok());
|
||||
}
|
||||
@@ -511,7 +529,7 @@ TEST_F(DungeonEditorSystemIntegrationTest, SaveLoadFunctionality) {
|
||||
// Test performance with multiple operations
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, PerformanceTest) {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
// Perform many operations
|
||||
for (int i = 0; i < 100; i++) {
|
||||
// Add sprite
|
||||
@@ -521,9 +539,9 @@ TEST_F(DungeonEditorSystemIntegrationTest, PerformanceTest) {
|
||||
sprite_data.x = i * 10;
|
||||
sprite_data.y = i * 10;
|
||||
sprite_data.layer = 0;
|
||||
|
||||
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddSprite(sprite_data).ok());
|
||||
|
||||
|
||||
// Add item
|
||||
DungeonEditorSystem::ItemData item_data;
|
||||
item_data.item_id = i;
|
||||
@@ -531,17 +549,20 @@ TEST_F(DungeonEditorSystemIntegrationTest, PerformanceTest) {
|
||||
item_data.x = i * 15;
|
||||
item_data.y = i * 15;
|
||||
item_data.room_id = 0x0000;
|
||||
|
||||
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddItem(item_data).ok());
|
||||
}
|
||||
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
||||
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
end_time - start_time);
|
||||
|
||||
// Should complete in reasonable time (less than 5 seconds for 200 operations)
|
||||
EXPECT_LT(duration.count(), 5000) << "Performance test too slow: " << duration.count() << "ms";
|
||||
|
||||
std::cout << "Performance test: 200 operations took " << duration.count() << "ms" << std::endl;
|
||||
EXPECT_LT(duration.count(), 5000)
|
||||
<< "Performance test too slow: " << duration.count() << "ms";
|
||||
|
||||
std::cout << "Performance test: 200 operations took " << duration.count()
|
||||
<< "ms" << std::endl;
|
||||
}
|
||||
|
||||
// Test error handling
|
||||
@@ -549,26 +570,26 @@ TEST_F(DungeonEditorSystemIntegrationTest, ErrorHandling) {
|
||||
// Test with invalid room ID
|
||||
auto invalid_room = dungeon_editor_system_->GetRoom(-1);
|
||||
EXPECT_FALSE(invalid_room.ok());
|
||||
|
||||
|
||||
auto invalid_room_large = dungeon_editor_system_->GetRoom(10000);
|
||||
EXPECT_FALSE(invalid_room_large.ok());
|
||||
|
||||
|
||||
// Test with invalid sprite ID
|
||||
auto invalid_sprite = dungeon_editor_system_->GetSprite(-1);
|
||||
EXPECT_FALSE(invalid_sprite.ok());
|
||||
|
||||
|
||||
// Test with invalid item ID
|
||||
auto invalid_item = dungeon_editor_system_->GetItem(-1);
|
||||
EXPECT_FALSE(invalid_item.ok());
|
||||
|
||||
|
||||
// Test with invalid entrance ID
|
||||
auto invalid_entrance = dungeon_editor_system_->GetEntrance(-1);
|
||||
EXPECT_FALSE(invalid_entrance.ok());
|
||||
|
||||
|
||||
// Test with invalid door ID
|
||||
auto invalid_door = dungeon_editor_system_->GetDoor(-1);
|
||||
EXPECT_FALSE(invalid_door.ok());
|
||||
|
||||
|
||||
// Test with invalid chest ID
|
||||
auto invalid_chest = dungeon_editor_system_->GetChest(-1);
|
||||
EXPECT_FALSE(invalid_chest.ok());
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
// Integration tests for dungeon object rendering using ObjectDrawer
|
||||
// Updated for DungeonEditorV2 architecture - uses ObjectDrawer (production system)
|
||||
// instead of the obsolete ObjectRenderer
|
||||
// Updated for DungeonEditorV2 architecture - uses ObjectDrawer (production
|
||||
// system) instead of the obsolete ObjectRenderer
|
||||
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/render/background_buffer.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "test_utils.h"
|
||||
#include "testing.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/gfx/render/background_buffer.h"
|
||||
#include "testing.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* @brief Tests for ObjectDrawer with realistic dungeon scenarios
|
||||
*
|
||||
*
|
||||
* These tests validate that ObjectDrawer correctly renders dungeon objects
|
||||
* to BackgroundBuffers using pattern-based drawing routines.
|
||||
*/
|
||||
@@ -56,18 +56,19 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
gfx::PaletteGroup CreateTestPaletteGroup() {
|
||||
gfx::PaletteGroup group;
|
||||
gfx::SnesPalette palette;
|
||||
|
||||
|
||||
// Create standard dungeon palette
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int intensity = i * 16;
|
||||
palette.AddColor(gfx::SnesColor(intensity, intensity, intensity));
|
||||
}
|
||||
|
||||
|
||||
group.AddPalette(palette);
|
||||
return group;
|
||||
}
|
||||
|
||||
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12, int layer = 0) {
|
||||
|
||||
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12,
|
||||
int layer = 0) {
|
||||
zelda3::RoomObject obj(id, x, y, size, layer);
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
@@ -83,15 +84,15 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
// Test basic object drawing
|
||||
TEST_F(DungeonObjectRenderingTests, BasicObjectDrawing) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // Wall
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // Wall
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 0)); // Floor
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
ASSERT_TRUE(status.ok()) << "Drawing failed: " << status.message();
|
||||
|
||||
|
||||
// Verify buffers have content
|
||||
auto& bg1_bitmap = bg1_->bitmap();
|
||||
EXPECT_TRUE(bg1_bitmap.is_active());
|
||||
@@ -101,28 +102,54 @@ TEST_F(DungeonObjectRenderingTests, BasicObjectDrawing) {
|
||||
// Test objects on different layers
|
||||
TEST_F(DungeonObjectRenderingTests, MultiLayerRendering) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // BG1
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 1)); // BG2
|
||||
objects.push_back(CreateTestObject(0x30, 15, 15, 0x12, 2)); // BG3
|
||||
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // BG1
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 1)); // BG2
|
||||
objects.push_back(CreateTestObject(0x30, 15, 15, 0x12, 2)); // BG3
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Both buffers should be active
|
||||
EXPECT_TRUE(bg1_->bitmap().is_active());
|
||||
EXPECT_TRUE(bg2_->bitmap().is_active());
|
||||
}
|
||||
|
||||
// Test that a compact buffer (preview-sized) receives pixels
|
||||
TEST_F(DungeonObjectRenderingTests, PreviewBufferRendersContent) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 1, 1, 0x12, 0)); // Small wall
|
||||
|
||||
gfx::BackgroundBuffer preview_bg(64, 64);
|
||||
gfx::BackgroundBuffer preview_bg2(64, 64);
|
||||
preview_bg.ClearBuffer();
|
||||
preview_bg2.ClearBuffer();
|
||||
|
||||
auto status =
|
||||
drawer_->DrawObjectList(objects, preview_bg, preview_bg2, palette_group_);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
auto& bitmap = preview_bg.bitmap();
|
||||
EXPECT_TRUE(bitmap.is_active());
|
||||
const auto& data = bitmap.data();
|
||||
size_t non_zero = 0;
|
||||
for (size_t i = 0; i < data.size(); i += 16) {
|
||||
if (data[i] != 0) {
|
||||
non_zero++;
|
||||
}
|
||||
}
|
||||
EXPECT_GT(non_zero, 0u);
|
||||
}
|
||||
|
||||
// Test empty object list
|
||||
TEST_F(DungeonObjectRenderingTests, EmptyObjectList) {
|
||||
std::vector<zelda3::RoomObject> objects; // Empty
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
// Should succeed (drawing nothing is valid)
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -131,40 +158,42 @@ TEST_F(DungeonObjectRenderingTests, EmptyObjectList) {
|
||||
// Test large object set
|
||||
TEST_F(DungeonObjectRenderingTests, LargeObjectSet) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
|
||||
|
||||
// Create 100 test objects
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int x = (i % 10) * 5;
|
||||
int y = (i / 10) * 5;
|
||||
objects.push_back(CreateTestObject(0x10 + (i % 20), x, y, 0x12, i % 2));
|
||||
}
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// Should complete in reasonable time
|
||||
EXPECT_LT(duration.count(), 1000) << "Rendered 100 objects in " << duration.count() << "ms";
|
||||
EXPECT_LT(duration.count(), 1000)
|
||||
<< "Rendered 100 objects in " << duration.count() << "ms";
|
||||
}
|
||||
|
||||
// Test boundary conditions
|
||||
TEST_F(DungeonObjectRenderingTests, BoundaryObjects) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
|
||||
|
||||
// Objects at boundaries
|
||||
objects.push_back(CreateTestObject(0x10, 0, 0, 0x12, 0)); // Origin
|
||||
objects.push_back(CreateTestObject(0x10, 63, 63, 0x12, 0)); // Max valid
|
||||
objects.push_back(CreateTestObject(0x10, 32, 32, 0x12, 0)); // Center
|
||||
|
||||
objects.push_back(CreateTestObject(0x10, 0, 0, 0x12, 0)); // Origin
|
||||
objects.push_back(CreateTestObject(0x10, 63, 63, 0x12, 0)); // Max valid
|
||||
objects.push_back(CreateTestObject(0x10, 32, 32, 0x12, 0)); // Center
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
@@ -173,24 +202,25 @@ TEST_F(DungeonObjectRenderingTests, BoundaryObjects) {
|
||||
TEST_F(DungeonObjectRenderingTests, VariousObjectTypes) {
|
||||
// Test common object types
|
||||
std::vector<int> object_types = {
|
||||
0x00, 0x01, 0x02, 0x03, // Floor/wall objects
|
||||
0x09, 0x0A, // Diagonal objects
|
||||
0x10, 0x11, 0x12, // Standard objects
|
||||
0x20, 0x21, // Decorative objects
|
||||
0x34, // Solid block
|
||||
0x00, 0x01, 0x02, 0x03, // Floor/wall objects
|
||||
0x09, 0x0A, // Diagonal objects
|
||||
0x10, 0x11, 0x12, // Standard objects
|
||||
0x20, 0x21, // Decorative objects
|
||||
0x34, // Solid block
|
||||
};
|
||||
|
||||
|
||||
for (int obj_type : object_types) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(obj_type, 10, 10, 0x12, 0));
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
|
||||
auto status =
|
||||
drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
// Some object types might not be valid, that's okay
|
||||
if (!status.ok()) {
|
||||
std::cout << "Object type 0x" << std::hex << obj_type << std::dec
|
||||
std::cout << "Object type 0x" << std::hex << obj_type << std::dec
|
||||
<< " not renderable: " << status.message() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -202,15 +232,15 @@ TEST_F(DungeonObjectRenderingTests, ErrorHandling) {
|
||||
zelda3::ObjectDrawer null_drawer(nullptr);
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5));
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
auto status = null_drawer.DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
|
||||
auto status =
|
||||
null_drawer.DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
// Integration tests for dungeon object rendering using ObjectDrawer
|
||||
// Updated for DungeonEditorV2 architecture - uses ObjectDrawer (production system)
|
||||
// instead of the obsolete ObjectRenderer
|
||||
// Updated for DungeonEditorV2 architecture - uses ObjectDrawer (production
|
||||
// system) instead of the obsolete ObjectRenderer
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "test_utils.h"
|
||||
#include "testing.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "testing.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* @brief Tests for ObjectDrawer with realistic dungeon scenarios
|
||||
*
|
||||
*
|
||||
* These tests validate that ObjectDrawer correctly renders dungeon objects
|
||||
* to BackgroundBuffers using pattern-based drawing routines.
|
||||
*/
|
||||
@@ -52,18 +52,19 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
gfx::PaletteGroup CreateTestPaletteGroup() {
|
||||
gfx::PaletteGroup group;
|
||||
gfx::SnesPalette palette;
|
||||
|
||||
|
||||
// Create standard dungeon palette
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int intensity = i * 16;
|
||||
palette.AddColor(gfx::SnesColor(intensity, intensity, intensity));
|
||||
}
|
||||
|
||||
|
||||
group.AddPalette(palette);
|
||||
return group;
|
||||
}
|
||||
|
||||
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12, int layer = 0) {
|
||||
|
||||
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12,
|
||||
int layer = 0) {
|
||||
zelda3::RoomObject obj(id, x, y, size, layer);
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
@@ -79,15 +80,15 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
// Test basic object drawing
|
||||
TEST_F(DungeonObjectRenderingTests, BasicObjectDrawing) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // Wall
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // Wall
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 0)); // Floor
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
ASSERT_TRUE(status.ok()) << "Drawing failed: " << status.message();
|
||||
|
||||
|
||||
// Verify buffers have content
|
||||
auto& bg1_bitmap = bg1_->bitmap();
|
||||
EXPECT_TRUE(bg1_bitmap.is_active());
|
||||
@@ -97,16 +98,16 @@ TEST_F(DungeonObjectRenderingTests, BasicObjectDrawing) {
|
||||
// Test objects on different layers
|
||||
TEST_F(DungeonObjectRenderingTests, MultiLayerRendering) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // BG1
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 1)); // BG2
|
||||
objects.push_back(CreateTestObject(0x30, 15, 15, 0x12, 2)); // BG3
|
||||
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // BG1
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 1)); // BG2
|
||||
objects.push_back(CreateTestObject(0x30, 15, 15, 0x12, 2)); // BG3
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Both buffers should be active
|
||||
EXPECT_TRUE(bg1_->bitmap().is_active());
|
||||
EXPECT_TRUE(bg2_->bitmap().is_active());
|
||||
@@ -115,10 +116,10 @@ TEST_F(DungeonObjectRenderingTests, MultiLayerRendering) {
|
||||
// Test empty object list
|
||||
TEST_F(DungeonObjectRenderingTests, EmptyObjectList) {
|
||||
std::vector<zelda3::RoomObject> objects; // Empty
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
// Should succeed (drawing nothing is valid)
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -127,40 +128,42 @@ TEST_F(DungeonObjectRenderingTests, EmptyObjectList) {
|
||||
// Test large object set
|
||||
TEST_F(DungeonObjectRenderingTests, LargeObjectSet) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
|
||||
|
||||
// Create 100 test objects
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int x = (i % 10) * 5;
|
||||
int y = (i / 10) * 5;
|
||||
objects.push_back(CreateTestObject(0x10 + (i % 20), x, y, 0x12, i % 2));
|
||||
}
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// Should complete in reasonable time
|
||||
EXPECT_LT(duration.count(), 1000) << "Rendered 100 objects in " << duration.count() << "ms";
|
||||
EXPECT_LT(duration.count(), 1000)
|
||||
<< "Rendered 100 objects in " << duration.count() << "ms";
|
||||
}
|
||||
|
||||
// Test boundary conditions
|
||||
TEST_F(DungeonObjectRenderingTests, BoundaryObjects) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
|
||||
|
||||
// Objects at boundaries
|
||||
objects.push_back(CreateTestObject(0x10, 0, 0, 0x12, 0)); // Origin
|
||||
objects.push_back(CreateTestObject(0x10, 63, 63, 0x12, 0)); // Max valid
|
||||
objects.push_back(CreateTestObject(0x10, 32, 32, 0x12, 0)); // Center
|
||||
|
||||
objects.push_back(CreateTestObject(0x10, 0, 0, 0x12, 0)); // Origin
|
||||
objects.push_back(CreateTestObject(0x10, 63, 63, 0x12, 0)); // Max valid
|
||||
objects.push_back(CreateTestObject(0x10, 32, 32, 0x12, 0)); // Center
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
@@ -169,24 +172,25 @@ TEST_F(DungeonObjectRenderingTests, BoundaryObjects) {
|
||||
TEST_F(DungeonObjectRenderingTests, VariousObjectTypes) {
|
||||
// Test common object types
|
||||
std::vector<int> object_types = {
|
||||
0x00, 0x01, 0x02, 0x03, // Floor/wall objects
|
||||
0x09, 0x0A, // Diagonal objects
|
||||
0x10, 0x11, 0x12, // Standard objects
|
||||
0x20, 0x21, // Decorative objects
|
||||
0x34, // Solid block
|
||||
0x00, 0x01, 0x02, 0x03, // Floor/wall objects
|
||||
0x09, 0x0A, // Diagonal objects
|
||||
0x10, 0x11, 0x12, // Standard objects
|
||||
0x20, 0x21, // Decorative objects
|
||||
0x34, // Solid block
|
||||
};
|
||||
|
||||
|
||||
for (int obj_type : object_types) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(obj_type, 10, 10, 0x12, 0));
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
|
||||
auto status =
|
||||
drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
// Some object types might not be valid, that's okay
|
||||
if (!status.ok()) {
|
||||
std::cout << "Object type 0x" << std::hex << obj_type << std::dec
|
||||
std::cout << "Object type 0x" << std::hex << obj_type << std::dec
|
||||
<< " not renderable: " << status.message() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -198,15 +202,15 @@ TEST_F(DungeonObjectRenderingTests, ErrorHandling) {
|
||||
zelda3::ObjectDrawer null_drawer(nullptr);
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5));
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
auto status = null_drawer.DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
|
||||
auto status =
|
||||
null_drawer.DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/dungeon/object_parser.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
@@ -18,70 +17,68 @@ class DungeonRenderingIntegrationTest : public ::testing::Test {
|
||||
// Create a mock ROM for testing
|
||||
rom_ = std::make_unique<Rom>();
|
||||
// Initialize with minimal ROM data for testing
|
||||
std::vector<uint8_t> mock_rom_data(1024 * 1024, 0); // 1MB mock ROM
|
||||
std::vector<uint8_t> mock_rom_data(1024 * 1024, 0); // 1MB mock ROM
|
||||
rom_->LoadFromData(mock_rom_data);
|
||||
|
||||
|
||||
// Create test rooms
|
||||
room_0x00_ = CreateTestRoom(0x00); // Link's House
|
||||
room_0x01_ = CreateTestRoom(0x01); // Another test room
|
||||
room_0x00_ = CreateTestRoom(0x00); // Link's House
|
||||
room_0x01_ = CreateTestRoom(0x01); // Another test room
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
rom_.reset();
|
||||
}
|
||||
void TearDown() override { rom_.reset(); }
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
|
||||
|
||||
// Create a test room with various objects
|
||||
Room CreateTestRoom(int room_id) {
|
||||
Room room(room_id, rom_.get());
|
||||
|
||||
|
||||
// Add some test objects to the room
|
||||
std::vector<RoomObject> objects;
|
||||
|
||||
|
||||
// Add floor objects (object 0x00)
|
||||
objects.emplace_back(0x00, 5, 5, 3, 0); // Horizontal floor
|
||||
objects.emplace_back(0x00, 10, 10, 5, 0); // Another floor section
|
||||
|
||||
objects.emplace_back(0x00, 5, 5, 3, 0); // Horizontal floor
|
||||
objects.emplace_back(0x00, 10, 10, 5, 0); // Another floor section
|
||||
|
||||
// Add wall objects (object 0x01)
|
||||
objects.emplace_back(0x01, 15, 15, 2, 0); // Vertical wall
|
||||
objects.emplace_back(0x01, 20, 20, 4, 1); // Horizontal wall on BG2
|
||||
|
||||
objects.emplace_back(0x01, 15, 15, 2, 0); // Vertical wall
|
||||
objects.emplace_back(0x01, 20, 20, 4, 1); // Horizontal wall on BG2
|
||||
|
||||
// Add diagonal stairs (object 0x09)
|
||||
objects.emplace_back(0x09, 25, 25, 6, 0); // Diagonal stairs
|
||||
|
||||
objects.emplace_back(0x09, 25, 25, 6, 0); // Diagonal stairs
|
||||
|
||||
// Add solid blocks (object 0x34)
|
||||
objects.emplace_back(0x34, 30, 30, 1, 0); // Solid block
|
||||
objects.emplace_back(0x34, 35, 35, 2, 1); // Another solid block on BG2
|
||||
|
||||
objects.emplace_back(0x34, 30, 30, 1, 0); // Solid block
|
||||
objects.emplace_back(0x34, 35, 35, 2, 1); // Another solid block on BG2
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
}
|
||||
|
||||
|
||||
// Add objects to room (this would normally be done by LoadObjects)
|
||||
for (const auto& obj : objects) {
|
||||
room.AddObject(obj);
|
||||
}
|
||||
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
|
||||
// Create a test palette
|
||||
gfx::SnesPalette CreateTestPalette() {
|
||||
gfx::SnesPalette palette;
|
||||
// Add some test colors
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 0)); // Transparent
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 0)); // Red
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 0)); // Green
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 255)); // Blue
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 0)); // Yellow
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 255)); // Magenta
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 255)); // Cyan
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 255)); // White
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 0)); // Transparent
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 0)); // Red
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 0)); // Green
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 255)); // Blue
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 0)); // Yellow
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 255)); // Magenta
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 255)); // Cyan
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 255)); // White
|
||||
return palette;
|
||||
}
|
||||
|
||||
|
||||
gfx::PaletteGroup CreateTestPaletteGroup() {
|
||||
gfx::PaletteGroup group;
|
||||
group.AddPalette(CreateTestPalette());
|
||||
@@ -96,19 +93,18 @@ class DungeonRenderingIntegrationTest : public ::testing::Test {
|
||||
// Test full room rendering with ObjectDrawer
|
||||
TEST_F(DungeonRenderingIntegrationTest, FullRoomRenderingWorks) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
|
||||
|
||||
// Test that room has objects
|
||||
EXPECT_GT(test_room.GetTileObjects().size(), 0);
|
||||
|
||||
|
||||
// Test ObjectDrawer can render the room
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
auto status = drawer.DrawObjectList(test_room.GetTileObjects(),
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
|
||||
auto status =
|
||||
drawer.DrawObjectList(test_room.GetTileObjects(), test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
@@ -116,21 +112,20 @@ TEST_F(DungeonRenderingIntegrationTest, FullRoomRenderingWorks) {
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithDifferentPalettes) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
|
||||
|
||||
// Test with different palette configurations
|
||||
std::vector<gfx::PaletteGroup> palette_groups;
|
||||
|
||||
|
||||
// Create multiple palette groups
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
palette_groups.push_back(CreateTestPaletteGroup());
|
||||
}
|
||||
|
||||
|
||||
for (const auto& palette_group : palette_groups) {
|
||||
auto status = drawer.DrawObjectList(test_room.GetTileObjects(),
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
auto status = drawer.DrawObjectList(test_room.GetTileObjects(),
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
}
|
||||
@@ -140,11 +135,11 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithMultipleLayers) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Separate objects by layer
|
||||
std::vector<RoomObject> bg1_objects;
|
||||
std::vector<RoomObject> bg2_objects;
|
||||
|
||||
|
||||
for (const auto& obj : test_room.GetTileObjects()) {
|
||||
if (obj.GetLayerValue() == 0) {
|
||||
bg1_objects.push_back(obj);
|
||||
@@ -152,22 +147,18 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithMultipleLayers) {
|
||||
bg2_objects.push_back(obj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Render BG1 objects
|
||||
if (!bg1_objects.empty()) {
|
||||
auto status = drawer.DrawObjectList(bg1_objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
auto status = drawer.DrawObjectList(bg1_objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
|
||||
// Render BG2 objects
|
||||
if (!bg2_objects.empty()) {
|
||||
auto status = drawer.DrawObjectList(bg2_objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
auto status = drawer.DrawObjectList(bg2_objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
}
|
||||
@@ -177,20 +168,18 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithVariousObjectSizes) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Group objects by size
|
||||
std::map<int, std::vector<RoomObject>> objects_by_size;
|
||||
|
||||
|
||||
for (const auto& obj : test_room.GetTileObjects()) {
|
||||
objects_by_size[obj.size_].push_back(obj);
|
||||
}
|
||||
|
||||
|
||||
// Render objects of each size
|
||||
for (const auto& [size, objects] : objects_by_size) {
|
||||
auto status = drawer.DrawObjectList(objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
auto status = drawer.DrawObjectList(objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
}
|
||||
@@ -199,41 +188,41 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithVariousObjectSizes) {
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingPerformance) {
|
||||
// Create a room with many objects
|
||||
Room large_room(0x00, rom_.get());
|
||||
|
||||
|
||||
// Add many test objects
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
int id = i % 65; // Cycle through object IDs 0-64
|
||||
int x = (i * 2) % 60; // Spread across buffer
|
||||
int id = i % 65; // Cycle through object IDs 0-64
|
||||
int x = (i * 2) % 60; // Spread across buffer
|
||||
int y = (i * 3) % 60;
|
||||
int size = (i % 8) + 1; // Size 1-8
|
||||
int layer = i % 2; // Alternate layers
|
||||
|
||||
int size = (i % 8) + 1; // Size 1-8
|
||||
int layer = i % 2; // Alternate layers
|
||||
|
||||
RoomObject obj(id, x, y, size, layer);
|
||||
obj.set_rom(rom_.get());
|
||||
large_room.AddObject(obj);
|
||||
}
|
||||
|
||||
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Time the rendering operation
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto status = drawer.DrawObjectList(large_room.GetTileObjects(),
|
||||
large_room.bg1_buffer(),
|
||||
large_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
|
||||
auto status = drawer.DrawObjectList(large_room.GetTileObjects(),
|
||||
large_room.bg1_buffer(),
|
||||
large_room.bg2_buffer(), palette_group);
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
end_time - start_time);
|
||||
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Should complete in reasonable time (less than 2 seconds for 200 objects)
|
||||
EXPECT_LT(duration.count(), 2000);
|
||||
|
||||
std::cout << "Rendered room with 200 objects in " << duration.count() << "ms" << std::endl;
|
||||
|
||||
std::cout << "Rendered room with 200 objects in " << duration.count() << "ms"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// Test room rendering with edge case coordinates
|
||||
@@ -241,26 +230,24 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithEdgeCaseCoordinates) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Add objects at edge coordinates
|
||||
std::vector<RoomObject> edge_objects;
|
||||
|
||||
edge_objects.emplace_back(0x34, 0, 0, 1, 0); // Origin
|
||||
edge_objects.emplace_back(0x34, 63, 63, 1, 0); // Near buffer edge
|
||||
edge_objects.emplace_back(0x34, 32, 32, 1, 0); // Center
|
||||
edge_objects.emplace_back(0x34, 1, 1, 1, 0); // Near origin
|
||||
edge_objects.emplace_back(0x34, 62, 62, 1, 0); // Near edge
|
||||
|
||||
|
||||
edge_objects.emplace_back(0x34, 0, 0, 1, 0); // Origin
|
||||
edge_objects.emplace_back(0x34, 63, 63, 1, 0); // Near buffer edge
|
||||
edge_objects.emplace_back(0x34, 32, 32, 1, 0); // Center
|
||||
edge_objects.emplace_back(0x34, 1, 1, 1, 0); // Near origin
|
||||
edge_objects.emplace_back(0x34, 62, 62, 1, 0); // Near edge
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : edge_objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
}
|
||||
|
||||
auto status = drawer.DrawObjectList(edge_objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
|
||||
auto status = drawer.DrawObjectList(edge_objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
@@ -269,56 +256,53 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithMixedObjectTypes) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Add various object types
|
||||
std::vector<RoomObject> mixed_objects;
|
||||
|
||||
|
||||
// Floor objects
|
||||
mixed_objects.emplace_back(0x00, 5, 5, 3, 0);
|
||||
mixed_objects.emplace_back(0x01, 10, 10, 2, 0);
|
||||
|
||||
|
||||
// Wall objects
|
||||
mixed_objects.emplace_back(0x02, 15, 15, 4, 0);
|
||||
mixed_objects.emplace_back(0x03, 20, 20, 1, 1);
|
||||
|
||||
|
||||
// Diagonal objects
|
||||
mixed_objects.emplace_back(0x09, 25, 25, 5, 0);
|
||||
mixed_objects.emplace_back(0x0A, 30, 30, 3, 0);
|
||||
|
||||
|
||||
// Solid objects
|
||||
mixed_objects.emplace_back(0x34, 35, 35, 1, 0);
|
||||
mixed_objects.emplace_back(0x33, 40, 40, 2, 1);
|
||||
|
||||
|
||||
// Decorative objects
|
||||
mixed_objects.emplace_back(0x36, 45, 45, 3, 0);
|
||||
mixed_objects.emplace_back(0x38, 50, 50, 1, 0);
|
||||
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : mixed_objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
}
|
||||
|
||||
auto status = drawer.DrawObjectList(mixed_objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
|
||||
auto status = drawer.DrawObjectList(mixed_objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
// Test room rendering error handling
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingErrorHandling) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
|
||||
|
||||
// Test with null ROM
|
||||
ObjectDrawer null_drawer(nullptr);
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
auto status = null_drawer.DrawObjectList(test_room.GetTileObjects(),
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
|
||||
auto status = null_drawer.DrawObjectList(
|
||||
test_room.GetTileObjects(), test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
|
||||
}
|
||||
@@ -328,26 +312,25 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithInvalidObjectData) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Create objects with invalid data
|
||||
std::vector<RoomObject> invalid_objects;
|
||||
|
||||
|
||||
invalid_objects.emplace_back(0x999, 5, 5, 1, 0); // Invalid object ID
|
||||
invalid_objects.emplace_back(0x00, -1, -1, 1, 0); // Negative coordinates
|
||||
invalid_objects.emplace_back(0x00, 100, 100, 1, 0); // Out of bounds coordinates
|
||||
invalid_objects.emplace_back(0x00, 100, 100, 1,
|
||||
0); // Out of bounds coordinates
|
||||
invalid_objects.emplace_back(0x00, 5, 5, 255, 0); // Maximum size
|
||||
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : invalid_objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
}
|
||||
|
||||
|
||||
// Should handle gracefully
|
||||
auto status = drawer.DrawObjectList(invalid_objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
auto status = drawer.DrawObjectList(invalid_objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
// Should succeed or fail gracefully
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "app/editor/message/message_data.h"
|
||||
@@ -211,23 +212,24 @@ TEST_F(MessageRomTest, BuildDictionaryEntries_CorrectSize) {
|
||||
TEST_F(MessageRomTest, ParseMessageData_CommandWithArgument_NoExtraCharacters) {
|
||||
// This test specifically checks for the bug where command arguments
|
||||
// were being incorrectly parsed as characters (e.g., capital 'A' after [W])
|
||||
// The bug was caused by using a range-based for loop while also tracking position
|
||||
|
||||
// The bug was caused by using a range-based for loop while also tracking
|
||||
// position
|
||||
|
||||
// Message: [W:01]ABC
|
||||
// Bytes: 0x6B (W command), 0x01 (argument), 0x00 (A), 0x01 (B), 0x02 (C)
|
||||
std::vector<uint8_t> data = {0x6B, 0x01, 0x00, 0x01, 0x02};
|
||||
|
||||
|
||||
editor::MessageData message;
|
||||
message.ID = 0;
|
||||
message.Address = 0;
|
||||
message.Data = data;
|
||||
|
||||
|
||||
std::vector<editor::MessageData> message_data_vector = {message};
|
||||
auto parsed = editor::ParseMessageData(message_data_vector, dictionary_);
|
||||
|
||||
|
||||
// Should be "[W:01]ABC" NOT "[W:01]BABC" or "[W:01]AABC"
|
||||
EXPECT_EQ(parsed[0], "[W:01]ABC");
|
||||
|
||||
|
||||
// The 'B' should not appear twice or be skipped
|
||||
EXPECT_EQ(parsed[0].find("BABC"), std::string::npos);
|
||||
EXPECT_EQ(parsed[0].find("AABC"), std::string::npos);
|
||||
@@ -237,20 +239,20 @@ TEST_F(MessageRomTest, ParseMessageData_MultipleCommandsWithArguments) {
|
||||
// Test multiple commands with arguments in sequence
|
||||
// [W:01][C:02]AB
|
||||
std::vector<uint8_t> data = {
|
||||
0x6B, 0x01, // [W:01] - Window border command with arg
|
||||
0x77, 0x02, // [C:02] - Color command with arg
|
||||
0x00, 0x01 // AB - Regular characters
|
||||
0x6B, 0x01, // [W:01] - Window border command with arg
|
||||
0x77, 0x02, // [C:02] - Color command with arg
|
||||
0x00, 0x01 // AB - Regular characters
|
||||
};
|
||||
|
||||
|
||||
editor::MessageData message;
|
||||
message.ID = 0;
|
||||
message.Data = data;
|
||||
|
||||
|
||||
std::vector<editor::MessageData> message_data_vector = {message};
|
||||
auto parsed = editor::ParseMessageData(message_data_vector, dictionary_);
|
||||
|
||||
|
||||
EXPECT_EQ(parsed[0], "[W:01][C:02]AB");
|
||||
|
||||
|
||||
// Make sure argument bytes (0x01, 0x02) weren't parsed as characters
|
||||
EXPECT_EQ(parsed[0].find("BAB"), std::string::npos);
|
||||
EXPECT_EQ(parsed[0].find("CAB"), std::string::npos);
|
||||
@@ -260,17 +262,17 @@ TEST_F(MessageRomTest, ParseMessageData_CommandWithoutArgument) {
|
||||
// Test command without argument followed by text
|
||||
// [K]ABC - Wait for key command (no arg) followed by ABC
|
||||
std::vector<uint8_t> data = {
|
||||
0x7E, // [K] - Wait for key (no argument)
|
||||
0x00, 0x01, 0x02 // ABC
|
||||
0x7E, // [K] - Wait for key (no argument)
|
||||
0x00, 0x01, 0x02 // ABC
|
||||
};
|
||||
|
||||
|
||||
editor::MessageData message;
|
||||
message.ID = 0;
|
||||
message.Data = data;
|
||||
|
||||
|
||||
std::vector<editor::MessageData> message_data_vector = {message};
|
||||
auto parsed = editor::ParseMessageData(message_data_vector, dictionary_);
|
||||
|
||||
|
||||
EXPECT_EQ(parsed[0], "[K]ABC");
|
||||
}
|
||||
|
||||
@@ -278,21 +280,21 @@ TEST_F(MessageRomTest, ParseMessageData_MixedCommands) {
|
||||
// Test mix of commands with and without arguments
|
||||
// [W:01]A[K]B[C:02]C
|
||||
std::vector<uint8_t> data = {
|
||||
0x6B, 0x01, // [W:01] - with arg
|
||||
0x00, // A
|
||||
0x7E, // [K] - no arg
|
||||
0x01, // B
|
||||
0x77, 0x02, // [C:02] - with arg
|
||||
0x02 // C
|
||||
0x6B, 0x01, // [W:01] - with arg
|
||||
0x00, // A
|
||||
0x7E, // [K] - no arg
|
||||
0x01, // B
|
||||
0x77, 0x02, // [C:02] - with arg
|
||||
0x02 // C
|
||||
};
|
||||
|
||||
|
||||
editor::MessageData message;
|
||||
message.ID = 0;
|
||||
message.Data = data;
|
||||
|
||||
|
||||
std::vector<editor::MessageData> message_data_vector = {message};
|
||||
auto parsed = editor::ParseMessageData(message_data_vector, dictionary_);
|
||||
|
||||
|
||||
EXPECT_EQ(parsed[0], "[W:01]A[K]B[C:02]C");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "testing.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
#include "zelda3/overworld/overworld_map.h"
|
||||
#include "testing.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
/**
|
||||
* @brief Comprehensive overworld integration test that validates YAZE C++
|
||||
* implementation against ZScream C# logic and existing test infrastructure
|
||||
*
|
||||
* @brief Comprehensive overworld integration test that validates YAZE C++
|
||||
* implementation against ZScream C# logic and existing test
|
||||
* infrastructure
|
||||
*
|
||||
* This test suite:
|
||||
* 1. Validates overworld loading logic matches ZScream behavior
|
||||
* 2. Tests integration with ZSCustomOverworld versions (vanilla, v2, v3)
|
||||
@@ -28,15 +30,15 @@ class OverworldIntegrationTest : public ::testing::Test {
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
|
||||
|
||||
// Check if we should use real ROM or mock data
|
||||
const char* rom_path_env = getenv("YAZE_TEST_ROM_PATH");
|
||||
const char* skip_rom_tests = getenv("YAZE_SKIP_ROM_TESTS");
|
||||
|
||||
|
||||
if (skip_rom_tests) {
|
||||
GTEST_SKIP() << "ROM tests disabled";
|
||||
}
|
||||
|
||||
|
||||
if (rom_path_env && std::filesystem::exists(rom_path_env)) {
|
||||
// Use real ROM for testing
|
||||
rom_ = std::make_unique<Rom>();
|
||||
@@ -47,7 +49,7 @@ class OverworldIntegrationTest : public ::testing::Test {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fall back to mock data
|
||||
use_real_rom_ = false;
|
||||
rom_ = std::make_unique<Rom>();
|
||||
@@ -63,36 +65,37 @@ class OverworldIntegrationTest : public ::testing::Test {
|
||||
|
||||
void SetupMockRomData() {
|
||||
mock_rom_data_.resize(0x200000, 0x00);
|
||||
|
||||
|
||||
// Basic ROM structure
|
||||
mock_rom_data_[0x140145] = 0xFF; // Vanilla ASM
|
||||
|
||||
mock_rom_data_[0x140145] = 0xFF; // Vanilla ASM
|
||||
|
||||
// Tile16 expansion flag
|
||||
mock_rom_data_[0x017D28] = 0x0F; // Vanilla
|
||||
|
||||
// Tile32 expansion flag
|
||||
mock_rom_data_[0x01772E] = 0x04; // Vanilla
|
||||
|
||||
mock_rom_data_[0x017D28] = 0x0F; // Vanilla
|
||||
|
||||
// Tile32 expansion flag
|
||||
mock_rom_data_[0x01772E] = 0x04; // Vanilla
|
||||
|
||||
// Basic map data
|
||||
for (int i = 0; i < 160; i++) {
|
||||
mock_rom_data_[0x012844 + i] = 0x00; // Small areas
|
||||
mock_rom_data_[0x012844 + i] = 0x00; // Small areas
|
||||
}
|
||||
|
||||
// Setup entrance data (matches ZScream Constants.OWEntranceMap/Pos/EntranceId)
|
||||
|
||||
// Setup entrance data (matches ZScream
|
||||
// Constants.OWEntranceMap/Pos/EntranceId)
|
||||
for (int i = 0; i < 129; i++) {
|
||||
mock_rom_data_[0x0DB96F + (i * 2)] = i & 0xFF; // Map ID
|
||||
mock_rom_data_[0x0DB96F + (i * 2)] = i & 0xFF; // Map ID
|
||||
mock_rom_data_[0x0DB96F + (i * 2) + 1] = (i >> 8) & 0xFF;
|
||||
mock_rom_data_[0x0DBA71 + (i * 2)] = (i * 16) & 0xFF; // Map Position
|
||||
mock_rom_data_[0x0DBA71 + (i * 2)] = (i * 16) & 0xFF; // Map Position
|
||||
mock_rom_data_[0x0DBA71 + (i * 2) + 1] = ((i * 16) >> 8) & 0xFF;
|
||||
mock_rom_data_[0x0DBB73 + i] = i & 0xFF; // Entrance ID
|
||||
mock_rom_data_[0x0DBB73 + i] = i & 0xFF; // Entrance ID
|
||||
}
|
||||
|
||||
|
||||
// Setup exit data (matches ZScream Constants.OWExit*)
|
||||
for (int i = 0; i < 0x4F; i++) {
|
||||
mock_rom_data_[0x015D8A + (i * 2)] = i & 0xFF; // Room ID
|
||||
mock_rom_data_[0x015D8A + (i * 2)] = i & 0xFF; // Room ID
|
||||
mock_rom_data_[0x015D8A + (i * 2) + 1] = (i >> 8) & 0xFF;
|
||||
mock_rom_data_[0x015E28 + i] = i & 0xFF; // Map ID
|
||||
mock_rom_data_[0x015E77 + (i * 2)] = i & 0xFF; // VRAM
|
||||
mock_rom_data_[0x015E28 + i] = i & 0xFF; // Map ID
|
||||
mock_rom_data_[0x015E77 + (i * 2)] = i & 0xFF; // VRAM
|
||||
mock_rom_data_[0x015E77 + (i * 2) + 1] = (i >> 8) & 0xFF;
|
||||
// Add other exit fields...
|
||||
}
|
||||
@@ -108,30 +111,30 @@ class OverworldIntegrationTest : public ::testing::Test {
|
||||
TEST_F(OverworldIntegrationTest, Tile32ExpansionDetection) {
|
||||
mock_rom_data_[0x01772E] = 0x04;
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Test expanded detection
|
||||
mock_rom_data_[0x01772E] = 0x05;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
}
|
||||
|
||||
// Test Tile16 expansion detection
|
||||
// Test Tile16 expansion detection
|
||||
TEST_F(OverworldIntegrationTest, Tile16ExpansionDetection) {
|
||||
mock_rom_data_[0x017D28] = 0x0F;
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Test expanded detection
|
||||
mock_rom_data_[0x017D28] = 0x10;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
}
|
||||
@@ -140,29 +143,30 @@ TEST_F(OverworldIntegrationTest, Tile16ExpansionDetection) {
|
||||
TEST_F(OverworldIntegrationTest, EntranceCoordinateCalculation) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& entrances = overworld_->entrances();
|
||||
EXPECT_EQ(entrances.size(), 129);
|
||||
|
||||
|
||||
// Verify coordinate calculation matches ZScream logic:
|
||||
// int p = mapPos >> 1;
|
||||
// int x = p % 64;
|
||||
// int y = p >> 6;
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512);
|
||||
// int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
const auto& entrance = entrances[i];
|
||||
|
||||
uint16_t map_pos = i * 16; // Our test data
|
||||
uint16_t map_id = i; // Our test data
|
||||
|
||||
|
||||
uint16_t map_pos = i * 16; // Our test data
|
||||
uint16_t map_id = i; // Our test data
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_x =
|
||||
(x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
|
||||
EXPECT_EQ(entrance.x_, expected_x);
|
||||
EXPECT_EQ(entrance.y_, expected_y);
|
||||
EXPECT_EQ(entrance.entrance_id_, i);
|
||||
@@ -174,10 +178,10 @@ TEST_F(OverworldIntegrationTest, EntranceCoordinateCalculation) {
|
||||
TEST_F(OverworldIntegrationTest, ExitDataLoading) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& exits = overworld_->exits();
|
||||
EXPECT_EQ(exits->size(), 0x4F);
|
||||
|
||||
|
||||
// Verify exit data matches our test data
|
||||
for (int i = 0; i < std::min(5, static_cast<int>(exits->size())); i++) {
|
||||
const auto& exit = exits->at(i);
|
||||
@@ -192,19 +196,19 @@ TEST_F(OverworldIntegrationTest, ASMVersionItemLoading) {
|
||||
// Test vanilla ASM (should limit to 0x80 maps)
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& items = overworld_->all_items();
|
||||
|
||||
|
||||
// Test v3+ ASM (should support all 0xA0 maps)
|
||||
mock_rom_data_[0x140145] = 0x03;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& items_v3 = overworld_->all_items();
|
||||
// v3 should have more comprehensive support
|
||||
EXPECT_GE(items_v3.size(), items.size());
|
||||
@@ -214,10 +218,10 @@ TEST_F(OverworldIntegrationTest, ASMVersionItemLoading) {
|
||||
TEST_F(OverworldIntegrationTest, MapSizeAssignment) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
|
||||
// Verify all maps are initialized
|
||||
for (const auto& map : maps) {
|
||||
EXPECT_GE(map.area_size(), AreaSizeEnum::SmallArea);
|
||||
@@ -230,16 +234,16 @@ TEST_F(OverworldIntegrationTest, ZSCustomOverworldVersionIntegration) {
|
||||
if (!use_real_rom_) {
|
||||
GTEST_SKIP() << "Real ROM required for ZSCustomOverworld version testing";
|
||||
}
|
||||
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Check ASM version detection
|
||||
auto version_byte = rom_->ReadByte(0x140145);
|
||||
ASSERT_TRUE(version_byte.ok());
|
||||
|
||||
|
||||
uint8_t asm_version = *version_byte;
|
||||
|
||||
|
||||
if (asm_version == 0xFF) {
|
||||
// Vanilla ROM
|
||||
EXPECT_FALSE(overworld_->expanded_tile16());
|
||||
@@ -250,50 +254,53 @@ TEST_F(OverworldIntegrationTest, ZSCustomOverworldVersionIntegration) {
|
||||
EXPECT_TRUE(overworld_->expanded_tile16());
|
||||
EXPECT_TRUE(overworld_->expanded_tile32());
|
||||
}
|
||||
|
||||
|
||||
// Verify version-specific features are properly detected
|
||||
if (asm_version >= 0x03) {
|
||||
// v3 features should be available
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160); // All 160 maps supported in v3
|
||||
EXPECT_EQ(maps.size(), 160); // All 160 maps supported in v3
|
||||
}
|
||||
}
|
||||
|
||||
// Test compatibility with RomDependentTestSuite infrastructure
|
||||
TEST_F(OverworldIntegrationTest, RomDependentTestSuiteCompatibility) {
|
||||
if (!use_real_rom_) {
|
||||
GTEST_SKIP() << "Real ROM required for RomDependentTestSuite compatibility testing";
|
||||
GTEST_SKIP()
|
||||
<< "Real ROM required for RomDependentTestSuite compatibility testing";
|
||||
}
|
||||
|
||||
// Test that our overworld loading works with the same patterns as RomDependentTestSuite
|
||||
|
||||
// Test that our overworld loading works with the same patterns as
|
||||
// RomDependentTestSuite
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Verify ROM-dependent features work correctly
|
||||
EXPECT_TRUE(overworld_->is_loaded());
|
||||
|
||||
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
|
||||
// Test that we can access the same data structures as RomDependentTestSuite
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(maps.size())); i++) {
|
||||
const auto& map = maps[i];
|
||||
|
||||
|
||||
// Verify map properties are accessible
|
||||
EXPECT_GE(map.area_graphics(), 0);
|
||||
EXPECT_GE(map.main_palette(), 0);
|
||||
EXPECT_GE(map.area_size(), AreaSizeEnum::SmallArea);
|
||||
EXPECT_LE(map.area_size(), AreaSizeEnum::TallArea);
|
||||
}
|
||||
|
||||
// Test that sprite data is accessible (matches RomDependentTestSuite expectations)
|
||||
|
||||
// Test that sprite data is accessible (matches RomDependentTestSuite
|
||||
// expectations)
|
||||
const auto& sprites = overworld_->sprites(0);
|
||||
EXPECT_EQ(sprites.size(), 3); // Three game states
|
||||
|
||||
EXPECT_EQ(sprites.size(), 3); // Three game states
|
||||
|
||||
// Test that item data is accessible
|
||||
const auto& items = overworld_->all_items();
|
||||
EXPECT_GE(items.size(), 0);
|
||||
|
||||
|
||||
// Test that entrance/exit data is accessible
|
||||
const auto& entrances = overworld_->entrances();
|
||||
const auto& exits = overworld_->exits();
|
||||
@@ -305,17 +312,17 @@ TEST_F(OverworldIntegrationTest, RomDependentTestSuiteCompatibility) {
|
||||
TEST_F(OverworldIntegrationTest, ComprehensiveDataIntegrity) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Verify all major data structures are properly loaded
|
||||
EXPECT_GT(overworld_->tiles16().size(), 0);
|
||||
EXPECT_GT(overworld_->tiles32_unique().size(), 0);
|
||||
|
||||
|
||||
// Verify map organization matches ZScream expectations
|
||||
const auto& map_tiles = overworld_->map_tiles();
|
||||
EXPECT_EQ(map_tiles.light_world.size(), 512);
|
||||
EXPECT_EQ(map_tiles.dark_world.size(), 512);
|
||||
EXPECT_EQ(map_tiles.special_world.size(), 512);
|
||||
|
||||
|
||||
// Verify each world has proper 512x512 tile data
|
||||
for (const auto& row : map_tiles.light_world) {
|
||||
EXPECT_EQ(row.size(), 512);
|
||||
@@ -326,16 +333,16 @@ TEST_F(OverworldIntegrationTest, ComprehensiveDataIntegrity) {
|
||||
for (const auto& row : map_tiles.special_world) {
|
||||
EXPECT_EQ(row.size(), 512);
|
||||
}
|
||||
|
||||
|
||||
// Verify overworld maps are properly initialized
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
|
||||
for (const auto& map : maps) {
|
||||
// TODO: Find a way to compare
|
||||
// EXPECT_TRUE(map.bitmap_data() != nullptr);
|
||||
}
|
||||
|
||||
|
||||
// Verify tile types are loaded
|
||||
const auto& tile_types = overworld_->all_tiles_types();
|
||||
EXPECT_EQ(tile_types.size(), 0x200);
|
||||
@@ -345,62 +352,64 @@ TEST_F(OverworldIntegrationTest, ComprehensiveDataIntegrity) {
|
||||
TEST_F(OverworldIntegrationTest, ZScreamCoordinateCompatibility) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& entrances = overworld_->entrances();
|
||||
EXPECT_EQ(entrances.size(), 129);
|
||||
|
||||
|
||||
// Test coordinate calculation matches ZScream logic exactly
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
const auto& entrance = entrances[i];
|
||||
|
||||
|
||||
// ZScream coordinate calculation:
|
||||
// int p = mapPos >> 1;
|
||||
// int x = p % 64;
|
||||
// int y = p >> 6;
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512);
|
||||
// int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) *
|
||||
// 512); int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
uint16_t map_pos = entrance.map_pos_;
|
||||
uint16_t map_id = entrance.map_id_;
|
||||
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_x =
|
||||
(x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
|
||||
EXPECT_EQ(entrance.x_, expected_x);
|
||||
EXPECT_EQ(entrance.y_, expected_y);
|
||||
}
|
||||
|
||||
|
||||
// Test hole coordinate calculation with 0x400 offset
|
||||
const auto& holes = overworld_->holes();
|
||||
EXPECT_EQ(holes.size(), 0x13);
|
||||
|
||||
|
||||
for (int i = 0; i < std::min(5, static_cast<int>(holes.size())); i++) {
|
||||
const auto& hole = holes[i];
|
||||
|
||||
|
||||
// ZScream hole coordinate calculation:
|
||||
// int p = (mapPos + 0x400) >> 1;
|
||||
// int x = p % 64;
|
||||
// int y = p >> 6;
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512);
|
||||
// int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) *
|
||||
// 512); int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
uint16_t map_pos = hole.map_pos_;
|
||||
uint16_t map_id = hole.map_id_;
|
||||
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_x =
|
||||
(x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
|
||||
EXPECT_EQ(hole.x_, expected_x);
|
||||
EXPECT_EQ(hole.y_, expected_y);
|
||||
EXPECT_TRUE(hole.is_hole_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
@@ -1,8 +1,8 @@
|
||||
// Integration tests for Room object load/save cycle with real ROM data
|
||||
// Phase 1, Task 2.1: Full round-trip verification
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
@@ -23,22 +23,22 @@ class RoomIntegrationTest : public ::testing::Test {
|
||||
void SetUp() override {
|
||||
// Load the ROM file
|
||||
rom_ = std::make_unique<Rom>();
|
||||
|
||||
|
||||
// Check if ROM file exists
|
||||
const char* rom_path = std::getenv("YAZE_TEST_ROM_PATH");
|
||||
if (!rom_path) {
|
||||
rom_path = "zelda3.sfc";
|
||||
}
|
||||
|
||||
|
||||
auto status = rom_->LoadFromFile(rom_path);
|
||||
if (!status.ok()) {
|
||||
GTEST_SKIP() << "ROM file not available: " << status.message();
|
||||
}
|
||||
|
||||
|
||||
// Create backup of ROM data for restoration after tests
|
||||
original_rom_data_ = rom_->vector();
|
||||
}
|
||||
|
||||
|
||||
void TearDown() override {
|
||||
// Restore original ROM data
|
||||
if (rom_ && !original_rom_data_.empty()) {
|
||||
@@ -47,7 +47,7 @@ class RoomIntegrationTest : public ::testing::Test {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::vector<uint8_t> original_rom_data_;
|
||||
};
|
||||
@@ -59,34 +59,34 @@ class RoomIntegrationTest : public ::testing::Test {
|
||||
TEST_F(RoomIntegrationTest, BasicLoadSaveRoundTrip) {
|
||||
// Load room 0 (Hyrule Castle Entrance)
|
||||
Room room1(0x00, rom_.get());
|
||||
|
||||
|
||||
// Get original object count
|
||||
size_t original_count = room1.GetTileObjects().size();
|
||||
ASSERT_GT(original_count, 0) << "Room should have objects";
|
||||
|
||||
|
||||
// Store original objects
|
||||
auto original_objects = room1.GetTileObjects();
|
||||
|
||||
|
||||
// Save the room (should write same data back)
|
||||
auto save_status = room1.SaveObjects();
|
||||
ASSERT_TRUE(save_status.ok()) << save_status.message();
|
||||
|
||||
|
||||
// Load the room again
|
||||
Room room2(0x00, rom_.get());
|
||||
|
||||
|
||||
// Verify object count matches
|
||||
EXPECT_EQ(room2.GetTileObjects().size(), original_count);
|
||||
|
||||
|
||||
// Verify each object matches
|
||||
auto reloaded_objects = room2.GetTileObjects();
|
||||
ASSERT_EQ(reloaded_objects.size(), original_objects.size());
|
||||
|
||||
|
||||
for (size_t i = 0; i < original_objects.size(); i++) {
|
||||
SCOPED_TRACE("Object " + std::to_string(i));
|
||||
|
||||
|
||||
const auto& orig = original_objects[i];
|
||||
const auto& reload = reloaded_objects[i];
|
||||
|
||||
|
||||
EXPECT_EQ(reload.id_, orig.id_) << "ID mismatch";
|
||||
EXPECT_EQ(reload.x(), orig.x()) << "X position mismatch";
|
||||
EXPECT_EQ(reload.y(), orig.y()) << "Y position mismatch";
|
||||
@@ -102,33 +102,34 @@ TEST_F(RoomIntegrationTest, BasicLoadSaveRoundTrip) {
|
||||
TEST_F(RoomIntegrationTest, MultiRoomLoadSaveRoundTrip) {
|
||||
// Test several different rooms to ensure broad coverage
|
||||
std::vector<int> test_rooms = {0x00, 0x01, 0x02, 0x10, 0x20};
|
||||
|
||||
|
||||
for (int room_id : test_rooms) {
|
||||
SCOPED_TRACE("Room " + std::to_string(room_id));
|
||||
|
||||
|
||||
// Load room
|
||||
Room room1(room_id, rom_.get());
|
||||
auto original_objects = room1.GetTileObjects();
|
||||
|
||||
|
||||
if (original_objects.empty()) {
|
||||
continue; // Skip empty rooms
|
||||
continue; // Skip empty rooms
|
||||
}
|
||||
|
||||
|
||||
// Save objects
|
||||
auto save_status = room1.SaveObjects();
|
||||
ASSERT_TRUE(save_status.ok()) << save_status.message();
|
||||
|
||||
|
||||
// Reload and verify
|
||||
Room room2(room_id, rom_.get());
|
||||
auto reloaded_objects = room2.GetTileObjects();
|
||||
|
||||
|
||||
EXPECT_EQ(reloaded_objects.size(), original_objects.size());
|
||||
|
||||
|
||||
// Verify objects match
|
||||
for (size_t i = 0; i < std::min(original_objects.size(), reloaded_objects.size()); i++) {
|
||||
for (size_t i = 0;
|
||||
i < std::min(original_objects.size(), reloaded_objects.size()); i++) {
|
||||
const auto& orig = original_objects[i];
|
||||
const auto& reload = reloaded_objects[i];
|
||||
|
||||
|
||||
EXPECT_EQ(reload.id_, orig.id_);
|
||||
EXPECT_EQ(reload.x(), orig.x());
|
||||
EXPECT_EQ(reload.y(), orig.y());
|
||||
@@ -145,36 +146,48 @@ TEST_F(RoomIntegrationTest, MultiRoomLoadSaveRoundTrip) {
|
||||
TEST_F(RoomIntegrationTest, LayerPreservation) {
|
||||
// Load a room known to have multiple layers
|
||||
Room room(0x01, rom_.get());
|
||||
|
||||
|
||||
auto objects = room.GetTileObjects();
|
||||
ASSERT_GT(objects.size(), 0);
|
||||
|
||||
|
||||
// Count objects per layer
|
||||
int layer0_count = 0, layer1_count = 0, layer2_count = 0;
|
||||
for (const auto& obj : objects) {
|
||||
switch (obj.GetLayerValue()) {
|
||||
case 0: layer0_count++; break;
|
||||
case 1: layer1_count++; break;
|
||||
case 2: layer2_count++; break;
|
||||
case 0:
|
||||
layer0_count++;
|
||||
break;
|
||||
case 1:
|
||||
layer1_count++;
|
||||
break;
|
||||
case 2:
|
||||
layer2_count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save and reload
|
||||
ASSERT_TRUE(room.SaveObjects().ok());
|
||||
|
||||
|
||||
Room room2(0x01, rom_.get());
|
||||
auto reloaded = room2.GetTileObjects();
|
||||
|
||||
|
||||
// Verify layer counts match
|
||||
int reload_layer0 = 0, reload_layer1 = 0, reload_layer2 = 0;
|
||||
for (const auto& obj : reloaded) {
|
||||
switch (obj.GetLayerValue()) {
|
||||
case 0: reload_layer0++; break;
|
||||
case 1: reload_layer1++; break;
|
||||
case 2: reload_layer2++; break;
|
||||
case 0:
|
||||
reload_layer0++;
|
||||
break;
|
||||
case 1:
|
||||
reload_layer1++;
|
||||
break;
|
||||
case 2:
|
||||
reload_layer2++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EXPECT_EQ(reload_layer0, layer0_count);
|
||||
EXPECT_EQ(reload_layer1, layer1_count);
|
||||
EXPECT_EQ(reload_layer2, layer2_count);
|
||||
@@ -186,15 +199,15 @@ TEST_F(RoomIntegrationTest, LayerPreservation) {
|
||||
|
||||
TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
||||
Room room(0x00, rom_.get());
|
||||
|
||||
|
||||
auto objects = room.GetTileObjects();
|
||||
ASSERT_GT(objects.size(), 0);
|
||||
|
||||
|
||||
// Count object types
|
||||
int type1_count = 0; // ID < 0x100
|
||||
int type2_count = 0; // ID 0x100-0x13F
|
||||
int type3_count = 0; // ID >= 0xF00
|
||||
|
||||
|
||||
for (const auto& obj : objects) {
|
||||
if (obj.id_ >= 0xF00) {
|
||||
type3_count++;
|
||||
@@ -204,13 +217,13 @@ TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
||||
type1_count++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save and reload
|
||||
ASSERT_TRUE(room.SaveObjects().ok());
|
||||
|
||||
|
||||
Room room2(0x00, rom_.get());
|
||||
auto reloaded = room2.GetTileObjects();
|
||||
|
||||
|
||||
// Verify type distribution matches
|
||||
int reload_type1 = 0, reload_type2 = 0, reload_type3 = 0;
|
||||
for (const auto& obj : reloaded) {
|
||||
@@ -222,7 +235,7 @@ TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
||||
reload_type1++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EXPECT_EQ(reload_type1, type1_count);
|
||||
EXPECT_EQ(reload_type2, type2_count);
|
||||
EXPECT_EQ(reload_type3, type3_count);
|
||||
@@ -235,54 +248,56 @@ TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
||||
TEST_F(RoomIntegrationTest, BinaryDataExactMatch) {
|
||||
// This test verifies that saving doesn't change ROM data
|
||||
// when no modifications are made
|
||||
|
||||
|
||||
Room room(0x02, rom_.get());
|
||||
|
||||
|
||||
// Get the ROM location where objects are stored
|
||||
auto rom_data = rom_->vector();
|
||||
int object_pointer = (rom_data[0x874C + 2] << 16) +
|
||||
(rom_data[0x874C + 1] << 8) +
|
||||
(rom_data[0x874C]);
|
||||
(rom_data[0x874C + 1] << 8) + (rom_data[0x874C]);
|
||||
object_pointer = SnesToPc(object_pointer);
|
||||
|
||||
|
||||
int room_address = object_pointer + (0x02 * 3);
|
||||
int tile_address = (rom_data[room_address + 2] << 16) +
|
||||
(rom_data[room_address + 1] << 8) +
|
||||
rom_data[room_address];
|
||||
(rom_data[room_address + 1] << 8) + rom_data[room_address];
|
||||
int objects_location = SnesToPc(tile_address);
|
||||
|
||||
|
||||
// Read original bytes (up to 500 bytes should cover most rooms)
|
||||
std::vector<uint8_t> original_bytes;
|
||||
for (int i = 0; i < 500 && objects_location + i < (int)rom_data.size(); i++) {
|
||||
original_bytes.push_back(rom_data[objects_location + i]);
|
||||
// Stop at final terminator
|
||||
if (i > 0 && original_bytes[i] == 0xFF && original_bytes[i-1] == 0xFF) {
|
||||
if (i > 0 && original_bytes[i] == 0xFF && original_bytes[i - 1] == 0xFF) {
|
||||
// Check if this is the final terminator (3rd layer end)
|
||||
bool might_be_final = true;
|
||||
for (int j = i - 10; j < i - 1; j += 2) {
|
||||
if (j >= 0 && original_bytes[j] == 0xFF && original_bytes[j+1] == 0xFF) {
|
||||
if (j >= 0 && original_bytes[j] == 0xFF &&
|
||||
original_bytes[j + 1] == 0xFF) {
|
||||
// Found another FF FF marker, keep going
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (might_be_final) break;
|
||||
if (might_be_final)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save objects (should write identical data)
|
||||
ASSERT_TRUE(room.SaveObjects().ok());
|
||||
|
||||
|
||||
// Read bytes after save
|
||||
rom_data = rom_->vector();
|
||||
std::vector<uint8_t> saved_bytes;
|
||||
for (size_t i = 0; i < original_bytes.size() && objects_location + i < rom_data.size(); i++) {
|
||||
for (size_t i = 0;
|
||||
i < original_bytes.size() && objects_location + i < rom_data.size();
|
||||
i++) {
|
||||
saved_bytes.push_back(rom_data[objects_location + i]);
|
||||
}
|
||||
|
||||
|
||||
// Verify binary match
|
||||
ASSERT_EQ(saved_bytes.size(), original_bytes.size());
|
||||
for (size_t i = 0; i < original_bytes.size(); i++) {
|
||||
EXPECT_EQ(saved_bytes[i], original_bytes[i])
|
||||
EXPECT_EQ(saved_bytes[i], original_bytes[i])
|
||||
<< "Byte mismatch at offset " << i;
|
||||
}
|
||||
}
|
||||
@@ -294,24 +309,27 @@ TEST_F(RoomIntegrationTest, BinaryDataExactMatch) {
|
||||
TEST_F(RoomIntegrationTest, KnownRoomData) {
|
||||
// Room 0x00 (Hyrule Castle Entrance) - verify known objects exist
|
||||
Room room(0x00, rom_.get());
|
||||
|
||||
|
||||
auto objects = room.GetTileObjects();
|
||||
ASSERT_GT(objects.size(), 0) << "Room 0x00 should have objects";
|
||||
|
||||
|
||||
// Verify we can find common object types
|
||||
bool found_type1 = false;
|
||||
bool found_layer0 = false;
|
||||
bool found_layer1 = false;
|
||||
|
||||
|
||||
for (const auto& obj : objects) {
|
||||
if (obj.id_ < 0x100) found_type1 = true;
|
||||
if (obj.GetLayerValue() == 0) found_layer0 = true;
|
||||
if (obj.GetLayerValue() == 1) found_layer1 = true;
|
||||
if (obj.id_ < 0x100)
|
||||
found_type1 = true;
|
||||
if (obj.GetLayerValue() == 0)
|
||||
found_layer0 = true;
|
||||
if (obj.GetLayerValue() == 1)
|
||||
found_layer1 = true;
|
||||
}
|
||||
|
||||
|
||||
EXPECT_TRUE(found_type1) << "Should have Type 1 objects";
|
||||
EXPECT_TRUE(found_layer0) << "Should have Layer 0 objects";
|
||||
|
||||
|
||||
// Verify coordinates are in valid range (0-63)
|
||||
for (const auto& obj : objects) {
|
||||
EXPECT_GE(obj.x(), 0);
|
||||
@@ -324,4 +342,3 @@ TEST_F(RoomIntegrationTest, KnownRoomData) {
|
||||
} // namespace test
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
@@ -12,23 +13,25 @@ namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
class SpritePositionTest : public ::testing::Test {
|
||||
protected:
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Try to load a vanilla ROM for testing
|
||||
rom_ = std::make_unique<Rom>();
|
||||
std::string rom_path = "bin/zelda3.sfc";
|
||||
|
||||
|
||||
// Check if ROM exists in build directory
|
||||
std::ifstream rom_file(rom_path);
|
||||
if (rom_file.good()) {
|
||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path).ok()) << "Failed to load ROM from " << rom_path;
|
||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path).ok())
|
||||
<< "Failed to load ROM from " << rom_path;
|
||||
} else {
|
||||
// Skip test if ROM not found
|
||||
GTEST_SKIP() << "ROM file not found at " << rom_path;
|
||||
}
|
||||
|
||||
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
ASSERT_TRUE(overworld_->Load(rom_.get()).ok()) << "Failed to load overworld";
|
||||
ASSERT_TRUE(overworld_->Load(rom_.get()).ok())
|
||||
<< "Failed to load overworld";
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
@@ -47,24 +50,28 @@ TEST_F(SpritePositionTest, SpriteCoordinateSystem) {
|
||||
const auto& sprites = overworld_->sprites(game_state);
|
||||
std::cout << "\n=== Game State " << game_state << " ===" << std::endl;
|
||||
std::cout << "Total sprites: " << sprites.size() << std::endl;
|
||||
|
||||
|
||||
int sprite_count = 0;
|
||||
for (const auto& sprite : sprites) {
|
||||
if (!sprite.deleted() && sprite_count < 10) { // Show first 10 sprites
|
||||
std::cout << "Sprite " << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(sprite.id()) << " (" << const_cast<Sprite&>(sprite).name() << ")" << std::endl;
|
||||
std::cout << " Map ID: 0x" << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< sprite.map_id() << std::endl;
|
||||
std::cout << " X: " << std::dec << sprite.x() << " (0x" << std::hex << sprite.x() << ")" << std::endl;
|
||||
std::cout << " Y: " << std::dec << sprite.y() << " (0x" << std::hex << sprite.y() << ")" << std::endl;
|
||||
if (!sprite.deleted() && sprite_count < 10) { // Show first 10 sprites
|
||||
std::cout << "Sprite " << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(sprite.id()) << " ("
|
||||
<< const_cast<Sprite&>(sprite).name() << ")" << std::endl;
|
||||
std::cout << " Map ID: 0x" << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << sprite.map_id() << std::endl;
|
||||
std::cout << " X: " << std::dec << sprite.x() << " (0x" << std::hex
|
||||
<< sprite.x() << ")" << std::endl;
|
||||
std::cout << " Y: " << std::dec << sprite.y() << " (0x" << std::hex
|
||||
<< sprite.y() << ")" << std::endl;
|
||||
std::cout << " map_x: " << std::dec << sprite.map_x() << std::endl;
|
||||
std::cout << " map_y: " << std::dec << sprite.map_y() << std::endl;
|
||||
|
||||
|
||||
// Calculate expected world ranges
|
||||
int world_start = game_state * 0x40;
|
||||
int world_end = world_start + 0x40;
|
||||
std::cout << " World range: 0x" << std::hex << world_start << " - 0x" << world_end << std::endl;
|
||||
|
||||
std::cout << " World range: 0x" << std::hex << world_start << " - 0x"
|
||||
<< world_end << std::endl;
|
||||
|
||||
sprite_count++;
|
||||
}
|
||||
}
|
||||
@@ -76,30 +83,32 @@ TEST_F(SpritePositionTest, SpriteFilteringLogic) {
|
||||
// Test the filtering logic used in DrawOverworldSprites
|
||||
for (int current_world = 0; current_world < 3; current_world++) {
|
||||
const auto& sprites = overworld_->sprites(current_world);
|
||||
|
||||
std::cout << "\n=== Testing World " << current_world << " Filtering ===" << std::endl;
|
||||
|
||||
|
||||
std::cout << "\n=== Testing World " << current_world
|
||||
<< " Filtering ===" << std::endl;
|
||||
|
||||
int visible_sprites = 0;
|
||||
int total_sprites = 0;
|
||||
|
||||
|
||||
for (const auto& sprite : sprites) {
|
||||
if (!sprite.deleted()) {
|
||||
total_sprites++;
|
||||
|
||||
|
||||
// This is the filtering logic from DrawOverworldSprites
|
||||
bool should_show = (sprite.map_id() < 0x40 + (current_world * 0x40) &&
|
||||
sprite.map_id() >= (current_world * 0x40));
|
||||
|
||||
sprite.map_id() >= (current_world * 0x40));
|
||||
|
||||
if (should_show) {
|
||||
visible_sprites++;
|
||||
std::cout << " Visible: Sprite 0x" << std::hex << static_cast<int>(sprite.id())
|
||||
<< " on map 0x" << sprite.map_id() << " at ("
|
||||
<< std::dec << sprite.x() << ", " << sprite.y() << ")" << std::endl;
|
||||
std::cout << " Visible: Sprite 0x" << std::hex
|
||||
<< static_cast<int>(sprite.id()) << " on map 0x"
|
||||
<< sprite.map_id() << " at (" << std::dec << sprite.x()
|
||||
<< ", " << sprite.y() << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "World " << current_world << ": " << visible_sprites << "/"
|
||||
|
||||
std::cout << "World " << current_world << ": " << visible_sprites << "/"
|
||||
<< total_sprites << " sprites visible" << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -109,45 +118,53 @@ TEST_F(SpritePositionTest, MapCoordinateCalculations) {
|
||||
// Test how map coordinates should be calculated
|
||||
for (int current_world = 0; current_world < 3; current_world++) {
|
||||
const auto& sprites = overworld_->sprites(current_world);
|
||||
|
||||
std::cout << "\n=== World " << current_world << " Coordinate Analysis ===" << std::endl;
|
||||
|
||||
|
||||
std::cout << "\n=== World " << current_world
|
||||
<< " Coordinate Analysis ===" << std::endl;
|
||||
|
||||
for (const auto& sprite : sprites) {
|
||||
if (!sprite.deleted() &&
|
||||
if (!sprite.deleted() &&
|
||||
sprite.map_id() < 0x40 + (current_world * 0x40) &&
|
||||
sprite.map_id() >= (current_world * 0x40)) {
|
||||
|
||||
// Calculate map position
|
||||
int sprite_map_id = sprite.map_id();
|
||||
int local_map_index = sprite_map_id - (current_world * 0x40);
|
||||
int map_col = local_map_index % 8;
|
||||
int map_row = local_map_index / 8;
|
||||
|
||||
int map_canvas_x = map_col * 512; // kOverworldMapSize
|
||||
|
||||
int map_canvas_x = map_col * 512; // kOverworldMapSize
|
||||
int map_canvas_y = map_row * 512;
|
||||
|
||||
std::cout << "Sprite 0x" << std::hex << static_cast<int>(sprite.id())
|
||||
|
||||
std::cout << "Sprite 0x" << std::hex << static_cast<int>(sprite.id())
|
||||
<< " on map 0x" << sprite_map_id << std::endl;
|
||||
std::cout << " Local map index: " << std::dec << local_map_index << std::endl;
|
||||
std::cout << " Map position: (" << map_col << ", " << map_row << ")" << std::endl;
|
||||
std::cout << " Map canvas pos: (" << map_canvas_x << ", " << map_canvas_y << ")" << std::endl;
|
||||
std::cout << " Sprite global pos: (" << sprite.x() << ", " << sprite.y() << ")" << std::endl;
|
||||
std::cout << " Sprite local pos: (" << sprite.map_x() << ", " << sprite.map_y() << ")" << std::endl;
|
||||
|
||||
std::cout << " Local map index: " << std::dec << local_map_index
|
||||
<< std::endl;
|
||||
std::cout << " Map position: (" << map_col << ", " << map_row << ")"
|
||||
<< std::endl;
|
||||
std::cout << " Map canvas pos: (" << map_canvas_x << ", "
|
||||
<< map_canvas_y << ")" << std::endl;
|
||||
std::cout << " Sprite global pos: (" << sprite.x() << ", "
|
||||
<< sprite.y() << ")" << std::endl;
|
||||
std::cout << " Sprite local pos: (" << sprite.map_x() << ", "
|
||||
<< sprite.map_y() << ")" << std::endl;
|
||||
|
||||
// Verify the calculation
|
||||
int expected_global_x = map_canvas_x + sprite.map_x();
|
||||
int expected_global_y = map_canvas_y + sprite.map_y();
|
||||
|
||||
std::cout << " Expected global: (" << expected_global_x << ", " << expected_global_y << ")" << std::endl;
|
||||
std::cout << " Actual global: (" << sprite.x() << ", " << sprite.y() << ")" << std::endl;
|
||||
|
||||
if (expected_global_x == sprite.x() && expected_global_y == sprite.y()) {
|
||||
|
||||
std::cout << " Expected global: (" << expected_global_x << ", "
|
||||
<< expected_global_y << ")" << std::endl;
|
||||
std::cout << " Actual global: (" << sprite.x() << ", " << sprite.y()
|
||||
<< ")" << std::endl;
|
||||
|
||||
if (expected_global_x == sprite.x() &&
|
||||
expected_global_y == sprite.y()) {
|
||||
std::cout << " ✓ Coordinates match!" << std::endl;
|
||||
} else {
|
||||
std::cout << " ✗ Coordinate mismatch!" << std::endl;
|
||||
}
|
||||
|
||||
break; // Only test first sprite for brevity
|
||||
|
||||
break; // Only test first sprite for brevity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "testing.h"
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "testing.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* @brief Enhanced ROM for testing that behaves like a real ROM but with test data
|
||||
*
|
||||
* @brief Enhanced ROM for testing that behaves like a real ROM but with test
|
||||
* data
|
||||
*
|
||||
* This class extends Rom to provide testing utilities while maintaining
|
||||
* all the real ROM functionality. Instead of mocking methods, it loads
|
||||
* real test data into the ROM.
|
||||
@@ -37,28 +37,28 @@ class MockRom : public Rom {
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Store object-specific test data for validation
|
||||
*/
|
||||
void SetObjectData(int object_id, const std::vector<uint8_t>& data) {
|
||||
object_data_[object_id] = data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Store room-specific test data for validation
|
||||
*/
|
||||
void SetRoomData(int room_id, const std::vector<uint8_t>& data) {
|
||||
room_data_[room_id] = data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check if object data has been set for testing
|
||||
*/
|
||||
bool HasObjectData(int object_id) const {
|
||||
return object_data_.find(object_id) != object_data_.end();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check if room data has been set for testing
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include <iostream>
|
||||
#include "cli/service/ai/service_factory.h"
|
||||
#include "cli/service/agent/conversational_agent_service.h"
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "cli/service/agent/conversational_agent_service.h"
|
||||
#include "cli/service/ai/service_factory.h"
|
||||
|
||||
using namespace yaze;
|
||||
using namespace yaze::cli;
|
||||
@@ -11,19 +12,19 @@ int main() {
|
||||
std::cout << "Test 1: Creating AI Service...\n";
|
||||
auto ai_service = CreateAIService();
|
||||
std::cout << "✅ AI Service created\n";
|
||||
|
||||
|
||||
std::cout << "Test 2: Creating Conversational Agent Service...\n";
|
||||
ConversationalAgentService service;
|
||||
std::cout << "✅ Conversational Agent Service created\n";
|
||||
|
||||
|
||||
std::cout << "Test 3: Creating ROM...\n";
|
||||
Rom rom;
|
||||
std::cout << "✅ ROM created\n";
|
||||
|
||||
|
||||
std::cout << "Test 4: Setting ROM context...\n";
|
||||
service.SetRomContext(&rom);
|
||||
std::cout << "✅ ROM context set\n";
|
||||
|
||||
|
||||
std::cout << "\n🎉 All tests passed!\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
#include <SDL.h>
|
||||
|
||||
#include "app/controller.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "app/gfx/backend/sdl2_renderer.h"
|
||||
#include "app/gui/core/style.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#ifdef IMGUI_ENABLE_TEST_ENGINE
|
||||
#include "imgui_test_engine/imgui_te_context.h"
|
||||
@@ -33,7 +33,7 @@ absl::Status TestEditor::Update() {
|
||||
ImGuiTestEngine_ShowTestEngineWindows(engine_, &show_demo_window);
|
||||
#else
|
||||
ImGui::Text("ImGui Test Engine not available in this build");
|
||||
(void)show_demo_window; // Suppress unused variable warning
|
||||
(void)show_demo_window; // Suppress unused variable warning
|
||||
#endif
|
||||
|
||||
ImGui::End();
|
||||
@@ -70,14 +70,15 @@ int RunIntegrationTest() {
|
||||
test_io.ConfigVerboseLevel = ImGuiTestVerboseLevel_Info;
|
||||
test_io.ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Debug;
|
||||
#else
|
||||
void* engine = nullptr; // Placeholder when test engine is disabled
|
||||
void* engine = nullptr; // Placeholder when test engine is disabled
|
||||
#endif
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
|
||||
// Initialize ImGui for SDL
|
||||
SDL_Renderer* sdl_renderer = static_cast<SDL_Renderer*>(test_renderer->GetBackendRenderer());
|
||||
SDL_Renderer* sdl_renderer =
|
||||
static_cast<SDL_Renderer*>(test_renderer->GetBackendRenderer());
|
||||
ImGui_ImplSDL2_InitForSDLRenderer(controller.window(), sdl_renderer);
|
||||
ImGui_ImplSDLRenderer2_Init(sdl_renderer);
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class TestEditor : public yaze::editor::Editor {
|
||||
#ifdef IMGUI_ENABLE_TEST_ENGINE
|
||||
ImGuiTestEngine* engine_;
|
||||
#else
|
||||
void* engine_; // Placeholder when test engine is disabled
|
||||
void* engine_; // Placeholder when test engine is disabled
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "test_utils.h"
|
||||
|
||||
#include "app/controller.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -6,14 +7,14 @@ namespace test {
|
||||
namespace gui {
|
||||
|
||||
void LoadRomInTest(ImGuiTestContext* ctx, const std::string& rom_path) {
|
||||
yaze::Controller* controller = (yaze::Controller*)ctx->Test->UserData;
|
||||
controller->OnEntry(rom_path);
|
||||
yaze::Controller* controller = (yaze::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());
|
||||
ctx->MenuClick(absl::StrFormat("Editors/%s", editor_name).c_str());
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
} // namespace gui
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "imgui_test_engine/imgui_te_context.h"
|
||||
#include "app/rom.h"
|
||||
#include "imgui_test_engine/imgui_te_context.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
@@ -76,7 +76,7 @@ class TestRomManager {
|
||||
// Read file
|
||||
std::vector<uint8_t> rom_data(file_size);
|
||||
file.read(reinterpret_cast<char*>(rom_data.data()), file_size);
|
||||
|
||||
|
||||
if (!file) {
|
||||
std::cerr << "Failed to read test ROM: " << rom_path << std::endl;
|
||||
return {};
|
||||
@@ -92,20 +92,20 @@ class TestRomManager {
|
||||
*/
|
||||
static std::vector<uint8_t> CreateMinimalTestRom(size_t size = 1024 * 1024) {
|
||||
std::vector<uint8_t> rom_data(size, 0);
|
||||
|
||||
|
||||
// Add minimal SNES header at 0x7FC0 (LoROM)
|
||||
const size_t header_offset = 0x7FC0;
|
||||
if (size > header_offset + 32) {
|
||||
// ROM title
|
||||
std::string title = "YAZE TEST ROM ";
|
||||
std::copy(title.begin(), title.end(), rom_data.begin() + header_offset);
|
||||
|
||||
|
||||
// Map mode (LoROM)
|
||||
rom_data[header_offset + 21] = 0x20;
|
||||
|
||||
|
||||
// ROM size (1MB)
|
||||
rom_data[header_offset + 23] = 0x0A;
|
||||
|
||||
|
||||
// Calculate and set checksum
|
||||
uint16_t checksum = 0;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
@@ -113,14 +113,14 @@ class TestRomManager {
|
||||
checksum += rom_data[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint16_t checksum_complement = checksum ^ 0xFFFF;
|
||||
rom_data[header_offset + 28] = checksum_complement & 0xFF;
|
||||
rom_data[header_offset + 29] = (checksum_complement >> 8) & 0xFF;
|
||||
rom_data[header_offset + 30] = checksum & 0xFF;
|
||||
rom_data[header_offset + 31] = (checksum >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
|
||||
return rom_data;
|
||||
}
|
||||
|
||||
@@ -131,26 +131,30 @@ class TestRomManager {
|
||||
static void SkipIfRomTestingDisabled(const std::string& test_name) {
|
||||
if (!IsRomTestingEnabled()) {
|
||||
GTEST_SKIP() << "ROM testing disabled or ROM file not found. "
|
||||
<< "Test: " << test_name << " requires: " << GetTestRomPath();
|
||||
<< "Test: " << test_name
|
||||
<< " requires: " << GetTestRomPath();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TestRomManager::BoundRomTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
rom_instance_ = std::make_unique<Rom>();
|
||||
}
|
||||
void SetUp() override { rom_instance_ = std::make_unique<Rom>(); }
|
||||
|
||||
void TearDown() override {
|
||||
rom_instance_.reset();
|
||||
rom_loaded_ = false;
|
||||
}
|
||||
|
||||
Rom* rom() { EnsureRomLoaded(); return rom_instance_.get(); }
|
||||
Rom* rom() {
|
||||
EnsureRomLoaded();
|
||||
return rom_instance_.get();
|
||||
}
|
||||
const Rom* rom() const { return rom_instance_.get(); }
|
||||
|
||||
std::string GetBoundRomPath() const { return TestRomManager::GetTestRomPath(); }
|
||||
std::string GetBoundRomPath() const {
|
||||
return TestRomManager::GetTestRomPath();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Rom> rom_instance_;
|
||||
@@ -170,11 +174,12 @@ class TestRomManager::BoundRomTest : public ::testing::Test {
|
||||
/**
|
||||
* @brief Test macro for ROM-dependent tests
|
||||
*/
|
||||
#define YAZE_ROM_TEST(test_case_name, test_name) \
|
||||
TEST(test_case_name, test_name) { \
|
||||
yaze::test::TestRomManager::SkipIfRomTestingDisabled(#test_case_name "." #test_name); \
|
||||
YAZE_ROM_TEST_BODY_##test_case_name##_##test_name(); \
|
||||
} \
|
||||
#define YAZE_ROM_TEST(test_case_name, test_name) \
|
||||
TEST(test_case_name, test_name) { \
|
||||
yaze::test::TestRomManager::SkipIfRomTestingDisabled(#test_case_name \
|
||||
"." #test_name); \
|
||||
YAZE_ROM_TEST_BODY_##test_case_name##_##test_name(); \
|
||||
} \
|
||||
void YAZE_ROM_TEST_BODY_##test_case_name##_##test_name()
|
||||
|
||||
/**
|
||||
@@ -196,7 +201,7 @@ namespace gui {
|
||||
void LoadRomInTest(ImGuiTestContext* ctx, const std::string& rom_path);
|
||||
void OpenEditorInTest(ImGuiTestContext* ctx, const std::string& editor_name);
|
||||
|
||||
} // namespace gui
|
||||
} // namespace gui
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
@@ -23,7 +23,9 @@ namespace test {
|
||||
|
||||
// StatusIs is a matcher that matches a status that has the same code and
|
||||
// message as the expected status.
|
||||
MATCHER_P(StatusIs, status, "") { return arg.code() == status; }
|
||||
MATCHER_P(StatusIs, status, "") {
|
||||
return arg.code() == status;
|
||||
}
|
||||
|
||||
// Support for testing absl::StatusOr.
|
||||
template <typename T>
|
||||
@@ -40,7 +42,9 @@ template <typename T>
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
MATCHER_P(IsOkAndHolds, value, "") { return IsOkAndHolds(arg, value); }
|
||||
MATCHER_P(IsOkAndHolds, value, "") {
|
||||
return IsOkAndHolds(arg, value);
|
||||
}
|
||||
|
||||
// Helper to test if a StatusOr contains an error with a specific message
|
||||
MATCHER_P(StatusIsWithMessage, message, "") {
|
||||
|
||||
@@ -53,14 +53,14 @@ TEST(ResourceCatalogTest, PatchSchemaIncludesAsarAndCreateActions) {
|
||||
EXPECT_EQ(actions[0].name, "apply");
|
||||
EXPECT_FALSE(actions[0].returns.empty());
|
||||
|
||||
auto has_asar = std::find_if(actions.begin(), actions.end(), [](const auto& action) {
|
||||
return action.name == "apply-asar";
|
||||
});
|
||||
auto has_asar = std::find_if(
|
||||
actions.begin(), actions.end(),
|
||||
[](const auto& action) { return action.name == "apply-asar"; });
|
||||
EXPECT_NE(has_asar, actions.end());
|
||||
|
||||
auto has_create = std::find_if(actions.begin(), actions.end(), [](const auto& action) {
|
||||
return action.name == "create";
|
||||
});
|
||||
auto has_create =
|
||||
std::find_if(actions.begin(), actions.end(),
|
||||
[](const auto& action) { return action.name == "create"; });
|
||||
EXPECT_NE(has_create, actions.end());
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@ TEST(ResourceCatalogTest, DungeonSchemaListsMetadataAndObjectsReturns) {
|
||||
|
||||
TEST(ResourceCatalogTest, YamlSerializationIncludesMetadataAndActions) {
|
||||
const auto& catalog = ResourceCatalog::Instance();
|
||||
std::string yaml = catalog.SerializeResourcesAsYaml(
|
||||
catalog.AllResources(), "0.1.0", "2025-10-01");
|
||||
std::string yaml = catalog.SerializeResourcesAsYaml(catalog.AllResources(),
|
||||
"0.1.0", "2025-10-01");
|
||||
|
||||
EXPECT_NE(yaml.find("version: \"0.1.0\""), std::string::npos);
|
||||
EXPECT_NE(yaml.find("name: \"patch\""), std::string::npos);
|
||||
|
||||
@@ -31,9 +31,9 @@ class Tile16ProposalGeneratorTest : public ::testing::Test {
|
||||
|
||||
TEST_F(Tile16ProposalGeneratorTest, ParseSetTileCommand_ValidCommand) {
|
||||
std::string command = "overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E";
|
||||
|
||||
|
||||
auto result = generator_->ParseSetTileCommand(command, nullptr);
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
EXPECT_EQ(result->map_id, 0);
|
||||
EXPECT_EQ(result->x, 10);
|
||||
@@ -43,19 +43,19 @@ TEST_F(Tile16ProposalGeneratorTest, ParseSetTileCommand_ValidCommand) {
|
||||
|
||||
TEST_F(Tile16ProposalGeneratorTest, ParseSetTileCommand_InvalidFormat) {
|
||||
std::string command = "overworld set-tile --map 0"; // Missing required args
|
||||
|
||||
|
||||
auto result = generator_->ParseSetTileCommand(command, nullptr);
|
||||
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_THAT(result.status().message(),
|
||||
EXPECT_THAT(result.status().message(),
|
||||
::testing::HasSubstr("Invalid command format"));
|
||||
}
|
||||
|
||||
TEST_F(Tile16ProposalGeneratorTest, ParseSetTileCommand_WrongCommandType) {
|
||||
std::string command = "overworld get-tile --map 0 --x 10 --y 20";
|
||||
|
||||
|
||||
auto result = generator_->ParseSetTileCommand(command, nullptr);
|
||||
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_THAT(result.status().message(),
|
||||
::testing::HasSubstr("Not a set-tile command"));
|
||||
@@ -66,65 +66,68 @@ TEST_F(Tile16ProposalGeneratorTest, ParseSetTileCommand_WrongCommandType) {
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(Tile16ProposalGeneratorTest, ParseSetAreaCommand_ValidCommand) {
|
||||
std::string command =
|
||||
"overworld set-area --map 0 --x 10 --y 20 --width 5 --height 3 --tile 0x02E";
|
||||
|
||||
std::string command =
|
||||
"overworld set-area --map 0 --x 10 --y 20 --width 5 --height 3 --tile "
|
||||
"0x02E";
|
||||
|
||||
auto result = generator_->ParseSetAreaCommand(command, nullptr);
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
EXPECT_EQ(result->size(), 15); // 5 width * 3 height = 15 tiles
|
||||
|
||||
|
||||
// Check first tile
|
||||
EXPECT_EQ((*result)[0].map_id, 0);
|
||||
EXPECT_EQ((*result)[0].x, 10);
|
||||
EXPECT_EQ((*result)[0].y, 20);
|
||||
EXPECT_EQ((*result)[0].new_tile, 0x02E);
|
||||
|
||||
|
||||
// Check last tile
|
||||
EXPECT_EQ((*result)[14].x, 14); // 10 + 4
|
||||
EXPECT_EQ((*result)[14].y, 22); // 20 + 2
|
||||
}
|
||||
|
||||
TEST_F(Tile16ProposalGeneratorTest, ParseSetAreaCommand_SingleTile) {
|
||||
std::string command =
|
||||
"overworld set-area --map 0 --x 10 --y 20 --width 1 --height 1 --tile 0x02E";
|
||||
|
||||
std::string command =
|
||||
"overworld set-area --map 0 --x 10 --y 20 --width 1 --height 1 --tile "
|
||||
"0x02E";
|
||||
|
||||
auto result = generator_->ParseSetAreaCommand(command, nullptr);
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_EQ(result->size(), 1);
|
||||
}
|
||||
|
||||
TEST_F(Tile16ProposalGeneratorTest, ParseSetAreaCommand_LargeArea) {
|
||||
std::string command =
|
||||
"overworld set-area --map 0 --x 0 --y 0 --width 32 --height 32 --tile 0x000";
|
||||
|
||||
std::string command =
|
||||
"overworld set-area --map 0 --x 0 --y 0 --width 32 --height 32 --tile "
|
||||
"0x000";
|
||||
|
||||
auto result = generator_->ParseSetAreaCommand(command, nullptr);
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_EQ(result->size(), 1024); // 32 * 32
|
||||
}
|
||||
|
||||
TEST_F(Tile16ProposalGeneratorTest, ParseSetAreaCommand_InvalidFormat) {
|
||||
std::string command = "overworld set-area --map 0 --x 10"; // Missing args
|
||||
|
||||
|
||||
auto result = generator_->ParseSetAreaCommand(command, nullptr);
|
||||
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_THAT(result.status().message(),
|
||||
::testing::HasSubstr("Invalid set-area command format"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ParseReplaceTileCommand Tests
|
||||
// ParseReplaceTileCommand Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(Tile16ProposalGeneratorTest, ParseReplaceTileCommand_NoROM) {
|
||||
std::string command =
|
||||
std::string command =
|
||||
"overworld replace-tile --map 0 --old-tile 0x02E --new-tile 0x030";
|
||||
|
||||
|
||||
auto result = generator_->ParseReplaceTileCommand(command, nullptr);
|
||||
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_THAT(result.status().message(),
|
||||
::testing::HasSubstr("ROM must be loaded"));
|
||||
@@ -132,9 +135,9 @@ TEST_F(Tile16ProposalGeneratorTest, ParseReplaceTileCommand_NoROM) {
|
||||
|
||||
TEST_F(Tile16ProposalGeneratorTest, ParseReplaceTileCommand_InvalidFormat) {
|
||||
std::string command = "overworld replace-tile --map 0"; // Missing tiles
|
||||
|
||||
|
||||
auto result = generator_->ParseReplaceTileCommand(command, nullptr);
|
||||
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_THAT(result.status().message(),
|
||||
::testing::HasSubstr("Invalid replace-tile command format"));
|
||||
@@ -147,12 +150,12 @@ TEST_F(Tile16ProposalGeneratorTest, ParseReplaceTileCommand_InvalidFormat) {
|
||||
TEST_F(Tile16ProposalGeneratorTest, GenerateFromCommands_MultipleCommands) {
|
||||
std::vector<std::string> commands = {
|
||||
"overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E",
|
||||
"overworld set-area --map 0 --x 5 --y 5 --width 2 --height 2 --tile 0x030"
|
||||
};
|
||||
|
||||
auto result = generator_->GenerateFromCommands(
|
||||
"Test prompt", commands, "test_ai", nullptr);
|
||||
|
||||
"overworld set-area --map 0 --x 5 --y 5 --width 2 --height 2 --tile "
|
||||
"0x030"};
|
||||
|
||||
auto result = generator_->GenerateFromCommands("Test prompt", commands,
|
||||
"test_ai", nullptr);
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
EXPECT_EQ(result->changes.size(), 5); // 1 from set-tile + 4 from set-area
|
||||
EXPECT_EQ(result->prompt, "Test prompt");
|
||||
@@ -162,10 +165,10 @@ TEST_F(Tile16ProposalGeneratorTest, GenerateFromCommands_MultipleCommands) {
|
||||
|
||||
TEST_F(Tile16ProposalGeneratorTest, GenerateFromCommands_EmptyCommands) {
|
||||
std::vector<std::string> commands = {};
|
||||
|
||||
auto result = generator_->GenerateFromCommands(
|
||||
"Test prompt", commands, "test_ai", nullptr);
|
||||
|
||||
|
||||
auto result = generator_->GenerateFromCommands("Test prompt", commands,
|
||||
"test_ai", nullptr);
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_THAT(result.status().message(),
|
||||
::testing::HasSubstr("No valid tile16 changes found"));
|
||||
@@ -178,10 +181,10 @@ TEST_F(Tile16ProposalGeneratorTest, GenerateFromCommands_IgnoresComments) {
|
||||
"# Another comment",
|
||||
"" // Empty line
|
||||
};
|
||||
|
||||
auto result = generator_->GenerateFromCommands(
|
||||
"Test prompt", commands, "test_ai", nullptr);
|
||||
|
||||
|
||||
auto result = generator_->GenerateFromCommands("Test prompt", commands,
|
||||
"test_ai", nullptr);
|
||||
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_EQ(result->changes.size(), 1); // Only the valid command
|
||||
}
|
||||
@@ -197,9 +200,9 @@ TEST_F(Tile16ProposalGeneratorTest, Tile16Change_ToString) {
|
||||
change.y = 20;
|
||||
change.old_tile = 0x02E;
|
||||
change.new_tile = 0x030;
|
||||
|
||||
|
||||
std::string result = change.ToString();
|
||||
|
||||
|
||||
EXPECT_THAT(result, ::testing::HasSubstr("Map 5"));
|
||||
EXPECT_THAT(result, ::testing::HasSubstr("(10,20)"));
|
||||
EXPECT_THAT(result, ::testing::HasSubstr("0x2e"));
|
||||
@@ -217,7 +220,7 @@ TEST_F(Tile16ProposalGeneratorTest, Proposal_ToJsonAndFromJson) {
|
||||
original.ai_service = "gemini";
|
||||
original.reasoning = "Test reasoning";
|
||||
original.status = Tile16Proposal::Status::PENDING;
|
||||
|
||||
|
||||
Tile16Change change;
|
||||
change.map_id = 5;
|
||||
change.x = 10;
|
||||
@@ -225,10 +228,10 @@ TEST_F(Tile16ProposalGeneratorTest, Proposal_ToJsonAndFromJson) {
|
||||
change.old_tile = 0x02E;
|
||||
change.new_tile = 0x030;
|
||||
original.changes.push_back(change);
|
||||
|
||||
|
||||
std::string json = original.ToJson();
|
||||
auto result = Tile16Proposal::FromJson(json);
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
EXPECT_EQ(result->id, original.id);
|
||||
EXPECT_EQ(result->prompt, original.prompt);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "core/asar_wrapper.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <fstream>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "test_utils.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace core {
|
||||
@@ -17,9 +19,7 @@ class AsarWrapperTest : public ::testing::Test {
|
||||
CreateTestFiles();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
CleanupTestFiles();
|
||||
}
|
||||
void TearDown() override { CleanupTestFiles(); }
|
||||
|
||||
void CreateTestFiles() {
|
||||
// Create test directory
|
||||
@@ -79,7 +79,7 @@ LDA unknown_operand
|
||||
TEST_F(AsarWrapperTest, InitializationAndShutdown) {
|
||||
// Test initialization
|
||||
ASSERT_FALSE(wrapper_->IsInitialized());
|
||||
|
||||
|
||||
auto status = wrapper_->Initialize();
|
||||
EXPECT_TRUE(status.ok()) << status.message();
|
||||
EXPECT_TRUE(wrapper_->IsInitialized());
|
||||
@@ -113,7 +113,7 @@ TEST_F(AsarWrapperTest, OperationsWithoutInitialization) {
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
auto patch_result = wrapper_->ApplyPatch(test_asm_path_.string(), rom_copy);
|
||||
EXPECT_FALSE(patch_result.ok());
|
||||
EXPECT_THAT(patch_result.status().message(),
|
||||
EXPECT_THAT(patch_result.status().message(),
|
||||
testing::HasSubstr("not initialized"));
|
||||
|
||||
auto symbols_result = wrapper_->ExtractSymbols(test_asm_path_.string());
|
||||
@@ -132,8 +132,8 @@ TEST_F(AsarWrapperTest, ValidPatchApplication) {
|
||||
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
|
||||
|
||||
const auto& result = patch_result.value();
|
||||
EXPECT_TRUE(result.success) << "Patch failed: "
|
||||
<< testing::PrintToString(result.errors);
|
||||
EXPECT_TRUE(result.success)
|
||||
<< "Patch failed: " << testing::PrintToString(result.errors);
|
||||
EXPECT_GT(result.rom_size, 0);
|
||||
EXPECT_EQ(rom_copy.size(), result.rom_size);
|
||||
|
||||
@@ -146,9 +146,10 @@ TEST_F(AsarWrapperTest, InvalidPatchHandling) {
|
||||
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
|
||||
auto patch_result = wrapper_->ApplyPatch(invalid_asm_path_.string(), rom_copy);
|
||||
auto patch_result =
|
||||
wrapper_->ApplyPatch(invalid_asm_path_.string(), rom_copy);
|
||||
EXPECT_FALSE(patch_result.ok());
|
||||
EXPECT_THAT(patch_result.status().message(),
|
||||
EXPECT_THAT(patch_result.status().message(),
|
||||
testing::HasSubstr("Patch failed"));
|
||||
}
|
||||
|
||||
@@ -181,7 +182,8 @@ TEST_F(AsarWrapperTest, SymbolExtraction) {
|
||||
|
||||
if (symbol.name == "testlabel") {
|
||||
found_testlabel = true;
|
||||
EXPECT_EQ(symbol.address, 0x008000); // Expected address from org directive
|
||||
EXPECT_EQ(symbol.address,
|
||||
0x008000); // Expected address from org directive
|
||||
} else if (symbol.name == "anotherlabel") {
|
||||
found_anotherlabel = true;
|
||||
}
|
||||
@@ -216,9 +218,10 @@ TEST_F(AsarWrapperTest, SymbolTableOperations) {
|
||||
|
||||
// Test symbols at address lookup
|
||||
if (testlabel_symbol) {
|
||||
auto symbols_at_addr = wrapper_->GetSymbolsAtAddress(testlabel_symbol->address);
|
||||
auto symbols_at_addr =
|
||||
wrapper_->GetSymbolsAtAddress(testlabel_symbol->address);
|
||||
EXPECT_GT(symbols_at_addr.size(), 0);
|
||||
|
||||
|
||||
bool found = false;
|
||||
for (const auto& symbol : symbols_at_addr) {
|
||||
if (symbol.name == "testlabel") {
|
||||
@@ -242,9 +245,9 @@ stringpatchlabel:
|
||||
)";
|
||||
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
auto patch_result = wrapper_->ApplyPatchFromString(
|
||||
patch_content, rom_copy, test_dir_.string());
|
||||
|
||||
auto patch_result = wrapper_->ApplyPatchFromString(patch_content, rom_copy,
|
||||
test_dir_.string());
|
||||
|
||||
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
|
||||
|
||||
const auto& result = patch_result.value();
|
||||
@@ -273,11 +276,11 @@ TEST_F(AsarWrapperTest, AssemblyValidation) {
|
||||
// Test invalid assembly
|
||||
auto invalid_status = wrapper_->ValidateAssembly(invalid_asm_path_.string());
|
||||
EXPECT_FALSE(invalid_status.ok());
|
||||
EXPECT_THAT(invalid_status.message(),
|
||||
EXPECT_THAT(invalid_status.message(),
|
||||
testing::AnyOf(testing::HasSubstr("validation failed"),
|
||||
testing::HasSubstr("Patch failed"),
|
||||
testing::HasSubstr("Unknown command"),
|
||||
testing::HasSubstr("Label")));
|
||||
testing::HasSubstr("Patch failed"),
|
||||
testing::HasSubstr("Unknown command"),
|
||||
testing::HasSubstr("Label")));
|
||||
}
|
||||
|
||||
TEST_F(AsarWrapperTest, ResetFunctionality) {
|
||||
@@ -294,10 +297,10 @@ TEST_F(AsarWrapperTest, ResetFunctionality) {
|
||||
|
||||
// Reset and verify state is cleared
|
||||
wrapper_->Reset();
|
||||
|
||||
|
||||
auto symbol_table_after = wrapper_->GetSymbolTable();
|
||||
EXPECT_EQ(symbol_table_after.size(), 0);
|
||||
|
||||
|
||||
auto errors = wrapper_->GetLastErrors();
|
||||
auto warnings = wrapper_->GetLastWarnings();
|
||||
EXPECT_EQ(errors.size(), 0);
|
||||
@@ -313,7 +316,7 @@ TEST_F(AsarWrapperTest, CreatePatchNotImplemented) {
|
||||
|
||||
std::string patch_path = test_dir_.string() + "/generated.asm";
|
||||
auto status = wrapper_->CreatePatch(original_rom, modified_rom, patch_path);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.message(), testing::HasSubstr("not yet implemented"));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "testing.h"
|
||||
|
||||
#include "util/hex.h"
|
||||
|
||||
#include "testing.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
@@ -100,4 +100,4 @@ TEST(HexTest, HexLongLong) {
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
} // namespace yaze
|
||||
@@ -71,4 +71,3 @@ TEST_F(ApuDspTest, GetSamplesReturnsSilenceAfterReset) {
|
||||
|
||||
} // namespace emu
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/emu/audio/apu.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
#include "app/emu/audio/spc700.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace emu {
|
||||
|
||||
class ApuIplHandshakeTest : public ::testing::Test {
|
||||
protected:
|
||||
protected:
|
||||
MemoryImpl mem;
|
||||
Apu* apu;
|
||||
|
||||
@@ -25,9 +25,9 @@ protected:
|
||||
|
||||
TEST_F(ApuIplHandshakeTest, SPC700StartsAtIplRomEntry) {
|
||||
// After reset, PC should be at IPL ROM reset vector
|
||||
uint16_t reset_vector = apu->spc700().read(0xFFFE) |
|
||||
(apu->spc700().read(0xFFFF) << 8);
|
||||
|
||||
uint16_t reset_vector =
|
||||
apu->spc700().read(0xFFFE) | (apu->spc700().read(0xFFFF) << 8);
|
||||
|
||||
// The IPL ROM reset vector should point to 0xFFC0 (start of IPL ROM)
|
||||
EXPECT_EQ(reset_vector, 0xFFC0);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ TEST_F(ApuIplHandshakeTest, SPC700StartsAtIplRomEntry) {
|
||||
TEST_F(ApuIplHandshakeTest, IplRomReadable) {
|
||||
// IPL ROM should be readable at 0xFFC0-0xFFFF after reset
|
||||
uint8_t first_byte = apu->Read(0xFFC0);
|
||||
|
||||
|
||||
// First byte of IPL ROM should be 0xCD (CMP Y, #$EF)
|
||||
EXPECT_EQ(first_byte, 0xCD);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ TEST_F(ApuIplHandshakeTest, IplRomReadable) {
|
||||
TEST_F(ApuIplHandshakeTest, CycleTrackingWorks) {
|
||||
// Execute one SPC700 opcode
|
||||
apu->spc700().RunOpcode();
|
||||
|
||||
|
||||
// GetLastOpcodeCycles should return a valid cycle count (2-12 typically)
|
||||
int cycles = apu->spc700().GetLastOpcodeCycles();
|
||||
EXPECT_GT(cycles, 0);
|
||||
@@ -54,15 +54,15 @@ TEST_F(ApuIplHandshakeTest, PortReadWrite) {
|
||||
// Write to input port from CPU side (simulating CPU writes to $2140-$2143)
|
||||
apu->in_ports_[0] = 0xAA;
|
||||
apu->in_ports_[1] = 0xBB;
|
||||
|
||||
|
||||
// SPC should be able to read these ports at $F4-$F7
|
||||
EXPECT_EQ(apu->Read(0xF4), 0xAA);
|
||||
EXPECT_EQ(apu->Read(0xF5), 0xBB);
|
||||
|
||||
|
||||
// Write to output ports from SPC side
|
||||
apu->Write(0xF4, 0xCC);
|
||||
apu->Write(0xF5, 0xDD);
|
||||
|
||||
|
||||
// CPU should be able to read these (simulating reads from $2140-$2143)
|
||||
EXPECT_EQ(apu->out_ports_[0], 0xCC);
|
||||
EXPECT_EQ(apu->out_ports_[1], 0xDD);
|
||||
@@ -71,21 +71,21 @@ TEST_F(ApuIplHandshakeTest, PortReadWrite) {
|
||||
TEST_F(ApuIplHandshakeTest, IplRomDisableViaControlRegister) {
|
||||
// IPL ROM is readable by default
|
||||
EXPECT_EQ(apu->Read(0xFFC0), 0xCD);
|
||||
|
||||
|
||||
// Write to control register ($F1) to disable IPL ROM (bit 7 = 1)
|
||||
apu->Write(0xF1, 0x80);
|
||||
|
||||
|
||||
// Now $FFC0-$FFFF should read from RAM instead of IPL ROM
|
||||
// RAM is initialized to 0, so we should read 0
|
||||
EXPECT_EQ(apu->Read(0xFFC0), 0x00);
|
||||
|
||||
|
||||
// Write something to RAM
|
||||
apu->ram[0xFFC0] = 0x42;
|
||||
EXPECT_EQ(apu->Read(0xFFC0), 0x42);
|
||||
|
||||
|
||||
// Re-enable IPL ROM (bit 7 = 0)
|
||||
apu->Write(0xF1, 0x00);
|
||||
|
||||
|
||||
// Should read IPL ROM again
|
||||
EXPECT_EQ(apu->Read(0xFFC0), 0xCD);
|
||||
}
|
||||
@@ -93,18 +93,18 @@ TEST_F(ApuIplHandshakeTest, IplRomDisableViaControlRegister) {
|
||||
TEST_F(ApuIplHandshakeTest, TimersEnableAndCount) {
|
||||
// Enable timer 0 via control register
|
||||
apu->Write(0xF1, 0x01);
|
||||
|
||||
|
||||
// Set timer 0 target to 4
|
||||
apu->Write(0xFA, 0x04);
|
||||
|
||||
|
||||
// Run enough cycles to trigger timer
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
apu->Cycle();
|
||||
}
|
||||
|
||||
|
||||
// Read timer 0 counter (auto-clears on read)
|
||||
uint8_t counter = apu->Read(0xFD);
|
||||
|
||||
|
||||
// Counter should be non-zero if timer is working
|
||||
EXPECT_GT(counter, 0);
|
||||
EXPECT_LE(counter, 0x0F);
|
||||
@@ -113,17 +113,17 @@ TEST_F(ApuIplHandshakeTest, TimersEnableAndCount) {
|
||||
TEST_F(ApuIplHandshakeTest, IplBootSequenceProgresses) {
|
||||
// This test verifies that the IPL ROM boot sequence can actually progress
|
||||
// without getting stuck in an infinite loop
|
||||
|
||||
|
||||
uint16_t initial_pc = apu->spc700().PC;
|
||||
|
||||
|
||||
// Run multiple opcodes to let the IPL boot sequence progress
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
apu->spc700().RunOpcode();
|
||||
apu->Cycle();
|
||||
}
|
||||
|
||||
|
||||
uint16_t final_pc = apu->spc700().PC;
|
||||
|
||||
|
||||
// PC should have advanced (boot sequence is progressing)
|
||||
// If it's stuck in a tight loop, PC won't change much
|
||||
EXPECT_NE(initial_pc, final_pc);
|
||||
@@ -131,15 +131,15 @@ TEST_F(ApuIplHandshakeTest, IplBootSequenceProgresses) {
|
||||
|
||||
TEST_F(ApuIplHandshakeTest, AccurateCycleCountsForCommonOpcodes) {
|
||||
// Test that specific opcodes return correct cycle counts
|
||||
|
||||
|
||||
// NOP (0x00) should take 2 cycles
|
||||
apu->spc700().PC = 0x0000;
|
||||
apu->ram[0x0000] = 0x00; // NOP
|
||||
apu->spc700().RunOpcode();
|
||||
apu->spc700().RunOpcode(); // Execute
|
||||
EXPECT_EQ(apu->spc700().GetLastOpcodeCycles(), 2);
|
||||
|
||||
// MOV A, #imm (0xE8) should take 2 cycles
|
||||
|
||||
// MOV A, #imm (0xE8) should take 2 cycles
|
||||
apu->spc700().PC = 0x0002;
|
||||
apu->ram[0x0002] = 0xE8; // MOV A, #imm
|
||||
apu->ram[0x0003] = 0x42; // immediate value
|
||||
@@ -150,4 +150,3 @@ TEST_F(ApuIplHandshakeTest, AccurateCycleCountsForCommonOpcodes) {
|
||||
|
||||
} // namespace emu
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -27,4 +27,3 @@ TEST(Spc700ResetTest, ResetVectorExecutesIplSequence) {
|
||||
|
||||
} // namespace emu
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/rom.h"
|
||||
@@ -172,7 +172,8 @@ TEST(LC_LZ2_CompressionTest, CompressionSingleSet) {
|
||||
TEST(LC_LZ2_CompressionTest, CompressionSingleWord) {
|
||||
Rom rom;
|
||||
uint8_t single_word[6] = {0x2A, 0x01, 0x2A, 0x01, 0x2A, 0x01};
|
||||
uint8_t single_word_expected[4] = {BUILD_HEADER(0x02, 0x06), 0x2A, 0x01, 0xFF};
|
||||
uint8_t single_word_expected[4] = {BUILD_HEADER(0x02, 0x06), 0x2A, 0x01,
|
||||
0xFF};
|
||||
|
||||
auto comp_result = ExpectCompressOk(rom, single_word, 6);
|
||||
EXPECT_THAT(single_word_expected, ElementsAreArray(comp_result.data(), 4));
|
||||
@@ -412,16 +413,16 @@ TEST(LC_LZ2_CompressionTest, DecompressionValidCommand) {
|
||||
TEST(LC_LZ2_CompressionTest, DecompressionMixingCommand) {
|
||||
Rom rom;
|
||||
uint8_t random1_i[11] = {BUILD_HEADER(0x01, 0x03),
|
||||
0x2A,
|
||||
BUILD_HEADER(0x00, 0x04),
|
||||
0x01,
|
||||
0x02,
|
||||
0x03,
|
||||
0x04,
|
||||
BUILD_HEADER(0x02, 0x02),
|
||||
0x0B,
|
||||
0x16,
|
||||
0xFF};
|
||||
0x2A,
|
||||
BUILD_HEADER(0x00, 0x04),
|
||||
0x01,
|
||||
0x02,
|
||||
0x03,
|
||||
0x04,
|
||||
BUILD_HEADER(0x02, 0x02),
|
||||
0x0B,
|
||||
0x16,
|
||||
0xFF};
|
||||
uint8_t random1_o[9] = {42, 42, 42, 1, 2, 3, 4, 11, 22};
|
||||
auto decomp_result = ExpectDecompressOk(rom, random1_i, 11);
|
||||
EXPECT_THAT(random1_o, ElementsAreArray(decomp_result.data(), 9));
|
||||
|
||||
@@ -27,7 +27,7 @@ TEST(SnesTileTest, UnpackBppTile) {
|
||||
// First pixel should be 3 (both bits set): plane0 bit7=1, plane1 bit7=1
|
||||
// Last pixel of first row should be 1: plane0 bit0=1, plane1 bit0=0
|
||||
std::vector<uint8_t> data2bpp = {
|
||||
0x81, 0x80, // Row 0: plane0=10000001, plane1=10000000
|
||||
0x81, 0x80, // Row 0: plane0=10000001, plane1=10000000
|
||||
0x00, 0x00, // Row 1: plane0=00000000, plane1=00000000
|
||||
0x00, 0x00, // Row 2: plane0=00000000, plane1=00000000
|
||||
0x00, 0x00, // Row 3: plane0=00000000, plane1=00000000
|
||||
@@ -37,25 +37,26 @@ TEST(SnesTileTest, UnpackBppTile) {
|
||||
0x01, 0x81 // Row 7: plane0=00000001, plane1=10000001
|
||||
};
|
||||
auto tile2bpp = gfx::UnpackBppTile(data2bpp, 0, 2);
|
||||
EXPECT_EQ(tile2bpp.data[0], 3); // First pixel: 1|1<<1 = 3
|
||||
EXPECT_EQ(tile2bpp.data[7], 1); // Last pixel of first row: 1|0<<1 = 1
|
||||
EXPECT_EQ(tile2bpp.data[56], 2); // First pixel of last row: 0|1<<1 = 2
|
||||
EXPECT_EQ(tile2bpp.data[63], 3); // Last pixel: 1|1<<1 = 3
|
||||
EXPECT_EQ(tile2bpp.data[0], 3); // First pixel: 1|1<<1 = 3
|
||||
EXPECT_EQ(tile2bpp.data[7], 1); // Last pixel of first row: 1|0<<1 = 1
|
||||
EXPECT_EQ(tile2bpp.data[56], 2); // First pixel of last row: 0|1<<1 = 2
|
||||
EXPECT_EQ(tile2bpp.data[63], 3); // Last pixel: 1|1<<1 = 3
|
||||
|
||||
// Test 4bpp tile unpacking
|
||||
// According to SnesLab: First planes 1&2 intertwined, then planes 3&4 intertwined
|
||||
// 32 bytes total: 16 bytes for planes 1&2, then 16 bytes for planes 3&4
|
||||
// Test 4bpp tile unpacking
|
||||
// According to SnesLab: First planes 1&2 intertwined, then planes 3&4
|
||||
// intertwined 32 bytes total: 16 bytes for planes 1&2, then 16 bytes for
|
||||
// planes 3&4
|
||||
std::vector<uint8_t> data4bpp = {
|
||||
// Planes 1&2 intertwined (rows 0-7)
|
||||
0x81, 0x80, // Row 0: bp1=10000001, bp2=10000000
|
||||
0x00, 0x00, // Row 1: bp1=00000000, bp2=00000000
|
||||
0x00, 0x00, // Row 1: bp1=00000000, bp2=00000000
|
||||
0x00, 0x00, // Row 2: bp1=00000000, bp2=00000000
|
||||
0x00, 0x00, // Row 3: bp1=00000000, bp2=00000000
|
||||
0x00, 0x00, // Row 4: bp1=00000000, bp2=00000000
|
||||
0x00, 0x00, // Row 5: bp1=00000000, bp2=00000000
|
||||
0x00, 0x00, // Row 6: bp1=00000000, bp2=00000000
|
||||
0x01, 0x81, // Row 7: bp1=00000001, bp2=10000001
|
||||
// Planes 3&4 intertwined (rows 0-7)
|
||||
// Planes 3&4 intertwined (rows 0-7)
|
||||
0x81, 0x80, // Row 0: bp3=10000001, bp4=10000000
|
||||
0x00, 0x00, // Row 1: bp3=00000000, bp4=00000000
|
||||
0x00, 0x00, // Row 2: bp3=00000000, bp4=00000000
|
||||
@@ -67,9 +68,11 @@ TEST(SnesTileTest, UnpackBppTile) {
|
||||
};
|
||||
auto tile4bpp = gfx::UnpackBppTile(data4bpp, 0, 4);
|
||||
EXPECT_EQ(tile4bpp.data[0], 0xF); // First pixel: 1|1<<1|1<<2|1<<3 = 15
|
||||
EXPECT_EQ(tile4bpp.data[7], 0x5); // Last pixel of first row: 1|0<<1|1<<2|0<<3 = 5
|
||||
EXPECT_EQ(tile4bpp.data[56], 0xA); // First pixel of last row: 0|1<<1|0<<2|1<<3 = 10
|
||||
EXPECT_EQ(tile4bpp.data[63], 0xF); // Last pixel: 1|1<<1|1<<2|1<<3 = 15
|
||||
EXPECT_EQ(tile4bpp.data[7],
|
||||
0x5); // Last pixel of first row: 1|0<<1|1<<2|0<<3 = 5
|
||||
EXPECT_EQ(tile4bpp.data[56],
|
||||
0xA); // First pixel of last row: 0|1<<1|0<<2|1<<3 = 10
|
||||
EXPECT_EQ(tile4bpp.data[63], 0xF); // Last pixel: 1|1<<1|1<<2|1<<3 = 15
|
||||
}
|
||||
|
||||
TEST(SnesTileTest, PackBppTile) {
|
||||
@@ -90,10 +93,14 @@ TEST(SnesTileTest, PackBppTile) {
|
||||
tile2bpp.data[56] = 2;
|
||||
tile2bpp.data[63] = 3;
|
||||
auto packed2bpp = gfx::PackBppTile(tile2bpp, 2);
|
||||
EXPECT_EQ(packed2bpp[0], 0x81); // First byte of first plane: pixel0=3→0x80, pixel7=1→0x01
|
||||
EXPECT_EQ(packed2bpp[1], 0x80); // First byte of second plane: pixel0=3→0x80, pixel7=1→0x00
|
||||
EXPECT_EQ(packed2bpp[14], 0x01); // Last byte of first plane: pixel56=2→0x00, pixel63=3→0x01
|
||||
EXPECT_EQ(packed2bpp[15], 0x81); // Last byte of second plane: pixel56=2→0x80, pixel63=3→0x01
|
||||
EXPECT_EQ(packed2bpp[0],
|
||||
0x81); // First byte of first plane: pixel0=3→0x80, pixel7=1→0x01
|
||||
EXPECT_EQ(packed2bpp[1],
|
||||
0x80); // First byte of second plane: pixel0=3→0x80, pixel7=1→0x00
|
||||
EXPECT_EQ(packed2bpp[14],
|
||||
0x01); // Last byte of first plane: pixel56=2→0x00, pixel63=3→0x01
|
||||
EXPECT_EQ(packed2bpp[15],
|
||||
0x81); // Last byte of second plane: pixel56=2→0x80, pixel63=3→0x01
|
||||
}
|
||||
|
||||
TEST(SnesTileTest, ConvertBpp) {
|
||||
@@ -107,11 +114,11 @@ TEST(SnesTileTest, ConvertBpp) {
|
||||
// Test 4bpp to 2bpp conversion (using only colors 0-3 for valid 2bpp)
|
||||
std::vector<uint8_t> data4bpp = {
|
||||
// Planes 1&2 (rows 0-7) - create colors 0-3 only
|
||||
0x80, 0x80, 0x40, 0x00, 0x20, 0x40, 0x10, 0x80, // rows 0-3
|
||||
0x08, 0x00, 0x04, 0x40, 0x02, 0x80, 0x01, 0x00, // rows 4-7
|
||||
0x80, 0x80, 0x40, 0x00, 0x20, 0x40, 0x10, 0x80, // rows 0-3
|
||||
0x08, 0x00, 0x04, 0x40, 0x02, 0x80, 0x01, 0x00, // rows 4-7
|
||||
// Planes 3&4 (rows 0-7) - all zeros to ensure colors stay ≤ 3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rows 0-3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // rows 4-7
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rows 0-3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // rows 4-7
|
||||
};
|
||||
auto converted2bpp = gfx::ConvertBpp(data4bpp, 4, 2);
|
||||
EXPECT_EQ(converted2bpp.size(), 16); // 2bpp tile is 16 bytes
|
||||
@@ -126,7 +133,7 @@ TEST(SnesTileTest, TileInfo) {
|
||||
EXPECT_TRUE(info.horizontal_mirror_);
|
||||
EXPECT_TRUE(info.over_);
|
||||
|
||||
// Test TileInfo from bytes
|
||||
// Test TileInfo from bytes
|
||||
gfx::TileInfo infoFromBytes(0x23, 0xED); // v=1, h=1, o=1, p=3, id=0x123
|
||||
EXPECT_EQ(infoFromBytes.id_, 0x123);
|
||||
EXPECT_EQ(infoFromBytes.palette_, 3);
|
||||
|
||||
@@ -18,7 +18,7 @@ class CanvasAutomationAPITest : public ::testing::Test {
|
||||
void SetUp() override {
|
||||
// Create a test canvas with known dimensions
|
||||
canvas_ = std::make_unique<gui::Canvas>("TestCanvas", ImVec2(512, 512),
|
||||
gui::CanvasGridSize::k16x16);
|
||||
gui::CanvasGridSize::k16x16);
|
||||
api_ = canvas_->GetAutomationAPI();
|
||||
ASSERT_NE(api_, nullptr);
|
||||
}
|
||||
@@ -34,21 +34,21 @@ class CanvasAutomationAPITest : public ::testing::Test {
|
||||
TEST_F(CanvasAutomationAPITest, TileToCanvas_BasicConversion) {
|
||||
// At 1.0x zoom, tile (0,0) should be at canvas (0,0)
|
||||
canvas_->set_global_scale(1.0f);
|
||||
|
||||
|
||||
ImVec2 pos = api_->TileToCanvas(0, 0);
|
||||
EXPECT_FLOAT_EQ(pos.x, 0.0f);
|
||||
EXPECT_FLOAT_EQ(pos.y, 0.0f);
|
||||
|
||||
|
||||
// Tile (1,0) should be at (16,0) for 16x16 grid
|
||||
pos = api_->TileToCanvas(1, 0);
|
||||
EXPECT_FLOAT_EQ(pos.x, 16.0f);
|
||||
EXPECT_FLOAT_EQ(pos.y, 0.0f);
|
||||
|
||||
|
||||
// Tile (0,1) should be at (0,16)
|
||||
pos = api_->TileToCanvas(0, 1);
|
||||
EXPECT_FLOAT_EQ(pos.x, 0.0f);
|
||||
EXPECT_FLOAT_EQ(pos.y, 16.0f);
|
||||
|
||||
|
||||
// Tile (10,10) should be at (160,160)
|
||||
pos = api_->TileToCanvas(10, 10);
|
||||
EXPECT_FLOAT_EQ(pos.x, 160.0f);
|
||||
@@ -58,11 +58,11 @@ TEST_F(CanvasAutomationAPITest, TileToCanvas_BasicConversion) {
|
||||
TEST_F(CanvasAutomationAPITest, TileToCanvas_WithZoom) {
|
||||
// At 2.0x zoom, tile coordinates should scale
|
||||
canvas_->set_global_scale(2.0f);
|
||||
|
||||
|
||||
ImVec2 pos = api_->TileToCanvas(1, 1);
|
||||
EXPECT_FLOAT_EQ(pos.x, 32.0f); // 1 * 16 * 2.0
|
||||
EXPECT_FLOAT_EQ(pos.y, 32.0f);
|
||||
|
||||
|
||||
// At 0.5x zoom, tile coordinates should scale down
|
||||
canvas_->set_global_scale(0.5f);
|
||||
pos = api_->TileToCanvas(10, 10);
|
||||
@@ -72,17 +72,17 @@ TEST_F(CanvasAutomationAPITest, TileToCanvas_WithZoom) {
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, CanvasToTile_BasicConversion) {
|
||||
canvas_->set_global_scale(1.0f);
|
||||
|
||||
|
||||
// Canvas (0,0) should be tile (0,0)
|
||||
ImVec2 tile = api_->CanvasToTile(ImVec2(0, 0));
|
||||
EXPECT_FLOAT_EQ(tile.x, 0.0f);
|
||||
EXPECT_FLOAT_EQ(tile.y, 0.0f);
|
||||
|
||||
|
||||
// Canvas (16,16) should be tile (1,1)
|
||||
tile = api_->CanvasToTile(ImVec2(16, 16));
|
||||
EXPECT_FLOAT_EQ(tile.x, 1.0f);
|
||||
EXPECT_FLOAT_EQ(tile.y, 1.0f);
|
||||
|
||||
|
||||
// Canvas (160,160) should be tile (10,10)
|
||||
tile = api_->CanvasToTile(ImVec2(160, 160));
|
||||
EXPECT_FLOAT_EQ(tile.x, 10.0f);
|
||||
@@ -92,11 +92,11 @@ TEST_F(CanvasAutomationAPITest, CanvasToTile_BasicConversion) {
|
||||
TEST_F(CanvasAutomationAPITest, CanvasToTile_WithZoom) {
|
||||
// At 2.0x zoom
|
||||
canvas_->set_global_scale(2.0f);
|
||||
|
||||
|
||||
ImVec2 tile = api_->CanvasToTile(ImVec2(32, 32));
|
||||
EXPECT_FLOAT_EQ(tile.x, 1.0f); // 32 / (16 * 2.0)
|
||||
EXPECT_FLOAT_EQ(tile.y, 1.0f);
|
||||
|
||||
|
||||
// At 0.5x zoom
|
||||
canvas_->set_global_scale(0.5f);
|
||||
tile = api_->CanvasToTile(ImVec2(8, 8));
|
||||
@@ -106,12 +106,12 @@ TEST_F(CanvasAutomationAPITest, CanvasToTile_WithZoom) {
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, CoordinateRoundTrip) {
|
||||
canvas_->set_global_scale(1.0f);
|
||||
|
||||
|
||||
// Test round-trip conversion
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
ImVec2 canvas_pos = api_->TileToCanvas(i, i);
|
||||
ImVec2 tile_pos = api_->CanvasToTile(canvas_pos);
|
||||
|
||||
|
||||
EXPECT_FLOAT_EQ(tile_pos.x, static_cast<float>(i));
|
||||
EXPECT_FLOAT_EQ(tile_pos.y, static_cast<float>(i));
|
||||
}
|
||||
@@ -131,7 +131,7 @@ TEST_F(CanvasAutomationAPITest, IsInBounds_InvalidCoordinates) {
|
||||
EXPECT_FALSE(api_->IsInBounds(-1, 0));
|
||||
EXPECT_FALSE(api_->IsInBounds(0, -1));
|
||||
EXPECT_FALSE(api_->IsInBounds(-1, -1));
|
||||
EXPECT_FALSE(api_->IsInBounds(32, 0)); // Out of bounds
|
||||
EXPECT_FALSE(api_->IsInBounds(32, 0)); // Out of bounds
|
||||
EXPECT_FALSE(api_->IsInBounds(0, 32));
|
||||
EXPECT_FALSE(api_->IsInBounds(100, 100));
|
||||
}
|
||||
@@ -147,11 +147,11 @@ TEST_F(CanvasAutomationAPITest, SetTileAt_WithCallback) {
|
||||
painted_tiles.push_back({x, y, tile_id});
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
// Paint some tiles
|
||||
EXPECT_TRUE(api_->SetTileAt(5, 5, 42));
|
||||
EXPECT_TRUE(api_->SetTileAt(10, 10, 100));
|
||||
|
||||
|
||||
ASSERT_EQ(painted_tiles.size(), 2);
|
||||
EXPECT_EQ(painted_tiles[0], std::make_tuple(5, 5, 42));
|
||||
EXPECT_EQ(painted_tiles[1], std::make_tuple(10, 10, 100));
|
||||
@@ -163,12 +163,12 @@ TEST_F(CanvasAutomationAPITest, SetTileAt_OutOfBounds) {
|
||||
callback_called = true;
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
// Out of bounds tiles should return false without calling callback
|
||||
EXPECT_FALSE(api_->SetTileAt(-1, 0, 42));
|
||||
EXPECT_FALSE(api_->SetTileAt(0, -1, 42));
|
||||
EXPECT_FALSE(api_->SetTileAt(100, 100, 42));
|
||||
|
||||
|
||||
EXPECT_FALSE(callback_called);
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ TEST_F(CanvasAutomationAPITest, GetTileAt_WithCallback) {
|
||||
api_->SetTileQueryCallback([](int x, int y) {
|
||||
return x * 100 + y; // Simple deterministic value
|
||||
});
|
||||
|
||||
|
||||
EXPECT_EQ(api_->GetTileAt(0, 0), 0);
|
||||
EXPECT_EQ(api_->GetTileAt(1, 0), 100);
|
||||
EXPECT_EQ(api_->GetTileAt(0, 1), 1);
|
||||
@@ -186,7 +186,7 @@ TEST_F(CanvasAutomationAPITest, GetTileAt_WithCallback) {
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, GetTileAt_OutOfBounds) {
|
||||
api_->SetTileQueryCallback([](int x, int y) { return 42; });
|
||||
|
||||
|
||||
EXPECT_EQ(api_->GetTileAt(-1, 0), -1);
|
||||
EXPECT_EQ(api_->GetTileAt(0, -1), -1);
|
||||
EXPECT_EQ(api_->GetTileAt(100, 100), -1);
|
||||
@@ -198,15 +198,10 @@ TEST_F(CanvasAutomationAPITest, SetTiles_BatchOperation) {
|
||||
painted_tiles.push_back({x, y, tile_id});
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
std::vector<std::tuple<int, int, int>> tiles_to_paint = {
|
||||
{0, 0, 10},
|
||||
{1, 0, 11},
|
||||
{2, 0, 12},
|
||||
{0, 1, 20},
|
||||
{1, 1, 21}
|
||||
};
|
||||
|
||||
{0, 0, 10}, {1, 0, 11}, {2, 0, 12}, {0, 1, 20}, {1, 1, 21}};
|
||||
|
||||
EXPECT_TRUE(api_->SetTiles(tiles_to_paint));
|
||||
EXPECT_EQ(painted_tiles.size(), 5);
|
||||
}
|
||||
@@ -217,7 +212,7 @@ TEST_F(CanvasAutomationAPITest, SetTiles_BatchOperation) {
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, SelectTile) {
|
||||
api_->SelectTile(5, 5);
|
||||
|
||||
|
||||
auto selection = api_->GetSelection();
|
||||
EXPECT_TRUE(selection.has_selection);
|
||||
EXPECT_EQ(selection.selected_tiles.size(), 1);
|
||||
@@ -225,13 +220,13 @@ TEST_F(CanvasAutomationAPITest, SelectTile) {
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, SelectTileRect) {
|
||||
api_->SelectTileRect(5, 5, 9, 9);
|
||||
|
||||
|
||||
auto selection = api_->GetSelection();
|
||||
EXPECT_TRUE(selection.has_selection);
|
||||
|
||||
|
||||
// 5x5 rectangle = 25 tiles
|
||||
EXPECT_EQ(selection.selected_tiles.size(), 25);
|
||||
|
||||
|
||||
// Check first and last tiles
|
||||
EXPECT_FLOAT_EQ(selection.selected_tiles[0].x, 5.0f);
|
||||
EXPECT_FLOAT_EQ(selection.selected_tiles[0].y, 5.0f);
|
||||
@@ -242,7 +237,7 @@ TEST_F(CanvasAutomationAPITest, SelectTileRect) {
|
||||
TEST_F(CanvasAutomationAPITest, SelectTileRect_SwappedCoordinates) {
|
||||
// Should handle coordinates in any order
|
||||
api_->SelectTileRect(9, 9, 5, 5); // Reversed
|
||||
|
||||
|
||||
auto selection = api_->GetSelection();
|
||||
EXPECT_TRUE(selection.has_selection);
|
||||
EXPECT_EQ(selection.selected_tiles.size(), 25);
|
||||
@@ -250,12 +245,12 @@ TEST_F(CanvasAutomationAPITest, SelectTileRect_SwappedCoordinates) {
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, ClearSelection) {
|
||||
api_->SelectTileRect(5, 5, 10, 10);
|
||||
|
||||
|
||||
auto selection = api_->GetSelection();
|
||||
EXPECT_TRUE(selection.has_selection);
|
||||
|
||||
|
||||
api_->ClearSelection();
|
||||
|
||||
|
||||
selection = api_->GetSelection();
|
||||
EXPECT_FALSE(selection.has_selection);
|
||||
EXPECT_EQ(selection.selected_tiles.size(), 0);
|
||||
@@ -265,7 +260,7 @@ TEST_F(CanvasAutomationAPITest, SelectTile_OutOfBounds) {
|
||||
api_->SelectTile(-1, 0);
|
||||
auto selection = api_->GetSelection();
|
||||
EXPECT_FALSE(selection.has_selection);
|
||||
|
||||
|
||||
api_->SelectTile(100, 100);
|
||||
selection = api_->GetSelection();
|
||||
EXPECT_FALSE(selection.has_selection);
|
||||
@@ -278,10 +273,10 @@ TEST_F(CanvasAutomationAPITest, SelectTile_OutOfBounds) {
|
||||
TEST_F(CanvasAutomationAPITest, SetZoom_ValidRange) {
|
||||
api_->SetZoom(1.0f);
|
||||
EXPECT_FLOAT_EQ(api_->GetZoom(), 1.0f);
|
||||
|
||||
|
||||
api_->SetZoom(2.0f);
|
||||
EXPECT_FLOAT_EQ(api_->GetZoom(), 2.0f);
|
||||
|
||||
|
||||
api_->SetZoom(0.5f);
|
||||
EXPECT_FLOAT_EQ(api_->GetZoom(), 0.5f);
|
||||
}
|
||||
@@ -290,10 +285,10 @@ TEST_F(CanvasAutomationAPITest, SetZoom_Clamping) {
|
||||
// Should clamp to 0.25 - 4.0 range
|
||||
api_->SetZoom(10.0f);
|
||||
EXPECT_LE(api_->GetZoom(), 4.0f);
|
||||
|
||||
|
||||
api_->SetZoom(0.1f);
|
||||
EXPECT_GE(api_->GetZoom(), 0.25f);
|
||||
|
||||
|
||||
api_->SetZoom(-1.0f);
|
||||
EXPECT_GE(api_->GetZoom(), 0.25f);
|
||||
}
|
||||
@@ -303,7 +298,7 @@ TEST_F(CanvasAutomationAPITest, ScrollToTile_ValidTile) {
|
||||
api_->ScrollToTile(0, 0, true);
|
||||
api_->ScrollToTile(10, 10, false);
|
||||
api_->ScrollToTile(15, 15, true);
|
||||
|
||||
|
||||
// Just verify no crash - actual scroll behavior depends on ImGui state
|
||||
}
|
||||
|
||||
@@ -311,7 +306,7 @@ TEST_F(CanvasAutomationAPITest, ScrollToTile_OutOfBounds) {
|
||||
// Should handle out of bounds gracefully
|
||||
api_->ScrollToTile(-1, 0, true);
|
||||
api_->ScrollToTile(100, 100, true);
|
||||
|
||||
|
||||
// Should not crash
|
||||
}
|
||||
|
||||
@@ -320,8 +315,9 @@ TEST_F(CanvasAutomationAPITest, CenterOn_ValidTile) {
|
||||
api_->CenterOn(10, 10);
|
||||
api_->CenterOn(0, 0);
|
||||
api_->CenterOn(20, 20);
|
||||
|
||||
// Verify scroll position changed (should be non-zero after centering on non-origin)
|
||||
|
||||
// Verify scroll position changed (should be non-zero after centering on
|
||||
// non-origin)
|
||||
ImVec2 scroll = canvas_->scrolling();
|
||||
// Scroll values will depend on canvas size, just verify they're set
|
||||
}
|
||||
@@ -329,7 +325,7 @@ TEST_F(CanvasAutomationAPITest, CenterOn_ValidTile) {
|
||||
TEST_F(CanvasAutomationAPITest, CenterOn_OutOfBounds) {
|
||||
api_->CenterOn(-1, 0);
|
||||
api_->CenterOn(100, 100);
|
||||
|
||||
|
||||
// Should not crash
|
||||
}
|
||||
|
||||
@@ -339,32 +335,32 @@ TEST_F(CanvasAutomationAPITest, CenterOn_OutOfBounds) {
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, GetDimensions) {
|
||||
canvas_->set_global_scale(1.0f);
|
||||
|
||||
|
||||
auto dims = api_->GetDimensions();
|
||||
EXPECT_EQ(dims.tile_size, 16); // 16x16 grid
|
||||
EXPECT_EQ(dims.tile_size, 16); // 16x16 grid
|
||||
EXPECT_EQ(dims.width_tiles, 32); // 512 / 16
|
||||
EXPECT_EQ(dims.height_tiles, 32);
|
||||
}
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, GetDimensions_WithZoom) {
|
||||
canvas_->set_global_scale(2.0f);
|
||||
|
||||
|
||||
auto dims = api_->GetDimensions();
|
||||
EXPECT_EQ(dims.tile_size, 16);
|
||||
EXPECT_EQ(dims.width_tiles, 16); // 512 / (16 * 2.0)
|
||||
EXPECT_EQ(dims.width_tiles, 16); // 512 / (16 * 2.0)
|
||||
EXPECT_EQ(dims.height_tiles, 16);
|
||||
}
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, GetVisibleRegion) {
|
||||
canvas_->set_global_scale(1.0f);
|
||||
canvas_->set_scrolling(ImVec2(0, 0));
|
||||
|
||||
|
||||
auto region = api_->GetVisibleRegion();
|
||||
|
||||
|
||||
// At origin with no scroll, should start at (0,0)
|
||||
EXPECT_GE(region.min_x, 0);
|
||||
EXPECT_GE(region.min_y, 0);
|
||||
|
||||
|
||||
// Should have valid bounds
|
||||
EXPECT_GE(region.max_x, region.min_x);
|
||||
EXPECT_GE(region.max_y, region.min_y);
|
||||
@@ -373,11 +369,11 @@ TEST_F(CanvasAutomationAPITest, GetVisibleRegion) {
|
||||
TEST_F(CanvasAutomationAPITest, IsTileVisible_AtOrigin) {
|
||||
canvas_->set_global_scale(1.0f);
|
||||
canvas_->set_scrolling(ImVec2(0, 0));
|
||||
|
||||
|
||||
// Tiles at origin should be visible
|
||||
EXPECT_TRUE(api_->IsTileVisible(0, 0));
|
||||
EXPECT_TRUE(api_->IsTileVisible(1, 1));
|
||||
|
||||
|
||||
// Tiles far away might not be visible (depends on canvas size)
|
||||
// We just verify the method doesn't crash
|
||||
api_->IsTileVisible(50, 50);
|
||||
@@ -396,36 +392,33 @@ TEST_F(CanvasAutomationAPITest, IsTileVisible_OutOfBounds) {
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, CompleteWorkflow) {
|
||||
// Simulate a complete automation workflow
|
||||
|
||||
|
||||
// 1. Set zoom level
|
||||
api_->SetZoom(1.0f);
|
||||
EXPECT_FLOAT_EQ(api_->GetZoom(), 1.0f);
|
||||
|
||||
|
||||
// 2. Select a tile region
|
||||
api_->SelectTileRect(0, 0, 4, 4);
|
||||
auto selection = api_->GetSelection();
|
||||
EXPECT_EQ(selection.selected_tiles.size(), 25);
|
||||
|
||||
|
||||
// 3. Query tile data with callback
|
||||
api_->SetTileQueryCallback([](int x, int y) {
|
||||
return x + y * 100;
|
||||
});
|
||||
|
||||
api_->SetTileQueryCallback([](int x, int y) { return x + y * 100; });
|
||||
|
||||
EXPECT_EQ(api_->GetTileAt(2, 3), 302);
|
||||
|
||||
|
||||
// 4. Paint tiles with callback
|
||||
std::vector<std::tuple<int, int, int>> painted;
|
||||
api_->SetTilePaintCallback([&](int x, int y, int tile_id) {
|
||||
painted.push_back({x, y, tile_id});
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
std::vector<std::tuple<int, int, int>> tiles = {
|
||||
{0, 0, 10}, {1, 0, 11}, {2, 0, 12}
|
||||
};
|
||||
{0, 0, 10}, {1, 0, 11}, {2, 0, 12}};
|
||||
EXPECT_TRUE(api_->SetTiles(tiles));
|
||||
EXPECT_EQ(painted.size(), 3);
|
||||
|
||||
|
||||
// 5. Clear selection
|
||||
api_->ClearSelection();
|
||||
selection = api_->GetSelection();
|
||||
@@ -434,19 +427,19 @@ TEST_F(CanvasAutomationAPITest, CompleteWorkflow) {
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, DifferentGridSizes) {
|
||||
// Test with 8x8 grid
|
||||
auto canvas_8x8 = std::make_unique<gui::Canvas>(
|
||||
"Test8x8", ImVec2(512, 512), gui::CanvasGridSize::k8x8);
|
||||
auto canvas_8x8 = std::make_unique<gui::Canvas>("Test8x8", ImVec2(512, 512),
|
||||
gui::CanvasGridSize::k8x8);
|
||||
auto api_8x8 = canvas_8x8->GetAutomationAPI();
|
||||
|
||||
|
||||
auto dims = api_8x8->GetDimensions();
|
||||
EXPECT_EQ(dims.tile_size, 8);
|
||||
EXPECT_EQ(dims.width_tiles, 64); // 512 / 8
|
||||
|
||||
|
||||
// Test with 32x32 grid
|
||||
auto canvas_32x32 = std::make_unique<gui::Canvas>(
|
||||
"Test32x32", ImVec2(512, 512), gui::CanvasGridSize::k32x32);
|
||||
auto api_32x32 = canvas_32x32->GetAutomationAPI();
|
||||
|
||||
|
||||
dims = api_32x32->GetDimensions();
|
||||
EXPECT_EQ(dims.tile_size, 32);
|
||||
EXPECT_EQ(dims.width_tiles, 16); // 512 / 32
|
||||
@@ -454,19 +447,19 @@ TEST_F(CanvasAutomationAPITest, DifferentGridSizes) {
|
||||
|
||||
TEST_F(CanvasAutomationAPITest, MultipleZoomLevels) {
|
||||
float zoom_levels[] = {0.25f, 0.5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f};
|
||||
|
||||
|
||||
for (float zoom : zoom_levels) {
|
||||
api_->SetZoom(zoom);
|
||||
float actual_zoom = api_->GetZoom();
|
||||
|
||||
|
||||
// Should be clamped to valid range
|
||||
EXPECT_GE(actual_zoom, 0.25f);
|
||||
EXPECT_LE(actual_zoom, 4.0f);
|
||||
|
||||
|
||||
// Coordinate conversion should still work
|
||||
ImVec2 canvas_pos = api_->TileToCanvas(10, 10);
|
||||
ImVec2 tile_pos = api_->CanvasToTile(canvas_pos);
|
||||
|
||||
|
||||
EXPECT_FLOAT_EQ(tile_pos.x, 10.0f);
|
||||
EXPECT_FLOAT_EQ(tile_pos.y, 10.0f);
|
||||
}
|
||||
@@ -474,4 +467,3 @@ TEST_F(CanvasAutomationAPITest, MultipleZoomLevels) {
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#include "app/gui/canvas/canvas.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/gui/canvas/canvas.h"
|
||||
#include "testing.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -27,8 +26,8 @@ class CanvasCoordinateSyncTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Create a test canvas with known dimensions (4096x4096 for overworld)
|
||||
canvas_ = std::make_unique<gui::Canvas>("OverworldCanvas", ImVec2(4096, 4096),
|
||||
gui::CanvasGridSize::k16x16);
|
||||
canvas_ = std::make_unique<gui::Canvas>(
|
||||
"OverworldCanvas", ImVec2(4096, 4096), gui::CanvasGridSize::k16x16);
|
||||
canvas_->set_global_scale(1.0f);
|
||||
}
|
||||
|
||||
@@ -100,16 +99,15 @@ TEST_F(CanvasCoordinateSyncTest, MapCalculation_SmallMaps) {
|
||||
|
||||
// Simulate hover at different world positions
|
||||
std::vector<ImVec2> test_positions = {
|
||||
ImVec2(0, 0), // Map (0, 0)
|
||||
ImVec2(512, 0), // Map (1, 0)
|
||||
ImVec2(0, 512), // Map (0, 1)
|
||||
ImVec2(512, 512), // Map (1, 1)
|
||||
ImVec2(1536, 1024), // Map (3, 2)
|
||||
ImVec2(0, 0), // Map (0, 0)
|
||||
ImVec2(512, 0), // Map (1, 0)
|
||||
ImVec2(0, 512), // Map (0, 1)
|
||||
ImVec2(512, 512), // Map (1, 1)
|
||||
ImVec2(1536, 1024), // Map (3, 2)
|
||||
};
|
||||
|
||||
std::vector<std::pair<int, int>> expected_maps = {
|
||||
{0, 0}, {1, 0}, {0, 1}, {1, 1}, {3, 2}
|
||||
};
|
||||
{0, 0}, {1, 0}, {0, 1}, {1, 1}, {3, 2}};
|
||||
|
||||
for (size_t i = 0; i < test_positions.size(); ++i) {
|
||||
ImVec2 pos = test_positions[i];
|
||||
@@ -127,15 +125,14 @@ TEST_F(CanvasCoordinateSyncTest, MapCalculation_LargeMaps) {
|
||||
|
||||
// Large maps should span multiple standard map coordinates
|
||||
std::vector<ImVec2> test_positions = {
|
||||
ImVec2(0, 0), // Large map (0, 0)
|
||||
ImVec2(1024, 0), // Large map (1, 0)
|
||||
ImVec2(0, 1024), // Large map (0, 1)
|
||||
ImVec2(2048, 2048), // Large map (2, 2)
|
||||
ImVec2(0, 0), // Large map (0, 0)
|
||||
ImVec2(1024, 0), // Large map (1, 0)
|
||||
ImVec2(0, 1024), // Large map (0, 1)
|
||||
ImVec2(2048, 2048), // Large map (2, 2)
|
||||
};
|
||||
|
||||
std::vector<std::pair<int, int>> expected_large_maps = {
|
||||
{0, 0}, {1, 0}, {0, 1}, {2, 2}
|
||||
};
|
||||
{0, 0}, {1, 0}, {0, 1}, {2, 2}};
|
||||
|
||||
for (size_t i = 0; i < test_positions.size(); ++i) {
|
||||
ImVec2 pos = test_positions[i];
|
||||
@@ -152,8 +149,8 @@ TEST_F(CanvasCoordinateSyncTest, MapCalculation_LargeMaps) {
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(CanvasCoordinateSyncTest, HoverPosition_ScaleInvariant) {
|
||||
// REGRESSION TEST: Hover position should be in world space regardless of scale
|
||||
// The bug was scale-dependent because it used screen coordinates
|
||||
// REGRESSION TEST: Hover position should be in world space regardless of
|
||||
// scale The bug was scale-dependent because it used screen coordinates
|
||||
|
||||
auto test_hover_at_scale = [&](float scale) {
|
||||
canvas_->set_global_scale(scale);
|
||||
@@ -186,7 +183,8 @@ TEST_F(CanvasCoordinateSyncTest, OverworldMapHighlight_UsesHoverNotDrawn) {
|
||||
// The pattern used in DrawOverworldEdits (line 664) for painting:
|
||||
auto drawn_pos = canvas_->drawn_tile_position();
|
||||
|
||||
// The pattern that SHOULD be used in CheckForCurrentMap (line 1041) for highlighting:
|
||||
// The pattern that SHOULD be used in CheckForCurrentMap (line 1041) for
|
||||
// highlighting:
|
||||
auto hover_pos = canvas_->hover_mouse_pos();
|
||||
|
||||
// These are different methods for different purposes:
|
||||
@@ -210,19 +208,19 @@ TEST_F(CanvasCoordinateSyncTest, OverworldMapIndex_From8x8Grid) {
|
||||
};
|
||||
|
||||
std::vector<TestCase> test_cases = {
|
||||
// Light World (0x00 - 0x3F)
|
||||
{ImVec2(0, 0), 0, 0}, // Map 0 (Light World)
|
||||
{ImVec2(512, 0), 0, 1}, // Map 1
|
||||
{ImVec2(1024, 512), 0, 10}, // Map 10 = 2 + 1*8
|
||||
// Light World (0x00 - 0x3F)
|
||||
{ImVec2(0, 0), 0, 0}, // Map 0 (Light World)
|
||||
{ImVec2(512, 0), 0, 1}, // Map 1
|
||||
{ImVec2(1024, 512), 0, 10}, // Map 10 = 2 + 1*8
|
||||
|
||||
// Dark World (0x40 - 0x7F)
|
||||
{ImVec2(0, 0), 1, 0x40}, // Map 0x40 (Dark World)
|
||||
{ImVec2(512, 0), 1, 0x41}, // Map 0x41
|
||||
{ImVec2(1024, 512), 1, 0x4A}, // Map 0x4A = 0x40 + 10
|
||||
// Dark World (0x40 - 0x7F)
|
||||
{ImVec2(0, 0), 1, 0x40}, // Map 0x40 (Dark World)
|
||||
{ImVec2(512, 0), 1, 0x41}, // Map 0x41
|
||||
{ImVec2(1024, 512), 1, 0x4A}, // Map 0x4A = 0x40 + 10
|
||||
|
||||
// Special World (0x80+)
|
||||
{ImVec2(0, 0), 2, 0x80}, // Map 0x80 (Special World)
|
||||
{ImVec2(512, 512), 2, 0x89}, // Map 0x89 = 0x80 + 9
|
||||
// Special World (0x80+)
|
||||
{ImVec2(0, 0), 2, 0x80}, // Map 0x80 (Special World)
|
||||
{ImVec2(512, 512), 2, 0x89}, // Map 0x89 = 0x80 + 9
|
||||
};
|
||||
|
||||
for (const auto& tc : test_cases) {
|
||||
@@ -237,8 +235,8 @@ TEST_F(CanvasCoordinateSyncTest, OverworldMapIndex_From8x8Grid) {
|
||||
}
|
||||
|
||||
EXPECT_EQ(hovered_map, tc.expected_map_index)
|
||||
<< "Failed for world " << tc.current_world
|
||||
<< " at position (" << tc.hover_pos.x << ", " << tc.hover_pos.y << ")";
|
||||
<< "Failed for world " << tc.current_world << " at position ("
|
||||
<< tc.hover_pos.x << ", " << tc.hover_pos.y << ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,12 +250,12 @@ TEST_F(CanvasCoordinateSyncTest, MapBoundaries_512x512) {
|
||||
|
||||
// Boundary coordinates (edges of maps)
|
||||
std::vector<ImVec2> boundary_positions = {
|
||||
ImVec2(511, 0), // Right edge of map 0
|
||||
ImVec2(512, 0), // Left edge of map 1
|
||||
ImVec2(0, 511), // Bottom edge of map 0
|
||||
ImVec2(0, 512), // Top edge of map 8
|
||||
ImVec2(511, 511), // Corner of map 0
|
||||
ImVec2(512, 512), // Corner of map 9
|
||||
ImVec2(511, 0), // Right edge of map 0
|
||||
ImVec2(512, 0), // Left edge of map 1
|
||||
ImVec2(0, 511), // Bottom edge of map 0
|
||||
ImVec2(0, 512), // Top edge of map 8
|
||||
ImVec2(511, 511), // Corner of map 0
|
||||
ImVec2(512, 512), // Corner of map 9
|
||||
};
|
||||
|
||||
for (const auto& pos : boundary_positions) {
|
||||
@@ -276,10 +274,10 @@ TEST_F(CanvasCoordinateSyncTest, MapBoundaries_1024x1024) {
|
||||
const int kLargeMapSize = 1024;
|
||||
|
||||
std::vector<ImVec2> boundary_positions = {
|
||||
ImVec2(1023, 0), // Right edge of large map 0
|
||||
ImVec2(1024, 0), // Left edge of large map 1
|
||||
ImVec2(0, 1023), // Bottom edge of large map 0
|
||||
ImVec2(0, 1024), // Top edge of large map 4 (0,1 in 4x4 grid)
|
||||
ImVec2(1023, 0), // Right edge of large map 0
|
||||
ImVec2(1024, 0), // Left edge of large map 1
|
||||
ImVec2(0, 1023), // Bottom edge of large map 0
|
||||
ImVec2(0, 1024), // Top edge of large map 4 (0,1 in 4x4 grid)
|
||||
};
|
||||
|
||||
for (const auto& pos : boundary_positions) {
|
||||
|
||||
@@ -18,7 +18,7 @@ class TileSelectorWidgetTest : public ::testing::Test {
|
||||
void SetUp() override {
|
||||
// Create a test canvas
|
||||
canvas_ = std::make_unique<gui::Canvas>("TestCanvas", ImVec2(512, 512),
|
||||
gui::CanvasGridSize::k16x16);
|
||||
gui::CanvasGridSize::k16x16);
|
||||
|
||||
// Create a test config
|
||||
config_.tile_size = 16;
|
||||
@@ -97,16 +97,16 @@ TEST_F(TileSelectorWidgetTest, TileOrigin) {
|
||||
|
||||
// Test tile at (1,0)
|
||||
origin = widget.TileOrigin(1);
|
||||
float expected_x = config_.draw_offset.x +
|
||||
(config_.tile_size * config_.display_scale);
|
||||
float expected_x =
|
||||
config_.draw_offset.x + (config_.tile_size * config_.display_scale);
|
||||
EXPECT_FLOAT_EQ(origin.x, expected_x);
|
||||
EXPECT_FLOAT_EQ(origin.y, config_.draw_offset.y);
|
||||
|
||||
// Test tile at (0,1) - first tile of second row
|
||||
origin = widget.TileOrigin(8);
|
||||
expected_x = config_.draw_offset.x;
|
||||
float expected_y = config_.draw_offset.y +
|
||||
(config_.tile_size * config_.display_scale);
|
||||
float expected_y =
|
||||
config_.draw_offset.y + (config_.tile_size * config_.display_scale);
|
||||
EXPECT_FLOAT_EQ(origin.x, expected_x);
|
||||
EXPECT_FLOAT_EQ(origin.y, expected_y);
|
||||
|
||||
@@ -191,4 +191,3 @@ TEST_F(TileSelectorWidgetTest, DifferentConfigs) {
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/transaction.h"
|
||||
#include "mocks/mock_rom.h"
|
||||
#include "testing.h"
|
||||
#include "app/transaction.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
@@ -196,7 +196,8 @@ TEST_F(RomTest, SaveTruncatesExistingFile) {
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
// Prepare ROM data and save to a temp file twice; second save should overwrite, not append
|
||||
// Prepare ROM data and save to a temp file twice; second save should
|
||||
// overwrite, not append
|
||||
EXPECT_OK(rom_.LoadFromData(kMockRomData, /*z3_load=*/false));
|
||||
|
||||
const char* tmp_name = "test_temp_rom.sfc";
|
||||
@@ -211,7 +212,8 @@ TEST_F(RomTest, SaveTruncatesExistingFile) {
|
||||
EXPECT_OK(rom_.WriteByte(0, 0xEE));
|
||||
EXPECT_OK(rom_.SaveToFile(settings));
|
||||
|
||||
// Load the saved file and verify size equals original data size and first byte matches
|
||||
// Load the saved file and verify size equals original data size and first
|
||||
// byte matches
|
||||
Rom verify;
|
||||
EXPECT_OK(verify.LoadFromFile(tmp_name, /*z3_load=*/false));
|
||||
EXPECT_EQ(verify.size(), kMockRomData.size());
|
||||
@@ -224,9 +226,10 @@ TEST_F(RomTest, TransactionRollbackRestoresOriginals) {
|
||||
EXPECT_OK(rom_.LoadFromData(kMockRomData, /*z3_load=*/false));
|
||||
// Force an out-of-range write to trigger failure after a successful write
|
||||
yaze::Transaction tx{rom_};
|
||||
auto status = tx.WriteByte(0x01, 0xAA) // valid
|
||||
.WriteWord(0xFFFF, 0xBBBB) // invalid: should fail and rollback
|
||||
.Commit();
|
||||
auto status =
|
||||
tx.WriteByte(0x01, 0xAA) // valid
|
||||
.WriteWord(0xFFFF, 0xBBBB) // invalid: should fail and rollback
|
||||
.Commit();
|
||||
EXPECT_FALSE(status.ok());
|
||||
auto b1 = rom_.ReadByte(0x01);
|
||||
ASSERT_TRUE(b1.ok());
|
||||
|
||||
@@ -29,7 +29,7 @@ TEST_F(SnesColorTest, SetRgbFromImGuiNormalizedValues) {
|
||||
|
||||
// Internal storage should be in 0-255 range
|
||||
auto rgb = color.rgb();
|
||||
EXPECT_FLOAT_EQ(rgb.x, 127.5f); // 0.5 * 255
|
||||
EXPECT_FLOAT_EQ(rgb.x, 127.5f); // 0.5 * 255
|
||||
EXPECT_FLOAT_EQ(rgb.y, 191.25f); // 0.75 * 255
|
||||
EXPECT_FLOAT_EQ(rgb.z, 255.0f); // 1.0 * 255
|
||||
EXPECT_FLOAT_EQ(rgb.w, 255.0f); // Alpha always 255
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/dungeon/object_parser.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
@@ -17,33 +16,31 @@ class ObjectRenderingTest : public ::testing::Test {
|
||||
// Create a mock ROM for testing
|
||||
rom_ = std::make_unique<Rom>();
|
||||
// Initialize with minimal ROM data for testing
|
||||
std::vector<uint8_t> mock_rom_data(1024 * 1024, 0); // 1MB mock ROM
|
||||
std::vector<uint8_t> mock_rom_data(1024 * 1024, 0); // 1MB mock ROM
|
||||
rom_->LoadFromData(mock_rom_data);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
rom_.reset();
|
||||
}
|
||||
void TearDown() override { rom_.reset(); }
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
gfx::BackgroundBuffer bg1_;
|
||||
gfx::BackgroundBuffer bg2_;
|
||||
|
||||
|
||||
// Create a test palette
|
||||
gfx::SnesPalette CreateTestPalette() {
|
||||
gfx::SnesPalette palette;
|
||||
// Add some test colors
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 0)); // Transparent
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 0)); // Red
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 0)); // Green
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 255)); // Blue
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 0)); // Yellow
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 255)); // Magenta
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 255)); // Cyan
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 255)); // White
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 0)); // Transparent
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 0)); // Red
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 0)); // Green
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 255)); // Blue
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 0)); // Yellow
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 255)); // Magenta
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 255)); // Cyan
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 255)); // White
|
||||
return palette;
|
||||
}
|
||||
|
||||
|
||||
gfx::PaletteGroup CreateTestPaletteGroup() {
|
||||
gfx::PaletteGroup group;
|
||||
group.AddPalette(CreateTestPalette());
|
||||
@@ -54,7 +51,7 @@ class ObjectRenderingTest : public ::testing::Test {
|
||||
// Test object drawer initialization
|
||||
TEST_F(ObjectRenderingTest, ObjectDrawerInitializesCorrectly) {
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
|
||||
|
||||
// Test that drawer can be created without errors
|
||||
EXPECT_NE(rom_.get(), nullptr);
|
||||
}
|
||||
@@ -62,31 +59,31 @@ TEST_F(ObjectRenderingTest, ObjectDrawerInitializesCorrectly) {
|
||||
// Test object parser draw routine detection
|
||||
TEST_F(ObjectRenderingTest, ObjectParserDetectsDrawRoutines) {
|
||||
ObjectParser parser(rom_.get());
|
||||
|
||||
|
||||
// Test common object IDs and their expected draw routines
|
||||
auto info_00 = parser.GetObjectDrawInfo(0x00);
|
||||
EXPECT_EQ(info_00.draw_routine_id, 0);
|
||||
EXPECT_EQ(info_00.routine_name, "Rightwards2x2_1to15or32");
|
||||
EXPECT_TRUE(info_00.is_horizontal);
|
||||
|
||||
|
||||
auto info_01 = parser.GetObjectDrawInfo(0x01);
|
||||
EXPECT_EQ(info_01.draw_routine_id, 1);
|
||||
EXPECT_EQ(info_01.routine_name, "Rightwards2x4_1to15or26");
|
||||
EXPECT_TRUE(info_01.is_horizontal);
|
||||
|
||||
|
||||
auto info_09 = parser.GetObjectDrawInfo(0x09);
|
||||
EXPECT_EQ(info_09.draw_routine_id, 5);
|
||||
EXPECT_EQ(info_09.routine_name, "DiagonalAcute_1to16");
|
||||
EXPECT_FALSE(info_09.is_horizontal);
|
||||
|
||||
|
||||
auto info_34 = parser.GetObjectDrawInfo(0x34);
|
||||
EXPECT_EQ(info_34.draw_routine_id, 16);
|
||||
EXPECT_EQ(info_34.routine_name, "Rightwards1x1Solid_1to16_plus3");
|
||||
EXPECT_TRUE(info_34.is_horizontal);
|
||||
|
||||
|
||||
// Test unmapped object defaults to solid block routine
|
||||
auto info_unknown = parser.GetObjectDrawInfo(0x999);
|
||||
EXPECT_EQ(info_unknown.draw_routine_id, 16); // Default solid routine
|
||||
EXPECT_EQ(info_unknown.draw_routine_id, 16); // Default solid routine
|
||||
EXPECT_EQ(info_unknown.routine_name, "DefaultSolid");
|
||||
}
|
||||
|
||||
@@ -94,25 +91,25 @@ TEST_F(ObjectRenderingTest, ObjectParserDetectsDrawRoutines) {
|
||||
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesVariousObjectTypes) {
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Test object 0x00 (horizontal floor tile)
|
||||
RoomObject floor_object(0x00, 10, 10, 3, 0); // ID, X, Y, size, layer
|
||||
|
||||
RoomObject floor_object(0x00, 10, 10, 3, 0); // ID, X, Y, size, layer
|
||||
|
||||
auto status = drawer.DrawObject(floor_object, bg1_, bg2_, palette_group);
|
||||
// Should succeed even if tiles aren't loaded (graceful handling)
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Test object 0x09 (diagonal stairs)
|
||||
RoomObject stair_object(0x09, 15, 15, 5, 0);
|
||||
stair_object.set_rom(rom_.get());
|
||||
|
||||
|
||||
status = drawer.DrawObject(stair_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Test object 0x34 (solid block)
|
||||
RoomObject block_object(0x34, 20, 20, 1, 0);
|
||||
block_object.set_rom(rom_.get());
|
||||
|
||||
|
||||
status = drawer.DrawObject(block_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
@@ -121,18 +118,18 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesVariousObjectTypes) {
|
||||
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesDifferentLayers) {
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Test BG1 layer object
|
||||
RoomObject bg1_object(0x00, 5, 5, 2, 0); // Layer 0 = BG1
|
||||
RoomObject bg1_object(0x00, 5, 5, 2, 0); // Layer 0 = BG1
|
||||
bg1_object.set_rom(rom_.get());
|
||||
|
||||
|
||||
auto status = drawer.DrawObject(bg1_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Test BG2 layer object
|
||||
RoomObject bg2_object(0x01, 10, 10, 2, 1); // Layer 1 = BG2
|
||||
RoomObject bg2_object(0x01, 10, 10, 2, 1); // Layer 1 = BG2
|
||||
bg2_object.set_rom(rom_.get());
|
||||
|
||||
|
||||
status = drawer.DrawObject(bg2_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
@@ -141,25 +138,25 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesDifferentLayers) {
|
||||
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesSizeVariations) {
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Test small object
|
||||
RoomObject small_object(0x00, 5, 5, 1, 0); // Size = 1
|
||||
RoomObject small_object(0x00, 5, 5, 1, 0); // Size = 1
|
||||
small_object.set_rom(rom_.get());
|
||||
|
||||
|
||||
auto status = drawer.DrawObject(small_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Test large object
|
||||
RoomObject large_object(0x00, 10, 10, 15, 0); // Size = 15
|
||||
RoomObject large_object(0x00, 10, 10, 15, 0); // Size = 15
|
||||
large_object.set_rom(rom_.get());
|
||||
|
||||
|
||||
status = drawer.DrawObject(large_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Test maximum size object
|
||||
RoomObject max_object(0x00, 15, 15, 31, 0); // Size = 31 (0x1F)
|
||||
RoomObject max_object(0x00, 15, 15, 31, 0); // Size = 31 (0x1F)
|
||||
max_object.set_rom(rom_.get());
|
||||
|
||||
|
||||
status = drawer.DrawObject(max_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
@@ -168,25 +165,25 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesSizeVariations) {
|
||||
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesEdgeCases) {
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Test object at origin
|
||||
RoomObject origin_object(0x34, 0, 0, 1, 0);
|
||||
origin_object.set_rom(rom_.get());
|
||||
|
||||
|
||||
auto status = drawer.DrawObject(origin_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Test object with zero size
|
||||
RoomObject zero_size_object(0x34, 10, 10, 0, 0);
|
||||
zero_size_object.set_rom(rom_.get());
|
||||
|
||||
|
||||
status = drawer.DrawObject(zero_size_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Test object with maximum coordinates
|
||||
RoomObject max_coord_object(0x34, 63, 63, 1, 0); // Near buffer edge
|
||||
RoomObject max_coord_object(0x34, 63, 63, 1, 0); // Near buffer edge
|
||||
max_coord_object.set_rom(rom_.get());
|
||||
|
||||
|
||||
status = drawer.DrawObject(max_coord_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
@@ -195,20 +192,20 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesEdgeCases) {
|
||||
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesMultipleObjects) {
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
std::vector<RoomObject> objects;
|
||||
|
||||
|
||||
// Create various test objects
|
||||
objects.emplace_back(0x00, 5, 5, 3, 0); // Horizontal floor
|
||||
objects.emplace_back(0x01, 10, 10, 2, 0); // Vertical floor
|
||||
objects.emplace_back(0x09, 15, 15, 4, 0); // Diagonal stairs
|
||||
objects.emplace_back(0x34, 20, 20, 1, 1); // Solid block on BG2
|
||||
|
||||
objects.emplace_back(0x00, 5, 5, 3, 0); // Horizontal floor
|
||||
objects.emplace_back(0x01, 10, 10, 2, 0); // Vertical floor
|
||||
objects.emplace_back(0x09, 15, 15, 4, 0); // Diagonal stairs
|
||||
objects.emplace_back(0x34, 20, 20, 1, 1); // Solid block on BG2
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
}
|
||||
|
||||
|
||||
auto status = drawer.DrawObjectList(objects, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
@@ -217,36 +214,36 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesMultipleObjects) {
|
||||
TEST_F(ObjectRenderingTest, DrawRoutinesWorkCorrectly) {
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Test rightward patterns
|
||||
RoomObject rightward_obj(0x00, 5, 5, 5, 0);
|
||||
rightward_obj.set_rom(rom_.get());
|
||||
|
||||
|
||||
auto status = drawer.DrawObject(rightward_obj, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Test diagonal patterns
|
||||
RoomObject diagonal_obj(0x09, 10, 10, 6, 0);
|
||||
diagonal_obj.set_rom(rom_.get());
|
||||
|
||||
|
||||
status = drawer.DrawObject(diagonal_obj, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Test solid block patterns
|
||||
RoomObject solid_obj(0x34, 15, 15, 8, 0);
|
||||
solid_obj.set_rom(rom_.get());
|
||||
|
||||
|
||||
status = drawer.DrawObject(solid_obj, bg1_, bg2_, palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
// Test object drawer error handling
|
||||
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesErrorsGracefully) {
|
||||
ObjectDrawer drawer(nullptr); // No ROM
|
||||
ObjectDrawer drawer(nullptr); // No ROM
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
RoomObject test_object(0x00, 5, 5, 1, 0);
|
||||
|
||||
|
||||
auto status = drawer.DrawObject(test_object, bg1_, bg2_, palette_group);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
|
||||
@@ -255,32 +252,30 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesErrorsGracefully) {
|
||||
// Test object parser with various object IDs
|
||||
TEST_F(ObjectRenderingTest, ObjectParserHandlesVariousObjectIDs) {
|
||||
ObjectParser parser(rom_.get());
|
||||
|
||||
|
||||
// Test subtype 1 objects (0x00-0xFF)
|
||||
for (int id = 0; id <= 0x40; id += 4) { // Test every 4th object
|
||||
for (int id = 0; id <= 0x40; id += 4) { // Test every 4th object
|
||||
auto info = parser.GetObjectDrawInfo(id);
|
||||
EXPECT_GE(info.draw_routine_id, 0);
|
||||
EXPECT_LT(info.draw_routine_id, 25); // Should be within valid range
|
||||
EXPECT_LT(info.draw_routine_id, 25); // Should be within valid range
|
||||
EXPECT_FALSE(info.routine_name.empty());
|
||||
}
|
||||
|
||||
|
||||
// Test some specific important objects
|
||||
std::vector<int16_t> important_objects = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0A, 0x0B, 0x15, 0x16, 0x21, 0x22, 0x2F, 0x30, 0x31, 0x32,
|
||||
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
|
||||
0x3D, 0x3E, 0x3F, 0x40
|
||||
};
|
||||
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
|
||||
0x15, 0x16, 0x21, 0x22, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40};
|
||||
|
||||
for (int16_t obj_id : important_objects) {
|
||||
auto info = parser.GetObjectDrawInfo(obj_id);
|
||||
EXPECT_GE(info.draw_routine_id, 0);
|
||||
EXPECT_LT(info.draw_routine_id, 25);
|
||||
EXPECT_FALSE(info.routine_name.empty());
|
||||
|
||||
|
||||
// Verify tile count is reasonable
|
||||
EXPECT_GT(info.tile_count, 0);
|
||||
EXPECT_LE(info.tile_count, 64); // Reasonable upper bound
|
||||
EXPECT_LE(info.tile_count, 64); // Reasonable upper bound
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,35 +283,35 @@ TEST_F(ObjectRenderingTest, ObjectParserHandlesVariousObjectIDs) {
|
||||
TEST_F(ObjectRenderingTest, ObjectDrawerPerformanceTest) {
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
std::vector<RoomObject> objects;
|
||||
|
||||
|
||||
// Create 100 test objects
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
int id = i % 65; // Cycle through object IDs 0-64
|
||||
int x = (i * 2) % 60; // Spread across buffer
|
||||
int id = i % 65; // Cycle through object IDs 0-64
|
||||
int x = (i * 2) % 60; // Spread across buffer
|
||||
int y = (i * 3) % 60;
|
||||
int size = (i % 8) + 1; // Size 1-8
|
||||
int layer = i % 2; // Alternate layers
|
||||
|
||||
int size = (i % 8) + 1; // Size 1-8
|
||||
int layer = i % 2; // Alternate layers
|
||||
|
||||
objects.emplace_back(id, x, y, size, layer);
|
||||
objects.back().set_rom(rom_.get());
|
||||
}
|
||||
|
||||
|
||||
// Time the drawing operation
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
auto status = drawer.DrawObjectList(objects, bg1_, bg2_, palette_group);
|
||||
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
end_time - start_time);
|
||||
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Should complete in reasonable time (less than 1 second for 100 objects)
|
||||
EXPECT_LT(duration.count(), 1000);
|
||||
|
||||
|
||||
std::cout << "Drew 100 objects in " << duration.count() << "ms" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Tests for Room object manipulation methods (Phase 3)
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
@@ -16,20 +17,20 @@ class RoomManipulationTest : public ::testing::Test {
|
||||
// Create a minimal ROM for testing
|
||||
std::vector<uint8_t> dummy_data(0x200000, 0);
|
||||
rom_->LoadFromData(dummy_data, false);
|
||||
|
||||
|
||||
room_ = std::make_unique<Room>(0, rom_.get());
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<Room> room_;
|
||||
};
|
||||
|
||||
TEST_F(RoomManipulationTest, AddObject) {
|
||||
RoomObject obj(0x10, 10, 20, 3, 0);
|
||||
|
||||
|
||||
auto status = room_->AddObject(obj);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
auto objects = room_->GetTileObjects();
|
||||
EXPECT_EQ(objects.size(), 1);
|
||||
EXPECT_EQ(objects[0].id_, 0x10);
|
||||
@@ -40,7 +41,7 @@ TEST_F(RoomManipulationTest, AddObject) {
|
||||
TEST_F(RoomManipulationTest, AddInvalidObject) {
|
||||
// Invalid X position (> 63)
|
||||
RoomObject obj(0x10, 100, 20, 3, 0);
|
||||
|
||||
|
||||
auto status = room_->AddObject(obj);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_EQ(room_->GetTileObjects().size(), 0);
|
||||
@@ -49,15 +50,15 @@ TEST_F(RoomManipulationTest, AddInvalidObject) {
|
||||
TEST_F(RoomManipulationTest, RemoveObject) {
|
||||
RoomObject obj1(0x10, 10, 20, 3, 0);
|
||||
RoomObject obj2(0x20, 15, 25, 2, 1);
|
||||
|
||||
|
||||
room_->AddObject(obj1);
|
||||
room_->AddObject(obj2);
|
||||
|
||||
|
||||
EXPECT_EQ(room_->GetTileObjects().size(), 2);
|
||||
|
||||
|
||||
auto status = room_->RemoveObject(0);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
auto objects = room_->GetTileObjects();
|
||||
EXPECT_EQ(objects.size(), 1);
|
||||
EXPECT_EQ(objects[0].id_, 0x20);
|
||||
@@ -71,11 +72,11 @@ TEST_F(RoomManipulationTest, RemoveInvalidIndex) {
|
||||
TEST_F(RoomManipulationTest, UpdateObject) {
|
||||
RoomObject obj(0x10, 10, 20, 3, 0);
|
||||
room_->AddObject(obj);
|
||||
|
||||
|
||||
RoomObject updated(0x20, 15, 25, 5, 1);
|
||||
auto status = room_->UpdateObject(0, updated);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
auto objects = room_->GetTileObjects();
|
||||
EXPECT_EQ(objects[0].id_, 0x20);
|
||||
EXPECT_EQ(objects[0].x(), 15);
|
||||
@@ -85,14 +86,14 @@ TEST_F(RoomManipulationTest, UpdateObject) {
|
||||
TEST_F(RoomManipulationTest, FindObjectAt) {
|
||||
RoomObject obj1(0x10, 10, 20, 3, 0);
|
||||
RoomObject obj2(0x20, 15, 25, 2, 1);
|
||||
|
||||
|
||||
room_->AddObject(obj1);
|
||||
room_->AddObject(obj2);
|
||||
|
||||
|
||||
auto result = room_->FindObjectAt(15, 25, 1);
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.value(), 1);
|
||||
|
||||
|
||||
auto not_found = room_->FindObjectAt(99, 99, 0);
|
||||
EXPECT_FALSE(not_found.ok());
|
||||
}
|
||||
@@ -101,19 +102,19 @@ TEST_F(RoomManipulationTest, ValidateObject) {
|
||||
// Valid Type 1 object
|
||||
RoomObject valid1(0x10, 10, 20, 3, 0);
|
||||
EXPECT_TRUE(room_->ValidateObject(valid1));
|
||||
|
||||
|
||||
// Valid Type 2 object
|
||||
RoomObject valid2(0x110, 30, 40, 0, 1);
|
||||
EXPECT_TRUE(room_->ValidateObject(valid2));
|
||||
|
||||
|
||||
// Invalid X (> 63)
|
||||
RoomObject invalid_x(0x10, 100, 20, 3, 0);
|
||||
EXPECT_FALSE(room_->ValidateObject(invalid_x));
|
||||
|
||||
|
||||
// Invalid layer (> 2)
|
||||
RoomObject invalid_layer(0x10, 10, 20, 3, 5);
|
||||
EXPECT_FALSE(room_->ValidateObject(invalid_layer));
|
||||
|
||||
|
||||
// Invalid size for Type 1 (> 15)
|
||||
RoomObject invalid_size(0x10, 10, 20, 20, 0);
|
||||
EXPECT_FALSE(room_->ValidateObject(invalid_size));
|
||||
@@ -125,21 +126,21 @@ TEST_F(RoomManipulationTest, MultipleOperations) {
|
||||
RoomObject obj(0x10 + i, i * 5, i * 6, i, 0);
|
||||
ASSERT_TRUE(room_->AddObject(obj).ok());
|
||||
}
|
||||
|
||||
|
||||
EXPECT_EQ(room_->GetTileObjects().size(), 5);
|
||||
|
||||
|
||||
// Update middle object
|
||||
RoomObject updated(0x99, 30, 35, 7, 1);
|
||||
ASSERT_TRUE(room_->UpdateObject(2, updated).ok());
|
||||
|
||||
|
||||
// Verify update
|
||||
auto objects = room_->GetTileObjects();
|
||||
EXPECT_EQ(objects[2].id_, 0x99);
|
||||
|
||||
|
||||
// Remove first object
|
||||
ASSERT_TRUE(room_->RemoveObject(0).ok());
|
||||
EXPECT_EQ(room_->GetTileObjects().size(), 4);
|
||||
|
||||
|
||||
// Verify first object is now what was second
|
||||
EXPECT_EQ(room_->GetTileObjects()[0].id_, 0x11);
|
||||
}
|
||||
@@ -149,16 +150,16 @@ TEST_F(RoomManipulationTest, LayerOrganization) {
|
||||
RoomObject layer0_obj(0x10, 10, 10, 2, 0);
|
||||
RoomObject layer1_obj(0x20, 20, 20, 3, 1);
|
||||
RoomObject layer2_obj(0x30, 30, 30, 4, 2);
|
||||
|
||||
|
||||
room_->AddObject(layer0_obj);
|
||||
room_->AddObject(layer1_obj);
|
||||
room_->AddObject(layer2_obj);
|
||||
|
||||
|
||||
// Verify can find by layer
|
||||
EXPECT_TRUE(room_->FindObjectAt(10, 10, 0).ok());
|
||||
EXPECT_TRUE(room_->FindObjectAt(20, 20, 1).ok());
|
||||
EXPECT_TRUE(room_->FindObjectAt(30, 30, 2).ok());
|
||||
|
||||
|
||||
// Wrong layer should not find
|
||||
EXPECT_FALSE(room_->FindObjectAt(10, 10, 1).ok());
|
||||
}
|
||||
@@ -166,4 +167,3 @@ TEST_F(RoomManipulationTest, LayerOrganization) {
|
||||
} // namespace test
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
// correctly for all three object types (Type1, Type2, Type3) based on
|
||||
// ZScream's proven implementation.
|
||||
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
namespace {
|
||||
@@ -45,15 +45,16 @@ TEST(RoomObjectEncodingTest, DetermineObjectTypeType3) {
|
||||
TEST(RoomObjectEncodingTest, Type1EncodeDecodeBasic) {
|
||||
// Type1: xxxxxxss yyyyyyss iiiiiiii
|
||||
// Example: Object ID 0x42, position (10, 20), size 3, layer 0
|
||||
|
||||
|
||||
RoomObject obj(0x42, 10, 20, 3, 0);
|
||||
|
||||
|
||||
// Encode
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
|
||||
|
||||
// Decode
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
|
||||
|
||||
auto decoded =
|
||||
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
|
||||
|
||||
// Verify
|
||||
EXPECT_EQ(decoded.id_, obj.id_);
|
||||
EXPECT_EQ(decoded.x(), obj.x());
|
||||
@@ -69,10 +70,11 @@ TEST(RoomObjectEncodingTest, Type1MaxValues) {
|
||||
// - X < 63 OR Size < 12 (b1 >= 0xFC triggers Type2 detection)
|
||||
// Safe max values: ID=0xF7, X=62, Y=63, Size=15
|
||||
RoomObject obj(0xF7, 62, 63, 15, 2);
|
||||
|
||||
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 2);
|
||||
|
||||
auto decoded =
|
||||
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 2);
|
||||
|
||||
EXPECT_EQ(decoded.id_, obj.id_);
|
||||
EXPECT_EQ(decoded.x(), obj.x());
|
||||
EXPECT_EQ(decoded.y(), obj.y());
|
||||
@@ -82,10 +84,11 @@ TEST(RoomObjectEncodingTest, Type1MaxValues) {
|
||||
TEST(RoomObjectEncodingTest, Type1MinValues) {
|
||||
// Test minimum values for Type1
|
||||
RoomObject obj(0x00, 0, 0, 0, 0);
|
||||
|
||||
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
|
||||
|
||||
auto decoded =
|
||||
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
|
||||
|
||||
EXPECT_EQ(decoded.id_, obj.id_);
|
||||
EXPECT_EQ(decoded.x(), obj.x());
|
||||
EXPECT_EQ(decoded.y(), obj.y());
|
||||
@@ -96,10 +99,11 @@ TEST(RoomObjectEncodingTest, Type1DifferentSizes) {
|
||||
// Test all valid size values (0-15)
|
||||
for (int size = 0; size <= 15; size++) {
|
||||
RoomObject obj(0x30, 15, 20, size, 1);
|
||||
|
||||
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
|
||||
|
||||
auto decoded =
|
||||
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
|
||||
|
||||
EXPECT_EQ(decoded.size(), size) << "Failed for size " << size;
|
||||
}
|
||||
}
|
||||
@@ -108,9 +112,9 @@ TEST(RoomObjectEncodingTest, Type1RealWorldExample1) {
|
||||
// Example from actual ROM: Wall object
|
||||
// Bytes: 0x28 0x50 0x10
|
||||
// Expected: X=10, Y=20, Size=0, ID=0x10
|
||||
|
||||
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(0x28, 0x50, 0x10, 0);
|
||||
|
||||
|
||||
EXPECT_EQ(decoded.x(), 10);
|
||||
EXPECT_EQ(decoded.y(), 20);
|
||||
EXPECT_EQ(decoded.size(), 0);
|
||||
@@ -120,9 +124,9 @@ TEST(RoomObjectEncodingTest, Type1RealWorldExample1) {
|
||||
TEST(RoomObjectEncodingTest, Type1RealWorldExample2) {
|
||||
// Example: Ceiling object with size
|
||||
// Correct bytes for X=10, Y=20, Size=3, ID=0x00: 0x28 0x53 0x00
|
||||
|
||||
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(0x28, 0x53, 0x00, 0);
|
||||
|
||||
|
||||
EXPECT_EQ(decoded.x(), 10);
|
||||
EXPECT_EQ(decoded.y(), 20);
|
||||
EXPECT_EQ(decoded.size(), 3);
|
||||
@@ -136,18 +140,19 @@ TEST(RoomObjectEncodingTest, Type1RealWorldExample2) {
|
||||
TEST(RoomObjectEncodingTest, Type2EncodeDecodeBasic) {
|
||||
// Type2: 111111xx xxxxyyyy yyiiiiii
|
||||
// Example: Object ID 0x125, position (15, 30), size ignored, layer 1
|
||||
|
||||
|
||||
RoomObject obj(0x125, 15, 30, 0, 1);
|
||||
|
||||
|
||||
// Encode
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
|
||||
|
||||
// Verify b1 starts with 0xFC
|
||||
EXPECT_GE(bytes.b1, 0xFC);
|
||||
|
||||
|
||||
// Decode
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
|
||||
|
||||
auto decoded =
|
||||
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
|
||||
|
||||
// Verify
|
||||
EXPECT_EQ(decoded.id_, obj.id_);
|
||||
EXPECT_EQ(decoded.x(), obj.x());
|
||||
@@ -161,10 +166,11 @@ TEST(RoomObjectEncodingTest, Type2MaxValues) {
|
||||
// Safe max: X=63, Y=59, ID=0x13F (b3 = ((59&0x03)<<6)|(0x3F) = 0xFF still!)
|
||||
// Even safer: X=63, Y=63, ID=0x11F (b3 = (0xC0|0x1F) = 0xDF < 0xF8)
|
||||
RoomObject obj(0x11F, 63, 63, 0, 2);
|
||||
|
||||
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 2);
|
||||
|
||||
auto decoded =
|
||||
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 2);
|
||||
|
||||
EXPECT_EQ(decoded.id_, obj.id_);
|
||||
EXPECT_EQ(decoded.x(), obj.x());
|
||||
EXPECT_EQ(decoded.y(), obj.y());
|
||||
@@ -173,12 +179,13 @@ TEST(RoomObjectEncodingTest, Type2MaxValues) {
|
||||
TEST(RoomObjectEncodingTest, Type2RealWorldExample) {
|
||||
// Example: Large brazier (object 0x11C)
|
||||
// Position (8, 12)
|
||||
|
||||
|
||||
RoomObject obj(0x11C, 8, 12, 0, 0);
|
||||
|
||||
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
|
||||
|
||||
auto decoded =
|
||||
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
|
||||
|
||||
EXPECT_EQ(decoded.id_, 0x11C);
|
||||
EXPECT_EQ(decoded.x(), 8);
|
||||
EXPECT_EQ(decoded.y(), 12);
|
||||
@@ -191,18 +198,19 @@ TEST(RoomObjectEncodingTest, Type2RealWorldExample) {
|
||||
TEST(RoomObjectEncodingTest, Type3EncodeDecodeChest) {
|
||||
// Type3: xxxxxxii yyyyyyii 11111iii
|
||||
// Example: Small chest (0xF99), position (5, 10)
|
||||
|
||||
|
||||
RoomObject obj(0xF99, 5, 10, 0, 0);
|
||||
|
||||
|
||||
// Encode
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
|
||||
|
||||
// Verify b3 >= 0xF8
|
||||
EXPECT_GE(bytes.b3, 0xF8);
|
||||
|
||||
|
||||
// Decode
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
|
||||
|
||||
auto decoded =
|
||||
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
|
||||
|
||||
// Verify
|
||||
EXPECT_EQ(decoded.id_, obj.id_);
|
||||
EXPECT_EQ(decoded.x(), obj.x());
|
||||
@@ -211,12 +219,13 @@ TEST(RoomObjectEncodingTest, Type3EncodeDecodeChest) {
|
||||
|
||||
TEST(RoomObjectEncodingTest, Type3EncodeDcodeBigChest) {
|
||||
// Example: Big chest (0xFB1), position (15, 20)
|
||||
|
||||
|
||||
RoomObject obj(0xFB1, 15, 20, 0, 1);
|
||||
|
||||
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
|
||||
|
||||
auto decoded =
|
||||
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
|
||||
|
||||
EXPECT_EQ(decoded.id_, 0xFB1);
|
||||
EXPECT_EQ(decoded.x(), 15);
|
||||
EXPECT_EQ(decoded.y(), 20);
|
||||
@@ -225,9 +234,9 @@ TEST(RoomObjectEncodingTest, Type3EncodeDcodeBigChest) {
|
||||
TEST(RoomObjectEncodingTest, Type3RealWorldExample) {
|
||||
// Example from ROM: Chest at position (10, 15)
|
||||
// Correct bytes for ID 0xF99: 0x29 0x3E 0xF9
|
||||
|
||||
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(0x29, 0x3E, 0xF9, 0);
|
||||
|
||||
|
||||
// Expected: X=10, Y=15, ID=0xF99 (small chest)
|
||||
EXPECT_EQ(decoded.x(), 10);
|
||||
EXPECT_EQ(decoded.y(), 15);
|
||||
@@ -242,40 +251,46 @@ TEST(RoomObjectEncodingTest, LayerPreservation) {
|
||||
// Test that layer information is preserved through encode/decode
|
||||
for (uint8_t layer = 0; layer <= 2; layer++) {
|
||||
RoomObject obj(0x42, 10, 20, 3, layer);
|
||||
|
||||
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, layer);
|
||||
|
||||
EXPECT_EQ(decoded.GetLayerValue(), layer) << "Failed for layer " << (int)layer;
|
||||
auto decoded =
|
||||
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, layer);
|
||||
|
||||
EXPECT_EQ(decoded.GetLayerValue(), layer)
|
||||
<< "Failed for layer " << (int)layer;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RoomObjectEncodingTest, BoundaryBetweenTypes) {
|
||||
// Test boundary values between object types
|
||||
// NOTE: Type1 can only go up to ID 0xF7 (b3 >= 0xF8 triggers Type3)
|
||||
|
||||
|
||||
// Last safe Type1 object
|
||||
RoomObject type1(0xF7, 10, 20, 3, 0);
|
||||
auto bytes1 = type1.EncodeObjectToBytes();
|
||||
auto decoded1 = RoomObject::DecodeObjectFromBytes(bytes1.b1, bytes1.b2, bytes1.b3, 0);
|
||||
auto decoded1 =
|
||||
RoomObject::DecodeObjectFromBytes(bytes1.b1, bytes1.b2, bytes1.b3, 0);
|
||||
EXPECT_EQ(decoded1.id_, 0xF7);
|
||||
|
||||
|
||||
// First Type2 object
|
||||
RoomObject type2(0x100, 10, 20, 0, 0);
|
||||
auto bytes2 = type2.EncodeObjectToBytes();
|
||||
auto decoded2 = RoomObject::DecodeObjectFromBytes(bytes2.b1, bytes2.b2, bytes2.b3, 0);
|
||||
auto decoded2 =
|
||||
RoomObject::DecodeObjectFromBytes(bytes2.b1, bytes2.b2, bytes2.b3, 0);
|
||||
EXPECT_EQ(decoded2.id_, 0x100);
|
||||
|
||||
|
||||
// Last Type2 object
|
||||
RoomObject type2_last(0x13F, 10, 20, 0, 0);
|
||||
auto bytes2_last = type2_last.EncodeObjectToBytes();
|
||||
auto decoded2_last = RoomObject::DecodeObjectFromBytes(bytes2_last.b1, bytes2_last.b2, bytes2_last.b3, 0);
|
||||
auto decoded2_last = RoomObject::DecodeObjectFromBytes(
|
||||
bytes2_last.b1, bytes2_last.b2, bytes2_last.b3, 0);
|
||||
EXPECT_EQ(decoded2_last.id_, 0x13F);
|
||||
|
||||
|
||||
// Type3 objects (start at 0xF80)
|
||||
RoomObject type3(0xF99, 10, 20, 0, 0);
|
||||
auto bytes3 = type3.EncodeObjectToBytes();
|
||||
auto decoded3 = RoomObject::DecodeObjectFromBytes(bytes3.b1, bytes3.b2, bytes3.b3, 0);
|
||||
auto decoded3 =
|
||||
RoomObject::DecodeObjectFromBytes(bytes3.b1, bytes3.b2, bytes3.b3, 0);
|
||||
EXPECT_EQ(decoded3.id_, 0xF99);
|
||||
}
|
||||
|
||||
@@ -283,13 +298,15 @@ TEST(RoomObjectEncodingTest, ZeroPosition) {
|
||||
// Test objects at position (0, 0)
|
||||
RoomObject type1(0x10, 0, 0, 0, 0);
|
||||
auto bytes1 = type1.EncodeObjectToBytes();
|
||||
auto decoded1 = RoomObject::DecodeObjectFromBytes(bytes1.b1, bytes1.b2, bytes1.b3, 0);
|
||||
auto decoded1 =
|
||||
RoomObject::DecodeObjectFromBytes(bytes1.b1, bytes1.b2, bytes1.b3, 0);
|
||||
EXPECT_EQ(decoded1.x(), 0);
|
||||
EXPECT_EQ(decoded1.y(), 0);
|
||||
|
||||
|
||||
RoomObject type2(0x110, 0, 0, 0, 0);
|
||||
auto bytes2 = type2.EncodeObjectToBytes();
|
||||
auto decoded2 = RoomObject::DecodeObjectFromBytes(bytes2.b1, bytes2.b2, bytes2.b3, 0);
|
||||
auto decoded2 =
|
||||
RoomObject::DecodeObjectFromBytes(bytes2.b1, bytes2.b2, bytes2.b3, 0);
|
||||
EXPECT_EQ(decoded2.x(), 0);
|
||||
EXPECT_EQ(decoded2.y(), 0);
|
||||
}
|
||||
@@ -301,21 +318,21 @@ TEST(RoomObjectEncodingTest, ZeroPosition) {
|
||||
TEST(RoomObjectEncodingTest, MultipleObjectsRoundTrip) {
|
||||
// Test encoding/decoding a batch of different objects
|
||||
std::vector<RoomObject> objects;
|
||||
|
||||
|
||||
// Add various objects
|
||||
objects.emplace_back(0x10, 5, 10, 2, 0); // Type1
|
||||
objects.emplace_back(0x42, 15, 20, 5, 1); // Type1
|
||||
objects.emplace_back(0x110, 8, 12, 0, 0); // Type2
|
||||
objects.emplace_back(0x125, 25, 30, 0, 1); // Type2
|
||||
objects.emplace_back(0xF99, 10, 15, 0, 0); // Type3 (chest)
|
||||
objects.emplace_back(0xFB1, 20, 25, 0, 2); // Type3 (big chest)
|
||||
|
||||
objects.emplace_back(0x10, 5, 10, 2, 0); // Type1
|
||||
objects.emplace_back(0x42, 15, 20, 5, 1); // Type1
|
||||
objects.emplace_back(0x110, 8, 12, 0, 0); // Type2
|
||||
objects.emplace_back(0x125, 25, 30, 0, 1); // Type2
|
||||
objects.emplace_back(0xF99, 10, 15, 0, 0); // Type3 (chest)
|
||||
objects.emplace_back(0xFB1, 20, 25, 0, 2); // Type3 (big chest)
|
||||
|
||||
for (size_t i = 0; i < objects.size(); i++) {
|
||||
auto& obj = objects[i];
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
auto decoded = RoomObject::DecodeObjectFromBytes(
|
||||
bytes.b1, bytes.b2, bytes.b3, obj.GetLayerValue());
|
||||
|
||||
|
||||
EXPECT_EQ(decoded.id_, obj.id_) << "Failed at index " << i;
|
||||
EXPECT_EQ(decoded.x(), obj.x()) << "Failed at index " << i;
|
||||
EXPECT_EQ(decoded.y(), obj.y()) << "Failed at index " << i;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
// Test the individual components independently
|
||||
@@ -10,7 +11,7 @@ namespace test {
|
||||
|
||||
/**
|
||||
* @brief Unit tests for individual dungeon components
|
||||
*
|
||||
*
|
||||
* These tests validate component behavior without requiring ROM files
|
||||
* or complex graphics initialization.
|
||||
*/
|
||||
@@ -18,35 +19,31 @@ namespace test {
|
||||
// Test DungeonToolset Component
|
||||
TEST(DungeonToolsetTest, BasicFunctionality) {
|
||||
editor::DungeonToolset toolset;
|
||||
|
||||
|
||||
// Test initial state
|
||||
EXPECT_EQ(toolset.background_type(), editor::DungeonToolset::kBackgroundAny);
|
||||
EXPECT_EQ(toolset.placement_type(), editor::DungeonToolset::kNoType);
|
||||
|
||||
|
||||
// Test state changes
|
||||
toolset.set_background_type(editor::DungeonToolset::kBackground1);
|
||||
EXPECT_EQ(toolset.background_type(), editor::DungeonToolset::kBackground1);
|
||||
|
||||
|
||||
toolset.set_placement_type(editor::DungeonToolset::kObject);
|
||||
EXPECT_EQ(toolset.placement_type(), editor::DungeonToolset::kObject);
|
||||
|
||||
|
||||
// Test all background types
|
||||
toolset.set_background_type(editor::DungeonToolset::kBackground2);
|
||||
EXPECT_EQ(toolset.background_type(), editor::DungeonToolset::kBackground2);
|
||||
|
||||
|
||||
toolset.set_background_type(editor::DungeonToolset::kBackground3);
|
||||
EXPECT_EQ(toolset.background_type(), editor::DungeonToolset::kBackground3);
|
||||
|
||||
|
||||
// Test all placement types
|
||||
std::vector<editor::DungeonToolset::PlacementType> placement_types = {
|
||||
editor::DungeonToolset::kSprite,
|
||||
editor::DungeonToolset::kItem,
|
||||
editor::DungeonToolset::kEntrance,
|
||||
editor::DungeonToolset::kDoor,
|
||||
editor::DungeonToolset::kChest,
|
||||
editor::DungeonToolset::kBlock
|
||||
};
|
||||
|
||||
editor::DungeonToolset::kSprite, editor::DungeonToolset::kItem,
|
||||
editor::DungeonToolset::kEntrance, editor::DungeonToolset::kDoor,
|
||||
editor::DungeonToolset::kChest, editor::DungeonToolset::kBlock};
|
||||
|
||||
for (auto type : placement_types) {
|
||||
toolset.set_placement_type(type);
|
||||
EXPECT_EQ(toolset.placement_type(), type);
|
||||
@@ -56,47 +53,48 @@ TEST(DungeonToolsetTest, BasicFunctionality) {
|
||||
// Test DungeonToolset Callbacks
|
||||
TEST(DungeonToolsetTest, CallbackFunctionality) {
|
||||
editor::DungeonToolset toolset;
|
||||
|
||||
|
||||
// Test callback setup (should not crash)
|
||||
bool undo_called = false;
|
||||
bool redo_called = false;
|
||||
bool palette_called = false;
|
||||
|
||||
|
||||
toolset.SetUndoCallback([&undo_called]() { undo_called = true; });
|
||||
toolset.SetRedoCallback([&redo_called]() { redo_called = true; });
|
||||
toolset.SetPaletteToggleCallback([&palette_called]() { palette_called = true; });
|
||||
|
||||
toolset.SetPaletteToggleCallback(
|
||||
[&palette_called]() { palette_called = true; });
|
||||
|
||||
// Callbacks are set but won't be triggered without UI interaction
|
||||
// The fact that we can set them without crashing validates the interface
|
||||
EXPECT_FALSE(undo_called); // Not called yet
|
||||
EXPECT_FALSE(redo_called); // Not called yet
|
||||
EXPECT_FALSE(undo_called); // Not called yet
|
||||
EXPECT_FALSE(redo_called); // Not called yet
|
||||
EXPECT_FALSE(palette_called); // Not called yet
|
||||
}
|
||||
|
||||
// Test DungeonUsageTracker Component
|
||||
TEST(DungeonUsageTrackerTest, BasicFunctionality) {
|
||||
editor::DungeonUsageTracker tracker;
|
||||
|
||||
|
||||
// Test initial state
|
||||
EXPECT_TRUE(tracker.GetBlocksetUsage().empty());
|
||||
EXPECT_TRUE(tracker.GetSpritesetUsage().empty());
|
||||
EXPECT_TRUE(tracker.GetPaletteUsage().empty());
|
||||
|
||||
|
||||
// Test initial selection state
|
||||
EXPECT_EQ(tracker.GetSelectedBlockset(), 0xFFFF);
|
||||
EXPECT_EQ(tracker.GetSelectedSpriteset(), 0xFFFF);
|
||||
EXPECT_EQ(tracker.GetSelectedPalette(), 0xFFFF);
|
||||
|
||||
|
||||
// Test selection setters
|
||||
tracker.SetSelectedBlockset(0x01);
|
||||
EXPECT_EQ(tracker.GetSelectedBlockset(), 0x01);
|
||||
|
||||
|
||||
tracker.SetSelectedSpriteset(0x02);
|
||||
EXPECT_EQ(tracker.GetSelectedSpriteset(), 0x02);
|
||||
|
||||
|
||||
tracker.SetSelectedPalette(0x03);
|
||||
EXPECT_EQ(tracker.GetSelectedPalette(), 0x03);
|
||||
|
||||
|
||||
// Test clear functionality
|
||||
tracker.ClearUsageStats();
|
||||
EXPECT_EQ(tracker.GetSelectedBlockset(), 0xFFFF);
|
||||
@@ -108,16 +106,16 @@ TEST(DungeonUsageTrackerTest, BasicFunctionality) {
|
||||
TEST(ComponentArchitectureTest, FileSizeReduction) {
|
||||
// This test validates that the refactoring actually reduced complexity
|
||||
// by ensuring the component files exist and are reasonably sized
|
||||
|
||||
|
||||
// The main dungeon_editor.cc should be significantly smaller
|
||||
// Before: ~1444 lines, Target: ~400-600 lines
|
||||
|
||||
|
||||
// We can't directly test file sizes, but we can test that
|
||||
// the components exist and function properly
|
||||
|
||||
|
||||
editor::DungeonToolset toolset;
|
||||
editor::DungeonUsageTracker tracker;
|
||||
|
||||
|
||||
// If we can create the components, the refactoring was successful
|
||||
EXPECT_EQ(toolset.background_type(), editor::DungeonToolset::kBackgroundAny);
|
||||
EXPECT_TRUE(tracker.GetBlocksetUsage().empty());
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "zelda3/dungeon/object_parser.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "zelda3/dungeon/object_parser.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
@@ -12,7 +11,7 @@ class ObjectParserStructsTest : public ::testing::Test {
|
||||
|
||||
TEST_F(ObjectParserStructsTest, ObjectRoutineInfoDefaultConstructor) {
|
||||
zelda3::ObjectRoutineInfo info;
|
||||
|
||||
|
||||
EXPECT_EQ(info.routine_ptr, 0);
|
||||
EXPECT_EQ(info.tile_ptr, 0);
|
||||
EXPECT_EQ(info.tile_count, 0);
|
||||
@@ -22,7 +21,7 @@ TEST_F(ObjectParserStructsTest, ObjectRoutineInfoDefaultConstructor) {
|
||||
|
||||
TEST_F(ObjectParserStructsTest, ObjectSubtypeInfoDefaultConstructor) {
|
||||
zelda3::ObjectSubtypeInfo info;
|
||||
|
||||
|
||||
EXPECT_EQ(info.subtype, 0);
|
||||
EXPECT_EQ(info.subtype_ptr, 0);
|
||||
EXPECT_EQ(info.routine_ptr, 0);
|
||||
@@ -31,7 +30,7 @@ TEST_F(ObjectParserStructsTest, ObjectSubtypeInfoDefaultConstructor) {
|
||||
|
||||
TEST_F(ObjectParserStructsTest, ObjectSizeInfoDefaultConstructor) {
|
||||
zelda3::ObjectSizeInfo info;
|
||||
|
||||
|
||||
EXPECT_EQ(info.width_tiles, 0);
|
||||
EXPECT_EQ(info.height_tiles, 0);
|
||||
EXPECT_TRUE(info.is_horizontal);
|
||||
@@ -41,13 +40,13 @@ TEST_F(ObjectParserStructsTest, ObjectSizeInfoDefaultConstructor) {
|
||||
|
||||
TEST_F(ObjectParserStructsTest, ObjectRoutineInfoAssignment) {
|
||||
zelda3::ObjectRoutineInfo info;
|
||||
|
||||
|
||||
info.routine_ptr = 0x12345;
|
||||
info.tile_ptr = 0x67890;
|
||||
info.tile_count = 8;
|
||||
info.is_repeatable = true;
|
||||
info.is_orientation_dependent = true;
|
||||
|
||||
|
||||
EXPECT_EQ(info.routine_ptr, 0x12345);
|
||||
EXPECT_EQ(info.tile_ptr, 0x67890);
|
||||
EXPECT_EQ(info.tile_count, 8);
|
||||
@@ -57,12 +56,12 @@ TEST_F(ObjectParserStructsTest, ObjectRoutineInfoAssignment) {
|
||||
|
||||
TEST_F(ObjectParserStructsTest, ObjectSubtypeInfoAssignment) {
|
||||
zelda3::ObjectSubtypeInfo info;
|
||||
|
||||
|
||||
info.subtype = 2;
|
||||
info.subtype_ptr = 0x83F0;
|
||||
info.routine_ptr = 0x8470;
|
||||
info.max_tile_count = 16;
|
||||
|
||||
|
||||
EXPECT_EQ(info.subtype, 2);
|
||||
EXPECT_EQ(info.subtype_ptr, 0x83F0);
|
||||
EXPECT_EQ(info.routine_ptr, 0x8470);
|
||||
@@ -71,13 +70,13 @@ TEST_F(ObjectParserStructsTest, ObjectSubtypeInfoAssignment) {
|
||||
|
||||
TEST_F(ObjectParserStructsTest, ObjectSizeInfoAssignment) {
|
||||
zelda3::ObjectSizeInfo info;
|
||||
|
||||
|
||||
info.width_tiles = 4;
|
||||
info.height_tiles = 2;
|
||||
info.is_horizontal = false;
|
||||
info.is_repeatable = true;
|
||||
info.repeat_count = 3;
|
||||
|
||||
|
||||
EXPECT_EQ(info.width_tiles, 4);
|
||||
EXPECT_EQ(info.height_tiles, 2);
|
||||
EXPECT_FALSE(info.is_horizontal);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
#include "zelda3/overworld/overworld_map.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -18,42 +20,44 @@ class OverworldTest : public ::testing::Test {
|
||||
// Create a mock ROM for testing
|
||||
rom_ = std::make_unique<Rom>();
|
||||
// Initialize with minimal ROM data for testing
|
||||
std::vector<uint8_t> mock_rom_data(0x200000, 0x00); // 2MB ROM filled with 0x00
|
||||
|
||||
std::vector<uint8_t> mock_rom_data(0x200000,
|
||||
0x00); // 2MB ROM filled with 0x00
|
||||
|
||||
// Set up some basic ROM data that OverworldMap expects
|
||||
mock_rom_data[0x140145] = 0xFF; // OverworldCustomASMHasBeenApplied = vanilla
|
||||
|
||||
mock_rom_data[0x140145] =
|
||||
0xFF; // OverworldCustomASMHasBeenApplied = vanilla
|
||||
|
||||
// Message IDs (2 bytes per map)
|
||||
for (int i = 0; i < 160; i++) { // 160 maps total
|
||||
for (int i = 0; i < 160; i++) { // 160 maps total
|
||||
mock_rom_data[0x3F51D + (i * 2)] = 0x00;
|
||||
mock_rom_data[0x3F51D + (i * 2) + 1] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
// Area graphics (1 byte per map)
|
||||
for (int i = 0; i < 160; i++) {
|
||||
mock_rom_data[0x7C9C + i] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
// Area palettes (1 byte per map)
|
||||
for (int i = 0; i < 160; i++) {
|
||||
mock_rom_data[0x7D1C + i] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
// Screen sizes (1 byte per map)
|
||||
for (int i = 0; i < 160; i++) {
|
||||
mock_rom_data[0x1788D + i] = 0x01; // Small area by default
|
||||
mock_rom_data[0x1788D + i] = 0x01; // Small area by default
|
||||
}
|
||||
|
||||
|
||||
// Sprite sets (1 byte per map)
|
||||
for (int i = 0; i < 160; i++) {
|
||||
mock_rom_data[0x7A41 + i] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
// Sprite palettes (1 byte per map)
|
||||
for (int i = 0; i < 160; i++) {
|
||||
mock_rom_data[0x7B41 + i] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
// Music (1 byte per map)
|
||||
for (int i = 0; i < 160; i++) {
|
||||
mock_rom_data[0x14303 + i] = 0x00;
|
||||
@@ -61,26 +65,26 @@ class OverworldTest : public ::testing::Test {
|
||||
mock_rom_data[0x14303 + 0x80 + i] = 0x00;
|
||||
mock_rom_data[0x14303 + 0xC0 + i] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
// Dark World music
|
||||
for (int i = 0; i < 64; i++) {
|
||||
mock_rom_data[0x14403 + i] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
// Special world graphics and palettes
|
||||
for (int i = 0; i < 32; i++) {
|
||||
mock_rom_data[0x16821 + i] = 0x00;
|
||||
mock_rom_data[0x16831 + i] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
// Special world sprite graphics and palettes
|
||||
for (int i = 0; i < 32; i++) {
|
||||
mock_rom_data[0x0166E1 + i] = 0x00;
|
||||
mock_rom_data[0x016701 + i] = 0x00;
|
||||
}
|
||||
|
||||
|
||||
rom_->LoadFromData(mock_rom_data);
|
||||
|
||||
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
}
|
||||
|
||||
@@ -96,7 +100,7 @@ class OverworldTest : public ::testing::Test {
|
||||
TEST_F(OverworldTest, OverworldMapInitialization) {
|
||||
// Test that OverworldMap can be created with valid parameters
|
||||
OverworldMap map(0, rom_.get());
|
||||
|
||||
|
||||
EXPECT_EQ(map.area_graphics(), 0);
|
||||
EXPECT_EQ(map.area_palette(), 0);
|
||||
EXPECT_EQ(map.message_id(), 0);
|
||||
@@ -117,27 +121,27 @@ TEST_F(OverworldTest, AreaSizeEnumValues) {
|
||||
|
||||
TEST_F(OverworldTest, OverworldMapSetters) {
|
||||
OverworldMap map(0, rom_.get());
|
||||
|
||||
|
||||
// Test main palette setter
|
||||
map.set_main_palette(5);
|
||||
EXPECT_EQ(map.main_palette(), 5);
|
||||
|
||||
|
||||
// Test area-specific background color setter
|
||||
map.set_area_specific_bg_color(0x7FFF);
|
||||
EXPECT_EQ(map.area_specific_bg_color(), 0x7FFF);
|
||||
|
||||
|
||||
// Test subscreen overlay setter
|
||||
map.set_subscreen_overlay(0x1234);
|
||||
EXPECT_EQ(map.subscreen_overlay(), 0x1234);
|
||||
|
||||
|
||||
// Test animated GFX setter
|
||||
map.set_animated_gfx(10);
|
||||
EXPECT_EQ(map.animated_gfx(), 10);
|
||||
|
||||
|
||||
// Test custom tileset setter
|
||||
map.set_custom_tileset(0, 20);
|
||||
EXPECT_EQ(map.custom_tileset(0), 20);
|
||||
|
||||
|
||||
// Test area size setter
|
||||
map.SetAreaSize(AreaSizeEnum::LargeArea);
|
||||
EXPECT_EQ(map.area_size(), AreaSizeEnum::LargeArea);
|
||||
@@ -145,14 +149,14 @@ TEST_F(OverworldTest, OverworldMapSetters) {
|
||||
|
||||
TEST_F(OverworldTest, OverworldMapLargeMapSetup) {
|
||||
OverworldMap map(0, rom_.get());
|
||||
|
||||
|
||||
// Test SetAsLargeMap
|
||||
map.SetAsLargeMap(10, 2);
|
||||
EXPECT_EQ(map.parent(), 10);
|
||||
EXPECT_EQ(map.large_index(), 2);
|
||||
EXPECT_TRUE(map.is_large_map());
|
||||
EXPECT_EQ(map.area_size(), AreaSizeEnum::LargeArea);
|
||||
|
||||
|
||||
// Test SetAsSmallMap
|
||||
map.SetAsSmallMap(5);
|
||||
EXPECT_EQ(map.parent(), 5);
|
||||
@@ -163,13 +167,13 @@ TEST_F(OverworldTest, OverworldMapLargeMapSetup) {
|
||||
|
||||
TEST_F(OverworldTest, OverworldMapCustomTilesetArray) {
|
||||
OverworldMap map(0, rom_.get());
|
||||
|
||||
|
||||
// Test setting all 8 custom tileset slots
|
||||
for (int i = 0; i < 8; i++) {
|
||||
map.set_custom_tileset(i, i + 10);
|
||||
EXPECT_EQ(map.custom_tileset(i), i + 10);
|
||||
}
|
||||
|
||||
|
||||
// Test mutable access
|
||||
for (int i = 0; i < 8; i++) {
|
||||
*map.mutable_custom_tileset(i) = i + 20;
|
||||
@@ -179,21 +183,21 @@ TEST_F(OverworldTest, OverworldMapCustomTilesetArray) {
|
||||
|
||||
TEST_F(OverworldTest, OverworldMapSpriteProperties) {
|
||||
OverworldMap map(0, rom_.get());
|
||||
|
||||
|
||||
// Test sprite graphics setters
|
||||
map.set_sprite_graphics(0, 1);
|
||||
map.set_sprite_graphics(1, 2);
|
||||
map.set_sprite_graphics(2, 3);
|
||||
|
||||
|
||||
EXPECT_EQ(map.sprite_graphics(0), 1);
|
||||
EXPECT_EQ(map.sprite_graphics(1), 2);
|
||||
EXPECT_EQ(map.sprite_graphics(2), 3);
|
||||
|
||||
|
||||
// Test sprite palette setters
|
||||
map.set_sprite_palette(0, 4);
|
||||
map.set_sprite_palette(1, 5);
|
||||
map.set_sprite_palette(2, 6);
|
||||
|
||||
|
||||
EXPECT_EQ(map.sprite_palette(0), 4);
|
||||
EXPECT_EQ(map.sprite_palette(1), 5);
|
||||
EXPECT_EQ(map.sprite_palette(2), 6);
|
||||
@@ -201,52 +205,52 @@ TEST_F(OverworldTest, OverworldMapSpriteProperties) {
|
||||
|
||||
TEST_F(OverworldTest, OverworldMapBasicProperties) {
|
||||
OverworldMap map(0, rom_.get());
|
||||
|
||||
|
||||
// Test basic property setters
|
||||
map.set_area_graphics(15);
|
||||
EXPECT_EQ(map.area_graphics(), 15);
|
||||
|
||||
|
||||
map.set_area_palette(8);
|
||||
EXPECT_EQ(map.area_palette(), 8);
|
||||
|
||||
|
||||
map.set_message_id(0x1234);
|
||||
EXPECT_EQ(map.message_id(), 0x1234);
|
||||
}
|
||||
|
||||
TEST_F(OverworldTest, OverworldMapMutableAccessors) {
|
||||
OverworldMap map(0, rom_.get());
|
||||
|
||||
|
||||
// Test mutable accessors
|
||||
*map.mutable_area_graphics() = 25;
|
||||
EXPECT_EQ(map.area_graphics(), 25);
|
||||
|
||||
|
||||
*map.mutable_area_palette() = 12;
|
||||
EXPECT_EQ(map.area_palette(), 12);
|
||||
|
||||
|
||||
*map.mutable_message_id() = 0x5678;
|
||||
EXPECT_EQ(map.message_id(), 0x5678);
|
||||
|
||||
|
||||
*map.mutable_main_palette() = 7;
|
||||
EXPECT_EQ(map.main_palette(), 7);
|
||||
|
||||
|
||||
*map.mutable_animated_gfx() = 15;
|
||||
EXPECT_EQ(map.animated_gfx(), 15);
|
||||
|
||||
|
||||
*map.mutable_subscreen_overlay() = 0x9ABC;
|
||||
EXPECT_EQ(map.subscreen_overlay(), 0x9ABC);
|
||||
}
|
||||
|
||||
TEST_F(OverworldTest, OverworldMapDestroy) {
|
||||
OverworldMap map(0, rom_.get());
|
||||
|
||||
|
||||
// Set some properties
|
||||
map.set_area_graphics(10);
|
||||
map.set_main_palette(5);
|
||||
map.SetAreaSize(AreaSizeEnum::LargeArea);
|
||||
|
||||
|
||||
// Destroy and verify reset
|
||||
map.Destroy();
|
||||
|
||||
|
||||
EXPECT_EQ(map.area_graphics(), 0);
|
||||
EXPECT_EQ(map.main_palette(), 0);
|
||||
EXPECT_EQ(map.area_size(), AreaSizeEnum::SmallArea);
|
||||
@@ -257,34 +261,34 @@ TEST_F(OverworldTest, OverworldMapDestroy) {
|
||||
TEST_F(OverworldTest, WorldBasedSpriteFiltering) {
|
||||
// This test verifies the logic used in DrawOverworldSprites
|
||||
// for filtering sprites by world
|
||||
|
||||
int current_world = 1; // Dark World
|
||||
int sprite_map_id = 0x50; // Map 0x50 (Dark World)
|
||||
|
||||
|
||||
int current_world = 1; // Dark World
|
||||
int sprite_map_id = 0x50; // Map 0x50 (Dark World)
|
||||
|
||||
// Test that sprite should be shown for Dark World
|
||||
bool should_show = (sprite_map_id < 0x40 + (current_world * 0x40) &&
|
||||
sprite_map_id >= (current_world * 0x40));
|
||||
EXPECT_TRUE(should_show);
|
||||
|
||||
|
||||
// Test that sprite should NOT be shown for Light World
|
||||
current_world = 0; // Light World
|
||||
current_world = 0; // Light World
|
||||
should_show = (sprite_map_id < 0x40 + (current_world * 0x40) &&
|
||||
sprite_map_id >= (current_world * 0x40));
|
||||
EXPECT_FALSE(should_show);
|
||||
|
||||
|
||||
// Test boundary conditions
|
||||
current_world = 1; // Dark World
|
||||
sprite_map_id = 0x40; // First Dark World map
|
||||
current_world = 1; // Dark World
|
||||
sprite_map_id = 0x40; // First Dark World map
|
||||
should_show = (sprite_map_id < 0x40 + (current_world * 0x40) &&
|
||||
sprite_map_id >= (current_world * 0x40));
|
||||
EXPECT_TRUE(should_show);
|
||||
|
||||
sprite_map_id = 0x7F; // Last Dark World map
|
||||
|
||||
sprite_map_id = 0x7F; // Last Dark World map
|
||||
should_show = (sprite_map_id < 0x40 + (current_world * 0x40) &&
|
||||
sprite_map_id >= (current_world * 0x40));
|
||||
EXPECT_TRUE(should_show);
|
||||
|
||||
sprite_map_id = 0x80; // First Special World map
|
||||
|
||||
sprite_map_id = 0x80; // First Special World map
|
||||
should_show = (sprite_map_id < 0x40 + (current_world * 0x40) &&
|
||||
sprite_map_id >= (current_world * 0x40));
|
||||
EXPECT_FALSE(should_show);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace test {
|
||||
|
||||
/**
|
||||
* @brief Simplified test framework for dungeon object rendering
|
||||
*
|
||||
*
|
||||
* This provides a clean, focused testing environment for dungeon object
|
||||
* functionality without the complexity of full integration tests.
|
||||
*/
|
||||
@@ -26,18 +26,18 @@ class TestDungeonObjects : public ::testing::Test {
|
||||
// Test helpers
|
||||
absl::Status CreateTestRom();
|
||||
absl::Status SetupObjectData();
|
||||
|
||||
|
||||
// Mock data generators
|
||||
std::vector<uint8_t> CreateObjectSubtypeTable(int base_addr, int count);
|
||||
std::vector<uint8_t> CreateTileData(int base_addr, int tile_count);
|
||||
std::vector<uint8_t> CreateRoomHeader(int room_id);
|
||||
|
||||
std::unique_ptr<MockRom> test_rom_;
|
||||
|
||||
|
||||
// Test constants
|
||||
static constexpr int kTestObjectId = 0x01;
|
||||
static constexpr int kTestRoomId = 0x00;
|
||||
static constexpr size_t kTestRomSize = 0x100000; // 1MB test ROM
|
||||
static constexpr size_t kTestRomSize = 0x100000; // 1MB test ROM
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -5,27 +5,28 @@
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <SDL.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "absl/debugging/failure_signal_handler.h"
|
||||
#include "absl/debugging/symbolize.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "app/controller.h"
|
||||
#include "app/gfx/backend/sdl2_renderer.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "e2e/canvas_selection_test.h"
|
||||
#include "e2e/dungeon_editor_smoke_test.h"
|
||||
#include "e2e/framework_smoke_test.h"
|
||||
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||
#include "imgui/imgui.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/platform/window.h"
|
||||
#include "app/controller.h"
|
||||
#include "app/gfx/backend/sdl2_renderer.h"
|
||||
#include "e2e/canvas_selection_test.h"
|
||||
#include "e2e/framework_smoke_test.h"
|
||||
#include "e2e/dungeon_editor_smoke_test.h"
|
||||
|
||||
// #include "test_editor.h" // Not used in main
|
||||
|
||||
@@ -34,17 +35,17 @@ namespace test {
|
||||
|
||||
// Test execution modes for AI agents and developers
|
||||
enum class TestMode {
|
||||
kAll, // Run all tests (default)
|
||||
kUnit, // Run only unit tests
|
||||
kIntegration, // Run only integration tests
|
||||
kE2E, // Run only end-to-end tests
|
||||
kRomDependent, // Run ROM-dependent tests only
|
||||
kZSCustomOverworld, // Run ZSCustomOverworld specific tests
|
||||
kCore, // Run core functionality tests
|
||||
kGraphics, // Run graphics-related tests
|
||||
kEditor, // Run editor tests
|
||||
kDeprecated, // Run deprecated tests (for cleanup)
|
||||
kSpecific // Run specific test pattern
|
||||
kAll, // Run all tests (default)
|
||||
kUnit, // Run only unit tests
|
||||
kIntegration, // Run only integration tests
|
||||
kE2E, // Run only end-to-end tests
|
||||
kRomDependent, // Run ROM-dependent tests only
|
||||
kZSCustomOverworld, // Run ZSCustomOverworld specific tests
|
||||
kCore, // Run core functionality tests
|
||||
kGraphics, // Run graphics-related tests
|
||||
kEditor, // Run editor tests
|
||||
kDeprecated, // Run deprecated tests (for cleanup)
|
||||
kSpecific // Run specific test pattern
|
||||
};
|
||||
|
||||
struct TestConfig {
|
||||
@@ -61,7 +62,7 @@ struct TestConfig {
|
||||
// Parse command line arguments for better AI agent testing support
|
||||
TestConfig ParseArguments(int argc, char* argv[]) {
|
||||
TestConfig config;
|
||||
|
||||
|
||||
std::cout << "Available options:\n"
|
||||
<< " --ui : Enable UI tests\n"
|
||||
<< " --show-gui : Show GUI during tests\n"
|
||||
@@ -71,10 +72,10 @@ TestConfig ParseArguments(int argc, char* argv[]) {
|
||||
<< " --rom=<path> : Specify ROM file path\n"
|
||||
<< " --pattern=<pat> : Run tests matching pattern\n"
|
||||
<< std::endl;
|
||||
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
std::cout << "YAZE Test Runner - Enhanced for AI Agent Testing\n\n";
|
||||
std::cout << "Usage: yaze_test [options] [test_pattern]\n\n";
|
||||
@@ -144,7 +145,7 @@ TestConfig ParseArguments(int argc, char* argv[]) {
|
||||
config.test_pattern = arg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -154,15 +155,15 @@ void SetupTestEnvironment(const TestConfig& config) {
|
||||
if (!config.rom_path.empty()) {
|
||||
SDL_setenv("YAZE_TEST_ROM_PATH", config.rom_path.c_str(), 1);
|
||||
}
|
||||
|
||||
|
||||
if (config.skip_rom_tests) {
|
||||
SDL_setenv("YAZE_SKIP_ROM_TESTS", "1", 1);
|
||||
}
|
||||
|
||||
|
||||
if (config.enable_ui_tests) {
|
||||
SDL_setenv("YAZE_ENABLE_UI_TESTS", "1", 1);
|
||||
}
|
||||
|
||||
|
||||
if (config.verbose) {
|
||||
SDL_setenv("YAZE_VERBOSE_TESTS", "1", 1);
|
||||
}
|
||||
@@ -171,7 +172,7 @@ void SetupTestEnvironment(const TestConfig& config) {
|
||||
// Configure Google Test filters based on test mode
|
||||
void ConfigureTestFilters(const TestConfig& config) {
|
||||
std::vector<std::string> filters;
|
||||
|
||||
|
||||
switch (config.mode) {
|
||||
case TestMode::kUnit:
|
||||
filters.push_back("UnitTest.*");
|
||||
@@ -215,16 +216,17 @@ void ConfigureTestFilters(const TestConfig& config) {
|
||||
// No filters - run all tests
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (!filters.empty()) {
|
||||
std::string filter_string;
|
||||
for (size_t i = 0; i < filters.size(); i++) {
|
||||
if (i > 0) filter_string += ":";
|
||||
if (i > 0)
|
||||
filter_string += ":";
|
||||
filter_string += filters[i];
|
||||
}
|
||||
|
||||
|
||||
::testing::GTEST_FLAG(filter) = filter_string;
|
||||
|
||||
|
||||
if (config.verbose) {
|
||||
std::cout << "Test filter: " << filter_string << std::endl;
|
||||
}
|
||||
@@ -254,45 +256,52 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// Parse command line arguments
|
||||
auto config = yaze::test::ParseArguments(argc, argv);
|
||||
|
||||
|
||||
// Set up test environment
|
||||
yaze::test::SetupTestEnvironment(config);
|
||||
|
||||
|
||||
// Configure test filters
|
||||
yaze::test::ConfigureTestFilters(config);
|
||||
|
||||
// Initialize Google Test
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
|
||||
if (config.enable_ui_tests) {
|
||||
// Create a window
|
||||
yaze::core::Window window;
|
||||
// Create renderer for test
|
||||
auto test_renderer = std::make_unique<yaze::gfx::SDL2Renderer>();
|
||||
yaze::core::CreateWindow(window, test_renderer.get(), SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
yaze::core::CreateWindow(window, test_renderer.get(),
|
||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
|
||||
// Renderer is now owned by test
|
||||
|
||||
// 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
|
||||
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.
|
||||
// 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;
|
||||
style.WindowRounding = 0.0f;
|
||||
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
|
||||
}
|
||||
|
||||
// Setup Platform/Renderer backends
|
||||
SDL_Renderer* sdl_renderer = static_cast<SDL_Renderer*>(test_renderer->GetBackendRenderer());
|
||||
SDL_Renderer* sdl_renderer =
|
||||
static_cast<SDL_Renderer*>(test_renderer->GetBackendRenderer());
|
||||
ImGui_ImplSDL2_InitForSDLRenderer(window.window_.get(), sdl_renderer);
|
||||
ImGui_ImplSDLRenderer2_Init(sdl_renderer);
|
||||
|
||||
@@ -304,23 +313,28 @@ int main(int argc, char* argv[]) {
|
||||
test_io.ConfigRunSpeed = config.test_speed; // Use configured speed
|
||||
test_io.ConfigVerboseLevel = ImGuiTestVerboseLevel_Info;
|
||||
test_io.ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Debug;
|
||||
|
||||
|
||||
// Log test speed mode
|
||||
const char* speed_name = "Fast";
|
||||
if (config.test_speed == ImGuiTestRunSpeed_Normal) speed_name = "Normal";
|
||||
else if (config.test_speed == ImGuiTestRunSpeed_Cinematic) speed_name = "Cinematic";
|
||||
if (config.test_speed == ImGuiTestRunSpeed_Normal)
|
||||
speed_name = "Normal";
|
||||
else if (config.test_speed == ImGuiTestRunSpeed_Cinematic)
|
||||
speed_name = "Cinematic";
|
||||
std::cout << "Running tests in " << speed_name << " mode" << std::endl;
|
||||
|
||||
|
||||
// Register E2E tests only for GUI test targets (they have the source files)
|
||||
#ifdef YAZE_GUI_TEST_TARGET
|
||||
ImGuiTest* smoke_test = IM_REGISTER_TEST(engine, "E2ETest", "FrameworkSmokeTest");
|
||||
ImGuiTest* smoke_test =
|
||||
IM_REGISTER_TEST(engine, "E2ETest", "FrameworkSmokeTest");
|
||||
smoke_test->TestFunc = E2ETest_FrameworkSmokeTest;
|
||||
|
||||
ImGuiTest* canvas_test = IM_REGISTER_TEST(engine, "E2ETest", "CanvasSelectionTest");
|
||||
ImGuiTest* canvas_test =
|
||||
IM_REGISTER_TEST(engine, "E2ETest", "CanvasSelectionTest");
|
||||
canvas_test->TestFunc = E2ETest_CanvasSelectionTest;
|
||||
canvas_test->UserData = &controller;
|
||||
|
||||
ImGuiTest* dungeon_test = IM_REGISTER_TEST(engine, "E2ETest", "DungeonEditorSmokeTest");
|
||||
|
||||
ImGuiTest* dungeon_test =
|
||||
IM_REGISTER_TEST(engine, "E2ETest", "DungeonEditorSmokeTest");
|
||||
dungeon_test->TestFunc = E2ETest_DungeonEditorV2SmokeTest;
|
||||
dungeon_test->UserData = &controller;
|
||||
#endif
|
||||
@@ -328,45 +342,47 @@ int main(int argc, char* argv[]) {
|
||||
// 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;
|
||||
}
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||
if (event.type == SDL_QUIT) {
|
||||
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);
|
||||
if (event.type == SDL_WINDOWEVENT &&
|
||||
event.window.event == SDL_WINDOWEVENT_CLOSE &&
|
||||
event.window.windowID == SDL_GetWindowID(window.window_.get())) {
|
||||
done = true;
|
||||
}
|
||||
controller.DoRender();
|
||||
}
|
||||
|
||||
// End the Dear ImGui frame
|
||||
ImGui::Render();
|
||||
test_renderer->Clear();
|
||||
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), sdl_renderer);
|
||||
test_renderer->Present();
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplSDLRenderer2_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// 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);
|
||||
}
|
||||
// Render the UI
|
||||
if (config.show_gui) {
|
||||
ImGuiTestEngine_ShowTestEngineWindows(engine, &config.show_gui);
|
||||
}
|
||||
controller.DoRender();
|
||||
|
||||
// Run test engine
|
||||
ImGuiTestEngine_PostSwap(engine);
|
||||
// End the Dear ImGui frame
|
||||
ImGui::Render();
|
||||
test_renderer->Clear();
|
||||
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), sdl_renderer);
|
||||
test_renderer->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
|
||||
@@ -388,10 +404,10 @@ int main(int argc, char* argv[]) {
|
||||
} else {
|
||||
// Run tests
|
||||
int result = RUN_ALL_TESTS();
|
||||
|
||||
|
||||
// Cleanup SDL
|
||||
SDL_Quit();
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// to ensure reliable test discovery and execution in automated environments
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "absl/debugging/failure_signal_handler.h"
|
||||
@@ -23,11 +24,11 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// Initialize Google Test with minimal configuration
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
|
||||
// Set up basic test environment
|
||||
::testing::FLAGS_gtest_color = "yes";
|
||||
::testing::FLAGS_gtest_print_time = true;
|
||||
|
||||
|
||||
// For CI builds, skip ROM-dependent tests by default
|
||||
// These tests require actual ROM files which aren't available in CI
|
||||
std::string filter = ::testing::GTEST_FLAG(filter);
|
||||
@@ -35,12 +36,12 @@ int main(int argc, char* argv[]) {
|
||||
// Default filter for CI: exclude ROM-dependent and E2E tests
|
||||
::testing::GTEST_FLAG(filter) = "-*RomTest*:-*E2E*:-*ZSCustomOverworld*";
|
||||
}
|
||||
|
||||
|
||||
std::cout << "Running YAZE tests in CI mode..." << std::endl;
|
||||
std::cout << "Test filter: " << ::testing::GTEST_FLAG(filter) << std::endl;
|
||||
|
||||
|
||||
// Run tests
|
||||
int result = RUN_ALL_TESTS();
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user