backend-infra-engineer: Release v0.3.3 snapshot
This commit is contained in:
@@ -21,17 +21,16 @@ using ::testing::Return;
|
||||
class MockGuiAutomationClient : public GuiAutomationClient {
|
||||
public:
|
||||
MockGuiAutomationClient() : GuiAutomationClient("localhost:50052") {}
|
||||
|
||||
|
||||
MOCK_METHOD(absl::Status, Connect, ());
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Ping, (const std::string&));
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Click,
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Click,
|
||||
(const std::string&, ClickType));
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Type,
|
||||
(const std::string&, const std::string&, bool));
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Wait,
|
||||
(const std::string&, int, int));
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Assert,
|
||||
(const std::string&));
|
||||
MOCK_METHOD(absl::StatusOr<AutomationResult>, Assert, (const std::string&));
|
||||
};
|
||||
|
||||
class AIGUIControllerTest : public ::testing::Test {
|
||||
@@ -42,12 +41,12 @@ class AIGUIControllerTest : public ::testing::Test {
|
||||
config.api_key = "test_key";
|
||||
config.model = "gemini-2.5-flash";
|
||||
gemini_service_ = std::make_unique<GeminiAIService>(config);
|
||||
|
||||
|
||||
gui_client_ = std::make_unique<MockGuiAutomationClient>();
|
||||
|
||||
controller_ = std::make_unique<AIGUIController>(
|
||||
gemini_service_.get(), gui_client_.get());
|
||||
|
||||
|
||||
controller_ = std::make_unique<AIGUIController>(gemini_service_.get(),
|
||||
gui_client_.get());
|
||||
|
||||
ControlLoopConfig loop_config;
|
||||
loop_config.max_iterations = 5;
|
||||
loop_config.enable_vision_verification = false; // Disable for unit tests
|
||||
@@ -68,16 +67,16 @@ TEST_F(AIGUIControllerTest, ExecuteClickAction_Success) {
|
||||
AIAction action(AIActionType::kClickButton);
|
||||
action.parameters["target"] = "button:Test";
|
||||
action.parameters["click_type"] = "left";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = true;
|
||||
result.message = "Click successful";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("button:Test", ClickType::kLeft))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok()) << status.status().message();
|
||||
EXPECT_TRUE(status->action_successful);
|
||||
}
|
||||
@@ -85,18 +84,18 @@ TEST_F(AIGUIControllerTest, ExecuteClickAction_Success) {
|
||||
TEST_F(AIGUIControllerTest, ExecuteClickAction_Failure) {
|
||||
AIAction action(AIActionType::kClickButton);
|
||||
action.parameters["target"] = "button:NonExistent";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = false;
|
||||
result.message = "Button not found";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("button:NonExistent", ClickType::kLeft))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.status().message(),
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("Click action failed"));
|
||||
}
|
||||
|
||||
@@ -105,20 +104,21 @@ TEST_F(AIGUIControllerTest, ExecuteClickAction_Failure) {
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(AIGUIControllerTest, ExecuteTypeAction_Success) {
|
||||
AIAction action(AIActionType::kSelectTile); // Using SelectTile as a type action
|
||||
AIAction action(
|
||||
AIActionType::kSelectTile); // Using SelectTile as a type action
|
||||
action.parameters["target"] = "input:TileID";
|
||||
action.parameters["text"] = "0x42";
|
||||
action.parameters["clear_first"] = "true";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = true;
|
||||
result.message = "Text entered";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Type("input:TileID", "0x42", true))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok());
|
||||
EXPECT_TRUE(status->action_successful);
|
||||
}
|
||||
@@ -131,16 +131,16 @@ TEST_F(AIGUIControllerTest, ExecuteWaitAction_Success) {
|
||||
AIAction action(AIActionType::kWait);
|
||||
action.parameters["condition"] = "window:OverworldEditor";
|
||||
action.parameters["timeout_ms"] = "2000";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = true;
|
||||
result.message = "Condition met";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Wait("window:OverworldEditor", 2000, 100))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok());
|
||||
EXPECT_TRUE(status->action_successful);
|
||||
}
|
||||
@@ -149,16 +149,16 @@ TEST_F(AIGUIControllerTest, ExecuteWaitAction_Timeout) {
|
||||
AIAction action(AIActionType::kWait);
|
||||
action.parameters["condition"] = "window:NonExistentWindow";
|
||||
action.parameters["timeout_ms"] = "100";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = false;
|
||||
result.message = "Timeout waiting for condition";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Wait("window:NonExistentWindow", 100, 100))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
}
|
||||
|
||||
@@ -169,18 +169,17 @@ TEST_F(AIGUIControllerTest, ExecuteWaitAction_Timeout) {
|
||||
TEST_F(AIGUIControllerTest, ExecuteVerifyAction_Success) {
|
||||
AIAction action(AIActionType::kVerifyTile);
|
||||
action.parameters["condition"] = "tile_placed";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = true;
|
||||
result.message = "Assertion passed";
|
||||
result.expected_value = "0x42";
|
||||
result.actual_value = "0x42";
|
||||
|
||||
EXPECT_CALL(*gui_client_, Assert("tile_placed"))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Assert("tile_placed")).WillOnce(Return(result));
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok());
|
||||
EXPECT_TRUE(status->action_successful);
|
||||
}
|
||||
@@ -188,25 +187,23 @@ TEST_F(AIGUIControllerTest, ExecuteVerifyAction_Success) {
|
||||
TEST_F(AIGUIControllerTest, ExecuteVerifyAction_Failure) {
|
||||
AIAction action(AIActionType::kVerifyTile);
|
||||
action.parameters["condition"] = "tile_placed";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = false;
|
||||
result.message = "Assertion failed";
|
||||
result.expected_value = "0x42";
|
||||
result.actual_value = "0x00";
|
||||
|
||||
EXPECT_CALL(*gui_client_, Assert("tile_placed"))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Assert("tile_placed")).WillOnce(Return(result));
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("Assert action failed"));
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("expected: 0x42"));
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("actual: 0x00"));
|
||||
EXPECT_THAT(status.status().message(), ::testing::HasSubstr("actual: 0x00"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -219,27 +216,27 @@ TEST_F(AIGUIControllerTest, ExecutePlaceTileAction_CompleteFlow) {
|
||||
action.parameters["x"] = "10";
|
||||
action.parameters["y"] = "20";
|
||||
action.parameters["tile"] = "0x42";
|
||||
|
||||
|
||||
AutomationResult result;
|
||||
result.success = true;
|
||||
|
||||
|
||||
// Expect sequence: open menu, wait for window, set map ID, click position
|
||||
testing::InSequence seq;
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("menu:Overworld", ClickType::kLeft))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Wait("window:Overworld Editor", 2000, 100))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Type("input:Map ID", "5", true))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click(::testing::_, ClickType::kLeft))
|
||||
.WillOnce(Return(result));
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok()) << status.status().message();
|
||||
EXPECT_TRUE(status->action_successful);
|
||||
}
|
||||
@@ -250,26 +247,26 @@ TEST_F(AIGUIControllerTest, ExecutePlaceTileAction_CompleteFlow) {
|
||||
|
||||
TEST_F(AIGUIControllerTest, ExecuteActions_MultipleActionsSuccess) {
|
||||
std::vector<AIAction> actions;
|
||||
|
||||
|
||||
AIAction action1(AIActionType::kClickButton);
|
||||
action1.parameters["target"] = "button:Overworld";
|
||||
actions.push_back(action1);
|
||||
|
||||
|
||||
AIAction action2(AIActionType::kWait);
|
||||
action2.parameters["condition"] = "window:OverworldEditor";
|
||||
actions.push_back(action2);
|
||||
|
||||
|
||||
AutomationResult success_result;
|
||||
success_result.success = true;
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("button:Overworld", ClickType::kLeft))
|
||||
.WillOnce(Return(success_result));
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Wait("window:OverworldEditor", 5000, 100))
|
||||
.WillOnce(Return(success_result));
|
||||
|
||||
|
||||
auto result = controller_->ExecuteActions(actions);
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
EXPECT_TRUE(result->success);
|
||||
EXPECT_EQ(result->actions_executed.size(), 2);
|
||||
@@ -277,28 +274,27 @@ TEST_F(AIGUIControllerTest, ExecuteActions_MultipleActionsSuccess) {
|
||||
|
||||
TEST_F(AIGUIControllerTest, ExecuteActions_StopsOnFirstFailure) {
|
||||
std::vector<AIAction> actions;
|
||||
|
||||
|
||||
AIAction action1(AIActionType::kClickButton);
|
||||
action1.parameters["target"] = "button:Test";
|
||||
actions.push_back(action1);
|
||||
|
||||
|
||||
AIAction action2(AIActionType::kClickButton);
|
||||
action2.parameters["target"] = "button:NeverReached";
|
||||
actions.push_back(action2);
|
||||
|
||||
|
||||
AutomationResult failure_result;
|
||||
failure_result.success = false;
|
||||
failure_result.message = "First action failed";
|
||||
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("button:Test", ClickType::kLeft))
|
||||
.WillOnce(Return(failure_result));
|
||||
|
||||
|
||||
// Second action should never be called
|
||||
EXPECT_CALL(*gui_client_, Click("button:NeverReached", _))
|
||||
.Times(0);
|
||||
|
||||
EXPECT_CALL(*gui_client_, Click("button:NeverReached", _)).Times(0);
|
||||
|
||||
auto result = controller_->ExecuteActions(actions);
|
||||
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_EQ(result->actions_executed.size(), 1);
|
||||
}
|
||||
@@ -309,9 +305,9 @@ TEST_F(AIGUIControllerTest, ExecuteActions_StopsOnFirstFailure) {
|
||||
|
||||
TEST_F(AIGUIControllerTest, ExecuteAction_InvalidActionType) {
|
||||
AIAction action(AIActionType::kInvalidAction);
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("Action type not implemented"));
|
||||
@@ -320,9 +316,9 @@ TEST_F(AIGUIControllerTest, ExecuteAction_InvalidActionType) {
|
||||
TEST_F(AIGUIControllerTest, ExecutePlaceTileAction_MissingParameters) {
|
||||
AIAction action(AIActionType::kPlaceTile);
|
||||
// Missing required parameters
|
||||
|
||||
|
||||
auto status = controller_->ExecuteSingleAction(action, false);
|
||||
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_THAT(status.status().message(),
|
||||
::testing::HasSubstr("requires map_id, x, y, and tile"));
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "cli/service/ai/ai_action_parser.h"
|
||||
#include "cli/service/ai/vision_action_refiner.h"
|
||||
#include "cli/service/ai/ai_gui_controller.h"
|
||||
#include "cli/service/ai/vision_action_refiner.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
#include "cli/service/gui/gui_automation_client.h"
|
||||
#include "cli/service/ai/gemini_ai_service.h"
|
||||
#include "cli/service/gui/gui_automation_client.h"
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
@@ -15,7 +14,7 @@ namespace test {
|
||||
|
||||
/**
|
||||
* @brief Integration tests for AI-controlled tile placement
|
||||
*
|
||||
*
|
||||
* These tests verify the complete pipeline:
|
||||
* 1. Parse natural language commands
|
||||
* 2. Execute actions via gRPC
|
||||
@@ -32,33 +31,33 @@ class AITilePlacementTest : public ::testing::Test {
|
||||
|
||||
TEST_F(AITilePlacementTest, ParsePlaceTileCommand) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
// Test basic tile placement command
|
||||
auto result = AIActionParser::ParseCommand(
|
||||
"Place tile 0x42 at overworld position (5, 7)");
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
EXPECT_EQ(result->size(), 3); // Select, Place, Save
|
||||
|
||||
|
||||
// Check first action (Select)
|
||||
EXPECT_EQ(result->at(0).type, AIActionType::kSelectTile);
|
||||
EXPECT_EQ(result->at(0).parameters.at("tile_id"), "66"); // 0x42 = 66
|
||||
|
||||
|
||||
// Check second action (Place)
|
||||
EXPECT_EQ(result->at(1).type, AIActionType::kPlaceTile);
|
||||
EXPECT_EQ(result->at(1).parameters.at("x"), "5");
|
||||
EXPECT_EQ(result->at(1).parameters.at("y"), "7");
|
||||
EXPECT_EQ(result->at(1).parameters.at("map_id"), "0");
|
||||
|
||||
|
||||
// Check third action (Save)
|
||||
EXPECT_EQ(result->at(2).type, AIActionType::kSaveTile);
|
||||
}
|
||||
|
||||
TEST_F(AITilePlacementTest, ParseSelectTileCommand) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
auto result = AIActionParser::ParseCommand("Select tile 100");
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_EQ(result->size(), 1);
|
||||
EXPECT_EQ(result->at(0).type, AIActionType::kSelectTile);
|
||||
@@ -67,9 +66,9 @@ TEST_F(AITilePlacementTest, ParseSelectTileCommand) {
|
||||
|
||||
TEST_F(AITilePlacementTest, ParseOpenEditorCommand) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
auto result = AIActionParser::ParseCommand("Open the overworld editor");
|
||||
|
||||
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_EQ(result->size(), 1);
|
||||
EXPECT_EQ(result->at(0).type, AIActionType::kOpenEditor);
|
||||
@@ -78,13 +77,10 @@ TEST_F(AITilePlacementTest, ParseOpenEditorCommand) {
|
||||
|
||||
TEST_F(AITilePlacementTest, ActionToStringRoundtrip) {
|
||||
using namespace cli::ai;
|
||||
|
||||
AIAction action(AIActionType::kPlaceTile, {
|
||||
{"x", "5"},
|
||||
{"y", "7"},
|
||||
{"tile_id", "42"}
|
||||
});
|
||||
|
||||
|
||||
AIAction action(AIActionType::kPlaceTile,
|
||||
{{"x", "5"}, {"y", "7"}, {"tile_id", "42"}});
|
||||
|
||||
std::string str = AIActionParser::ActionToString(action);
|
||||
EXPECT_FALSE(str.empty());
|
||||
EXPECT_TRUE(str.find("5") != std::string::npos);
|
||||
@@ -99,14 +95,14 @@ TEST_F(AITilePlacementTest, DISABLED_VisionAnalysisBasic) {
|
||||
if (!api_key || std::string(api_key).empty()) {
|
||||
GTEST_SKIP() << "GEMINI_API_KEY not set";
|
||||
}
|
||||
|
||||
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key;
|
||||
config.model = "gemini-2.5-flash";
|
||||
|
||||
|
||||
cli::GeminiAIService gemini_service(config);
|
||||
cli::ai::VisionActionRefiner refiner(&gemini_service);
|
||||
|
||||
|
||||
// Would need actual screenshots for real test
|
||||
// This is a structure test
|
||||
EXPECT_TRUE(true);
|
||||
@@ -117,35 +113,35 @@ TEST_F(AITilePlacementTest, DISABLED_FullAIControlLoop) {
|
||||
// 1. YAZE GUI running with gRPC test harness
|
||||
// 2. Gemini API key for vision
|
||||
// 3. Test ROM loaded
|
||||
|
||||
|
||||
const char* api_key = std::getenv("GEMINI_API_KEY");
|
||||
if (!api_key || std::string(api_key).empty()) {
|
||||
GTEST_SKIP() << "GEMINI_API_KEY not set";
|
||||
}
|
||||
|
||||
|
||||
// Initialize services
|
||||
cli::GeminiConfig gemini_config;
|
||||
gemini_config.api_key = api_key;
|
||||
cli::GeminiAIService gemini_service(gemini_config);
|
||||
|
||||
|
||||
cli::GuiAutomationClient gui_client("localhost:50051");
|
||||
auto connect_status = gui_client.Connect();
|
||||
if (!connect_status.ok()) {
|
||||
GTEST_SKIP() << "GUI test harness not available: "
|
||||
GTEST_SKIP() << "GUI test harness not available: "
|
||||
<< connect_status.message();
|
||||
}
|
||||
|
||||
|
||||
// Create AI controller
|
||||
cli::ai::AIGUIController controller(&gemini_service, &gui_client);
|
||||
cli::ai::ControlLoopConfig config;
|
||||
config.max_iterations = 5;
|
||||
config.enable_vision_verification = true;
|
||||
controller.Initialize(config);
|
||||
|
||||
|
||||
// Execute command
|
||||
auto result = controller.ExecuteCommand(
|
||||
"Place tile 0x42 at overworld position (5, 7)");
|
||||
|
||||
auto result =
|
||||
controller.ExecuteCommand("Place tile 0x42 at overworld position (5, 7)");
|
||||
|
||||
if (result.ok()) {
|
||||
EXPECT_TRUE(result->success);
|
||||
EXPECT_GT(result->iterations_performed, 0);
|
||||
@@ -156,16 +152,14 @@ TEST_F(AITilePlacementTest, DISABLED_FullAIControlLoop) {
|
||||
|
||||
TEST_F(AITilePlacementTest, ActionRefinement) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
// Test refinement logic with a failed action
|
||||
VisionAnalysisResult analysis;
|
||||
analysis.action_successful = false;
|
||||
analysis.error_message = "Element not found";
|
||||
|
||||
AIAction original_action(AIActionType::kClickButton, {
|
||||
{"button", "save"}
|
||||
});
|
||||
|
||||
|
||||
AIAction original_action(AIActionType::kClickButton, {{"button", "save"}});
|
||||
|
||||
// Would need VisionActionRefiner for real test
|
||||
// This verifies the structure compiles
|
||||
EXPECT_TRUE(true);
|
||||
@@ -173,15 +167,12 @@ TEST_F(AITilePlacementTest, ActionRefinement) {
|
||||
|
||||
TEST_F(AITilePlacementTest, MultipleCommandsParsing) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
// Test that we can parse multiple commands in sequence
|
||||
std::vector<std::string> commands = {
|
||||
"Open overworld editor",
|
||||
"Select tile 0x42",
|
||||
"Place tile at position (5, 7)",
|
||||
"Save changes"
|
||||
};
|
||||
|
||||
"Open overworld editor", "Select tile 0x42",
|
||||
"Place tile at position (5, 7)", "Save changes"};
|
||||
|
||||
for (const auto& cmd : commands) {
|
||||
auto result = AIActionParser::ParseCommand(cmd);
|
||||
// At least some should parse successfully
|
||||
@@ -193,13 +184,13 @@ TEST_F(AITilePlacementTest, MultipleCommandsParsing) {
|
||||
|
||||
TEST_F(AITilePlacementTest, HexAndDecimalParsing) {
|
||||
using namespace cli::ai;
|
||||
|
||||
|
||||
// Test hex notation
|
||||
auto hex_result = AIActionParser::ParseCommand("Select tile 0xFF");
|
||||
if (hex_result.ok() && !hex_result->empty()) {
|
||||
EXPECT_EQ(hex_result->at(0).parameters.at("tile_id"), "255");
|
||||
}
|
||||
|
||||
|
||||
// Test decimal notation
|
||||
auto dec_result = AIActionParser::ParseCommand("Select tile 255");
|
||||
if (dec_result.ok() && !dec_result->empty()) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "cli/service/ai/gemini_ai_service.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
#include "app/service/screenshot_utils.h"
|
||||
@@ -20,51 +20,47 @@ class GeminiVisionTest : public ::testing::Test {
|
||||
if (!api_key || std::string(api_key).empty()) {
|
||||
GTEST_SKIP() << "GEMINI_API_KEY not set. Skipping multimodal tests.";
|
||||
}
|
||||
|
||||
|
||||
api_key_ = api_key;
|
||||
|
||||
|
||||
// Create test data directory
|
||||
test_dir_ = std::filesystem::temp_directory_path() / "yaze_multimodal_test";
|
||||
std::filesystem::create_directories(test_dir_);
|
||||
}
|
||||
|
||||
|
||||
void TearDown() override {
|
||||
// Clean up test directory
|
||||
if (std::filesystem::exists(test_dir_)) {
|
||||
std::filesystem::remove_all(test_dir_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Helper: Create a simple test image (16x16 PNG)
|
||||
std::filesystem::path CreateTestImage() {
|
||||
auto image_path = test_dir_ / "test_image.png";
|
||||
|
||||
|
||||
// Create a minimal PNG file (16x16 red square)
|
||||
// PNG signature + IHDR + IDAT + IEND
|
||||
const unsigned char png_data[] = {
|
||||
// PNG signature
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
|
||||
// IHDR chunk
|
||||
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,
|
||||
0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x91, 0x68,
|
||||
0x36,
|
||||
// IDAT chunk (minimal data)
|
||||
0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54,
|
||||
0x08, 0x99, 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00,
|
||||
0x03, 0x01, 0x01, 0x00, 0x18, 0xDD, 0x8D, 0xB4,
|
||||
// IEND chunk
|
||||
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44,
|
||||
0xAE, 0x42, 0x60, 0x82
|
||||
};
|
||||
|
||||
// PNG signature
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
|
||||
// IHDR chunk
|
||||
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10,
|
||||
0x00, 0x00, 0x00, 0x10, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x91, 0x68,
|
||||
0x36,
|
||||
// IDAT chunk (minimal data)
|
||||
0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0xF8,
|
||||
0xCF, 0xC0, 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0x18, 0xDD, 0x8D, 0xB4,
|
||||
// IEND chunk
|
||||
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82};
|
||||
|
||||
std::ofstream file(image_path, std::ios::binary);
|
||||
file.write(reinterpret_cast<const char*>(png_data), sizeof(png_data));
|
||||
file.close();
|
||||
|
||||
|
||||
return image_path;
|
||||
}
|
||||
|
||||
|
||||
std::string api_key_;
|
||||
std::filesystem::path test_dir_;
|
||||
};
|
||||
@@ -74,22 +70,20 @@ TEST_F(GeminiVisionTest, BasicImageAnalysis) {
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash"; // Vision-capable model
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
// Create test image
|
||||
auto image_path = CreateTestImage();
|
||||
ASSERT_TRUE(std::filesystem::exists(image_path));
|
||||
|
||||
|
||||
// Send multimodal request
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
"Describe this image in one sentence."
|
||||
);
|
||||
|
||||
image_path.string(), "Describe this image in one sentence.");
|
||||
|
||||
ASSERT_TRUE(response.ok()) << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
|
||||
|
||||
std::cout << "Vision API response: " << response->text_response << std::endl;
|
||||
}
|
||||
|
||||
@@ -98,42 +92,41 @@ TEST_F(GeminiVisionTest, ImageWithSpecificPrompt) {
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
auto image_path = CreateTestImage();
|
||||
|
||||
|
||||
// Ask specific question about the image
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
"What color is the dominant color in this image? Answer with just the color name."
|
||||
);
|
||||
|
||||
"What color is the dominant color in this image? Answer with just the "
|
||||
"color name.");
|
||||
|
||||
ASSERT_TRUE(response.ok()) << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
|
||||
|
||||
// Response should mention "red" since we created a red square
|
||||
std::string response_lower = response->text_response;
|
||||
std::transform(response_lower.begin(), response_lower.end(),
|
||||
response_lower.begin(), ::tolower);
|
||||
response_lower.begin(), ::tolower);
|
||||
EXPECT_TRUE(response_lower.find("red") != std::string::npos ||
|
||||
response_lower.find("pink") != std::string::npos)
|
||||
<< "Expected color 'red' or 'pink' in response: " << response->text_response;
|
||||
<< "Expected color 'red' or 'pink' in response: "
|
||||
<< response->text_response;
|
||||
}
|
||||
|
||||
TEST_F(GeminiVisionTest, InvalidImagePath) {
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
// Try with non-existent image
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
"/nonexistent/image.png",
|
||||
"Describe this image."
|
||||
);
|
||||
|
||||
auto response = service.GenerateMultimodalResponse("/nonexistent/image.png",
|
||||
"Describe this image.");
|
||||
|
||||
EXPECT_FALSE(response.ok());
|
||||
EXPECT_TRUE(absl::IsNotFound(response.status()) ||
|
||||
absl::IsInternal(response.status()));
|
||||
@@ -144,32 +137,31 @@ TEST_F(GeminiVisionTest, InvalidImagePath) {
|
||||
TEST_F(GeminiVisionTest, ScreenshotCaptureIntegration) {
|
||||
// Note: This test requires a running YAZE instance with gRPC test harness
|
||||
// Skip if we can't connect
|
||||
|
||||
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
// Attempt to capture a screenshot
|
||||
auto screenshot_result = yaze::test::CaptureHarnessScreenshot(
|
||||
(test_dir_ / "screenshot.png").string());
|
||||
|
||||
|
||||
if (!screenshot_result.ok()) {
|
||||
GTEST_SKIP() << "Screenshot capture failed (YAZE may not be running): "
|
||||
<< screenshot_result.status().message();
|
||||
}
|
||||
|
||||
|
||||
// Analyze the captured screenshot
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
screenshot_result->file_path,
|
||||
"What UI elements are visible in this screenshot? List them."
|
||||
);
|
||||
|
||||
"What UI elements are visible in this screenshot? List them.");
|
||||
|
||||
ASSERT_TRUE(response.ok()) << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
|
||||
|
||||
std::cout << "Screenshot analysis: " << response->text_response << std::endl;
|
||||
}
|
||||
#endif
|
||||
@@ -180,21 +172,20 @@ TEST_F(GeminiVisionTest, MultipleRequestsSequential) {
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
auto image_path = CreateTestImage();
|
||||
|
||||
|
||||
// Make 3 sequential requests
|
||||
const int num_requests = 3;
|
||||
for (int i = 0; i < num_requests; ++i) {
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
absl::StrCat("Request ", i + 1, ": Describe this image briefly.")
|
||||
);
|
||||
|
||||
ASSERT_TRUE(response.ok()) << "Request " << i + 1 << " failed: "
|
||||
<< response.status().message();
|
||||
absl::StrCat("Request ", i + 1, ": Describe this image briefly."));
|
||||
|
||||
ASSERT_TRUE(response.ok())
|
||||
<< "Request " << i + 1 << " failed: " << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
}
|
||||
}
|
||||
@@ -205,21 +196,19 @@ TEST_F(GeminiVisionTest, RateLimitHandling) {
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
|
||||
auto image_path = CreateTestImage();
|
||||
|
||||
|
||||
// Make many rapid requests (may hit rate limit)
|
||||
int successful = 0;
|
||||
int rate_limited = 0;
|
||||
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
"Describe this image."
|
||||
);
|
||||
|
||||
auto response = service.GenerateMultimodalResponse(image_path.string(),
|
||||
"Describe this image.");
|
||||
|
||||
if (response.ok()) {
|
||||
successful++;
|
||||
} else if (absl::IsResourceExhausted(response.status()) ||
|
||||
@@ -227,13 +216,14 @@ TEST_F(GeminiVisionTest, RateLimitHandling) {
|
||||
rate_limited++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// At least some requests should succeed
|
||||
EXPECT_GT(successful, 0) << "No successful requests out of 10";
|
||||
|
||||
|
||||
// If we hit rate limits, that's expected behavior (not a failure)
|
||||
if (rate_limited > 0) {
|
||||
std::cout << "Note: Hit rate limit on " << rate_limited << " out of 10 requests (expected)" << std::endl;
|
||||
std::cout << "Note: Hit rate limit on " << rate_limited
|
||||
<< " out of 10 requests (expected)" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "core/asar_wrapper.h"
|
||||
#include "app/rom.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "app/rom.h"
|
||||
#include "core/asar_wrapper.h"
|
||||
#include "testing.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
namespace integration {
|
||||
@@ -18,11 +17,12 @@ class AsarIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
wrapper_ = std::make_unique<core::AsarWrapper>();
|
||||
|
||||
|
||||
// Create test directory
|
||||
test_dir_ = std::filesystem::temp_directory_path() / "yaze_asar_integration";
|
||||
test_dir_ =
|
||||
std::filesystem::temp_directory_path() / "yaze_asar_integration";
|
||||
std::filesystem::create_directories(test_dir_);
|
||||
|
||||
|
||||
CreateTestRom();
|
||||
CreateTestAssemblyFiles();
|
||||
}
|
||||
@@ -40,35 +40,35 @@ class AsarIntegrationTest : public ::testing::Test {
|
||||
void CreateTestRom() {
|
||||
// Create a minimal SNES ROM structure
|
||||
test_rom_.resize(1024 * 1024, 0); // 1MB ROM
|
||||
|
||||
|
||||
// Add SNES header at 0x7FC0 (LoROM)
|
||||
const uint32_t header_offset = 0x7FC0;
|
||||
|
||||
|
||||
// ROM title (21 bytes)
|
||||
std::string title = "YAZE TEST ROM ";
|
||||
std::copy(title.begin(), title.end(), test_rom_.begin() + header_offset);
|
||||
|
||||
|
||||
// Map mode (byte 21) - LoROM
|
||||
test_rom_[header_offset + 21] = 0x20;
|
||||
|
||||
|
||||
// Cartridge type (byte 22)
|
||||
test_rom_[header_offset + 22] = 0x00;
|
||||
|
||||
|
||||
// ROM size (byte 23) - 1MB
|
||||
test_rom_[header_offset + 23] = 0x0A;
|
||||
|
||||
|
||||
// SRAM size (byte 24)
|
||||
test_rom_[header_offset + 24] = 0x00;
|
||||
|
||||
|
||||
// Country code (byte 25)
|
||||
test_rom_[header_offset + 25] = 0x01;
|
||||
|
||||
|
||||
// Developer ID (byte 26)
|
||||
test_rom_[header_offset + 26] = 0x00;
|
||||
|
||||
|
||||
// Version (byte 27)
|
||||
test_rom_[header_offset + 27] = 0x00;
|
||||
|
||||
|
||||
// Calculate and set checksum complement and checksum
|
||||
uint16_t checksum = 0;
|
||||
for (size_t i = 0; i < test_rom_.size(); ++i) {
|
||||
@@ -77,13 +77,13 @@ class AsarIntegrationTest : public ::testing::Test {
|
||||
checksum += test_rom_[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint16_t checksum_complement = checksum ^ 0xFFFF;
|
||||
test_rom_[header_offset + 28] = checksum_complement & 0xFF;
|
||||
test_rom_[header_offset + 29] = (checksum_complement >> 8) & 0xFF;
|
||||
test_rom_[header_offset + 30] = checksum & 0xFF;
|
||||
test_rom_[header_offset + 31] = (checksum >> 8) & 0xFF;
|
||||
|
||||
|
||||
// Add some code at the reset vector location
|
||||
const uint32_t reset_vector_offset = 0x8000;
|
||||
test_rom_[reset_vector_offset] = 0x18; // CLC
|
||||
@@ -246,8 +246,9 @@ org $00FFE0
|
||||
|
||||
// Create test graphics binary
|
||||
std::ofstream gfx_file(test_dir_ / "test_graphics.bin", std::ios::binary);
|
||||
std::vector<uint8_t> graphics_data(2048, 0x55); // Test pattern
|
||||
gfx_file.write(reinterpret_cast<char*>(graphics_data.data()), graphics_data.size());
|
||||
std::vector<uint8_t> graphics_data(2048, 0x55); // Test pattern
|
||||
gfx_file.write(reinterpret_cast<char*>(graphics_data.data()),
|
||||
graphics_data.size());
|
||||
gfx_file.close();
|
||||
|
||||
// Create advanced assembly with macros and includes
|
||||
@@ -339,12 +340,13 @@ TEST_F(AsarIntegrationTest, FullWorkflowIntegration) {
|
||||
size_t original_size = rom_copy.size();
|
||||
|
||||
// Apply comprehensive patch
|
||||
auto patch_result = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
|
||||
auto patch_result =
|
||||
wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
|
||||
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
|
||||
|
||||
const auto& result = patch_result.value();
|
||||
EXPECT_TRUE(result.success) << "Patch failed with errors: "
|
||||
<< testing::PrintToString(result.errors);
|
||||
EXPECT_TRUE(result.success)
|
||||
<< "Patch failed with errors: " << testing::PrintToString(result.errors);
|
||||
|
||||
// Verify ROM was modified correctly
|
||||
EXPECT_NE(rom_copy, test_rom_);
|
||||
@@ -352,7 +354,7 @@ TEST_F(AsarIntegrationTest, FullWorkflowIntegration) {
|
||||
|
||||
// Verify symbols were extracted
|
||||
EXPECT_GT(result.symbols.size(), 0);
|
||||
|
||||
|
||||
// Check for specific expected symbols
|
||||
bool found_main_entry = false;
|
||||
bool found_init_graphics = false;
|
||||
@@ -379,13 +381,14 @@ TEST_F(AsarIntegrationTest, AdvancedFeaturesIntegration) {
|
||||
|
||||
// Test advanced assembly features (macros, conditionals, etc.)
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
|
||||
auto patch_result = wrapper_->ApplyPatch(advanced_asm_path_.string(), rom_copy);
|
||||
|
||||
auto patch_result =
|
||||
wrapper_->ApplyPatch(advanced_asm_path_.string(), rom_copy);
|
||||
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
|
||||
|
||||
const auto& result = patch_result.value();
|
||||
EXPECT_TRUE(result.success) << "Advanced patch failed: "
|
||||
<< testing::PrintToString(result.errors);
|
||||
EXPECT_TRUE(result.success)
|
||||
<< "Advanced patch failed: " << testing::PrintToString(result.errors);
|
||||
|
||||
// Verify symbols from advanced assembly
|
||||
bool found_advanced_entry = false;
|
||||
@@ -408,25 +411,25 @@ TEST_F(AsarIntegrationTest, ErrorHandlingIntegration) {
|
||||
|
||||
// Test error handling with intentionally broken assembly
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
|
||||
|
||||
auto patch_result = wrapper_->ApplyPatch(error_asm_path_.string(), rom_copy);
|
||||
|
||||
|
||||
// Should fail due to errors in assembly
|
||||
EXPECT_FALSE(patch_result.ok());
|
||||
|
||||
|
||||
// Verify error message contains useful information
|
||||
EXPECT_THAT(patch_result.status().message(),
|
||||
testing::AnyOf(
|
||||
testing::HasSubstr("invalid"),
|
||||
testing::HasSubstr("unknown"),
|
||||
testing::HasSubstr("error")));
|
||||
EXPECT_THAT(patch_result.status().message(),
|
||||
testing::AnyOf(testing::HasSubstr("invalid"),
|
||||
testing::HasSubstr("unknown"),
|
||||
testing::HasSubstr("error")));
|
||||
}
|
||||
|
||||
TEST_F(AsarIntegrationTest, SymbolExtractionWorkflow) {
|
||||
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||
|
||||
// Extract symbols without applying patch
|
||||
auto symbols_result = wrapper_->ExtractSymbols(comprehensive_asm_path_.string());
|
||||
auto symbols_result =
|
||||
wrapper_->ExtractSymbols(comprehensive_asm_path_.string());
|
||||
ASSERT_TRUE(symbols_result.ok()) << symbols_result.status().message();
|
||||
|
||||
const auto& symbols = symbols_result.value();
|
||||
@@ -434,7 +437,8 @@ TEST_F(AsarIntegrationTest, SymbolExtractionWorkflow) {
|
||||
|
||||
// Test symbol table operations
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
auto patch_result = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
|
||||
auto patch_result =
|
||||
wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
|
||||
ASSERT_TRUE(patch_result.ok());
|
||||
|
||||
// Test symbol lookup by name
|
||||
@@ -465,7 +469,8 @@ TEST_F(AsarIntegrationTest, MultipleOperationsIntegration) {
|
||||
std::vector<uint8_t> rom_copy2 = test_rom_;
|
||||
|
||||
// First patch
|
||||
auto result1 = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy1);
|
||||
auto result1 =
|
||||
wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy1);
|
||||
ASSERT_TRUE(result1.ok());
|
||||
EXPECT_TRUE(result1->success);
|
||||
|
||||
@@ -497,8 +502,9 @@ subroutine_test:
|
||||
)";
|
||||
|
||||
std::vector<uint8_t> rom_copy = test_rom_;
|
||||
auto result = wrapper_->ApplyPatchFromString(patch_content, rom_copy, test_dir_.string());
|
||||
|
||||
auto result = wrapper_->ApplyPatchFromString(patch_content, rom_copy,
|
||||
test_dir_.string());
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
EXPECT_TRUE(result->success);
|
||||
EXPECT_GT(result->symbols.size(), 0);
|
||||
@@ -524,16 +530,17 @@ TEST_F(AsarIntegrationTest, LargeRomHandling) {
|
||||
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||
|
||||
// Create a larger ROM for testing
|
||||
std::vector<uint8_t> large_rom(4 * 1024 * 1024, 0); // 4MB ROM
|
||||
|
||||
std::vector<uint8_t> large_rom(4 * 1024 * 1024, 0); // 4MB ROM
|
||||
|
||||
// Set up basic SNES header
|
||||
const uint32_t header_offset = 0x7FC0;
|
||||
std::string title = "LARGE ROM TEST ";
|
||||
std::copy(title.begin(), title.end(), large_rom.begin() + header_offset);
|
||||
large_rom[header_offset + 21] = 0x20; // LoROM
|
||||
large_rom[header_offset + 23] = 0x0C; // 4MB
|
||||
large_rom[header_offset + 21] = 0x20; // LoROM
|
||||
large_rom[header_offset + 23] = 0x0C; // 4MB
|
||||
|
||||
auto result = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), large_rom);
|
||||
auto result =
|
||||
wrapper_->ApplyPatch(comprehensive_asm_path_.string(), large_rom);
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_TRUE(result->success);
|
||||
EXPECT_EQ(large_rom.size(), result->rom_size);
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
#endif
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "core/asar_wrapper.h"
|
||||
#include "app/rom.h"
|
||||
#include "core/asar_wrapper.h"
|
||||
#include "test_utils.h"
|
||||
#include "testing.h"
|
||||
|
||||
@@ -330,7 +331,8 @@ TEST_F(AsarRomIntegrationTest, GameplayModificationPatch) {
|
||||
// Check health modification at 0x7EF36C -> ROM offset would need calculation
|
||||
// For a proper test, we'd need to convert SNES addresses to ROM offsets
|
||||
|
||||
// Check if custom routine was inserted at 0xC000 -> ROM offset 0x18000 (in LoROM)
|
||||
// Check if custom routine was inserted at 0xC000 -> ROM offset 0x18000 (in
|
||||
// LoROM)
|
||||
const uint32_t rom_offset = 0x18000; // Bank $00:C000 in LoROM
|
||||
if (rom_offset < rom_copy.size()) {
|
||||
// Check for SEP #$20 instruction (0xE2 0x20)
|
||||
|
||||
@@ -23,7 +23,8 @@ TEST_F(DungeonEditorIntegrationTest, LoadMultipleRooms) {
|
||||
// Test loading several different rooms
|
||||
for (int room_id : {0x00, 0x01, 0x02, 0x10, 0x20}) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), room_id);
|
||||
EXPECT_NE(room.rom(), nullptr) << "Failed to load room " << std::hex << room_id;
|
||||
EXPECT_NE(room.rom(), nullptr)
|
||||
<< "Failed to load room " << std::hex << room_id;
|
||||
room.LoadObjects();
|
||||
// Some rooms may be empty, but loading should not fail
|
||||
}
|
||||
@@ -32,7 +33,7 @@ TEST_F(DungeonEditorIntegrationTest, LoadMultipleRooms) {
|
||||
TEST_F(DungeonEditorIntegrationTest, DungeonEditorInitialization) {
|
||||
// Initialize the editor before loading
|
||||
dungeon_editor_->Initialize();
|
||||
|
||||
|
||||
// Now load should succeed
|
||||
auto status = dungeon_editor_->Load();
|
||||
ASSERT_TRUE(status.ok()) << "Load failed: " << status.message();
|
||||
@@ -45,20 +46,22 @@ TEST_F(DungeonEditorIntegrationTest, DungeonEditorInitialization) {
|
||||
TEST_F(DungeonEditorIntegrationTest, ObjectEncodingRoundTrip) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
|
||||
auto encoded = room.EncodeObjects();
|
||||
EXPECT_FALSE(encoded.empty());
|
||||
EXPECT_EQ(encoded[encoded.size()-1], 0xFF); // Terminator
|
||||
EXPECT_EQ(encoded[encoded.size() - 1], 0xFF); // Terminator
|
||||
}
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, EncodeType1Object) {
|
||||
// Type 1: xxxxxxss yyyyyyss iiiiiiii (ID < 0x100)
|
||||
zelda3::RoomObject obj(0x10, 5, 7, 0x12, 0); // id, x, y, size, layer
|
||||
zelda3::RoomObject obj(0x10, 5, 7, 0x12, 0); // id, x, y, size, layer
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
|
||||
|
||||
// Verify encoding format
|
||||
EXPECT_EQ((bytes.b1 >> 2), 5) << "X coordinate should be in upper 6 bits of b1";
|
||||
EXPECT_EQ((bytes.b2 >> 2), 7) << "Y coordinate should be in upper 6 bits of b2";
|
||||
EXPECT_EQ((bytes.b1 >> 2), 5)
|
||||
<< "X coordinate should be in upper 6 bits of b1";
|
||||
EXPECT_EQ((bytes.b2 >> 2), 7)
|
||||
<< "Y coordinate should be in upper 6 bits of b2";
|
||||
EXPECT_EQ(bytes.b3, 0x10) << "Object ID should be in b3";
|
||||
}
|
||||
|
||||
@@ -66,9 +69,10 @@ TEST_F(DungeonEditorIntegrationTest, EncodeType2Object) {
|
||||
// Type 2: 111111xx xxxxyyyy yyiiiiii (ID >= 0x100 && < 0x200)
|
||||
zelda3::RoomObject obj(0x150, 12, 8, 0, 0);
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
|
||||
|
||||
// Verify Type 2 marker
|
||||
EXPECT_EQ((bytes.b1 & 0xFC), 0xFC) << "Type 2 objects should have 111111 prefix";
|
||||
EXPECT_EQ((bytes.b1 & 0xFC), 0xFC)
|
||||
<< "Type 2 objects should have 111111 prefix";
|
||||
}
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, EncodeType3Object) {
|
||||
@@ -103,13 +107,13 @@ TEST_F(DungeonEditorIntegrationTest, AddObjectToRoom) {
|
||||
TEST_F(DungeonEditorIntegrationTest, RemoveObjectFromRoom) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
|
||||
size_t initial_count = room.GetTileObjects().size();
|
||||
ASSERT_GT(initial_count, 0) << "Room should have at least one object";
|
||||
|
||||
|
||||
// Remove first object
|
||||
auto status = room.RemoveObject(0);
|
||||
|
||||
|
||||
EXPECT_TRUE(status.ok()) << "Failed to remove object: " << status.message();
|
||||
EXPECT_EQ(room.GetTileObjects().size(), initial_count - 1);
|
||||
}
|
||||
@@ -117,16 +121,16 @@ TEST_F(DungeonEditorIntegrationTest, RemoveObjectFromRoom) {
|
||||
TEST_F(DungeonEditorIntegrationTest, UpdateObjectInRoom) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
|
||||
ASSERT_FALSE(room.GetTileObjects().empty());
|
||||
|
||||
|
||||
// Update first object's position
|
||||
zelda3::RoomObject updated_obj = room.GetTileObjects()[0];
|
||||
updated_obj.x_ = 15;
|
||||
updated_obj.y_ = 15;
|
||||
|
||||
|
||||
auto status = room.UpdateObject(0, updated_obj);
|
||||
|
||||
|
||||
EXPECT_TRUE(status.ok()) << "Failed to update object: " << status.message();
|
||||
EXPECT_EQ(room.GetTileObjects()[0].x_, 15);
|
||||
EXPECT_EQ(room.GetTileObjects()[0].y_, 15);
|
||||
@@ -158,23 +162,23 @@ TEST_F(DungeonEditorIntegrationTest, ValidateObjectBounds) {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Save/Load Round-Trip Tests
|
||||
// Save/Load Round-Trip Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, SaveAndReloadRoom) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
|
||||
size_t original_count = room.GetTileObjects().size();
|
||||
|
||||
|
||||
// Encode objects
|
||||
auto encoded = room.EncodeObjects();
|
||||
EXPECT_FALSE(encoded.empty());
|
||||
|
||||
|
||||
// Create a new room and decode
|
||||
auto room2 = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room2.LoadObjects();
|
||||
|
||||
|
||||
// Verify object count matches
|
||||
EXPECT_EQ(room2.GetTileObjects().size(), original_count);
|
||||
}
|
||||
@@ -186,14 +190,14 @@ TEST_F(DungeonEditorIntegrationTest, SaveAndReloadRoom) {
|
||||
TEST_F(DungeonEditorIntegrationTest, RenderObjectWithTiles) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
|
||||
ASSERT_FALSE(room.GetTileObjects().empty());
|
||||
|
||||
|
||||
// Ensure tiles are loaded for first object
|
||||
auto& obj = room.GetTileObjects()[0];
|
||||
const_cast<zelda3::RoomObject&>(obj).set_rom(rom_.get());
|
||||
const_cast<zelda3::RoomObject&>(obj).EnsureTilesLoaded();
|
||||
|
||||
|
||||
EXPECT_FALSE(obj.tiles_.empty()) << "Object should have tiles after loading";
|
||||
}
|
||||
|
||||
@@ -203,27 +207,27 @@ TEST_F(DungeonEditorIntegrationTest, RenderObjectWithTiles) {
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, ObjectsOnDifferentLayers) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
|
||||
|
||||
// Add objects on different layers
|
||||
zelda3::RoomObject obj_bg1(0x10, 5, 5, 0, 0); // Layer 0 (BG2)
|
||||
zelda3::RoomObject obj_bg2(0x11, 6, 6, 0, 1); // Layer 1 (BG1)
|
||||
zelda3::RoomObject obj_bg3(0x12, 7, 7, 0, 2); // Layer 2 (BG3)
|
||||
|
||||
zelda3::RoomObject obj_bg1(0x10, 5, 5, 0, 0); // Layer 0 (BG2)
|
||||
zelda3::RoomObject obj_bg2(0x11, 6, 6, 0, 1); // Layer 1 (BG1)
|
||||
zelda3::RoomObject obj_bg3(0x12, 7, 7, 0, 2); // Layer 2 (BG3)
|
||||
|
||||
room.AddObject(obj_bg1);
|
||||
room.AddObject(obj_bg2);
|
||||
room.AddObject(obj_bg3);
|
||||
|
||||
|
||||
// Encode and verify layer separation
|
||||
auto encoded = room.EncodeObjects();
|
||||
|
||||
|
||||
// Should have layer terminators (0xFF 0xFF between layers)
|
||||
int terminator_count = 0;
|
||||
for (size_t i = 0; i < encoded.size() - 1; i++) {
|
||||
if (encoded[i] == 0xFF && encoded[i+1] == 0xFF) {
|
||||
if (encoded[i] == 0xFF && encoded[i + 1] == 0xFF) {
|
||||
terminator_count++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EXPECT_GE(terminator_count, 2) << "Should have at least 2 layer terminators";
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
|
||||
#include "app/editor/dungeon/dungeon_editor_v2.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* @brief Integration test framework using real ROM data
|
||||
*
|
||||
*
|
||||
* Updated for DungeonEditorV2 with card-based architecture
|
||||
*/
|
||||
class DungeonEditorIntegrationTest : public ::testing::Test {
|
||||
@@ -31,15 +31,15 @@ class DungeonEditorIntegrationTest : public ::testing::Test {
|
||||
}
|
||||
ASSERT_TRUE(status.ok()) << "Could not load zelda3.sfc from any location";
|
||||
ASSERT_TRUE(rom_->InitializeForTesting().ok());
|
||||
|
||||
|
||||
// Initialize DungeonEditorV2 with ROM
|
||||
dungeon_editor_ = std::make_unique<editor::DungeonEditorV2>();
|
||||
dungeon_editor_->set_rom(rom_.get());
|
||||
|
||||
|
||||
// Load editor data
|
||||
auto load_status = dungeon_editor_->Load();
|
||||
ASSERT_TRUE(load_status.ok()) << "Failed to load dungeon editor: "
|
||||
<< load_status.message();
|
||||
ASSERT_TRUE(load_status.ok())
|
||||
<< "Failed to load dungeon editor: " << load_status.message();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
@@ -49,7 +49,7 @@ class DungeonEditorIntegrationTest : public ::testing::Test {
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<editor::DungeonEditorV2> dungeon_editor_;
|
||||
|
||||
|
||||
static constexpr int kTestRoomId = 0x01;
|
||||
};
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ TEST_F(DungeonEditorV2IntegrationTest, LoadWithoutRom) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, LoadSequence) {
|
||||
// Test the full initialization sequence
|
||||
dungeon_editor_v2_->Initialize();
|
||||
|
||||
|
||||
auto load_status = dungeon_editor_v2_->Load();
|
||||
ASSERT_TRUE(load_status.ok());
|
||||
|
||||
|
||||
// After loading, Update() should work
|
||||
(void)dungeon_editor_v2_->Update();
|
||||
}
|
||||
@@ -63,7 +63,7 @@ TEST_F(DungeonEditorV2IntegrationTest, UpdateBeforeLoad) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, UpdateAfterLoad) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
(void)dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Update should delegate to components
|
||||
auto status = dungeon_editor_v2_->Update();
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -85,7 +85,7 @@ TEST_F(DungeonEditorV2IntegrationTest, SaveAfterLoad) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
auto load_status = dungeon_editor_v2_->Load();
|
||||
ASSERT_TRUE(load_status.ok());
|
||||
|
||||
|
||||
// Save should delegate to room objects
|
||||
auto save_status = dungeon_editor_v2_->Save();
|
||||
EXPECT_TRUE(save_status.ok());
|
||||
@@ -98,10 +98,10 @@ TEST_F(DungeonEditorV2IntegrationTest, SaveAfterLoad) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, AddRoomTab) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
(void)dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Add a room tab
|
||||
dungeon_editor_v2_->add_room(kTestRoomId);
|
||||
|
||||
|
||||
// This should not crash or fail
|
||||
auto status = dungeon_editor_v2_->Update();
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -110,12 +110,12 @@ TEST_F(DungeonEditorV2IntegrationTest, AddRoomTab) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, AddMultipleRoomTabs) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
(void)dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Add multiple rooms
|
||||
dungeon_editor_v2_->add_room(0x00);
|
||||
dungeon_editor_v2_->add_room(0x01);
|
||||
dungeon_editor_v2_->add_room(0x02);
|
||||
|
||||
|
||||
auto status = dungeon_editor_v2_->Update();
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
@@ -128,7 +128,7 @@ TEST_F(DungeonEditorV2IntegrationTest, RoomLoaderDelegation) {
|
||||
// Verify that Load() delegates to room_loader_
|
||||
dungeon_editor_v2_->Initialize();
|
||||
auto status = dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// If Load succeeds, room_loader_ must have worked
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
@@ -137,7 +137,7 @@ TEST_F(DungeonEditorV2IntegrationTest, ComponentsInitializedAfterLoad) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
auto status = dungeon_editor_v2_->Load();
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// After Load(), all components should be properly initialized
|
||||
// We can't directly test this, but Update() should work
|
||||
(void)dungeon_editor_v2_->Update();
|
||||
@@ -151,7 +151,7 @@ TEST_F(DungeonEditorV2IntegrationTest, SetRomAfterConstruction) {
|
||||
// Create editor without ROM
|
||||
editor::DungeonEditorV2 editor;
|
||||
EXPECT_EQ(editor.rom(), nullptr);
|
||||
|
||||
|
||||
// Set ROM
|
||||
editor.set_rom(rom_.get());
|
||||
EXPECT_EQ(editor.rom(), rom_.get());
|
||||
@@ -161,12 +161,12 @@ TEST_F(DungeonEditorV2IntegrationTest, SetRomAfterConstruction) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, SetRomAndLoad) {
|
||||
// Create editor without ROM
|
||||
editor::DungeonEditorV2 editor;
|
||||
|
||||
|
||||
// Set ROM and load
|
||||
editor.set_rom(rom_.get());
|
||||
editor.Initialize();
|
||||
auto status = editor.Load();
|
||||
|
||||
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
|
||||
@@ -174,19 +174,20 @@ TEST_F(DungeonEditorV2IntegrationTest, SetRomAndLoad) {
|
||||
// Unimplemented Methods Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonEditorV2IntegrationTest, UnimplementedMethods) {
|
||||
// These should return UnimplementedError
|
||||
EXPECT_EQ(dungeon_editor_v2_->Undo().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Redo().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Cut().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Copy().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Paste().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Find().code(),
|
||||
TEST_F(DungeonEditorV2IntegrationTest, EditingCommandsFallback) {
|
||||
// Undo/Redo should report precondition when history is empty
|
||||
EXPECT_EQ(dungeon_editor_v2_->Undo().code(),
|
||||
absl::StatusCode::kFailedPrecondition);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Redo().code(),
|
||||
absl::StatusCode::kFailedPrecondition);
|
||||
|
||||
// Cut/Copy/Paste should be callable even without a selection
|
||||
EXPECT_EQ(dungeon_editor_v2_->Cut().code(), absl::StatusCode::kOk);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Copy().code(), absl::StatusCode::kOk);
|
||||
EXPECT_EQ(dungeon_editor_v2_->Paste().code(), absl::StatusCode::kOk);
|
||||
|
||||
// Find remains unimplemented
|
||||
EXPECT_EQ(dungeon_editor_v2_->Find().code(),
|
||||
absl::StatusCode::kUnimplemented);
|
||||
}
|
||||
|
||||
@@ -198,7 +199,7 @@ TEST_F(DungeonEditorV2IntegrationTest, MultipleUpdateCycles) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
auto load_status = dungeon_editor_v2_->Load();
|
||||
ASSERT_TRUE(load_status.ok());
|
||||
|
||||
|
||||
// Run multiple update cycles
|
||||
for (int i = 0; i < 10; i++) {
|
||||
(void)dungeon_editor_v2_->Update();
|
||||
@@ -212,10 +213,10 @@ TEST_F(DungeonEditorV2IntegrationTest, MultipleUpdateCycles) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, InvalidRoomId) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
(void)dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Add invalid room ID (beyond 0x128)
|
||||
dungeon_editor_v2_->add_room(0x200);
|
||||
|
||||
|
||||
// Update should handle gracefully
|
||||
auto status = dungeon_editor_v2_->Update();
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -224,10 +225,10 @@ TEST_F(DungeonEditorV2IntegrationTest, InvalidRoomId) {
|
||||
TEST_F(DungeonEditorV2IntegrationTest, NegativeRoomId) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
(void)dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Add negative room ID
|
||||
dungeon_editor_v2_->add_room(-1);
|
||||
|
||||
|
||||
// Update should handle gracefully
|
||||
auto status = dungeon_editor_v2_->Update();
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -235,11 +236,11 @@ TEST_F(DungeonEditorV2IntegrationTest, NegativeRoomId) {
|
||||
|
||||
TEST_F(DungeonEditorV2IntegrationTest, LoadTwice) {
|
||||
dungeon_editor_v2_->Initialize();
|
||||
|
||||
|
||||
// Load twice
|
||||
auto status1 = dungeon_editor_v2_->Load();
|
||||
auto status2 = dungeon_editor_v2_->Load();
|
||||
|
||||
|
||||
// Both should succeed
|
||||
EXPECT_TRUE(status1.ok());
|
||||
EXPECT_TRUE(status2.ok());
|
||||
@@ -247,4 +248,3 @@ TEST_F(DungeonEditorV2IntegrationTest, LoadTwice) {
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace test {
|
||||
|
||||
/**
|
||||
* @brief Integration test framework for DungeonEditorV2
|
||||
*
|
||||
*
|
||||
* Tests the simplified component delegation architecture
|
||||
*/
|
||||
class DungeonEditorV2IntegrationTest : public ::testing::Test {
|
||||
@@ -29,7 +29,7 @@ class DungeonEditorV2IntegrationTest : public ::testing::Test {
|
||||
status = rom_->LoadFromFile("zelda3.sfc");
|
||||
}
|
||||
ASSERT_TRUE(status.ok()) << "Could not load zelda3.sfc from any location";
|
||||
|
||||
|
||||
// Create V2 editor with ROM
|
||||
dungeon_editor_v2_ = std::make_unique<editor::DungeonEditorV2>(rom_.get());
|
||||
}
|
||||
@@ -41,7 +41,7 @@ class DungeonEditorV2IntegrationTest : public ::testing::Test {
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<editor::DungeonEditorV2> dungeon_editor_v2_;
|
||||
|
||||
|
||||
static constexpr int kTestRoomId = 0x01;
|
||||
};
|
||||
|
||||
@@ -49,4 +49,3 @@ class DungeonEditorV2IntegrationTest : public ::testing::Test {
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_TEST_INTEGRATION_DUNGEON_EDITOR_V2_TEST_H
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "app/platform/window.h"
|
||||
#include "app/gui/core/style.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||
#include "imgui/imgui.h"
|
||||
@@ -20,13 +20,15 @@
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
EditorIntegrationTest::EditorIntegrationTest()
|
||||
EditorIntegrationTest::EditorIntegrationTest()
|
||||
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||
: engine_(nullptr), show_demo_window_(true)
|
||||
: engine_(nullptr),
|
||||
show_demo_window_(true)
|
||||
#else
|
||||
|
||||
|
||||
#endif
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
EditorIntegrationTest::~EditorIntegrationTest() {
|
||||
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||
@@ -40,7 +42,8 @@ EditorIntegrationTest::~EditorIntegrationTest() {
|
||||
absl::Status EditorIntegrationTest::Initialize() {
|
||||
// Create renderer for test
|
||||
test_renderer_ = std::make_unique<gfx::SDL2Renderer>();
|
||||
RETURN_IF_ERROR(core::CreateWindow(window_, test_renderer_.get(), SDL_WINDOW_RESIZABLE));
|
||||
RETURN_IF_ERROR(
|
||||
core::CreateWindow(window_, test_renderer_.get(), SDL_WINDOW_RESIZABLE));
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
@@ -57,7 +60,8 @@ absl::Status EditorIntegrationTest::Initialize() {
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
|
||||
// Initialize ImGui for SDL
|
||||
SDL_Renderer* sdl_renderer = static_cast<SDL_Renderer*>(test_renderer_->GetBackendRenderer());
|
||||
SDL_Renderer* sdl_renderer =
|
||||
static_cast<SDL_Renderer*>(test_renderer_->GetBackendRenderer());
|
||||
ImGui_ImplSDL2_InitForSDLRenderer(controller_.window(), sdl_renderer);
|
||||
ImGui_ImplSDLRenderer2_Init(sdl_renderer);
|
||||
|
||||
@@ -98,16 +102,15 @@ int EditorIntegrationTest::RunTest() {
|
||||
|
||||
absl::Status EditorIntegrationTest::Update() {
|
||||
ImGui::NewFrame();
|
||||
|
||||
|
||||
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||
// Show test engine windows
|
||||
ImGuiTestEngine_ShowTestEngineWindows(engine_, &show_demo_window_);
|
||||
#endif
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
// Helper methods for testing with a ROM
|
||||
absl::Status EditorIntegrationTest::LoadTestRom(const std::string& filename) {
|
||||
test_rom_ = std::make_unique<Rom>();
|
||||
@@ -125,7 +128,8 @@ absl::Status EditorIntegrationTest::SaveTestRom(const std::string& filename) {
|
||||
return test_rom_->SaveToFile(settings);
|
||||
}
|
||||
|
||||
absl::Status EditorIntegrationTest::TestEditorInitialize(editor::Editor* editor) {
|
||||
absl::Status EditorIntegrationTest::TestEditorInitialize(
|
||||
editor::Editor* editor) {
|
||||
if (!editor) {
|
||||
return absl::InternalError("Editor is null");
|
||||
}
|
||||
@@ -204,4 +208,4 @@ absl::Status EditorIntegrationTest::TestEditorClear(editor::Editor* editor) {
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
} // namespace yaze
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
#include "app/editor/editor.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/controller.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "app/editor/editor.h"
|
||||
#include "app/gfx/backend/sdl2_renderer.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "app/rom.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||
#include "imgui_test_engine/imgui_te_context.h"
|
||||
@@ -21,12 +21,14 @@ namespace test {
|
||||
/**
|
||||
* @class EditorIntegrationTest
|
||||
* @brief Base class for editor integration tests
|
||||
*
|
||||
* This class provides common functionality for testing editors in the application.
|
||||
* It sets up the test environment and provides helper methods for ROM operations.
|
||||
*
|
||||
* For UI interaction testing, use the ImGui test engine API directly within your test functions:
|
||||
*
|
||||
*
|
||||
* This class provides common functionality for testing editors in the
|
||||
* application. It sets up the test environment and provides helper methods for
|
||||
* ROM operations.
|
||||
*
|
||||
* For UI interaction testing, use the ImGui test engine API directly within
|
||||
* your test functions:
|
||||
*
|
||||
* ImGuiTest* test = IM_REGISTER_TEST(engine, "test_suite", "test_name");
|
||||
* test->TestFunc = [](ImGuiTestContext* ctx) {
|
||||
* ctx->SetRef("Window Name");
|
||||
@@ -40,10 +42,10 @@ class EditorIntegrationTest {
|
||||
|
||||
// Initialize the test environment
|
||||
absl::Status Initialize();
|
||||
|
||||
|
||||
// Run the test
|
||||
int RunTest();
|
||||
|
||||
|
||||
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
|
||||
// Register tests for a specific editor
|
||||
virtual void RegisterTests(ImGuiTestEngine* engine) = 0;
|
||||
@@ -51,16 +53,15 @@ class EditorIntegrationTest {
|
||||
// Default implementation when ImGui Test Engine is disabled
|
||||
virtual void RegisterTests(void* engine) {}
|
||||
#endif
|
||||
|
||||
|
||||
// Update the test environment
|
||||
virtual absl::Status Update();
|
||||
|
||||
protected:
|
||||
|
||||
// Helper methods for testing with a ROM
|
||||
absl::Status LoadTestRom(const std::string& filename);
|
||||
absl::Status SaveTestRom(const std::string& filename);
|
||||
|
||||
|
||||
// Helper methods for testing with a specific editor
|
||||
absl::Status TestEditorInitialize(editor::Editor* editor);
|
||||
absl::Status TestEditorLoad(editor::Editor* editor);
|
||||
@@ -88,4 +89,4 @@ class EditorIntegrationTest {
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_TEST_EDITOR_INTEGRATION_TEST_H
|
||||
#endif // YAZE_TEST_EDITOR_INTEGRATION_TEST_H
|
||||
@@ -1,17 +1,18 @@
|
||||
#include "app/editor/overworld/tile16_editor.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "app/rom.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gfx/backend/sdl2_renderer.h"
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/render/tilemap.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
@@ -28,7 +29,7 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
|
||||
// Clean up SDL
|
||||
if (window_initialized_) {
|
||||
auto shutdown_result = core::ShutdownWindow(test_window_);
|
||||
(void)shutdown_result; // Suppress unused variable warning
|
||||
(void)shutdown_result; // Suppress unused variable warning
|
||||
window_initialized_ = false;
|
||||
}
|
||||
}
|
||||
@@ -42,41 +43,46 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
|
||||
// Load the test ROM
|
||||
rom_ = std::make_unique<Rom>();
|
||||
auto load_result = rom_->LoadFromFile(YAZE_TEST_ROM_PATH);
|
||||
ASSERT_TRUE(load_result.ok()) << "Failed to load test ROM: " << load_result.message();
|
||||
|
||||
ASSERT_TRUE(load_result.ok())
|
||||
<< "Failed to load test ROM: " << load_result.message();
|
||||
|
||||
// Load overworld data
|
||||
overworld_ = std::make_unique<zelda3::Overworld>(rom_.get());
|
||||
auto overworld_load_result = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(overworld_load_result.ok()) << "Failed to load overworld: " << overworld_load_result.message();
|
||||
|
||||
ASSERT_TRUE(overworld_load_result.ok())
|
||||
<< "Failed to load overworld: " << overworld_load_result.message();
|
||||
|
||||
// Create tile16 blockset
|
||||
auto tile16_data = overworld_->tile16_blockset_data();
|
||||
auto palette = overworld_->current_area_palette();
|
||||
|
||||
|
||||
tile16_blockset_ = std::make_unique<gfx::Tilemap>(
|
||||
gfx::CreateTilemap(nullptr, tile16_data, 0x80, 0x2000, 16,
|
||||
zelda3::kNumTile16Individual, palette));
|
||||
|
||||
gfx::CreateTilemap(nullptr, tile16_data, 0x80, 0x2000, 16,
|
||||
zelda3::kNumTile16Individual, palette));
|
||||
|
||||
// Create graphics bitmap
|
||||
current_gfx_bmp_ = std::make_unique<gfx::Bitmap>();
|
||||
current_gfx_bmp_->Create(0x80, 512, 0x40, overworld_->current_graphics());
|
||||
current_gfx_bmp_->SetPalette(palette);
|
||||
gfx::Arena::Get().QueueTextureCommand(
|
||||
gfx::Arena::TextureCommandType::CREATE, current_gfx_bmp_.get());
|
||||
|
||||
|
||||
// Create tile16 blockset bitmap
|
||||
tile16_blockset_bmp_ = std::make_unique<gfx::Bitmap>();
|
||||
tile16_blockset_bmp_->Create(0x80, 0x2000, 0x08, tile16_data);
|
||||
tile16_blockset_bmp_->SetPalette(palette);
|
||||
gfx::Arena::Get().QueueTextureCommand(
|
||||
gfx::Arena::TextureCommandType::CREATE, tile16_blockset_bmp_.get());
|
||||
|
||||
|
||||
// Initialize the tile16 editor
|
||||
editor_ = std::make_unique<Tile16Editor>(rom_.get(), tile16_blockset_.get());
|
||||
auto init_result = editor_->Initialize(*tile16_blockset_bmp_, *current_gfx_bmp_,
|
||||
*overworld_->mutable_all_tiles_types());
|
||||
ASSERT_TRUE(init_result.ok()) << "Failed to initialize editor: " << init_result.message();
|
||||
|
||||
editor_ =
|
||||
std::make_unique<Tile16Editor>(rom_.get(), tile16_blockset_.get());
|
||||
auto init_result =
|
||||
editor_->Initialize(*tile16_blockset_bmp_, *current_gfx_bmp_,
|
||||
*overworld_->mutable_all_tiles_types());
|
||||
ASSERT_TRUE(init_result.ok())
|
||||
<< "Failed to initialize editor: " << init_result.message();
|
||||
|
||||
rom_loaded_ = true;
|
||||
#else
|
||||
// Fallback for non-ROM tests
|
||||
@@ -87,17 +93,19 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
|
||||
#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
protected:
|
||||
static void InitializeTestEnvironment() {
|
||||
// Create renderer for test
|
||||
test_renderer_ = std::make_unique<gfx::SDL2Renderer>();
|
||||
auto window_result = core::CreateWindow(test_window_, test_renderer_.get(), SDL_WINDOW_HIDDEN);
|
||||
auto window_result = core::CreateWindow(test_window_, test_renderer_.get(),
|
||||
SDL_WINDOW_HIDDEN);
|
||||
if (window_result.ok()) {
|
||||
window_initialized_ = true;
|
||||
} else {
|
||||
window_initialized_ = false;
|
||||
// Log the error but don't fail test setup
|
||||
std::cerr << "Failed to initialize test window: " << window_result.message() << std::endl;
|
||||
std::cerr << "Failed to initialize test window: "
|
||||
<< window_result.message() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,16 +133,16 @@ TEST_F(Tile16EditorIntegrationTest, BasicValidation) {
|
||||
// Test with invalid tile ID
|
||||
EXPECT_FALSE(editor_->IsTile16Valid(-1));
|
||||
EXPECT_FALSE(editor_->IsTile16Valid(9999));
|
||||
|
||||
|
||||
// Test scratch space operations with invalid slots
|
||||
auto save_invalid = editor_->SaveTile16ToScratchSpace(-1);
|
||||
EXPECT_FALSE(save_invalid.ok());
|
||||
EXPECT_EQ(save_invalid.code(), absl::StatusCode::kInvalidArgument);
|
||||
|
||||
|
||||
auto load_invalid = editor_->LoadTile16FromScratchSpace(5);
|
||||
EXPECT_FALSE(load_invalid.ok());
|
||||
EXPECT_EQ(load_invalid.code(), absl::StatusCode::kInvalidArgument);
|
||||
|
||||
|
||||
// Test valid scratch space clearing
|
||||
auto clear_valid = editor_->ClearScratchSpace(0);
|
||||
EXPECT_TRUE(clear_valid.ok());
|
||||
@@ -146,7 +154,7 @@ TEST_F(Tile16EditorIntegrationTest, ValidateTile16DataWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Test validation with properly loaded ROM
|
||||
auto status = editor_->ValidateTile16Data();
|
||||
EXPECT_TRUE(status.ok()) << "Validation failed: " << status.message();
|
||||
@@ -160,19 +168,21 @@ TEST_F(Tile16EditorIntegrationTest, SetCurrentTileWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Test setting a valid tile
|
||||
auto valid_tile_result = editor_->SetCurrentTile(0);
|
||||
EXPECT_TRUE(valid_tile_result.ok()) << "Failed to set tile 0: " << valid_tile_result.message();
|
||||
|
||||
EXPECT_TRUE(valid_tile_result.ok())
|
||||
<< "Failed to set tile 0: " << valid_tile_result.message();
|
||||
|
||||
auto valid_tile_result2 = editor_->SetCurrentTile(100);
|
||||
EXPECT_TRUE(valid_tile_result2.ok()) << "Failed to set tile 100: " << valid_tile_result2.message();
|
||||
|
||||
EXPECT_TRUE(valid_tile_result2.ok())
|
||||
<< "Failed to set tile 100: " << valid_tile_result2.message();
|
||||
|
||||
// Test invalid ranges still fail
|
||||
auto invalid_low = editor_->SetCurrentTile(-1);
|
||||
EXPECT_FALSE(invalid_low.ok());
|
||||
EXPECT_EQ(invalid_low.code(), absl::StatusCode::kOutOfRange);
|
||||
|
||||
|
||||
auto invalid_high = editor_->SetCurrentTile(10000);
|
||||
EXPECT_FALSE(invalid_high.ok());
|
||||
EXPECT_EQ(invalid_high.code(), absl::StatusCode::kOutOfRange);
|
||||
@@ -186,20 +196,24 @@ TEST_F(Tile16EditorIntegrationTest, FlipOperationsWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Set a valid tile first
|
||||
auto set_result = editor_->SetCurrentTile(1);
|
||||
ASSERT_TRUE(set_result.ok()) << "Failed to set initial tile: " << set_result.message();
|
||||
|
||||
ASSERT_TRUE(set_result.ok())
|
||||
<< "Failed to set initial tile: " << set_result.message();
|
||||
|
||||
// Test flip operations
|
||||
auto flip_h_result = editor_->FlipTile16Horizontal();
|
||||
EXPECT_TRUE(flip_h_result.ok()) << "Horizontal flip failed: " << flip_h_result.message();
|
||||
|
||||
EXPECT_TRUE(flip_h_result.ok())
|
||||
<< "Horizontal flip failed: " << flip_h_result.message();
|
||||
|
||||
auto flip_v_result = editor_->FlipTile16Vertical();
|
||||
EXPECT_TRUE(flip_v_result.ok()) << "Vertical flip failed: " << flip_v_result.message();
|
||||
|
||||
EXPECT_TRUE(flip_v_result.ok())
|
||||
<< "Vertical flip failed: " << flip_v_result.message();
|
||||
|
||||
auto rotate_result = editor_->RotateTile16();
|
||||
EXPECT_TRUE(rotate_result.ok()) << "Rotation failed: " << rotate_result.message();
|
||||
EXPECT_TRUE(rotate_result.ok())
|
||||
<< "Rotation failed: " << rotate_result.message();
|
||||
#else
|
||||
GTEST_SKIP() << "ROM tests disabled";
|
||||
#endif
|
||||
@@ -210,18 +224,19 @@ TEST_F(Tile16EditorIntegrationTest, UndoRedoWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Set a tile and perform an operation to create undo state
|
||||
auto set_result = editor_->SetCurrentTile(1);
|
||||
ASSERT_TRUE(set_result.ok());
|
||||
|
||||
|
||||
auto clear_result = editor_->ClearTile16();
|
||||
ASSERT_TRUE(clear_result.ok()) << "Clear operation failed: " << clear_result.message();
|
||||
|
||||
ASSERT_TRUE(clear_result.ok())
|
||||
<< "Clear operation failed: " << clear_result.message();
|
||||
|
||||
// Test undo
|
||||
auto undo_result = editor_->Undo();
|
||||
EXPECT_TRUE(undo_result.ok()) << "Undo failed: " << undo_result.message();
|
||||
|
||||
|
||||
// Test redo
|
||||
auto redo_result = editor_->Redo();
|
||||
EXPECT_TRUE(redo_result.ok()) << "Redo failed: " << redo_result.message();
|
||||
@@ -235,18 +250,21 @@ TEST_F(Tile16EditorIntegrationTest, PaletteOperationsWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Test palette cycling
|
||||
auto cycle_forward = editor_->CyclePalette(true);
|
||||
EXPECT_TRUE(cycle_forward.ok()) << "Palette cycle forward failed: " << cycle_forward.message();
|
||||
|
||||
EXPECT_TRUE(cycle_forward.ok())
|
||||
<< "Palette cycle forward failed: " << cycle_forward.message();
|
||||
|
||||
auto cycle_backward = editor_->CyclePalette(false);
|
||||
EXPECT_TRUE(cycle_backward.ok()) << "Palette cycle backward failed: " << cycle_backward.message();
|
||||
|
||||
EXPECT_TRUE(cycle_backward.ok())
|
||||
<< "Palette cycle backward failed: " << cycle_backward.message();
|
||||
|
||||
// Test valid palette preview
|
||||
auto valid_palette = editor_->PreviewPaletteChange(3);
|
||||
EXPECT_TRUE(valid_palette.ok()) << "Palette preview failed: " << valid_palette.message();
|
||||
|
||||
EXPECT_TRUE(valid_palette.ok())
|
||||
<< "Palette preview failed: " << valid_palette.message();
|
||||
|
||||
// Test invalid palette
|
||||
auto invalid_palette = editor_->PreviewPaletteChange(10);
|
||||
EXPECT_FALSE(invalid_palette.ok());
|
||||
@@ -261,15 +279,15 @@ TEST_F(Tile16EditorIntegrationTest, CopyPasteOperationsWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Set a tile first
|
||||
auto set_result = editor_->SetCurrentTile(10);
|
||||
ASSERT_TRUE(set_result.ok());
|
||||
|
||||
|
||||
// Test copy operation
|
||||
auto copy_result = editor_->CopyTile16ToClipboard(10);
|
||||
EXPECT_TRUE(copy_result.ok()) << "Copy failed: " << copy_result.message();
|
||||
|
||||
|
||||
// Test paste operation
|
||||
auto paste_result = editor_->PasteTile16FromClipboard();
|
||||
EXPECT_TRUE(paste_result.ok()) << "Paste failed: " << paste_result.message();
|
||||
@@ -283,22 +301,25 @@ TEST_F(Tile16EditorIntegrationTest, ScratchSpaceWithROM) {
|
||||
if (!rom_loaded_) {
|
||||
GTEST_SKIP() << "ROM not loaded, skipping integration test";
|
||||
}
|
||||
|
||||
|
||||
// Set a tile first
|
||||
auto set_result = editor_->SetCurrentTile(15);
|
||||
ASSERT_TRUE(set_result.ok());
|
||||
|
||||
|
||||
// Test scratch space save
|
||||
auto save_result = editor_->SaveTile16ToScratchSpace(0);
|
||||
EXPECT_TRUE(save_result.ok()) << "Scratch save failed: " << save_result.message();
|
||||
|
||||
EXPECT_TRUE(save_result.ok())
|
||||
<< "Scratch save failed: " << save_result.message();
|
||||
|
||||
// Test scratch space load
|
||||
auto load_result = editor_->LoadTile16FromScratchSpace(0);
|
||||
EXPECT_TRUE(load_result.ok()) << "Scratch load failed: " << load_result.message();
|
||||
|
||||
EXPECT_TRUE(load_result.ok())
|
||||
<< "Scratch load failed: " << load_result.message();
|
||||
|
||||
// Test scratch space clear
|
||||
auto clear_result = editor_->ClearScratchSpace(0);
|
||||
EXPECT_TRUE(clear_result.ok()) << "Scratch clear failed: " << clear_result.message();
|
||||
EXPECT_TRUE(clear_result.ok())
|
||||
<< "Scratch clear failed: " << clear_result.message();
|
||||
#else
|
||||
GTEST_SKIP() << "ROM tests disabled";
|
||||
#endif
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <chrono>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "zelda3/dungeon/dungeon_object_editor.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
@@ -19,18 +20,18 @@ class DungeonEditorSystemIntegrationTest : public ::testing::Test {
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
|
||||
|
||||
// Use the real ROM from build directory
|
||||
rom_path_ = "build/bin/zelda3.sfc";
|
||||
|
||||
|
||||
// Load ROM
|
||||
rom_ = std::make_unique<Rom>();
|
||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path_).ok());
|
||||
|
||||
|
||||
// Initialize dungeon editor system
|
||||
dungeon_editor_system_ = std::make_unique<DungeonEditorSystem>(rom_.get());
|
||||
ASSERT_TRUE(dungeon_editor_system_->Initialize().ok());
|
||||
|
||||
|
||||
// Load test room data
|
||||
ASSERT_TRUE(LoadTestRoomData().ok());
|
||||
}
|
||||
@@ -43,22 +44,23 @@ class DungeonEditorSystemIntegrationTest : public ::testing::Test {
|
||||
absl::Status LoadTestRoomData() {
|
||||
// Load representative rooms for testing
|
||||
test_rooms_ = {0x0000, 0x0001, 0x0002, 0x0010, 0x0012, 0x0020};
|
||||
|
||||
|
||||
for (int room_id : test_rooms_) {
|
||||
auto room_result = dungeon_editor_system_->GetRoom(room_id);
|
||||
if (room_result.ok()) {
|
||||
rooms_[room_id] = room_result.value();
|
||||
std::cout << "Loaded room 0x" << std::hex << room_id << std::dec << std::endl;
|
||||
std::cout << "Loaded room 0x" << std::hex << room_id << std::dec
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
std::string rom_path_;
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<DungeonEditorSystem> dungeon_editor_system_;
|
||||
|
||||
|
||||
std::vector<int> test_rooms_;
|
||||
std::map<int, Room> rooms_;
|
||||
};
|
||||
@@ -74,19 +76,21 @@ TEST_F(DungeonEditorSystemIntegrationTest, BasicInitialization) {
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, RoomLoadingAndManagement) {
|
||||
// Test loading a specific room
|
||||
auto room_result = dungeon_editor_system_->GetRoom(0x0000);
|
||||
ASSERT_TRUE(room_result.ok()) << "Failed to load room 0x0000: " << room_result.status().message();
|
||||
|
||||
ASSERT_TRUE(room_result.ok())
|
||||
<< "Failed to load room 0x0000: " << room_result.status().message();
|
||||
|
||||
const auto& room = room_result.value();
|
||||
// Note: room_id_ is private, so we can't directly access it in tests
|
||||
|
||||
|
||||
// Test setting current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
EXPECT_EQ(dungeon_editor_system_->GetCurrentRoom(), 0x0000);
|
||||
|
||||
|
||||
// Test loading another room
|
||||
auto room2_result = dungeon_editor_system_->GetRoom(0x0001);
|
||||
ASSERT_TRUE(room2_result.ok()) << "Failed to load room 0x0001: " << room2_result.status().message();
|
||||
|
||||
ASSERT_TRUE(room2_result.ok())
|
||||
<< "Failed to load room 0x0001: " << room2_result.status().message();
|
||||
|
||||
const auto& room2 = room2_result.value();
|
||||
// Note: room_id_ is private, so we can't directly access it in tests
|
||||
}
|
||||
@@ -96,22 +100,22 @@ TEST_F(DungeonEditorSystemIntegrationTest, ObjectEditorIntegration) {
|
||||
// Get object editor from system
|
||||
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||
ASSERT_NE(object_editor, nullptr);
|
||||
|
||||
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Test object insertion
|
||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
|
||||
// Verify objects were added
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||
|
||||
|
||||
// Test object selection
|
||||
ASSERT_TRUE(object_editor->SelectObject(5 * 16, 5 * 16).ok());
|
||||
auto selection = object_editor->GetSelection();
|
||||
EXPECT_EQ(selection.selected_objects.size(), 1);
|
||||
|
||||
|
||||
// Test object deletion
|
||||
ASSERT_TRUE(object_editor->DeleteSelectedObjects().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 1);
|
||||
@@ -121,7 +125,7 @@ TEST_F(DungeonEditorSystemIntegrationTest, ObjectEditorIntegration) {
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, SpriteManagement) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Create sprite data
|
||||
DungeonEditorSystem::SpriteData sprite_data;
|
||||
sprite_data.sprite_id = 1;
|
||||
@@ -131,31 +135,32 @@ TEST_F(DungeonEditorSystemIntegrationTest, SpriteManagement) {
|
||||
sprite_data.y = 100;
|
||||
sprite_data.layer = 0;
|
||||
sprite_data.is_active = true;
|
||||
|
||||
|
||||
// Add sprite
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddSprite(sprite_data).ok());
|
||||
|
||||
|
||||
// Get sprites for room
|
||||
auto sprites_result = dungeon_editor_system_->GetSpritesByRoom(0x0000);
|
||||
ASSERT_TRUE(sprites_result.ok()) << "Failed to get sprites: " << sprites_result.status().message();
|
||||
|
||||
ASSERT_TRUE(sprites_result.ok())
|
||||
<< "Failed to get sprites: " << sprites_result.status().message();
|
||||
|
||||
const auto& sprites = sprites_result.value();
|
||||
EXPECT_EQ(sprites.size(), 1);
|
||||
EXPECT_EQ(sprites[0].sprite_id, 1);
|
||||
EXPECT_EQ(sprites[0].name, "Test Sprite");
|
||||
|
||||
|
||||
// Update sprite
|
||||
sprite_data.x = 150;
|
||||
ASSERT_TRUE(dungeon_editor_system_->UpdateSprite(1, sprite_data).ok());
|
||||
|
||||
|
||||
// Get updated sprite
|
||||
auto sprite_result = dungeon_editor_system_->GetSprite(1);
|
||||
ASSERT_TRUE(sprite_result.ok());
|
||||
EXPECT_EQ(sprite_result.value().x, 150);
|
||||
|
||||
|
||||
// Remove sprite
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveSprite(1).ok());
|
||||
|
||||
|
||||
// Verify sprite was removed
|
||||
auto sprites_after = dungeon_editor_system_->GetSpritesByRoom(0x0000);
|
||||
ASSERT_TRUE(sprites_after.ok());
|
||||
@@ -166,7 +171,7 @@ TEST_F(DungeonEditorSystemIntegrationTest, SpriteManagement) {
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, ItemManagement) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Create item data
|
||||
DungeonEditorSystem::ItemData item_data;
|
||||
item_data.item_id = 1;
|
||||
@@ -176,31 +181,32 @@ TEST_F(DungeonEditorSystemIntegrationTest, ItemManagement) {
|
||||
item_data.y = 200;
|
||||
item_data.room_id = 0x0000;
|
||||
item_data.is_hidden = false;
|
||||
|
||||
|
||||
// Add item
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddItem(item_data).ok());
|
||||
|
||||
|
||||
// Get items for room
|
||||
auto items_result = dungeon_editor_system_->GetItemsByRoom(0x0000);
|
||||
ASSERT_TRUE(items_result.ok()) << "Failed to get items: " << items_result.status().message();
|
||||
|
||||
ASSERT_TRUE(items_result.ok())
|
||||
<< "Failed to get items: " << items_result.status().message();
|
||||
|
||||
const auto& items = items_result.value();
|
||||
EXPECT_EQ(items.size(), 1);
|
||||
EXPECT_EQ(items[0].item_id, 1);
|
||||
EXPECT_EQ(items[0].name, "Small Key");
|
||||
|
||||
|
||||
// Update item
|
||||
item_data.is_hidden = true;
|
||||
ASSERT_TRUE(dungeon_editor_system_->UpdateItem(1, item_data).ok());
|
||||
|
||||
|
||||
// Get updated item
|
||||
auto item_result = dungeon_editor_system_->GetItem(1);
|
||||
ASSERT_TRUE(item_result.ok());
|
||||
EXPECT_TRUE(item_result.value().is_hidden);
|
||||
|
||||
|
||||
// Remove item
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveItem(1).ok());
|
||||
|
||||
|
||||
// Verify item was removed
|
||||
auto items_after = dungeon_editor_system_->GetItemsByRoom(0x0000);
|
||||
ASSERT_TRUE(items_after.ok());
|
||||
@@ -221,32 +227,35 @@ TEST_F(DungeonEditorSystemIntegrationTest, EntranceManagement) {
|
||||
entrance_data.target_x = 200;
|
||||
entrance_data.target_y = 200;
|
||||
entrance_data.is_bidirectional = true;
|
||||
|
||||
|
||||
// Add entrance
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddEntrance(entrance_data).ok());
|
||||
|
||||
|
||||
// Get entrances for room
|
||||
auto entrances_result = dungeon_editor_system_->GetEntrancesByRoom(0x0000);
|
||||
ASSERT_TRUE(entrances_result.ok()) << "Failed to get entrances: " << entrances_result.status().message();
|
||||
|
||||
ASSERT_TRUE(entrances_result.ok())
|
||||
<< "Failed to get entrances: " << entrances_result.status().message();
|
||||
|
||||
const auto& entrances = entrances_result.value();
|
||||
EXPECT_EQ(entrances.size(), 1);
|
||||
EXPECT_EQ(entrances[0].name, "Test Entrance");
|
||||
|
||||
|
||||
// Store the entrance ID for later removal
|
||||
int entrance_id = entrances[0].entrance_id;
|
||||
|
||||
|
||||
// Test room connection
|
||||
ASSERT_TRUE(dungeon_editor_system_->ConnectRooms(0x0000, 0x0001, 150, 150, 250, 250).ok());
|
||||
|
||||
ASSERT_TRUE(
|
||||
dungeon_editor_system_->ConnectRooms(0x0000, 0x0001, 150, 150, 250, 250)
|
||||
.ok());
|
||||
|
||||
// Get updated entrances
|
||||
auto entrances_after = dungeon_editor_system_->GetEntrancesByRoom(0x0000);
|
||||
ASSERT_TRUE(entrances_after.ok());
|
||||
EXPECT_GE(entrances_after.value().size(), 1);
|
||||
|
||||
|
||||
// Remove entrance using the correct ID
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveEntrance(entrance_id).ok());
|
||||
|
||||
|
||||
// Verify entrance was removed
|
||||
auto entrances_final = dungeon_editor_system_->GetEntrancesByRoom(0x0000);
|
||||
ASSERT_TRUE(entrances_final.ok());
|
||||
@@ -262,47 +271,48 @@ TEST_F(DungeonEditorSystemIntegrationTest, DoorManagement) {
|
||||
door_data.room_id = 0x0000;
|
||||
door_data.x = 100;
|
||||
door_data.y = 100;
|
||||
door_data.direction = 0; // up
|
||||
door_data.direction = 0; // up
|
||||
door_data.target_room_id = 0x0001;
|
||||
door_data.target_x = 200;
|
||||
door_data.target_y = 200;
|
||||
door_data.requires_key = false;
|
||||
door_data.key_type = 0;
|
||||
door_data.is_locked = false;
|
||||
|
||||
|
||||
// Add door
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddDoor(door_data).ok());
|
||||
|
||||
|
||||
// Get doors for room
|
||||
auto doors_result = dungeon_editor_system_->GetDoorsByRoom(0x0000);
|
||||
ASSERT_TRUE(doors_result.ok()) << "Failed to get doors: " << doors_result.status().message();
|
||||
|
||||
ASSERT_TRUE(doors_result.ok())
|
||||
<< "Failed to get doors: " << doors_result.status().message();
|
||||
|
||||
const auto& doors = doors_result.value();
|
||||
EXPECT_EQ(doors.size(), 1);
|
||||
EXPECT_EQ(doors[0].door_id, 1);
|
||||
EXPECT_EQ(doors[0].name, "Test Door");
|
||||
|
||||
|
||||
// Update door
|
||||
door_data.is_locked = true;
|
||||
ASSERT_TRUE(dungeon_editor_system_->UpdateDoor(1, door_data).ok());
|
||||
|
||||
|
||||
// Get updated door
|
||||
auto door_result = dungeon_editor_system_->GetDoor(1);
|
||||
ASSERT_TRUE(door_result.ok());
|
||||
EXPECT_TRUE(door_result.value().is_locked);
|
||||
|
||||
|
||||
// Set door key requirement
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetDoorKeyRequirement(1, true, 1).ok());
|
||||
|
||||
|
||||
// Get door with key requirement
|
||||
auto door_with_key = dungeon_editor_system_->GetDoor(1);
|
||||
ASSERT_TRUE(door_with_key.ok());
|
||||
EXPECT_TRUE(door_with_key.value().requires_key);
|
||||
EXPECT_EQ(door_with_key.value().key_type, 1);
|
||||
|
||||
|
||||
// Remove door
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveDoor(1).ok());
|
||||
|
||||
|
||||
// Verify door was removed
|
||||
auto doors_after = dungeon_editor_system_->GetDoorsByRoom(0x0000);
|
||||
ASSERT_TRUE(doors_after.ok());
|
||||
@@ -321,39 +331,40 @@ TEST_F(DungeonEditorSystemIntegrationTest, ChestManagement) {
|
||||
chest_data.item_id = 10;
|
||||
chest_data.item_quantity = 1;
|
||||
chest_data.is_opened = false;
|
||||
|
||||
|
||||
// Add chest
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddChest(chest_data).ok());
|
||||
|
||||
|
||||
// Get chests for room
|
||||
auto chests_result = dungeon_editor_system_->GetChestsByRoom(0x0000);
|
||||
ASSERT_TRUE(chests_result.ok()) << "Failed to get chests: " << chests_result.status().message();
|
||||
|
||||
ASSERT_TRUE(chests_result.ok())
|
||||
<< "Failed to get chests: " << chests_result.status().message();
|
||||
|
||||
const auto& chests = chests_result.value();
|
||||
EXPECT_EQ(chests.size(), 1);
|
||||
EXPECT_EQ(chests[0].chest_id, 1);
|
||||
EXPECT_EQ(chests[0].item_id, 10);
|
||||
|
||||
|
||||
// Update chest item
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetChestItem(1, 20, 5).ok());
|
||||
|
||||
|
||||
// Get updated chest
|
||||
auto chest_result = dungeon_editor_system_->GetChest(1);
|
||||
ASSERT_TRUE(chest_result.ok());
|
||||
EXPECT_EQ(chest_result.value().item_id, 20);
|
||||
EXPECT_EQ(chest_result.value().item_quantity, 5);
|
||||
|
||||
|
||||
// Set chest as opened
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetChestOpened(1, true).ok());
|
||||
|
||||
|
||||
// Get opened chest
|
||||
auto opened_chest = dungeon_editor_system_->GetChest(1);
|
||||
ASSERT_TRUE(opened_chest.ok());
|
||||
EXPECT_TRUE(opened_chest.value().is_opened);
|
||||
|
||||
|
||||
// Remove chest
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveChest(1).ok());
|
||||
|
||||
|
||||
// Verify chest was removed
|
||||
auto chests_after = dungeon_editor_system_->GetChestsByRoom(0x0000);
|
||||
ASSERT_TRUE(chests_after.ok());
|
||||
@@ -374,25 +385,29 @@ TEST_F(DungeonEditorSystemIntegrationTest, RoomPropertiesManagement) {
|
||||
properties.is_shop_room = false;
|
||||
properties.music_id = 1;
|
||||
properties.ambient_sound_id = 0;
|
||||
|
||||
|
||||
// Set room properties
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetRoomProperties(0x0000, properties).ok());
|
||||
|
||||
ASSERT_TRUE(
|
||||
dungeon_editor_system_->SetRoomProperties(0x0000, properties).ok());
|
||||
|
||||
// Get room properties
|
||||
auto properties_result = dungeon_editor_system_->GetRoomProperties(0x0000);
|
||||
ASSERT_TRUE(properties_result.ok()) << "Failed to get room properties: " << properties_result.status().message();
|
||||
|
||||
ASSERT_TRUE(properties_result.ok()) << "Failed to get room properties: "
|
||||
<< properties_result.status().message();
|
||||
|
||||
const auto& retrieved_properties = properties_result.value();
|
||||
EXPECT_EQ(retrieved_properties.room_id, 0x0000);
|
||||
EXPECT_EQ(retrieved_properties.name, "Test Room");
|
||||
EXPECT_EQ(retrieved_properties.description, "A test room for integration testing");
|
||||
EXPECT_EQ(retrieved_properties.description,
|
||||
"A test room for integration testing");
|
||||
EXPECT_EQ(retrieved_properties.dungeon_id, 1);
|
||||
|
||||
|
||||
// Update properties
|
||||
properties.name = "Updated Test Room";
|
||||
properties.is_boss_room = true;
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetRoomProperties(0x0000, properties).ok());
|
||||
|
||||
ASSERT_TRUE(
|
||||
dungeon_editor_system_->SetRoomProperties(0x0000, properties).ok());
|
||||
|
||||
// Verify update
|
||||
auto updated_properties = dungeon_editor_system_->GetRoomProperties(0x0000);
|
||||
ASSERT_TRUE(updated_properties.ok());
|
||||
@@ -415,14 +430,15 @@ TEST_F(DungeonEditorSystemIntegrationTest, DungeonSettingsManagement) {
|
||||
settings.has_map = true;
|
||||
settings.has_compass = true;
|
||||
settings.has_big_key = true;
|
||||
|
||||
|
||||
// Set dungeon settings
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetDungeonSettings(settings).ok());
|
||||
|
||||
|
||||
// Get dungeon settings
|
||||
auto settings_result = dungeon_editor_system_->GetDungeonSettings();
|
||||
ASSERT_TRUE(settings_result.ok()) << "Failed to get dungeon settings: " << settings_result.status().message();
|
||||
|
||||
ASSERT_TRUE(settings_result.ok()) << "Failed to get dungeon settings: "
|
||||
<< settings_result.status().message();
|
||||
|
||||
const auto& retrieved_settings = settings_result.value();
|
||||
EXPECT_EQ(retrieved_settings.dungeon_id, 1);
|
||||
EXPECT_EQ(retrieved_settings.name, "Test Dungeon");
|
||||
@@ -438,31 +454,31 @@ TEST_F(DungeonEditorSystemIntegrationTest, DungeonSettingsManagement) {
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, UndoRedoFunctionality) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Get object editor
|
||||
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||
ASSERT_NE(object_editor, nullptr);
|
||||
|
||||
|
||||
// Add some objects
|
||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
|
||||
// Verify objects were added
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||
|
||||
|
||||
// Test undo
|
||||
ASSERT_TRUE(dungeon_editor_system_->Undo().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 1);
|
||||
|
||||
|
||||
// Test redo
|
||||
ASSERT_TRUE(dungeon_editor_system_->Redo().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||
|
||||
|
||||
// Test multiple undos
|
||||
ASSERT_TRUE(dungeon_editor_system_->Undo().ok());
|
||||
ASSERT_TRUE(dungeon_editor_system_->Undo().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 0);
|
||||
|
||||
|
||||
// Test multiple redos
|
||||
ASSERT_TRUE(dungeon_editor_system_->Redo().ok());
|
||||
ASSERT_TRUE(dungeon_editor_system_->Redo().ok());
|
||||
@@ -473,37 +489,39 @@ TEST_F(DungeonEditorSystemIntegrationTest, UndoRedoFunctionality) {
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, ValidationFunctionality) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Validate room
|
||||
auto room_validation = dungeon_editor_system_->ValidateRoom(0x0000);
|
||||
ASSERT_TRUE(room_validation.ok()) << "Room validation failed: " << room_validation.message();
|
||||
|
||||
ASSERT_TRUE(room_validation.ok())
|
||||
<< "Room validation failed: " << room_validation.message();
|
||||
|
||||
// Validate dungeon
|
||||
auto dungeon_validation = dungeon_editor_system_->ValidateDungeon();
|
||||
ASSERT_TRUE(dungeon_validation.ok()) << "Dungeon validation failed: " << dungeon_validation.message();
|
||||
ASSERT_TRUE(dungeon_validation.ok())
|
||||
<< "Dungeon validation failed: " << dungeon_validation.message();
|
||||
}
|
||||
|
||||
// Test save/load functionality
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, SaveLoadFunctionality) {
|
||||
// Set current room and add some objects
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
|
||||
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||
ASSERT_NE(object_editor, nullptr);
|
||||
|
||||
|
||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
|
||||
// Save room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SaveRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Reload room
|
||||
ASSERT_TRUE(dungeon_editor_system_->ReloadRoom(0x0000).ok());
|
||||
|
||||
|
||||
// Verify objects are still there
|
||||
auto reloaded_objects = object_editor->GetObjects();
|
||||
EXPECT_EQ(reloaded_objects.size(), 2);
|
||||
|
||||
|
||||
// Save entire dungeon
|
||||
ASSERT_TRUE(dungeon_editor_system_->SaveDungeon().ok());
|
||||
}
|
||||
@@ -511,7 +529,7 @@ TEST_F(DungeonEditorSystemIntegrationTest, SaveLoadFunctionality) {
|
||||
// Test performance with multiple operations
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, PerformanceTest) {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
// Perform many operations
|
||||
for (int i = 0; i < 100; i++) {
|
||||
// Add sprite
|
||||
@@ -521,9 +539,9 @@ TEST_F(DungeonEditorSystemIntegrationTest, PerformanceTest) {
|
||||
sprite_data.x = i * 10;
|
||||
sprite_data.y = i * 10;
|
||||
sprite_data.layer = 0;
|
||||
|
||||
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddSprite(sprite_data).ok());
|
||||
|
||||
|
||||
// Add item
|
||||
DungeonEditorSystem::ItemData item_data;
|
||||
item_data.item_id = i;
|
||||
@@ -531,17 +549,20 @@ TEST_F(DungeonEditorSystemIntegrationTest, PerformanceTest) {
|
||||
item_data.x = i * 15;
|
||||
item_data.y = i * 15;
|
||||
item_data.room_id = 0x0000;
|
||||
|
||||
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddItem(item_data).ok());
|
||||
}
|
||||
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
||||
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
end_time - start_time);
|
||||
|
||||
// Should complete in reasonable time (less than 5 seconds for 200 operations)
|
||||
EXPECT_LT(duration.count(), 5000) << "Performance test too slow: " << duration.count() << "ms";
|
||||
|
||||
std::cout << "Performance test: 200 operations took " << duration.count() << "ms" << std::endl;
|
||||
EXPECT_LT(duration.count(), 5000)
|
||||
<< "Performance test too slow: " << duration.count() << "ms";
|
||||
|
||||
std::cout << "Performance test: 200 operations took " << duration.count()
|
||||
<< "ms" << std::endl;
|
||||
}
|
||||
|
||||
// Test error handling
|
||||
@@ -549,26 +570,26 @@ TEST_F(DungeonEditorSystemIntegrationTest, ErrorHandling) {
|
||||
// Test with invalid room ID
|
||||
auto invalid_room = dungeon_editor_system_->GetRoom(-1);
|
||||
EXPECT_FALSE(invalid_room.ok());
|
||||
|
||||
|
||||
auto invalid_room_large = dungeon_editor_system_->GetRoom(10000);
|
||||
EXPECT_FALSE(invalid_room_large.ok());
|
||||
|
||||
|
||||
// Test with invalid sprite ID
|
||||
auto invalid_sprite = dungeon_editor_system_->GetSprite(-1);
|
||||
EXPECT_FALSE(invalid_sprite.ok());
|
||||
|
||||
|
||||
// Test with invalid item ID
|
||||
auto invalid_item = dungeon_editor_system_->GetItem(-1);
|
||||
EXPECT_FALSE(invalid_item.ok());
|
||||
|
||||
|
||||
// Test with invalid entrance ID
|
||||
auto invalid_entrance = dungeon_editor_system_->GetEntrance(-1);
|
||||
EXPECT_FALSE(invalid_entrance.ok());
|
||||
|
||||
|
||||
// Test with invalid door ID
|
||||
auto invalid_door = dungeon_editor_system_->GetDoor(-1);
|
||||
EXPECT_FALSE(invalid_door.ok());
|
||||
|
||||
|
||||
// Test with invalid chest ID
|
||||
auto invalid_chest = dungeon_editor_system_->GetChest(-1);
|
||||
EXPECT_FALSE(invalid_chest.ok());
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
// Integration tests for dungeon object rendering using ObjectDrawer
|
||||
// Updated for DungeonEditorV2 architecture - uses ObjectDrawer (production system)
|
||||
// instead of the obsolete ObjectRenderer
|
||||
// Updated for DungeonEditorV2 architecture - uses ObjectDrawer (production
|
||||
// system) instead of the obsolete ObjectRenderer
|
||||
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/render/background_buffer.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "test_utils.h"
|
||||
#include "testing.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/gfx/render/background_buffer.h"
|
||||
#include "testing.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* @brief Tests for ObjectDrawer with realistic dungeon scenarios
|
||||
*
|
||||
*
|
||||
* These tests validate that ObjectDrawer correctly renders dungeon objects
|
||||
* to BackgroundBuffers using pattern-based drawing routines.
|
||||
*/
|
||||
@@ -56,18 +56,19 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
gfx::PaletteGroup CreateTestPaletteGroup() {
|
||||
gfx::PaletteGroup group;
|
||||
gfx::SnesPalette palette;
|
||||
|
||||
|
||||
// Create standard dungeon palette
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int intensity = i * 16;
|
||||
palette.AddColor(gfx::SnesColor(intensity, intensity, intensity));
|
||||
}
|
||||
|
||||
|
||||
group.AddPalette(palette);
|
||||
return group;
|
||||
}
|
||||
|
||||
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12, int layer = 0) {
|
||||
|
||||
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12,
|
||||
int layer = 0) {
|
||||
zelda3::RoomObject obj(id, x, y, size, layer);
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
@@ -83,15 +84,15 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
// Test basic object drawing
|
||||
TEST_F(DungeonObjectRenderingTests, BasicObjectDrawing) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // Wall
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // Wall
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 0)); // Floor
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
ASSERT_TRUE(status.ok()) << "Drawing failed: " << status.message();
|
||||
|
||||
|
||||
// Verify buffers have content
|
||||
auto& bg1_bitmap = bg1_->bitmap();
|
||||
EXPECT_TRUE(bg1_bitmap.is_active());
|
||||
@@ -101,28 +102,54 @@ TEST_F(DungeonObjectRenderingTests, BasicObjectDrawing) {
|
||||
// Test objects on different layers
|
||||
TEST_F(DungeonObjectRenderingTests, MultiLayerRendering) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // BG1
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 1)); // BG2
|
||||
objects.push_back(CreateTestObject(0x30, 15, 15, 0x12, 2)); // BG3
|
||||
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // BG1
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 1)); // BG2
|
||||
objects.push_back(CreateTestObject(0x30, 15, 15, 0x12, 2)); // BG3
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Both buffers should be active
|
||||
EXPECT_TRUE(bg1_->bitmap().is_active());
|
||||
EXPECT_TRUE(bg2_->bitmap().is_active());
|
||||
}
|
||||
|
||||
// Test that a compact buffer (preview-sized) receives pixels
|
||||
TEST_F(DungeonObjectRenderingTests, PreviewBufferRendersContent) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 1, 1, 0x12, 0)); // Small wall
|
||||
|
||||
gfx::BackgroundBuffer preview_bg(64, 64);
|
||||
gfx::BackgroundBuffer preview_bg2(64, 64);
|
||||
preview_bg.ClearBuffer();
|
||||
preview_bg2.ClearBuffer();
|
||||
|
||||
auto status =
|
||||
drawer_->DrawObjectList(objects, preview_bg, preview_bg2, palette_group_);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
auto& bitmap = preview_bg.bitmap();
|
||||
EXPECT_TRUE(bitmap.is_active());
|
||||
const auto& data = bitmap.data();
|
||||
size_t non_zero = 0;
|
||||
for (size_t i = 0; i < data.size(); i += 16) {
|
||||
if (data[i] != 0) {
|
||||
non_zero++;
|
||||
}
|
||||
}
|
||||
EXPECT_GT(non_zero, 0u);
|
||||
}
|
||||
|
||||
// Test empty object list
|
||||
TEST_F(DungeonObjectRenderingTests, EmptyObjectList) {
|
||||
std::vector<zelda3::RoomObject> objects; // Empty
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
// Should succeed (drawing nothing is valid)
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -131,40 +158,42 @@ TEST_F(DungeonObjectRenderingTests, EmptyObjectList) {
|
||||
// Test large object set
|
||||
TEST_F(DungeonObjectRenderingTests, LargeObjectSet) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
|
||||
|
||||
// Create 100 test objects
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int x = (i % 10) * 5;
|
||||
int y = (i / 10) * 5;
|
||||
objects.push_back(CreateTestObject(0x10 + (i % 20), x, y, 0x12, i % 2));
|
||||
}
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// Should complete in reasonable time
|
||||
EXPECT_LT(duration.count(), 1000) << "Rendered 100 objects in " << duration.count() << "ms";
|
||||
EXPECT_LT(duration.count(), 1000)
|
||||
<< "Rendered 100 objects in " << duration.count() << "ms";
|
||||
}
|
||||
|
||||
// Test boundary conditions
|
||||
TEST_F(DungeonObjectRenderingTests, BoundaryObjects) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
|
||||
|
||||
// Objects at boundaries
|
||||
objects.push_back(CreateTestObject(0x10, 0, 0, 0x12, 0)); // Origin
|
||||
objects.push_back(CreateTestObject(0x10, 63, 63, 0x12, 0)); // Max valid
|
||||
objects.push_back(CreateTestObject(0x10, 32, 32, 0x12, 0)); // Center
|
||||
|
||||
objects.push_back(CreateTestObject(0x10, 0, 0, 0x12, 0)); // Origin
|
||||
objects.push_back(CreateTestObject(0x10, 63, 63, 0x12, 0)); // Max valid
|
||||
objects.push_back(CreateTestObject(0x10, 32, 32, 0x12, 0)); // Center
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
@@ -173,24 +202,25 @@ TEST_F(DungeonObjectRenderingTests, BoundaryObjects) {
|
||||
TEST_F(DungeonObjectRenderingTests, VariousObjectTypes) {
|
||||
// Test common object types
|
||||
std::vector<int> object_types = {
|
||||
0x00, 0x01, 0x02, 0x03, // Floor/wall objects
|
||||
0x09, 0x0A, // Diagonal objects
|
||||
0x10, 0x11, 0x12, // Standard objects
|
||||
0x20, 0x21, // Decorative objects
|
||||
0x34, // Solid block
|
||||
0x00, 0x01, 0x02, 0x03, // Floor/wall objects
|
||||
0x09, 0x0A, // Diagonal objects
|
||||
0x10, 0x11, 0x12, // Standard objects
|
||||
0x20, 0x21, // Decorative objects
|
||||
0x34, // Solid block
|
||||
};
|
||||
|
||||
|
||||
for (int obj_type : object_types) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(obj_type, 10, 10, 0x12, 0));
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
|
||||
auto status =
|
||||
drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
// Some object types might not be valid, that's okay
|
||||
if (!status.ok()) {
|
||||
std::cout << "Object type 0x" << std::hex << obj_type << std::dec
|
||||
std::cout << "Object type 0x" << std::hex << obj_type << std::dec
|
||||
<< " not renderable: " << status.message() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -202,15 +232,15 @@ TEST_F(DungeonObjectRenderingTests, ErrorHandling) {
|
||||
zelda3::ObjectDrawer null_drawer(nullptr);
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5));
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
auto status = null_drawer.DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
|
||||
auto status =
|
||||
null_drawer.DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
// Integration tests for dungeon object rendering using ObjectDrawer
|
||||
// Updated for DungeonEditorV2 architecture - uses ObjectDrawer (production system)
|
||||
// instead of the obsolete ObjectRenderer
|
||||
// Updated for DungeonEditorV2 architecture - uses ObjectDrawer (production
|
||||
// system) instead of the obsolete ObjectRenderer
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "test_utils.h"
|
||||
#include "testing.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "testing.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* @brief Tests for ObjectDrawer with realistic dungeon scenarios
|
||||
*
|
||||
*
|
||||
* These tests validate that ObjectDrawer correctly renders dungeon objects
|
||||
* to BackgroundBuffers using pattern-based drawing routines.
|
||||
*/
|
||||
@@ -52,18 +52,19 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
gfx::PaletteGroup CreateTestPaletteGroup() {
|
||||
gfx::PaletteGroup group;
|
||||
gfx::SnesPalette palette;
|
||||
|
||||
|
||||
// Create standard dungeon palette
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int intensity = i * 16;
|
||||
palette.AddColor(gfx::SnesColor(intensity, intensity, intensity));
|
||||
}
|
||||
|
||||
|
||||
group.AddPalette(palette);
|
||||
return group;
|
||||
}
|
||||
|
||||
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12, int layer = 0) {
|
||||
|
||||
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12,
|
||||
int layer = 0) {
|
||||
zelda3::RoomObject obj(id, x, y, size, layer);
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
@@ -79,15 +80,15 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
// Test basic object drawing
|
||||
TEST_F(DungeonObjectRenderingTests, BasicObjectDrawing) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // Wall
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // Wall
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 0)); // Floor
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
ASSERT_TRUE(status.ok()) << "Drawing failed: " << status.message();
|
||||
|
||||
|
||||
// Verify buffers have content
|
||||
auto& bg1_bitmap = bg1_->bitmap();
|
||||
EXPECT_TRUE(bg1_bitmap.is_active());
|
||||
@@ -97,16 +98,16 @@ TEST_F(DungeonObjectRenderingTests, BasicObjectDrawing) {
|
||||
// Test objects on different layers
|
||||
TEST_F(DungeonObjectRenderingTests, MultiLayerRendering) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // BG1
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 1)); // BG2
|
||||
objects.push_back(CreateTestObject(0x30, 15, 15, 0x12, 2)); // BG3
|
||||
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // BG1
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 1)); // BG2
|
||||
objects.push_back(CreateTestObject(0x30, 15, 15, 0x12, 2)); // BG3
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Both buffers should be active
|
||||
EXPECT_TRUE(bg1_->bitmap().is_active());
|
||||
EXPECT_TRUE(bg2_->bitmap().is_active());
|
||||
@@ -115,10 +116,10 @@ TEST_F(DungeonObjectRenderingTests, MultiLayerRendering) {
|
||||
// Test empty object list
|
||||
TEST_F(DungeonObjectRenderingTests, EmptyObjectList) {
|
||||
std::vector<zelda3::RoomObject> objects; // Empty
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
// Should succeed (drawing nothing is valid)
|
||||
EXPECT_TRUE(status.ok());
|
||||
@@ -127,40 +128,42 @@ TEST_F(DungeonObjectRenderingTests, EmptyObjectList) {
|
||||
// Test large object set
|
||||
TEST_F(DungeonObjectRenderingTests, LargeObjectSet) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
|
||||
|
||||
// Create 100 test objects
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int x = (i % 10) * 5;
|
||||
int y = (i / 10) * 5;
|
||||
objects.push_back(CreateTestObject(0x10 + (i % 20), x, y, 0x12, i % 2));
|
||||
}
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// Should complete in reasonable time
|
||||
EXPECT_LT(duration.count(), 1000) << "Rendered 100 objects in " << duration.count() << "ms";
|
||||
EXPECT_LT(duration.count(), 1000)
|
||||
<< "Rendered 100 objects in " << duration.count() << "ms";
|
||||
}
|
||||
|
||||
// Test boundary conditions
|
||||
TEST_F(DungeonObjectRenderingTests, BoundaryObjects) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
|
||||
|
||||
// Objects at boundaries
|
||||
objects.push_back(CreateTestObject(0x10, 0, 0, 0x12, 0)); // Origin
|
||||
objects.push_back(CreateTestObject(0x10, 63, 63, 0x12, 0)); // Max valid
|
||||
objects.push_back(CreateTestObject(0x10, 32, 32, 0x12, 0)); // Center
|
||||
|
||||
objects.push_back(CreateTestObject(0x10, 0, 0, 0x12, 0)); // Origin
|
||||
objects.push_back(CreateTestObject(0x10, 63, 63, 0x12, 0)); // Max valid
|
||||
objects.push_back(CreateTestObject(0x10, 32, 32, 0x12, 0)); // Center
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
EXPECT_TRUE(status.ok());
|
||||
}
|
||||
@@ -169,24 +172,25 @@ TEST_F(DungeonObjectRenderingTests, BoundaryObjects) {
|
||||
TEST_F(DungeonObjectRenderingTests, VariousObjectTypes) {
|
||||
// Test common object types
|
||||
std::vector<int> object_types = {
|
||||
0x00, 0x01, 0x02, 0x03, // Floor/wall objects
|
||||
0x09, 0x0A, // Diagonal objects
|
||||
0x10, 0x11, 0x12, // Standard objects
|
||||
0x20, 0x21, // Decorative objects
|
||||
0x34, // Solid block
|
||||
0x00, 0x01, 0x02, 0x03, // Floor/wall objects
|
||||
0x09, 0x0A, // Diagonal objects
|
||||
0x10, 0x11, 0x12, // Standard objects
|
||||
0x20, 0x21, // Decorative objects
|
||||
0x34, // Solid block
|
||||
};
|
||||
|
||||
|
||||
for (int obj_type : object_types) {
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(obj_type, 10, 10, 0x12, 0));
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
|
||||
auto status =
|
||||
drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
// Some object types might not be valid, that's okay
|
||||
if (!status.ok()) {
|
||||
std::cout << "Object type 0x" << std::hex << obj_type << std::dec
|
||||
std::cout << "Object type 0x" << std::hex << obj_type << std::dec
|
||||
<< " not renderable: " << status.message() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -198,15 +202,15 @@ TEST_F(DungeonObjectRenderingTests, ErrorHandling) {
|
||||
zelda3::ObjectDrawer null_drawer(nullptr);
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5));
|
||||
|
||||
|
||||
bg1_->ClearBuffer();
|
||||
bg2_->ClearBuffer();
|
||||
|
||||
auto status = null_drawer.DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
|
||||
auto status =
|
||||
null_drawer.DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/dungeon/object_parser.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
@@ -18,70 +17,68 @@ class DungeonRenderingIntegrationTest : public ::testing::Test {
|
||||
// Create a mock ROM for testing
|
||||
rom_ = std::make_unique<Rom>();
|
||||
// Initialize with minimal ROM data for testing
|
||||
std::vector<uint8_t> mock_rom_data(1024 * 1024, 0); // 1MB mock ROM
|
||||
std::vector<uint8_t> mock_rom_data(1024 * 1024, 0); // 1MB mock ROM
|
||||
rom_->LoadFromData(mock_rom_data);
|
||||
|
||||
|
||||
// Create test rooms
|
||||
room_0x00_ = CreateTestRoom(0x00); // Link's House
|
||||
room_0x01_ = CreateTestRoom(0x01); // Another test room
|
||||
room_0x00_ = CreateTestRoom(0x00); // Link's House
|
||||
room_0x01_ = CreateTestRoom(0x01); // Another test room
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
rom_.reset();
|
||||
}
|
||||
void TearDown() override { rom_.reset(); }
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
|
||||
|
||||
// Create a test room with various objects
|
||||
Room CreateTestRoom(int room_id) {
|
||||
Room room(room_id, rom_.get());
|
||||
|
||||
|
||||
// Add some test objects to the room
|
||||
std::vector<RoomObject> objects;
|
||||
|
||||
|
||||
// Add floor objects (object 0x00)
|
||||
objects.emplace_back(0x00, 5, 5, 3, 0); // Horizontal floor
|
||||
objects.emplace_back(0x00, 10, 10, 5, 0); // Another floor section
|
||||
|
||||
objects.emplace_back(0x00, 5, 5, 3, 0); // Horizontal floor
|
||||
objects.emplace_back(0x00, 10, 10, 5, 0); // Another floor section
|
||||
|
||||
// Add wall objects (object 0x01)
|
||||
objects.emplace_back(0x01, 15, 15, 2, 0); // Vertical wall
|
||||
objects.emplace_back(0x01, 20, 20, 4, 1); // Horizontal wall on BG2
|
||||
|
||||
objects.emplace_back(0x01, 15, 15, 2, 0); // Vertical wall
|
||||
objects.emplace_back(0x01, 20, 20, 4, 1); // Horizontal wall on BG2
|
||||
|
||||
// Add diagonal stairs (object 0x09)
|
||||
objects.emplace_back(0x09, 25, 25, 6, 0); // Diagonal stairs
|
||||
|
||||
objects.emplace_back(0x09, 25, 25, 6, 0); // Diagonal stairs
|
||||
|
||||
// Add solid blocks (object 0x34)
|
||||
objects.emplace_back(0x34, 30, 30, 1, 0); // Solid block
|
||||
objects.emplace_back(0x34, 35, 35, 2, 1); // Another solid block on BG2
|
||||
|
||||
objects.emplace_back(0x34, 30, 30, 1, 0); // Solid block
|
||||
objects.emplace_back(0x34, 35, 35, 2, 1); // Another solid block on BG2
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
}
|
||||
|
||||
|
||||
// Add objects to room (this would normally be done by LoadObjects)
|
||||
for (const auto& obj : objects) {
|
||||
room.AddObject(obj);
|
||||
}
|
||||
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
|
||||
// Create a test palette
|
||||
gfx::SnesPalette CreateTestPalette() {
|
||||
gfx::SnesPalette palette;
|
||||
// Add some test colors
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 0)); // Transparent
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 0)); // Red
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 0)); // Green
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 255)); // Blue
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 0)); // Yellow
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 255)); // Magenta
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 255)); // Cyan
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 255)); // White
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 0)); // Transparent
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 0)); // Red
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 0)); // Green
|
||||
palette.AddColor(gfx::SnesColor(0, 0, 255)); // Blue
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 0)); // Yellow
|
||||
palette.AddColor(gfx::SnesColor(255, 0, 255)); // Magenta
|
||||
palette.AddColor(gfx::SnesColor(0, 255, 255)); // Cyan
|
||||
palette.AddColor(gfx::SnesColor(255, 255, 255)); // White
|
||||
return palette;
|
||||
}
|
||||
|
||||
|
||||
gfx::PaletteGroup CreateTestPaletteGroup() {
|
||||
gfx::PaletteGroup group;
|
||||
group.AddPalette(CreateTestPalette());
|
||||
@@ -96,19 +93,18 @@ class DungeonRenderingIntegrationTest : public ::testing::Test {
|
||||
// Test full room rendering with ObjectDrawer
|
||||
TEST_F(DungeonRenderingIntegrationTest, FullRoomRenderingWorks) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
|
||||
|
||||
// Test that room has objects
|
||||
EXPECT_GT(test_room.GetTileObjects().size(), 0);
|
||||
|
||||
|
||||
// Test ObjectDrawer can render the room
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
auto status = drawer.DrawObjectList(test_room.GetTileObjects(),
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
|
||||
auto status =
|
||||
drawer.DrawObjectList(test_room.GetTileObjects(), test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
@@ -116,21 +112,20 @@ TEST_F(DungeonRenderingIntegrationTest, FullRoomRenderingWorks) {
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithDifferentPalettes) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
|
||||
|
||||
// Test with different palette configurations
|
||||
std::vector<gfx::PaletteGroup> palette_groups;
|
||||
|
||||
|
||||
// Create multiple palette groups
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
palette_groups.push_back(CreateTestPaletteGroup());
|
||||
}
|
||||
|
||||
|
||||
for (const auto& palette_group : palette_groups) {
|
||||
auto status = drawer.DrawObjectList(test_room.GetTileObjects(),
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
auto status = drawer.DrawObjectList(test_room.GetTileObjects(),
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
}
|
||||
@@ -140,11 +135,11 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithMultipleLayers) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Separate objects by layer
|
||||
std::vector<RoomObject> bg1_objects;
|
||||
std::vector<RoomObject> bg2_objects;
|
||||
|
||||
|
||||
for (const auto& obj : test_room.GetTileObjects()) {
|
||||
if (obj.GetLayerValue() == 0) {
|
||||
bg1_objects.push_back(obj);
|
||||
@@ -152,22 +147,18 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithMultipleLayers) {
|
||||
bg2_objects.push_back(obj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Render BG1 objects
|
||||
if (!bg1_objects.empty()) {
|
||||
auto status = drawer.DrawObjectList(bg1_objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
auto status = drawer.DrawObjectList(bg1_objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
|
||||
// Render BG2 objects
|
||||
if (!bg2_objects.empty()) {
|
||||
auto status = drawer.DrawObjectList(bg2_objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
auto status = drawer.DrawObjectList(bg2_objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
}
|
||||
@@ -177,20 +168,18 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithVariousObjectSizes) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Group objects by size
|
||||
std::map<int, std::vector<RoomObject>> objects_by_size;
|
||||
|
||||
|
||||
for (const auto& obj : test_room.GetTileObjects()) {
|
||||
objects_by_size[obj.size_].push_back(obj);
|
||||
}
|
||||
|
||||
|
||||
// Render objects of each size
|
||||
for (const auto& [size, objects] : objects_by_size) {
|
||||
auto status = drawer.DrawObjectList(objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
auto status = drawer.DrawObjectList(objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
}
|
||||
@@ -199,41 +188,41 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithVariousObjectSizes) {
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingPerformance) {
|
||||
// Create a room with many objects
|
||||
Room large_room(0x00, rom_.get());
|
||||
|
||||
|
||||
// Add many test objects
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
int id = i % 65; // Cycle through object IDs 0-64
|
||||
int x = (i * 2) % 60; // Spread across buffer
|
||||
int id = i % 65; // Cycle through object IDs 0-64
|
||||
int x = (i * 2) % 60; // Spread across buffer
|
||||
int y = (i * 3) % 60;
|
||||
int size = (i % 8) + 1; // Size 1-8
|
||||
int layer = i % 2; // Alternate layers
|
||||
|
||||
int size = (i % 8) + 1; // Size 1-8
|
||||
int layer = i % 2; // Alternate layers
|
||||
|
||||
RoomObject obj(id, x, y, size, layer);
|
||||
obj.set_rom(rom_.get());
|
||||
large_room.AddObject(obj);
|
||||
}
|
||||
|
||||
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Time the rendering operation
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto status = drawer.DrawObjectList(large_room.GetTileObjects(),
|
||||
large_room.bg1_buffer(),
|
||||
large_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
|
||||
auto status = drawer.DrawObjectList(large_room.GetTileObjects(),
|
||||
large_room.bg1_buffer(),
|
||||
large_room.bg2_buffer(), palette_group);
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
end_time - start_time);
|
||||
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
|
||||
|
||||
// Should complete in reasonable time (less than 2 seconds for 200 objects)
|
||||
EXPECT_LT(duration.count(), 2000);
|
||||
|
||||
std::cout << "Rendered room with 200 objects in " << duration.count() << "ms" << std::endl;
|
||||
|
||||
std::cout << "Rendered room with 200 objects in " << duration.count() << "ms"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// Test room rendering with edge case coordinates
|
||||
@@ -241,26 +230,24 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithEdgeCaseCoordinates) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Add objects at edge coordinates
|
||||
std::vector<RoomObject> edge_objects;
|
||||
|
||||
edge_objects.emplace_back(0x34, 0, 0, 1, 0); // Origin
|
||||
edge_objects.emplace_back(0x34, 63, 63, 1, 0); // Near buffer edge
|
||||
edge_objects.emplace_back(0x34, 32, 32, 1, 0); // Center
|
||||
edge_objects.emplace_back(0x34, 1, 1, 1, 0); // Near origin
|
||||
edge_objects.emplace_back(0x34, 62, 62, 1, 0); // Near edge
|
||||
|
||||
|
||||
edge_objects.emplace_back(0x34, 0, 0, 1, 0); // Origin
|
||||
edge_objects.emplace_back(0x34, 63, 63, 1, 0); // Near buffer edge
|
||||
edge_objects.emplace_back(0x34, 32, 32, 1, 0); // Center
|
||||
edge_objects.emplace_back(0x34, 1, 1, 1, 0); // Near origin
|
||||
edge_objects.emplace_back(0x34, 62, 62, 1, 0); // Near edge
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : edge_objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
}
|
||||
|
||||
auto status = drawer.DrawObjectList(edge_objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
|
||||
auto status = drawer.DrawObjectList(edge_objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
@@ -269,56 +256,53 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithMixedObjectTypes) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Add various object types
|
||||
std::vector<RoomObject> mixed_objects;
|
||||
|
||||
|
||||
// Floor objects
|
||||
mixed_objects.emplace_back(0x00, 5, 5, 3, 0);
|
||||
mixed_objects.emplace_back(0x01, 10, 10, 2, 0);
|
||||
|
||||
|
||||
// Wall objects
|
||||
mixed_objects.emplace_back(0x02, 15, 15, 4, 0);
|
||||
mixed_objects.emplace_back(0x03, 20, 20, 1, 1);
|
||||
|
||||
|
||||
// Diagonal objects
|
||||
mixed_objects.emplace_back(0x09, 25, 25, 5, 0);
|
||||
mixed_objects.emplace_back(0x0A, 30, 30, 3, 0);
|
||||
|
||||
|
||||
// Solid objects
|
||||
mixed_objects.emplace_back(0x34, 35, 35, 1, 0);
|
||||
mixed_objects.emplace_back(0x33, 40, 40, 2, 1);
|
||||
|
||||
|
||||
// Decorative objects
|
||||
mixed_objects.emplace_back(0x36, 45, 45, 3, 0);
|
||||
mixed_objects.emplace_back(0x38, 50, 50, 1, 0);
|
||||
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : mixed_objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
}
|
||||
|
||||
auto status = drawer.DrawObjectList(mixed_objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
|
||||
auto status = drawer.DrawObjectList(mixed_objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
// Test room rendering error handling
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingErrorHandling) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
|
||||
|
||||
// Test with null ROM
|
||||
ObjectDrawer null_drawer(nullptr);
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
auto status = null_drawer.DrawObjectList(test_room.GetTileObjects(),
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
|
||||
auto status = null_drawer.DrawObjectList(
|
||||
test_room.GetTileObjects(), test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
EXPECT_FALSE(status.ok());
|
||||
EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
|
||||
}
|
||||
@@ -328,26 +312,25 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithInvalidObjectData) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
|
||||
// Create objects with invalid data
|
||||
std::vector<RoomObject> invalid_objects;
|
||||
|
||||
|
||||
invalid_objects.emplace_back(0x999, 5, 5, 1, 0); // Invalid object ID
|
||||
invalid_objects.emplace_back(0x00, -1, -1, 1, 0); // Negative coordinates
|
||||
invalid_objects.emplace_back(0x00, 100, 100, 1, 0); // Out of bounds coordinates
|
||||
invalid_objects.emplace_back(0x00, 100, 100, 1,
|
||||
0); // Out of bounds coordinates
|
||||
invalid_objects.emplace_back(0x00, 5, 5, 255, 0); // Maximum size
|
||||
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : invalid_objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
}
|
||||
|
||||
|
||||
// Should handle gracefully
|
||||
auto status = drawer.DrawObjectList(invalid_objects,
|
||||
test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(),
|
||||
palette_group);
|
||||
|
||||
auto status = drawer.DrawObjectList(invalid_objects, test_room.bg1_buffer(),
|
||||
test_room.bg2_buffer(), palette_group);
|
||||
|
||||
// Should succeed or fail gracefully
|
||||
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "app/editor/message/message_data.h"
|
||||
@@ -211,23 +212,24 @@ TEST_F(MessageRomTest, BuildDictionaryEntries_CorrectSize) {
|
||||
TEST_F(MessageRomTest, ParseMessageData_CommandWithArgument_NoExtraCharacters) {
|
||||
// This test specifically checks for the bug where command arguments
|
||||
// were being incorrectly parsed as characters (e.g., capital 'A' after [W])
|
||||
// The bug was caused by using a range-based for loop while also tracking position
|
||||
|
||||
// The bug was caused by using a range-based for loop while also tracking
|
||||
// position
|
||||
|
||||
// Message: [W:01]ABC
|
||||
// Bytes: 0x6B (W command), 0x01 (argument), 0x00 (A), 0x01 (B), 0x02 (C)
|
||||
std::vector<uint8_t> data = {0x6B, 0x01, 0x00, 0x01, 0x02};
|
||||
|
||||
|
||||
editor::MessageData message;
|
||||
message.ID = 0;
|
||||
message.Address = 0;
|
||||
message.Data = data;
|
||||
|
||||
|
||||
std::vector<editor::MessageData> message_data_vector = {message};
|
||||
auto parsed = editor::ParseMessageData(message_data_vector, dictionary_);
|
||||
|
||||
|
||||
// Should be "[W:01]ABC" NOT "[W:01]BABC" or "[W:01]AABC"
|
||||
EXPECT_EQ(parsed[0], "[W:01]ABC");
|
||||
|
||||
|
||||
// The 'B' should not appear twice or be skipped
|
||||
EXPECT_EQ(parsed[0].find("BABC"), std::string::npos);
|
||||
EXPECT_EQ(parsed[0].find("AABC"), std::string::npos);
|
||||
@@ -237,20 +239,20 @@ TEST_F(MessageRomTest, ParseMessageData_MultipleCommandsWithArguments) {
|
||||
// Test multiple commands with arguments in sequence
|
||||
// [W:01][C:02]AB
|
||||
std::vector<uint8_t> data = {
|
||||
0x6B, 0x01, // [W:01] - Window border command with arg
|
||||
0x77, 0x02, // [C:02] - Color command with arg
|
||||
0x00, 0x01 // AB - Regular characters
|
||||
0x6B, 0x01, // [W:01] - Window border command with arg
|
||||
0x77, 0x02, // [C:02] - Color command with arg
|
||||
0x00, 0x01 // AB - Regular characters
|
||||
};
|
||||
|
||||
|
||||
editor::MessageData message;
|
||||
message.ID = 0;
|
||||
message.Data = data;
|
||||
|
||||
|
||||
std::vector<editor::MessageData> message_data_vector = {message};
|
||||
auto parsed = editor::ParseMessageData(message_data_vector, dictionary_);
|
||||
|
||||
|
||||
EXPECT_EQ(parsed[0], "[W:01][C:02]AB");
|
||||
|
||||
|
||||
// Make sure argument bytes (0x01, 0x02) weren't parsed as characters
|
||||
EXPECT_EQ(parsed[0].find("BAB"), std::string::npos);
|
||||
EXPECT_EQ(parsed[0].find("CAB"), std::string::npos);
|
||||
@@ -260,17 +262,17 @@ TEST_F(MessageRomTest, ParseMessageData_CommandWithoutArgument) {
|
||||
// Test command without argument followed by text
|
||||
// [K]ABC - Wait for key command (no arg) followed by ABC
|
||||
std::vector<uint8_t> data = {
|
||||
0x7E, // [K] - Wait for key (no argument)
|
||||
0x00, 0x01, 0x02 // ABC
|
||||
0x7E, // [K] - Wait for key (no argument)
|
||||
0x00, 0x01, 0x02 // ABC
|
||||
};
|
||||
|
||||
|
||||
editor::MessageData message;
|
||||
message.ID = 0;
|
||||
message.Data = data;
|
||||
|
||||
|
||||
std::vector<editor::MessageData> message_data_vector = {message};
|
||||
auto parsed = editor::ParseMessageData(message_data_vector, dictionary_);
|
||||
|
||||
|
||||
EXPECT_EQ(parsed[0], "[K]ABC");
|
||||
}
|
||||
|
||||
@@ -278,21 +280,21 @@ TEST_F(MessageRomTest, ParseMessageData_MixedCommands) {
|
||||
// Test mix of commands with and without arguments
|
||||
// [W:01]A[K]B[C:02]C
|
||||
std::vector<uint8_t> data = {
|
||||
0x6B, 0x01, // [W:01] - with arg
|
||||
0x00, // A
|
||||
0x7E, // [K] - no arg
|
||||
0x01, // B
|
||||
0x77, 0x02, // [C:02] - with arg
|
||||
0x02 // C
|
||||
0x6B, 0x01, // [W:01] - with arg
|
||||
0x00, // A
|
||||
0x7E, // [K] - no arg
|
||||
0x01, // B
|
||||
0x77, 0x02, // [C:02] - with arg
|
||||
0x02 // C
|
||||
};
|
||||
|
||||
|
||||
editor::MessageData message;
|
||||
message.ID = 0;
|
||||
message.Data = data;
|
||||
|
||||
|
||||
std::vector<editor::MessageData> message_data_vector = {message};
|
||||
auto parsed = editor::ParseMessageData(message_data_vector, dictionary_);
|
||||
|
||||
|
||||
EXPECT_EQ(parsed[0], "[W:01]A[K]B[C:02]C");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "testing.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
#include "zelda3/overworld/overworld_map.h"
|
||||
#include "testing.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
/**
|
||||
* @brief Comprehensive overworld integration test that validates YAZE C++
|
||||
* implementation against ZScream C# logic and existing test infrastructure
|
||||
*
|
||||
* @brief Comprehensive overworld integration test that validates YAZE C++
|
||||
* implementation against ZScream C# logic and existing test
|
||||
* infrastructure
|
||||
*
|
||||
* This test suite:
|
||||
* 1. Validates overworld loading logic matches ZScream behavior
|
||||
* 2. Tests integration with ZSCustomOverworld versions (vanilla, v2, v3)
|
||||
@@ -28,15 +30,15 @@ class OverworldIntegrationTest : public ::testing::Test {
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
|
||||
|
||||
// Check if we should use real ROM or mock data
|
||||
const char* rom_path_env = getenv("YAZE_TEST_ROM_PATH");
|
||||
const char* skip_rom_tests = getenv("YAZE_SKIP_ROM_TESTS");
|
||||
|
||||
|
||||
if (skip_rom_tests) {
|
||||
GTEST_SKIP() << "ROM tests disabled";
|
||||
}
|
||||
|
||||
|
||||
if (rom_path_env && std::filesystem::exists(rom_path_env)) {
|
||||
// Use real ROM for testing
|
||||
rom_ = std::make_unique<Rom>();
|
||||
@@ -47,7 +49,7 @@ class OverworldIntegrationTest : public ::testing::Test {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fall back to mock data
|
||||
use_real_rom_ = false;
|
||||
rom_ = std::make_unique<Rom>();
|
||||
@@ -63,36 +65,37 @@ class OverworldIntegrationTest : public ::testing::Test {
|
||||
|
||||
void SetupMockRomData() {
|
||||
mock_rom_data_.resize(0x200000, 0x00);
|
||||
|
||||
|
||||
// Basic ROM structure
|
||||
mock_rom_data_[0x140145] = 0xFF; // Vanilla ASM
|
||||
|
||||
mock_rom_data_[0x140145] = 0xFF; // Vanilla ASM
|
||||
|
||||
// Tile16 expansion flag
|
||||
mock_rom_data_[0x017D28] = 0x0F; // Vanilla
|
||||
|
||||
// Tile32 expansion flag
|
||||
mock_rom_data_[0x01772E] = 0x04; // Vanilla
|
||||
|
||||
mock_rom_data_[0x017D28] = 0x0F; // Vanilla
|
||||
|
||||
// Tile32 expansion flag
|
||||
mock_rom_data_[0x01772E] = 0x04; // Vanilla
|
||||
|
||||
// Basic map data
|
||||
for (int i = 0; i < 160; i++) {
|
||||
mock_rom_data_[0x012844 + i] = 0x00; // Small areas
|
||||
mock_rom_data_[0x012844 + i] = 0x00; // Small areas
|
||||
}
|
||||
|
||||
// Setup entrance data (matches ZScream Constants.OWEntranceMap/Pos/EntranceId)
|
||||
|
||||
// Setup entrance data (matches ZScream
|
||||
// Constants.OWEntranceMap/Pos/EntranceId)
|
||||
for (int i = 0; i < 129; i++) {
|
||||
mock_rom_data_[0x0DB96F + (i * 2)] = i & 0xFF; // Map ID
|
||||
mock_rom_data_[0x0DB96F + (i * 2)] = i & 0xFF; // Map ID
|
||||
mock_rom_data_[0x0DB96F + (i * 2) + 1] = (i >> 8) & 0xFF;
|
||||
mock_rom_data_[0x0DBA71 + (i * 2)] = (i * 16) & 0xFF; // Map Position
|
||||
mock_rom_data_[0x0DBA71 + (i * 2)] = (i * 16) & 0xFF; // Map Position
|
||||
mock_rom_data_[0x0DBA71 + (i * 2) + 1] = ((i * 16) >> 8) & 0xFF;
|
||||
mock_rom_data_[0x0DBB73 + i] = i & 0xFF; // Entrance ID
|
||||
mock_rom_data_[0x0DBB73 + i] = i & 0xFF; // Entrance ID
|
||||
}
|
||||
|
||||
|
||||
// Setup exit data (matches ZScream Constants.OWExit*)
|
||||
for (int i = 0; i < 0x4F; i++) {
|
||||
mock_rom_data_[0x015D8A + (i * 2)] = i & 0xFF; // Room ID
|
||||
mock_rom_data_[0x015D8A + (i * 2)] = i & 0xFF; // Room ID
|
||||
mock_rom_data_[0x015D8A + (i * 2) + 1] = (i >> 8) & 0xFF;
|
||||
mock_rom_data_[0x015E28 + i] = i & 0xFF; // Map ID
|
||||
mock_rom_data_[0x015E77 + (i * 2)] = i & 0xFF; // VRAM
|
||||
mock_rom_data_[0x015E28 + i] = i & 0xFF; // Map ID
|
||||
mock_rom_data_[0x015E77 + (i * 2)] = i & 0xFF; // VRAM
|
||||
mock_rom_data_[0x015E77 + (i * 2) + 1] = (i >> 8) & 0xFF;
|
||||
// Add other exit fields...
|
||||
}
|
||||
@@ -108,30 +111,30 @@ class OverworldIntegrationTest : public ::testing::Test {
|
||||
TEST_F(OverworldIntegrationTest, Tile32ExpansionDetection) {
|
||||
mock_rom_data_[0x01772E] = 0x04;
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Test expanded detection
|
||||
mock_rom_data_[0x01772E] = 0x05;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
}
|
||||
|
||||
// Test Tile16 expansion detection
|
||||
// Test Tile16 expansion detection
|
||||
TEST_F(OverworldIntegrationTest, Tile16ExpansionDetection) {
|
||||
mock_rom_data_[0x017D28] = 0x0F;
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Test expanded detection
|
||||
mock_rom_data_[0x017D28] = 0x10;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
}
|
||||
@@ -140,29 +143,30 @@ TEST_F(OverworldIntegrationTest, Tile16ExpansionDetection) {
|
||||
TEST_F(OverworldIntegrationTest, EntranceCoordinateCalculation) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& entrances = overworld_->entrances();
|
||||
EXPECT_EQ(entrances.size(), 129);
|
||||
|
||||
|
||||
// Verify coordinate calculation matches ZScream logic:
|
||||
// int p = mapPos >> 1;
|
||||
// int x = p % 64;
|
||||
// int y = p >> 6;
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512);
|
||||
// int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
const auto& entrance = entrances[i];
|
||||
|
||||
uint16_t map_pos = i * 16; // Our test data
|
||||
uint16_t map_id = i; // Our test data
|
||||
|
||||
|
||||
uint16_t map_pos = i * 16; // Our test data
|
||||
uint16_t map_id = i; // Our test data
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_x =
|
||||
(x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
|
||||
EXPECT_EQ(entrance.x_, expected_x);
|
||||
EXPECT_EQ(entrance.y_, expected_y);
|
||||
EXPECT_EQ(entrance.entrance_id_, i);
|
||||
@@ -174,10 +178,10 @@ TEST_F(OverworldIntegrationTest, EntranceCoordinateCalculation) {
|
||||
TEST_F(OverworldIntegrationTest, ExitDataLoading) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& exits = overworld_->exits();
|
||||
EXPECT_EQ(exits->size(), 0x4F);
|
||||
|
||||
|
||||
// Verify exit data matches our test data
|
||||
for (int i = 0; i < std::min(5, static_cast<int>(exits->size())); i++) {
|
||||
const auto& exit = exits->at(i);
|
||||
@@ -192,19 +196,19 @@ TEST_F(OverworldIntegrationTest, ASMVersionItemLoading) {
|
||||
// Test vanilla ASM (should limit to 0x80 maps)
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& items = overworld_->all_items();
|
||||
|
||||
|
||||
// Test v3+ ASM (should support all 0xA0 maps)
|
||||
mock_rom_data_[0x140145] = 0x03;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& items_v3 = overworld_->all_items();
|
||||
// v3 should have more comprehensive support
|
||||
EXPECT_GE(items_v3.size(), items.size());
|
||||
@@ -214,10 +218,10 @@ TEST_F(OverworldIntegrationTest, ASMVersionItemLoading) {
|
||||
TEST_F(OverworldIntegrationTest, MapSizeAssignment) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
|
||||
// Verify all maps are initialized
|
||||
for (const auto& map : maps) {
|
||||
EXPECT_GE(map.area_size(), AreaSizeEnum::SmallArea);
|
||||
@@ -230,16 +234,16 @@ TEST_F(OverworldIntegrationTest, ZSCustomOverworldVersionIntegration) {
|
||||
if (!use_real_rom_) {
|
||||
GTEST_SKIP() << "Real ROM required for ZSCustomOverworld version testing";
|
||||
}
|
||||
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Check ASM version detection
|
||||
auto version_byte = rom_->ReadByte(0x140145);
|
||||
ASSERT_TRUE(version_byte.ok());
|
||||
|
||||
|
||||
uint8_t asm_version = *version_byte;
|
||||
|
||||
|
||||
if (asm_version == 0xFF) {
|
||||
// Vanilla ROM
|
||||
EXPECT_FALSE(overworld_->expanded_tile16());
|
||||
@@ -250,50 +254,53 @@ TEST_F(OverworldIntegrationTest, ZSCustomOverworldVersionIntegration) {
|
||||
EXPECT_TRUE(overworld_->expanded_tile16());
|
||||
EXPECT_TRUE(overworld_->expanded_tile32());
|
||||
}
|
||||
|
||||
|
||||
// Verify version-specific features are properly detected
|
||||
if (asm_version >= 0x03) {
|
||||
// v3 features should be available
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160); // All 160 maps supported in v3
|
||||
EXPECT_EQ(maps.size(), 160); // All 160 maps supported in v3
|
||||
}
|
||||
}
|
||||
|
||||
// Test compatibility with RomDependentTestSuite infrastructure
|
||||
TEST_F(OverworldIntegrationTest, RomDependentTestSuiteCompatibility) {
|
||||
if (!use_real_rom_) {
|
||||
GTEST_SKIP() << "Real ROM required for RomDependentTestSuite compatibility testing";
|
||||
GTEST_SKIP()
|
||||
<< "Real ROM required for RomDependentTestSuite compatibility testing";
|
||||
}
|
||||
|
||||
// Test that our overworld loading works with the same patterns as RomDependentTestSuite
|
||||
|
||||
// Test that our overworld loading works with the same patterns as
|
||||
// RomDependentTestSuite
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Verify ROM-dependent features work correctly
|
||||
EXPECT_TRUE(overworld_->is_loaded());
|
||||
|
||||
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
|
||||
// Test that we can access the same data structures as RomDependentTestSuite
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(maps.size())); i++) {
|
||||
const auto& map = maps[i];
|
||||
|
||||
|
||||
// Verify map properties are accessible
|
||||
EXPECT_GE(map.area_graphics(), 0);
|
||||
EXPECT_GE(map.main_palette(), 0);
|
||||
EXPECT_GE(map.area_size(), AreaSizeEnum::SmallArea);
|
||||
EXPECT_LE(map.area_size(), AreaSizeEnum::TallArea);
|
||||
}
|
||||
|
||||
// Test that sprite data is accessible (matches RomDependentTestSuite expectations)
|
||||
|
||||
// Test that sprite data is accessible (matches RomDependentTestSuite
|
||||
// expectations)
|
||||
const auto& sprites = overworld_->sprites(0);
|
||||
EXPECT_EQ(sprites.size(), 3); // Three game states
|
||||
|
||||
EXPECT_EQ(sprites.size(), 3); // Three game states
|
||||
|
||||
// Test that item data is accessible
|
||||
const auto& items = overworld_->all_items();
|
||||
EXPECT_GE(items.size(), 0);
|
||||
|
||||
|
||||
// Test that entrance/exit data is accessible
|
||||
const auto& entrances = overworld_->entrances();
|
||||
const auto& exits = overworld_->exits();
|
||||
@@ -305,17 +312,17 @@ TEST_F(OverworldIntegrationTest, RomDependentTestSuiteCompatibility) {
|
||||
TEST_F(OverworldIntegrationTest, ComprehensiveDataIntegrity) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
// Verify all major data structures are properly loaded
|
||||
EXPECT_GT(overworld_->tiles16().size(), 0);
|
||||
EXPECT_GT(overworld_->tiles32_unique().size(), 0);
|
||||
|
||||
|
||||
// Verify map organization matches ZScream expectations
|
||||
const auto& map_tiles = overworld_->map_tiles();
|
||||
EXPECT_EQ(map_tiles.light_world.size(), 512);
|
||||
EXPECT_EQ(map_tiles.dark_world.size(), 512);
|
||||
EXPECT_EQ(map_tiles.special_world.size(), 512);
|
||||
|
||||
|
||||
// Verify each world has proper 512x512 tile data
|
||||
for (const auto& row : map_tiles.light_world) {
|
||||
EXPECT_EQ(row.size(), 512);
|
||||
@@ -326,16 +333,16 @@ TEST_F(OverworldIntegrationTest, ComprehensiveDataIntegrity) {
|
||||
for (const auto& row : map_tiles.special_world) {
|
||||
EXPECT_EQ(row.size(), 512);
|
||||
}
|
||||
|
||||
|
||||
// Verify overworld maps are properly initialized
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
|
||||
for (const auto& map : maps) {
|
||||
// TODO: Find a way to compare
|
||||
// EXPECT_TRUE(map.bitmap_data() != nullptr);
|
||||
}
|
||||
|
||||
|
||||
// Verify tile types are loaded
|
||||
const auto& tile_types = overworld_->all_tiles_types();
|
||||
EXPECT_EQ(tile_types.size(), 0x200);
|
||||
@@ -345,62 +352,64 @@ TEST_F(OverworldIntegrationTest, ComprehensiveDataIntegrity) {
|
||||
TEST_F(OverworldIntegrationTest, ZScreamCoordinateCompatibility) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
|
||||
const auto& entrances = overworld_->entrances();
|
||||
EXPECT_EQ(entrances.size(), 129);
|
||||
|
||||
|
||||
// Test coordinate calculation matches ZScream logic exactly
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
const auto& entrance = entrances[i];
|
||||
|
||||
|
||||
// ZScream coordinate calculation:
|
||||
// int p = mapPos >> 1;
|
||||
// int x = p % 64;
|
||||
// int y = p >> 6;
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512);
|
||||
// int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) *
|
||||
// 512); int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
uint16_t map_pos = entrance.map_pos_;
|
||||
uint16_t map_id = entrance.map_id_;
|
||||
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_x =
|
||||
(x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
|
||||
EXPECT_EQ(entrance.x_, expected_x);
|
||||
EXPECT_EQ(entrance.y_, expected_y);
|
||||
}
|
||||
|
||||
|
||||
// Test hole coordinate calculation with 0x400 offset
|
||||
const auto& holes = overworld_->holes();
|
||||
EXPECT_EQ(holes.size(), 0x13);
|
||||
|
||||
|
||||
for (int i = 0; i < std::min(5, static_cast<int>(holes.size())); i++) {
|
||||
const auto& hole = holes[i];
|
||||
|
||||
|
||||
// ZScream hole coordinate calculation:
|
||||
// int p = (mapPos + 0x400) >> 1;
|
||||
// int x = p % 64;
|
||||
// int y = p >> 6;
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512);
|
||||
// int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) *
|
||||
// 512); int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
uint16_t map_pos = hole.map_pos_;
|
||||
uint16_t map_id = hole.map_id_;
|
||||
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_x =
|
||||
(x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
|
||||
EXPECT_EQ(hole.x_, expected_x);
|
||||
EXPECT_EQ(hole.y_, expected_y);
|
||||
EXPECT_TRUE(hole.is_hole_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
@@ -1,8 +1,8 @@
|
||||
// Integration tests for Room object load/save cycle with real ROM data
|
||||
// Phase 1, Task 2.1: Full round-trip verification
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
@@ -23,22 +23,22 @@ class RoomIntegrationTest : public ::testing::Test {
|
||||
void SetUp() override {
|
||||
// Load the ROM file
|
||||
rom_ = std::make_unique<Rom>();
|
||||
|
||||
|
||||
// Check if ROM file exists
|
||||
const char* rom_path = std::getenv("YAZE_TEST_ROM_PATH");
|
||||
if (!rom_path) {
|
||||
rom_path = "zelda3.sfc";
|
||||
}
|
||||
|
||||
|
||||
auto status = rom_->LoadFromFile(rom_path);
|
||||
if (!status.ok()) {
|
||||
GTEST_SKIP() << "ROM file not available: " << status.message();
|
||||
}
|
||||
|
||||
|
||||
// Create backup of ROM data for restoration after tests
|
||||
original_rom_data_ = rom_->vector();
|
||||
}
|
||||
|
||||
|
||||
void TearDown() override {
|
||||
// Restore original ROM data
|
||||
if (rom_ && !original_rom_data_.empty()) {
|
||||
@@ -47,7 +47,7 @@ class RoomIntegrationTest : public ::testing::Test {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::vector<uint8_t> original_rom_data_;
|
||||
};
|
||||
@@ -59,34 +59,34 @@ class RoomIntegrationTest : public ::testing::Test {
|
||||
TEST_F(RoomIntegrationTest, BasicLoadSaveRoundTrip) {
|
||||
// Load room 0 (Hyrule Castle Entrance)
|
||||
Room room1(0x00, rom_.get());
|
||||
|
||||
|
||||
// Get original object count
|
||||
size_t original_count = room1.GetTileObjects().size();
|
||||
ASSERT_GT(original_count, 0) << "Room should have objects";
|
||||
|
||||
|
||||
// Store original objects
|
||||
auto original_objects = room1.GetTileObjects();
|
||||
|
||||
|
||||
// Save the room (should write same data back)
|
||||
auto save_status = room1.SaveObjects();
|
||||
ASSERT_TRUE(save_status.ok()) << save_status.message();
|
||||
|
||||
|
||||
// Load the room again
|
||||
Room room2(0x00, rom_.get());
|
||||
|
||||
|
||||
// Verify object count matches
|
||||
EXPECT_EQ(room2.GetTileObjects().size(), original_count);
|
||||
|
||||
|
||||
// Verify each object matches
|
||||
auto reloaded_objects = room2.GetTileObjects();
|
||||
ASSERT_EQ(reloaded_objects.size(), original_objects.size());
|
||||
|
||||
|
||||
for (size_t i = 0; i < original_objects.size(); i++) {
|
||||
SCOPED_TRACE("Object " + std::to_string(i));
|
||||
|
||||
|
||||
const auto& orig = original_objects[i];
|
||||
const auto& reload = reloaded_objects[i];
|
||||
|
||||
|
||||
EXPECT_EQ(reload.id_, orig.id_) << "ID mismatch";
|
||||
EXPECT_EQ(reload.x(), orig.x()) << "X position mismatch";
|
||||
EXPECT_EQ(reload.y(), orig.y()) << "Y position mismatch";
|
||||
@@ -102,33 +102,34 @@ TEST_F(RoomIntegrationTest, BasicLoadSaveRoundTrip) {
|
||||
TEST_F(RoomIntegrationTest, MultiRoomLoadSaveRoundTrip) {
|
||||
// Test several different rooms to ensure broad coverage
|
||||
std::vector<int> test_rooms = {0x00, 0x01, 0x02, 0x10, 0x20};
|
||||
|
||||
|
||||
for (int room_id : test_rooms) {
|
||||
SCOPED_TRACE("Room " + std::to_string(room_id));
|
||||
|
||||
|
||||
// Load room
|
||||
Room room1(room_id, rom_.get());
|
||||
auto original_objects = room1.GetTileObjects();
|
||||
|
||||
|
||||
if (original_objects.empty()) {
|
||||
continue; // Skip empty rooms
|
||||
continue; // Skip empty rooms
|
||||
}
|
||||
|
||||
|
||||
// Save objects
|
||||
auto save_status = room1.SaveObjects();
|
||||
ASSERT_TRUE(save_status.ok()) << save_status.message();
|
||||
|
||||
|
||||
// Reload and verify
|
||||
Room room2(room_id, rom_.get());
|
||||
auto reloaded_objects = room2.GetTileObjects();
|
||||
|
||||
|
||||
EXPECT_EQ(reloaded_objects.size(), original_objects.size());
|
||||
|
||||
|
||||
// Verify objects match
|
||||
for (size_t i = 0; i < std::min(original_objects.size(), reloaded_objects.size()); i++) {
|
||||
for (size_t i = 0;
|
||||
i < std::min(original_objects.size(), reloaded_objects.size()); i++) {
|
||||
const auto& orig = original_objects[i];
|
||||
const auto& reload = reloaded_objects[i];
|
||||
|
||||
|
||||
EXPECT_EQ(reload.id_, orig.id_);
|
||||
EXPECT_EQ(reload.x(), orig.x());
|
||||
EXPECT_EQ(reload.y(), orig.y());
|
||||
@@ -145,36 +146,48 @@ TEST_F(RoomIntegrationTest, MultiRoomLoadSaveRoundTrip) {
|
||||
TEST_F(RoomIntegrationTest, LayerPreservation) {
|
||||
// Load a room known to have multiple layers
|
||||
Room room(0x01, rom_.get());
|
||||
|
||||
|
||||
auto objects = room.GetTileObjects();
|
||||
ASSERT_GT(objects.size(), 0);
|
||||
|
||||
|
||||
// Count objects per layer
|
||||
int layer0_count = 0, layer1_count = 0, layer2_count = 0;
|
||||
for (const auto& obj : objects) {
|
||||
switch (obj.GetLayerValue()) {
|
||||
case 0: layer0_count++; break;
|
||||
case 1: layer1_count++; break;
|
||||
case 2: layer2_count++; break;
|
||||
case 0:
|
||||
layer0_count++;
|
||||
break;
|
||||
case 1:
|
||||
layer1_count++;
|
||||
break;
|
||||
case 2:
|
||||
layer2_count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save and reload
|
||||
ASSERT_TRUE(room.SaveObjects().ok());
|
||||
|
||||
|
||||
Room room2(0x01, rom_.get());
|
||||
auto reloaded = room2.GetTileObjects();
|
||||
|
||||
|
||||
// Verify layer counts match
|
||||
int reload_layer0 = 0, reload_layer1 = 0, reload_layer2 = 0;
|
||||
for (const auto& obj : reloaded) {
|
||||
switch (obj.GetLayerValue()) {
|
||||
case 0: reload_layer0++; break;
|
||||
case 1: reload_layer1++; break;
|
||||
case 2: reload_layer2++; break;
|
||||
case 0:
|
||||
reload_layer0++;
|
||||
break;
|
||||
case 1:
|
||||
reload_layer1++;
|
||||
break;
|
||||
case 2:
|
||||
reload_layer2++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EXPECT_EQ(reload_layer0, layer0_count);
|
||||
EXPECT_EQ(reload_layer1, layer1_count);
|
||||
EXPECT_EQ(reload_layer2, layer2_count);
|
||||
@@ -186,15 +199,15 @@ TEST_F(RoomIntegrationTest, LayerPreservation) {
|
||||
|
||||
TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
||||
Room room(0x00, rom_.get());
|
||||
|
||||
|
||||
auto objects = room.GetTileObjects();
|
||||
ASSERT_GT(objects.size(), 0);
|
||||
|
||||
|
||||
// Count object types
|
||||
int type1_count = 0; // ID < 0x100
|
||||
int type2_count = 0; // ID 0x100-0x13F
|
||||
int type3_count = 0; // ID >= 0xF00
|
||||
|
||||
|
||||
for (const auto& obj : objects) {
|
||||
if (obj.id_ >= 0xF00) {
|
||||
type3_count++;
|
||||
@@ -204,13 +217,13 @@ TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
||||
type1_count++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save and reload
|
||||
ASSERT_TRUE(room.SaveObjects().ok());
|
||||
|
||||
|
||||
Room room2(0x00, rom_.get());
|
||||
auto reloaded = room2.GetTileObjects();
|
||||
|
||||
|
||||
// Verify type distribution matches
|
||||
int reload_type1 = 0, reload_type2 = 0, reload_type3 = 0;
|
||||
for (const auto& obj : reloaded) {
|
||||
@@ -222,7 +235,7 @@ TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
||||
reload_type1++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EXPECT_EQ(reload_type1, type1_count);
|
||||
EXPECT_EQ(reload_type2, type2_count);
|
||||
EXPECT_EQ(reload_type3, type3_count);
|
||||
@@ -235,54 +248,56 @@ TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
||||
TEST_F(RoomIntegrationTest, BinaryDataExactMatch) {
|
||||
// This test verifies that saving doesn't change ROM data
|
||||
// when no modifications are made
|
||||
|
||||
|
||||
Room room(0x02, rom_.get());
|
||||
|
||||
|
||||
// Get the ROM location where objects are stored
|
||||
auto rom_data = rom_->vector();
|
||||
int object_pointer = (rom_data[0x874C + 2] << 16) +
|
||||
(rom_data[0x874C + 1] << 8) +
|
||||
(rom_data[0x874C]);
|
||||
(rom_data[0x874C + 1] << 8) + (rom_data[0x874C]);
|
||||
object_pointer = SnesToPc(object_pointer);
|
||||
|
||||
|
||||
int room_address = object_pointer + (0x02 * 3);
|
||||
int tile_address = (rom_data[room_address + 2] << 16) +
|
||||
(rom_data[room_address + 1] << 8) +
|
||||
rom_data[room_address];
|
||||
(rom_data[room_address + 1] << 8) + rom_data[room_address];
|
||||
int objects_location = SnesToPc(tile_address);
|
||||
|
||||
|
||||
// Read original bytes (up to 500 bytes should cover most rooms)
|
||||
std::vector<uint8_t> original_bytes;
|
||||
for (int i = 0; i < 500 && objects_location + i < (int)rom_data.size(); i++) {
|
||||
original_bytes.push_back(rom_data[objects_location + i]);
|
||||
// Stop at final terminator
|
||||
if (i > 0 && original_bytes[i] == 0xFF && original_bytes[i-1] == 0xFF) {
|
||||
if (i > 0 && original_bytes[i] == 0xFF && original_bytes[i - 1] == 0xFF) {
|
||||
// Check if this is the final terminator (3rd layer end)
|
||||
bool might_be_final = true;
|
||||
for (int j = i - 10; j < i - 1; j += 2) {
|
||||
if (j >= 0 && original_bytes[j] == 0xFF && original_bytes[j+1] == 0xFF) {
|
||||
if (j >= 0 && original_bytes[j] == 0xFF &&
|
||||
original_bytes[j + 1] == 0xFF) {
|
||||
// Found another FF FF marker, keep going
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (might_be_final) break;
|
||||
if (might_be_final)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save objects (should write identical data)
|
||||
ASSERT_TRUE(room.SaveObjects().ok());
|
||||
|
||||
|
||||
// Read bytes after save
|
||||
rom_data = rom_->vector();
|
||||
std::vector<uint8_t> saved_bytes;
|
||||
for (size_t i = 0; i < original_bytes.size() && objects_location + i < rom_data.size(); i++) {
|
||||
for (size_t i = 0;
|
||||
i < original_bytes.size() && objects_location + i < rom_data.size();
|
||||
i++) {
|
||||
saved_bytes.push_back(rom_data[objects_location + i]);
|
||||
}
|
||||
|
||||
|
||||
// Verify binary match
|
||||
ASSERT_EQ(saved_bytes.size(), original_bytes.size());
|
||||
for (size_t i = 0; i < original_bytes.size(); i++) {
|
||||
EXPECT_EQ(saved_bytes[i], original_bytes[i])
|
||||
EXPECT_EQ(saved_bytes[i], original_bytes[i])
|
||||
<< "Byte mismatch at offset " << i;
|
||||
}
|
||||
}
|
||||
@@ -294,24 +309,27 @@ TEST_F(RoomIntegrationTest, BinaryDataExactMatch) {
|
||||
TEST_F(RoomIntegrationTest, KnownRoomData) {
|
||||
// Room 0x00 (Hyrule Castle Entrance) - verify known objects exist
|
||||
Room room(0x00, rom_.get());
|
||||
|
||||
|
||||
auto objects = room.GetTileObjects();
|
||||
ASSERT_GT(objects.size(), 0) << "Room 0x00 should have objects";
|
||||
|
||||
|
||||
// Verify we can find common object types
|
||||
bool found_type1 = false;
|
||||
bool found_layer0 = false;
|
||||
bool found_layer1 = false;
|
||||
|
||||
|
||||
for (const auto& obj : objects) {
|
||||
if (obj.id_ < 0x100) found_type1 = true;
|
||||
if (obj.GetLayerValue() == 0) found_layer0 = true;
|
||||
if (obj.GetLayerValue() == 1) found_layer1 = true;
|
||||
if (obj.id_ < 0x100)
|
||||
found_type1 = true;
|
||||
if (obj.GetLayerValue() == 0)
|
||||
found_layer0 = true;
|
||||
if (obj.GetLayerValue() == 1)
|
||||
found_layer1 = true;
|
||||
}
|
||||
|
||||
|
||||
EXPECT_TRUE(found_type1) << "Should have Type 1 objects";
|
||||
EXPECT_TRUE(found_layer0) << "Should have Layer 0 objects";
|
||||
|
||||
|
||||
// Verify coordinates are in valid range (0-63)
|
||||
for (const auto& obj : objects) {
|
||||
EXPECT_GE(obj.x(), 0);
|
||||
@@ -324,4 +342,3 @@ TEST_F(RoomIntegrationTest, KnownRoomData) {
|
||||
} // namespace test
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
@@ -12,23 +13,25 @@ namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
class SpritePositionTest : public ::testing::Test {
|
||||
protected:
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Try to load a vanilla ROM for testing
|
||||
rom_ = std::make_unique<Rom>();
|
||||
std::string rom_path = "bin/zelda3.sfc";
|
||||
|
||||
|
||||
// Check if ROM exists in build directory
|
||||
std::ifstream rom_file(rom_path);
|
||||
if (rom_file.good()) {
|
||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path).ok()) << "Failed to load ROM from " << rom_path;
|
||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path).ok())
|
||||
<< "Failed to load ROM from " << rom_path;
|
||||
} else {
|
||||
// Skip test if ROM not found
|
||||
GTEST_SKIP() << "ROM file not found at " << rom_path;
|
||||
}
|
||||
|
||||
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
ASSERT_TRUE(overworld_->Load(rom_.get()).ok()) << "Failed to load overworld";
|
||||
ASSERT_TRUE(overworld_->Load(rom_.get()).ok())
|
||||
<< "Failed to load overworld";
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
@@ -47,24 +50,28 @@ TEST_F(SpritePositionTest, SpriteCoordinateSystem) {
|
||||
const auto& sprites = overworld_->sprites(game_state);
|
||||
std::cout << "\n=== Game State " << game_state << " ===" << std::endl;
|
||||
std::cout << "Total sprites: " << sprites.size() << std::endl;
|
||||
|
||||
|
||||
int sprite_count = 0;
|
||||
for (const auto& sprite : sprites) {
|
||||
if (!sprite.deleted() && sprite_count < 10) { // Show first 10 sprites
|
||||
std::cout << "Sprite " << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(sprite.id()) << " (" << const_cast<Sprite&>(sprite).name() << ")" << std::endl;
|
||||
std::cout << " Map ID: 0x" << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< sprite.map_id() << std::endl;
|
||||
std::cout << " X: " << std::dec << sprite.x() << " (0x" << std::hex << sprite.x() << ")" << std::endl;
|
||||
std::cout << " Y: " << std::dec << sprite.y() << " (0x" << std::hex << sprite.y() << ")" << std::endl;
|
||||
if (!sprite.deleted() && sprite_count < 10) { // Show first 10 sprites
|
||||
std::cout << "Sprite " << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(sprite.id()) << " ("
|
||||
<< const_cast<Sprite&>(sprite).name() << ")" << std::endl;
|
||||
std::cout << " Map ID: 0x" << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << sprite.map_id() << std::endl;
|
||||
std::cout << " X: " << std::dec << sprite.x() << " (0x" << std::hex
|
||||
<< sprite.x() << ")" << std::endl;
|
||||
std::cout << " Y: " << std::dec << sprite.y() << " (0x" << std::hex
|
||||
<< sprite.y() << ")" << std::endl;
|
||||
std::cout << " map_x: " << std::dec << sprite.map_x() << std::endl;
|
||||
std::cout << " map_y: " << std::dec << sprite.map_y() << std::endl;
|
||||
|
||||
|
||||
// Calculate expected world ranges
|
||||
int world_start = game_state * 0x40;
|
||||
int world_end = world_start + 0x40;
|
||||
std::cout << " World range: 0x" << std::hex << world_start << " - 0x" << world_end << std::endl;
|
||||
|
||||
std::cout << " World range: 0x" << std::hex << world_start << " - 0x"
|
||||
<< world_end << std::endl;
|
||||
|
||||
sprite_count++;
|
||||
}
|
||||
}
|
||||
@@ -76,30 +83,32 @@ TEST_F(SpritePositionTest, SpriteFilteringLogic) {
|
||||
// Test the filtering logic used in DrawOverworldSprites
|
||||
for (int current_world = 0; current_world < 3; current_world++) {
|
||||
const auto& sprites = overworld_->sprites(current_world);
|
||||
|
||||
std::cout << "\n=== Testing World " << current_world << " Filtering ===" << std::endl;
|
||||
|
||||
|
||||
std::cout << "\n=== Testing World " << current_world
|
||||
<< " Filtering ===" << std::endl;
|
||||
|
||||
int visible_sprites = 0;
|
||||
int total_sprites = 0;
|
||||
|
||||
|
||||
for (const auto& sprite : sprites) {
|
||||
if (!sprite.deleted()) {
|
||||
total_sprites++;
|
||||
|
||||
|
||||
// This is the filtering logic from DrawOverworldSprites
|
||||
bool should_show = (sprite.map_id() < 0x40 + (current_world * 0x40) &&
|
||||
sprite.map_id() >= (current_world * 0x40));
|
||||
|
||||
sprite.map_id() >= (current_world * 0x40));
|
||||
|
||||
if (should_show) {
|
||||
visible_sprites++;
|
||||
std::cout << " Visible: Sprite 0x" << std::hex << static_cast<int>(sprite.id())
|
||||
<< " on map 0x" << sprite.map_id() << " at ("
|
||||
<< std::dec << sprite.x() << ", " << sprite.y() << ")" << std::endl;
|
||||
std::cout << " Visible: Sprite 0x" << std::hex
|
||||
<< static_cast<int>(sprite.id()) << " on map 0x"
|
||||
<< sprite.map_id() << " at (" << std::dec << sprite.x()
|
||||
<< ", " << sprite.y() << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "World " << current_world << ": " << visible_sprites << "/"
|
||||
|
||||
std::cout << "World " << current_world << ": " << visible_sprites << "/"
|
||||
<< total_sprites << " sprites visible" << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -109,45 +118,53 @@ TEST_F(SpritePositionTest, MapCoordinateCalculations) {
|
||||
// Test how map coordinates should be calculated
|
||||
for (int current_world = 0; current_world < 3; current_world++) {
|
||||
const auto& sprites = overworld_->sprites(current_world);
|
||||
|
||||
std::cout << "\n=== World " << current_world << " Coordinate Analysis ===" << std::endl;
|
||||
|
||||
|
||||
std::cout << "\n=== World " << current_world
|
||||
<< " Coordinate Analysis ===" << std::endl;
|
||||
|
||||
for (const auto& sprite : sprites) {
|
||||
if (!sprite.deleted() &&
|
||||
if (!sprite.deleted() &&
|
||||
sprite.map_id() < 0x40 + (current_world * 0x40) &&
|
||||
sprite.map_id() >= (current_world * 0x40)) {
|
||||
|
||||
// Calculate map position
|
||||
int sprite_map_id = sprite.map_id();
|
||||
int local_map_index = sprite_map_id - (current_world * 0x40);
|
||||
int map_col = local_map_index % 8;
|
||||
int map_row = local_map_index / 8;
|
||||
|
||||
int map_canvas_x = map_col * 512; // kOverworldMapSize
|
||||
|
||||
int map_canvas_x = map_col * 512; // kOverworldMapSize
|
||||
int map_canvas_y = map_row * 512;
|
||||
|
||||
std::cout << "Sprite 0x" << std::hex << static_cast<int>(sprite.id())
|
||||
|
||||
std::cout << "Sprite 0x" << std::hex << static_cast<int>(sprite.id())
|
||||
<< " on map 0x" << sprite_map_id << std::endl;
|
||||
std::cout << " Local map index: " << std::dec << local_map_index << std::endl;
|
||||
std::cout << " Map position: (" << map_col << ", " << map_row << ")" << std::endl;
|
||||
std::cout << " Map canvas pos: (" << map_canvas_x << ", " << map_canvas_y << ")" << std::endl;
|
||||
std::cout << " Sprite global pos: (" << sprite.x() << ", " << sprite.y() << ")" << std::endl;
|
||||
std::cout << " Sprite local pos: (" << sprite.map_x() << ", " << sprite.map_y() << ")" << std::endl;
|
||||
|
||||
std::cout << " Local map index: " << std::dec << local_map_index
|
||||
<< std::endl;
|
||||
std::cout << " Map position: (" << map_col << ", " << map_row << ")"
|
||||
<< std::endl;
|
||||
std::cout << " Map canvas pos: (" << map_canvas_x << ", "
|
||||
<< map_canvas_y << ")" << std::endl;
|
||||
std::cout << " Sprite global pos: (" << sprite.x() << ", "
|
||||
<< sprite.y() << ")" << std::endl;
|
||||
std::cout << " Sprite local pos: (" << sprite.map_x() << ", "
|
||||
<< sprite.map_y() << ")" << std::endl;
|
||||
|
||||
// Verify the calculation
|
||||
int expected_global_x = map_canvas_x + sprite.map_x();
|
||||
int expected_global_y = map_canvas_y + sprite.map_y();
|
||||
|
||||
std::cout << " Expected global: (" << expected_global_x << ", " << expected_global_y << ")" << std::endl;
|
||||
std::cout << " Actual global: (" << sprite.x() << ", " << sprite.y() << ")" << std::endl;
|
||||
|
||||
if (expected_global_x == sprite.x() && expected_global_y == sprite.y()) {
|
||||
|
||||
std::cout << " Expected global: (" << expected_global_x << ", "
|
||||
<< expected_global_y << ")" << std::endl;
|
||||
std::cout << " Actual global: (" << sprite.x() << ", " << sprite.y()
|
||||
<< ")" << std::endl;
|
||||
|
||||
if (expected_global_x == sprite.x() &&
|
||||
expected_global_y == sprite.y()) {
|
||||
std::cout << " ✓ Coordinates match!" << std::endl;
|
||||
} else {
|
||||
std::cout << " ✗ Coordinate mismatch!" << std::endl;
|
||||
}
|
||||
|
||||
break; // Only test first sprite for brevity
|
||||
|
||||
break; // Only test first sprite for brevity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user