backend-infra-engineer: Release v0.3.3 snapshot

This commit is contained in:
scawful
2025-11-21 21:35:50 -05:00
parent 3d71417f62
commit 476dd1cd1c
818 changed files with 65706 additions and 35514 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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\": []"));
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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"));

View File

@@ -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()) {

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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)

View File

@@ -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";
}

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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());

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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
*/

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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
};

View File

@@ -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

View File

@@ -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

View File

@@ -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, "") {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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"));
}

View File

@@ -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

View File

@@ -71,4 +71,3 @@ TEST_F(ApuDspTest, GetSamplesReturnsSilenceAfterReset) {
} // namespace emu
} // namespace yaze

View File

@@ -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

View File

@@ -27,4 +27,3 @@ TEST(Spc700ResetTest, ResetVectorExecutesIplSequence) {
} // namespace emu
} // namespace yaze

View File

@@ -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));

View File

@@ -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);

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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());

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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());

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}