backend-infra-engineer: Release v0.3.9-hotfix7 snapshot
This commit is contained in:
479
test/integration/agent/tool_dispatcher_test.cc
Normal file
479
test/integration/agent/tool_dispatcher_test.cc
Normal file
@@ -0,0 +1,479 @@
|
||||
/**
|
||||
* @file tool_dispatcher_test.cc
|
||||
* @brief Integration tests for the ToolDispatcher
|
||||
*
|
||||
* Tests the ToolDispatcher's ability to route tool calls to the appropriate
|
||||
* handlers, manage tool preferences, and handle errors gracefully.
|
||||
*/
|
||||
|
||||
#include "cli/service/agent/tool_dispatcher.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "cli/service/ai/common.h"
|
||||
#include "mocks/mock_rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
namespace agent {
|
||||
namespace {
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::Not;
|
||||
|
||||
// Test fixture for ToolDispatcher tests
|
||||
class ToolDispatcherTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Create test directories and files for filesystem tests
|
||||
test_dir_ = std::filesystem::temp_directory_path() / "yaze_dispatcher_test";
|
||||
std::filesystem::create_directories(test_dir_);
|
||||
|
||||
// Create a test file
|
||||
std::ofstream(test_dir_ / "test.txt") << "Test file content for dispatcher";
|
||||
|
||||
// Initialize mock ROM
|
||||
std::vector<uint8_t> test_data(1024, 0);
|
||||
auto status = mock_rom_.SetTestData(test_data);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// Set up dispatcher with ROM context
|
||||
dispatcher_.SetRomContext(&mock_rom_);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// Clean up test directory
|
||||
std::filesystem::remove_all(test_dir_);
|
||||
}
|
||||
|
||||
ToolCall CreateToolCall(const std::string& name,
|
||||
const std::map<std::string, std::string>& args = {}) {
|
||||
ToolCall call;
|
||||
call.tool_name = name;
|
||||
call.args = args;
|
||||
return call;
|
||||
}
|
||||
|
||||
std::filesystem::path test_dir_;
|
||||
yaze::test::MockRom mock_rom_;
|
||||
ToolDispatcher dispatcher_;
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Filesystem Tool Dispatch Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(ToolDispatcherTest, FilesystemListDispatch) {
|
||||
auto call = CreateToolCall("filesystem-list", {
|
||||
{"path", test_dir_.string()}
|
||||
});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
// Should succeed
|
||||
EXPECT_TRUE(result.ok()) << result.status().message();
|
||||
|
||||
// Result should contain JSON output
|
||||
if (result.ok()) {
|
||||
EXPECT_THAT(*result, HasSubstr("test.txt"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, FilesystemReadDispatch) {
|
||||
auto call = CreateToolCall("filesystem-read", {
|
||||
{"path", (test_dir_ / "test.txt").string()}
|
||||
});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
EXPECT_TRUE(result.ok()) << result.status().message();
|
||||
|
||||
if (result.ok()) {
|
||||
EXPECT_THAT(*result, HasSubstr("Test file content"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, FilesystemExistsDispatch) {
|
||||
auto call = CreateToolCall("filesystem-exists", {
|
||||
{"path", (test_dir_ / "test.txt").string()}
|
||||
});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
EXPECT_TRUE(result.ok()) << result.status().message();
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, FilesystemInfoDispatch) {
|
||||
auto call = CreateToolCall("filesystem-info", {
|
||||
{"path", (test_dir_ / "test.txt").string()}
|
||||
});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
EXPECT_TRUE(result.ok()) << result.status().message();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Build Tool Dispatch Tests (Placeholder - Build tools may not be fully implemented)
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(ToolDispatcherTest, BuildStatusDispatch) {
|
||||
auto call = CreateToolCall("build-status", {});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
// Build tools may not be fully implemented yet
|
||||
// This test verifies the dispatch routing works
|
||||
// It may fail with "handler not implemented" which is acceptable
|
||||
if (!result.ok()) {
|
||||
EXPECT_TRUE(absl::IsInternal(result.status()) ||
|
||||
absl::IsUnimplemented(result.status()))
|
||||
<< "Unexpected error: " << result.status().message();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, BuildConfigureDispatch) {
|
||||
auto call = CreateToolCall("build-configure", {
|
||||
{"preset", "mac-dbg"}
|
||||
});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
// Build tools may not be fully implemented yet
|
||||
if (!result.ok()) {
|
||||
EXPECT_TRUE(absl::IsInternal(result.status()) ||
|
||||
absl::IsUnimplemented(result.status()))
|
||||
<< "Unexpected error: " << result.status().message();
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Tool Preferences Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(ToolDispatcherTest, ToolPreferencesDisableFilesystem) {
|
||||
ToolDispatcher::ToolPreferences prefs;
|
||||
prefs.filesystem = false;
|
||||
|
||||
dispatcher_.SetToolPreferences(prefs);
|
||||
|
||||
auto call = CreateToolCall("filesystem-list", {
|
||||
{"path", test_dir_.string()}
|
||||
});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_TRUE(absl::IsFailedPrecondition(result.status()))
|
||||
<< "Expected FailedPrecondition when tool is disabled, got: "
|
||||
<< result.status().message();
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, ToolPreferencesDisableBuild) {
|
||||
ToolDispatcher::ToolPreferences prefs;
|
||||
prefs.build = false;
|
||||
|
||||
dispatcher_.SetToolPreferences(prefs);
|
||||
|
||||
auto call = CreateToolCall("build-status", {});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_TRUE(absl::IsFailedPrecondition(result.status()))
|
||||
<< "Expected FailedPrecondition when tool is disabled, got: "
|
||||
<< result.status().message();
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, ToolPreferencesDisableDungeon) {
|
||||
ToolDispatcher::ToolPreferences prefs;
|
||||
prefs.dungeon = false;
|
||||
|
||||
dispatcher_.SetToolPreferences(prefs);
|
||||
|
||||
auto call = CreateToolCall("dungeon-describe-room", {
|
||||
{"room", "0"}
|
||||
});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_TRUE(absl::IsFailedPrecondition(result.status()))
|
||||
<< "Expected FailedPrecondition when tool is disabled, got: "
|
||||
<< result.status().message();
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, ToolPreferencesDisableOverworld) {
|
||||
ToolDispatcher::ToolPreferences prefs;
|
||||
prefs.overworld = false;
|
||||
|
||||
dispatcher_.SetToolPreferences(prefs);
|
||||
|
||||
auto call = CreateToolCall("overworld-describe-map", {
|
||||
{"map", "0"}
|
||||
});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_TRUE(absl::IsFailedPrecondition(result.status()))
|
||||
<< "Expected FailedPrecondition when tool is disabled, got: "
|
||||
<< result.status().message();
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, ToolPreferencesEnableMultipleCategories) {
|
||||
ToolDispatcher::ToolPreferences prefs;
|
||||
prefs.filesystem = true;
|
||||
prefs.build = true;
|
||||
prefs.dungeon = false;
|
||||
prefs.overworld = false;
|
||||
|
||||
dispatcher_.SetToolPreferences(prefs);
|
||||
|
||||
// Filesystem should work
|
||||
auto fs_call = CreateToolCall("filesystem-exists", {
|
||||
{"path", test_dir_.string()}
|
||||
});
|
||||
auto fs_result = dispatcher_.Dispatch(fs_call);
|
||||
EXPECT_TRUE(fs_result.ok()) << fs_result.status().message();
|
||||
|
||||
// Dungeon should be disabled
|
||||
auto dungeon_call = CreateToolCall("dungeon-describe-room", {
|
||||
{"room", "0"}
|
||||
});
|
||||
auto dungeon_result = dispatcher_.Dispatch(dungeon_call);
|
||||
EXPECT_FALSE(dungeon_result.ok());
|
||||
EXPECT_TRUE(absl::IsFailedPrecondition(dungeon_result.status()));
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, GetToolPreferencesReturnsSetPreferences) {
|
||||
ToolDispatcher::ToolPreferences prefs;
|
||||
prefs.filesystem = false;
|
||||
prefs.build = true;
|
||||
prefs.dungeon = false;
|
||||
prefs.overworld = true;
|
||||
|
||||
dispatcher_.SetToolPreferences(prefs);
|
||||
|
||||
const auto& retrieved_prefs = dispatcher_.preferences();
|
||||
EXPECT_FALSE(retrieved_prefs.filesystem);
|
||||
EXPECT_TRUE(retrieved_prefs.build);
|
||||
EXPECT_FALSE(retrieved_prefs.dungeon);
|
||||
EXPECT_TRUE(retrieved_prefs.overworld);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Error Handling Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(ToolDispatcherTest, InvalidToolCallReturnsError) {
|
||||
auto call = CreateToolCall("invalid-nonexistent-tool", {});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_TRUE(absl::IsInvalidArgument(result.status()))
|
||||
<< "Expected InvalidArgument for unknown tool, got: "
|
||||
<< result.status().message();
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, EmptyToolNameReturnsError) {
|
||||
auto call = CreateToolCall("", {});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_TRUE(absl::IsInvalidArgument(result.status()))
|
||||
<< "Expected InvalidArgument for empty tool name, got: "
|
||||
<< result.status().message();
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, MissingRequiredArgumentsHandled) {
|
||||
// Try to call a tool that requires arguments without providing them
|
||||
auto call = CreateToolCall("filesystem-read", {}); // Missing --path
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
// Should fail due to missing required argument
|
||||
EXPECT_FALSE(result.ok())
|
||||
<< "Expected error for missing required argument";
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, InvalidArgumentValuesHandled) {
|
||||
auto call = CreateToolCall("filesystem-read", {
|
||||
{"path", "/definitely/nonexistent/path/to/file.txt"}
|
||||
});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
// Should fail due to nonexistent path (security validation or not found)
|
||||
EXPECT_FALSE(result.ok());
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ROM Context Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(ToolDispatcherTest, DispatchWithoutRomContextFails) {
|
||||
ToolDispatcher dispatcher; // No ROM context set
|
||||
|
||||
auto call = CreateToolCall("dungeon-describe-room", {
|
||||
{"room", "0"}
|
||||
});
|
||||
|
||||
auto result = dispatcher.Dispatch(call);
|
||||
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_TRUE(absl::IsFailedPrecondition(result.status()))
|
||||
<< "Expected FailedPrecondition without ROM context, got: "
|
||||
<< result.status().message();
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, FilesystemToolsWorkWithoutRomData) {
|
||||
// Create a new dispatcher with ROM set but not loaded with real data
|
||||
ToolDispatcher dispatcher;
|
||||
dispatcher.SetRomContext(&mock_rom_);
|
||||
|
||||
auto call = CreateToolCall("filesystem-exists", {
|
||||
{"path", test_dir_.string()}
|
||||
});
|
||||
|
||||
auto result = dispatcher.Dispatch(call);
|
||||
|
||||
// Filesystem tools don't need actual ROM data
|
||||
EXPECT_TRUE(result.ok()) << result.status().message();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Tool Name Resolution Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(ToolDispatcherTest, ResourceListToolResolves) {
|
||||
auto call = CreateToolCall("resource-list", {
|
||||
{"type", "dungeon"}
|
||||
});
|
||||
|
||||
// This test verifies the tool name resolves correctly
|
||||
// The actual execution might fail if labels aren't loaded
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
// We just verify it's not "unknown tool"
|
||||
EXPECT_FALSE(absl::IsInvalidArgument(result.status()) &&
|
||||
result.status().message().find("Unknown tool") != std::string::npos)
|
||||
<< "resource-list should be a known tool";
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, GuiToolResolves) {
|
||||
auto call = CreateToolCall("gui-discover-tool", {});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
// GUI tools should resolve, even if execution fails
|
||||
EXPECT_FALSE(absl::IsInvalidArgument(result.status()) &&
|
||||
result.status().message().find("Unknown tool") != std::string::npos)
|
||||
<< "gui-discover-tool should be a known tool";
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, MessageToolResolves) {
|
||||
auto call = CreateToolCall("message-list", {});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
// Message tools should resolve
|
||||
EXPECT_FALSE(absl::IsInvalidArgument(result.status()) &&
|
||||
result.status().message().find("Unknown tool") != std::string::npos)
|
||||
<< "message-list should be a known tool";
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, MusicToolResolves) {
|
||||
auto call = CreateToolCall("music-list", {});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
// Music tools should resolve
|
||||
EXPECT_FALSE(absl::IsInvalidArgument(result.status()) &&
|
||||
result.status().message().find("Unknown tool") != std::string::npos)
|
||||
<< "music-list should be a known tool";
|
||||
}
|
||||
|
||||
TEST_F(ToolDispatcherTest, SpriteToolResolves) {
|
||||
auto call = CreateToolCall("sprite-list", {});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
// Sprite tools should resolve
|
||||
EXPECT_FALSE(absl::IsInvalidArgument(result.status()) &&
|
||||
result.status().message().find("Unknown tool") != std::string::npos)
|
||||
<< "sprite-list should be a known tool";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Default Preferences Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(ToolDispatcherTest, DefaultPreferencesEnableAllBasicTools) {
|
||||
ToolDispatcher dispatcher;
|
||||
const auto& prefs = dispatcher.preferences();
|
||||
|
||||
EXPECT_TRUE(prefs.resources);
|
||||
EXPECT_TRUE(prefs.dungeon);
|
||||
EXPECT_TRUE(prefs.overworld);
|
||||
EXPECT_TRUE(prefs.messages);
|
||||
EXPECT_TRUE(prefs.dialogue);
|
||||
EXPECT_TRUE(prefs.gui);
|
||||
EXPECT_TRUE(prefs.music);
|
||||
EXPECT_TRUE(prefs.sprite);
|
||||
EXPECT_TRUE(prefs.filesystem);
|
||||
EXPECT_TRUE(prefs.build);
|
||||
}
|
||||
|
||||
#ifndef YAZE_WITH_GRPC
|
||||
TEST_F(ToolDispatcherTest, EmulatorDisabledWithoutGrpc) {
|
||||
ToolDispatcher dispatcher;
|
||||
const auto& prefs = dispatcher.preferences();
|
||||
|
||||
// Emulator should be disabled when GRPC is not available
|
||||
EXPECT_FALSE(prefs.emulator);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
TEST_F(ToolDispatcherTest, EmulatorEnabledWithGrpc) {
|
||||
ToolDispatcher dispatcher;
|
||||
const auto& prefs = dispatcher.preferences();
|
||||
|
||||
// Emulator should be enabled when GRPC is available
|
||||
EXPECT_TRUE(prefs.emulator);
|
||||
}
|
||||
#endif
|
||||
|
||||
// =============================================================================
|
||||
// JSON Output Format Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(ToolDispatcherTest, OutputIsValidJson) {
|
||||
auto call = CreateToolCall("filesystem-exists", {
|
||||
{"path", test_dir_.string()}
|
||||
});
|
||||
|
||||
auto result = dispatcher_.Dispatch(call);
|
||||
|
||||
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||
|
||||
// Basic JSON structure validation
|
||||
std::string output = *result;
|
||||
// Output should contain JSON-like structure (braces or brackets)
|
||||
EXPECT_TRUE(output.find('{') != std::string::npos ||
|
||||
output.find('[') != std::string::npos)
|
||||
<< "Expected JSON output, got: " << output;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace agent
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
@@ -40,8 +40,8 @@ EditorIntegrationTest::~EditorIntegrationTest() {
|
||||
}
|
||||
|
||||
absl::Status EditorIntegrationTest::Initialize() {
|
||||
// Create renderer for test
|
||||
test_renderer_ = std::make_unique<gfx::SDL2Renderer>();
|
||||
// Create renderer for test (uses factory for SDL2/SDL3 selection)
|
||||
test_renderer_ = gfx::RendererFactory::Create();
|
||||
RETURN_IF_ERROR(
|
||||
core::CreateWindow(window_, test_renderer_.get(), SDL_WINDOW_RESIZABLE));
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "app/controller.h"
|
||||
#include "app/editor/editor.h"
|
||||
#include "app/gfx/backend/sdl2_renderer.h"
|
||||
#include "app/gfx/backend/renderer_factory.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "app/rom.h"
|
||||
#include "imgui/imgui.h"
|
||||
@@ -83,7 +83,7 @@ class EditorIntegrationTest {
|
||||
#endif
|
||||
std::unique_ptr<Rom> test_rom_;
|
||||
core::Window window_;
|
||||
std::unique_ptr<gfx::SDL2Renderer> test_renderer_;
|
||||
std::unique_ptr<gfx::IRenderer> test_renderer_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/backend/sdl2_renderer.h"
|
||||
#include "app/gfx/backend/renderer_factory.h"
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/render/tilemap.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
@@ -95,8 +95,8 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
|
||||
|
||||
protected:
|
||||
static void InitializeTestEnvironment() {
|
||||
// Create renderer for test
|
||||
test_renderer_ = std::make_unique<gfx::SDL2Renderer>();
|
||||
// Create renderer for test (uses factory for SDL2/SDL3 selection)
|
||||
test_renderer_ = gfx::RendererFactory::Create();
|
||||
auto window_result = core::CreateWindow(test_window_, test_renderer_.get(),
|
||||
SDL_WINDOW_HIDDEN);
|
||||
if (window_result.ok()) {
|
||||
@@ -111,7 +111,7 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
|
||||
|
||||
static bool window_initialized_;
|
||||
static core::Window test_window_;
|
||||
static std::unique_ptr<gfx::SDL2Renderer> test_renderer_;
|
||||
static std::unique_ptr<gfx::IRenderer> test_renderer_;
|
||||
|
||||
bool rom_loaded_ = false;
|
||||
std::unique_ptr<Rom> rom_;
|
||||
@@ -126,7 +126,7 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
|
||||
// Static member definitions
|
||||
bool Tile16EditorIntegrationTest::window_initialized_ = false;
|
||||
core::Window Tile16EditorIntegrationTest::test_window_;
|
||||
std::unique_ptr<gfx::SDL2Renderer> Tile16EditorIntegrationTest::test_renderer_;
|
||||
std::unique_ptr<gfx::IRenderer> Tile16EditorIntegrationTest::test_renderer_;
|
||||
|
||||
// Basic validation tests (no ROM required)
|
||||
TEST_F(Tile16EditorIntegrationTest, BasicValidation) {
|
||||
|
||||
254
test/integration/memory_debugging_test.cc
Normal file
254
test/integration/memory_debugging_test.cc
Normal file
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* @file memory_debugging_test.cc
|
||||
* @brief Integration test for memory breakpoints and watchpoints
|
||||
*
|
||||
* This test verifies that memory breakpoints and watchpoints trigger
|
||||
* correctly during actual CPU execution via the memory bus.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
#include "app/emu/emulator.h"
|
||||
#include "app/emu/snes.h"
|
||||
#include "app/emu/debug/breakpoint_manager.h"
|
||||
#include "app/emu/debug/watchpoint_manager.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace emu {
|
||||
namespace {
|
||||
|
||||
class MemoryDebuggingTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Create a minimal test ROM with some test code
|
||||
CreateTestRom();
|
||||
|
||||
// Initialize emulator with test ROM
|
||||
emulator_.Initialize(nullptr, test_rom_data_);
|
||||
emulator_.set_debugging(true); // Enable debugging mode
|
||||
}
|
||||
|
||||
void CreateTestRom() {
|
||||
// Create a minimal ROM that tests memory access
|
||||
test_rom_data_.resize(0x8000); // 32KB ROM
|
||||
|
||||
// ROM header (simplified)
|
||||
std::memset(test_rom_data_.data(), 0, test_rom_data_.size());
|
||||
|
||||
// Test program at reset vector ($8000 in LoROM)
|
||||
// This simple program:
|
||||
// 1. Writes $42 to WRAM address $0000
|
||||
// 2. Reads from WRAM address $0000
|
||||
// 3. Writes $FF to WRAM address $1000
|
||||
// 4. Infinite loop
|
||||
|
||||
size_t code_offset = 0x0000; // Start of ROM
|
||||
uint8_t test_code[] = {
|
||||
// LDA #$42
|
||||
0xA9, 0x42,
|
||||
// STA $7E0000 (WRAM)
|
||||
0x8F, 0x00, 0x00, 0x7E,
|
||||
// LDA $7E0000 (WRAM)
|
||||
0xAF, 0x00, 0x00, 0x7E,
|
||||
// LDA #$FF
|
||||
0xA9, 0xFF,
|
||||
// STA $7E1000 (WRAM)
|
||||
0x8F, 0x00, 0x10, 0x7E,
|
||||
// Infinite loop: JMP $8000
|
||||
0x4C, 0x00, 0x80
|
||||
};
|
||||
|
||||
std::memcpy(test_rom_data_.data() + code_offset, test_code, sizeof(test_code));
|
||||
|
||||
// Set reset vector to $8000
|
||||
test_rom_data_[0x7FFC] = 0x00;
|
||||
test_rom_data_[0x7FFD] = 0x80;
|
||||
}
|
||||
|
||||
Emulator emulator_;
|
||||
std::vector<uint8_t> test_rom_data_;
|
||||
};
|
||||
|
||||
TEST_F(MemoryDebuggingTest, MemoryWriteBreakpoint) {
|
||||
// Add a write breakpoint at WRAM $0000
|
||||
uint32_t bp_id = emulator_.breakpoint_manager().AddBreakpoint(
|
||||
0x7E0000, // WRAM address
|
||||
BreakpointManager::Type::WRITE,
|
||||
BreakpointManager::CpuType::CPU_65816,
|
||||
"", // No condition
|
||||
"Test write breakpoint at WRAM $0000"
|
||||
);
|
||||
|
||||
// Track if breakpoint was hit
|
||||
bool breakpoint_hit = false;
|
||||
int cycles_executed = 0;
|
||||
const int max_cycles = 100;
|
||||
|
||||
// Run emulation for a limited number of cycles
|
||||
while (cycles_executed < max_cycles && !breakpoint_hit) {
|
||||
// Step one instruction
|
||||
emulator_.snes().cpu().RunOpcode();
|
||||
cycles_executed++;
|
||||
|
||||
// Check if we're still running (breakpoint stops execution)
|
||||
if (!emulator_.snes().running()) {
|
||||
breakpoint_hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the breakpoint was hit
|
||||
EXPECT_TRUE(breakpoint_hit) << "Write breakpoint at $7E0000 should have been hit";
|
||||
|
||||
// Verify the breakpoint manager recorded the hit
|
||||
auto breakpoints = emulator_.breakpoint_manager().GetAllBreakpoints();
|
||||
auto it = std::find_if(breakpoints.begin(), breakpoints.end(),
|
||||
[bp_id](const auto& bp) { return bp.id == bp_id; });
|
||||
ASSERT_NE(it, breakpoints.end());
|
||||
EXPECT_GT(it->hit_count, 0) << "Breakpoint hit count should be > 0";
|
||||
}
|
||||
|
||||
TEST_F(MemoryDebuggingTest, MemoryReadBreakpoint) {
|
||||
// Add a read breakpoint at WRAM $0000
|
||||
uint32_t bp_id = emulator_.breakpoint_manager().AddBreakpoint(
|
||||
0x7E0000, // WRAM address
|
||||
BreakpointManager::Type::READ,
|
||||
BreakpointManager::CpuType::CPU_65816,
|
||||
"", // No condition
|
||||
"Test read breakpoint at WRAM $0000"
|
||||
);
|
||||
|
||||
// Track if breakpoint was hit
|
||||
bool breakpoint_hit = false;
|
||||
int cycles_executed = 0;
|
||||
const int max_cycles = 100;
|
||||
|
||||
// Run emulation - should hit on the LDA $7E0000 instruction
|
||||
while (cycles_executed < max_cycles && !breakpoint_hit) {
|
||||
emulator_.snes().cpu().RunOpcode();
|
||||
cycles_executed++;
|
||||
|
||||
if (!emulator_.snes().running()) {
|
||||
breakpoint_hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the breakpoint was hit
|
||||
EXPECT_TRUE(breakpoint_hit) << "Read breakpoint at $7E0000 should have been hit";
|
||||
}
|
||||
|
||||
TEST_F(MemoryDebuggingTest, WatchpointTracking) {
|
||||
// Add a watchpoint at WRAM $0000 (track both reads and writes)
|
||||
uint32_t wp_id = emulator_.watchpoint_manager().AddWatchpoint(
|
||||
0x7E0000, // Start address
|
||||
0x7E0000, // End address (single byte)
|
||||
true, // Track reads
|
||||
true, // Track writes
|
||||
false, // Don't break on access
|
||||
"Test watchpoint at WRAM $0000"
|
||||
);
|
||||
|
||||
// Run emulation for several instructions
|
||||
const int instructions_to_execute = 10;
|
||||
for (int i = 0; i < instructions_to_execute; i++) {
|
||||
emulator_.snes().cpu().RunOpcode();
|
||||
}
|
||||
|
||||
// Get watchpoint history
|
||||
auto history = emulator_.watchpoint_manager().GetHistory(0x7E0000, 10);
|
||||
|
||||
// We should have at least one write and one read in the history
|
||||
bool found_write = false;
|
||||
bool found_read = false;
|
||||
|
||||
for (const auto& access : history) {
|
||||
if (access.is_write) {
|
||||
found_write = true;
|
||||
EXPECT_EQ(access.new_value, 0x42) << "Written value should be $42";
|
||||
} else {
|
||||
found_read = true;
|
||||
EXPECT_EQ(access.new_value, 0x42) << "Read value should be $42";
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(found_write) << "Should have recorded a write to $7E0000";
|
||||
EXPECT_TRUE(found_read) << "Should have recorded a read from $7E0000";
|
||||
}
|
||||
|
||||
TEST_F(MemoryDebuggingTest, WatchpointBreakOnAccess) {
|
||||
// Add a watchpoint that breaks on write access
|
||||
uint32_t wp_id = emulator_.watchpoint_manager().AddWatchpoint(
|
||||
0x7E1000, // Start address
|
||||
0x7E1000, // End address
|
||||
false, // Don't track reads
|
||||
true, // Track writes
|
||||
true, // Break on access
|
||||
"Test breaking watchpoint at WRAM $1000"
|
||||
);
|
||||
|
||||
// Track if watchpoint caused a break
|
||||
bool watchpoint_triggered = false;
|
||||
int cycles_executed = 0;
|
||||
const int max_cycles = 100;
|
||||
|
||||
// Run emulation - should break when writing to $7E1000
|
||||
while (cycles_executed < max_cycles && !watchpoint_triggered) {
|
||||
emulator_.snes().cpu().RunOpcode();
|
||||
cycles_executed++;
|
||||
|
||||
if (!emulator_.snes().running()) {
|
||||
// Check if we stopped due to watchpoint
|
||||
auto history = emulator_.watchpoint_manager().GetHistory(0x7E1000, 1);
|
||||
if (!history.empty()) {
|
||||
watchpoint_triggered = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(watchpoint_triggered) << "Watchpoint at $7E1000 should have triggered a break";
|
||||
|
||||
// Verify the access was logged
|
||||
auto history = emulator_.watchpoint_manager().GetHistory(0x7E1000, 10);
|
||||
ASSERT_FALSE(history.empty());
|
||||
EXPECT_TRUE(history[0].is_write);
|
||||
EXPECT_EQ(history[0].new_value, 0xFF) << "Written value should be $FF";
|
||||
}
|
||||
|
||||
TEST_F(MemoryDebuggingTest, DebuggingDisabledPerformance) {
|
||||
// Disable debugging - callbacks should not be invoked
|
||||
emulator_.set_debugging(false);
|
||||
|
||||
// Add breakpoints and watchpoints (they shouldn't trigger)
|
||||
emulator_.breakpoint_manager().AddBreakpoint(
|
||||
0x7E0000, BreakpointManager::Type::WRITE,
|
||||
BreakpointManager::CpuType::CPU_65816
|
||||
);
|
||||
|
||||
emulator_.watchpoint_manager().AddWatchpoint(
|
||||
0x7E0000, 0x7E0000, true, true, true
|
||||
);
|
||||
|
||||
// Run emulation - should not break
|
||||
const int instructions_to_execute = 20;
|
||||
bool unexpectedly_stopped = false;
|
||||
|
||||
for (int i = 0; i < instructions_to_execute; i++) {
|
||||
emulator_.snes().cpu().RunOpcode();
|
||||
if (!emulator_.snes().running()) {
|
||||
unexpectedly_stopped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_FALSE(unexpectedly_stopped)
|
||||
<< "Emulation should not stop when debugging is disabled";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace emu
|
||||
} // namespace yaze
|
||||
@@ -16,9 +16,9 @@ namespace zelda3 {
|
||||
class DungeonEditorSystemIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Skip tests on Linux for automated github builds
|
||||
// Skip on Linux CI - requires ROM file and graphics context
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
GTEST_SKIP() << "Dungeon editor tests require ROM file (unavailable on Linux CI)";
|
||||
#endif
|
||||
|
||||
// Use the real ROM from build directory
|
||||
|
||||
@@ -133,9 +133,9 @@ TEST_F(DungeonObjectRenderingTests, PreviewBufferRendersContent) {
|
||||
|
||||
auto& bitmap = preview_bg.bitmap();
|
||||
EXPECT_TRUE(bitmap.is_active());
|
||||
const auto& data = bitmap.data();
|
||||
const auto data = bitmap.data();
|
||||
size_t non_zero = 0;
|
||||
for (size_t i = 0; i < data.size(); i += 16) {
|
||||
for (size_t i = 0; i < bitmap.size(); i += 16) {
|
||||
if (data[i] != 0) {
|
||||
non_zero++;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
/**
|
||||
* @file dungeon_rendering_test.cc
|
||||
* @brief Integration tests for dungeon rendering with mock ROM data
|
||||
*
|
||||
* ============================================================================
|
||||
* DEPRECATED - DO NOT USE - November 2025
|
||||
* ============================================================================
|
||||
*
|
||||
* This file is DEPRECATED and excluded from the build. It duplicates coverage
|
||||
* already provided by dungeon_object_rendering_tests.cc but uses mock ROM data
|
||||
* instead of the proper TestRomManager fixture.
|
||||
*
|
||||
* REPLACEMENT:
|
||||
* - Use test/integration/zelda3/dungeon_object_rendering_tests.cc instead
|
||||
*
|
||||
* The replacement file provides:
|
||||
* - Same test scenarios (basic drawing, multi-layer, boundaries, error handling)
|
||||
* - Proper TestRomManager::BoundRomTest fixture for ROM access
|
||||
* - Cleaner test organization following project standards
|
||||
*
|
||||
* This file is kept for reference only.
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/render/background_buffer.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
|
||||
@@ -10,12 +10,12 @@ namespace test {
|
||||
class DungeonRoomTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Skip tests on Linux for automated github builds
|
||||
// Skip on Linux CI - requires ROM file
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
GTEST_SKIP() << "Dungeon room tests require ROM file (unavailable on Linux CI)";
|
||||
#else
|
||||
if (!rom_.LoadFromFile("./zelda3.sfc").ok()) {
|
||||
GTEST_SKIP_("Failed to load test ROM");
|
||||
GTEST_SKIP() << "Failed to load test ROM (zelda3.sfc)";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class OverworldIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
GTEST_SKIP() << "Overworld integration tests require ROM (unavailable on Linux CI)";
|
||||
#endif
|
||||
|
||||
// Check if we should use real ROM or mock data
|
||||
@@ -339,8 +339,12 @@ TEST_F(OverworldIntegrationTest, ComprehensiveDataIntegrity) {
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
for (const auto& map : maps) {
|
||||
// TODO: Find a way to compare
|
||||
// EXPECT_TRUE(map.bitmap_data() != nullptr);
|
||||
// NOTE: Bitmap validation requires graphics system initialization.
|
||||
// OverworldMap::bitmap() returns a reference to an internal Bitmap object,
|
||||
// but bitmap data is only populated after LoadAreaGraphics() is called
|
||||
// with an initialized SDL/graphics context. For unit testing without
|
||||
// graphics, we validate map structure properties instead.
|
||||
EXPECT_GE(map.area_graphics(), 0);
|
||||
}
|
||||
|
||||
// Verify tile types are loaded
|
||||
|
||||
Reference in New Issue
Block a user