backend-infra-engineer: Release v0.3.0 snapshot

This commit is contained in:
scawful
2025-09-27 00:25:45 -04:00
parent 8ce29e1436
commit e32ac75b9c
346 changed files with 55946 additions and 11764 deletions

View File

@@ -0,0 +1,544 @@
#include <gtest/gtest.h>
#include <filesystem>
#include <fstream>
#include "app/core/asar_wrapper.h"
#include "app/rom.h"
#include "absl/status/status.h"
#include "testing.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
namespace yaze {
namespace test {
namespace integration {
class AsarIntegrationTest : public ::testing::Test {
protected:
void SetUp() override {
wrapper_ = std::make_unique<app::core::AsarWrapper>();
// Create test directory
test_dir_ = std::filesystem::temp_directory_path() / "yaze_asar_integration";
std::filesystem::create_directories(test_dir_);
CreateTestRom();
CreateTestAssemblyFiles();
}
void TearDown() override {
try {
if (std::filesystem::exists(test_dir_)) {
std::filesystem::remove_all(test_dir_);
}
} catch (const std::exception& e) {
// Ignore cleanup errors
}
}
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) {
if (i != header_offset + 28 && i != header_offset + 29 &&
i != header_offset + 30 && i != header_offset + 31) {
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
test_rom_[reset_vector_offset + 1] = 0xFB; // XCE
test_rom_[reset_vector_offset + 2] = 0x4C; // JMP abs
test_rom_[reset_vector_offset + 3] = 0x00; // $8000
test_rom_[reset_vector_offset + 4] = 0x80;
}
void CreateTestAssemblyFiles() {
// Create comprehensive test assembly
comprehensive_asm_path_ = test_dir_ / "comprehensive_test.asm";
std::ofstream comp_file(comprehensive_asm_path_);
comp_file << R"(
; Comprehensive Asar test for Yaze integration
!addr = $7E0000
; Test basic assembly
org $008000
main_entry:
sei ; Disable interrupts
clc ; Clear carry
xce ; Switch to native mode
; Set up stack
rep #$30 ; 16-bit A and X/Y
ldx #$1FFF
txs
; Test data writing
lda #$1234
sta !addr
; Call subroutines
jsr init_graphics
jsr init_sound
; Main loop
main_loop:
jsr update_game
jsr wait_vblank
bra main_loop
; Graphics initialization
init_graphics:
pha
phx
phy
; Set up PPU registers
sep #$20 ; 8-bit A
lda #$80
sta $2100 ; Force blank
; Clear VRAM
rep #$20 ; 16-bit A
lda #$8000
sta $2116 ; VRAM address
lda #$0000
ldx #$8000
clear_vram_loop:
sta $2118 ; Write to VRAM
dex
bne clear_vram_loop
ply
plx
pla
rts
; Sound initialization
init_sound:
pha
; Initialize APU
sep #$20 ; 8-bit A
lda #$00
sta $2140 ; APU port 0
sta $2141 ; APU port 1
sta $2142 ; APU port 2
sta $2143 ; APU port 3
pla
rts
; Game update routine
update_game:
pha
; Read controller
lda $4212 ; PPU status
and #$01
bne update_game ; Wait for vblank end
lda $4016 ; Controller 1
; Process input here
pla
rts
; Wait for vertical blank
wait_vblank:
pha
wait_vb_loop:
lda $4212 ; PPU status
and #$80
beq wait_vb_loop ; Wait for vblank
pla
rts
; Data tables
org $00A000
graphics_data:
incbin "test_graphics.bin"
sound_data:
db $00, $01, $02, $03, $04, $05, $06, $07
db $08, $09, $0A, $0B, $0C, $0D, $0E, $0F
; String data for testing
text_data:
db "YAZE INTEGRATION TEST", $00
; Math functions
calculate_distance:
; Input: A = x1, X = y1, stack = x2, y2
; Output: A = distance
pha
phx
; Calculate dx = x2 - x1
pla ; Get x1
pha ; Save it back
; ... distance calculation here
plx
pla
rts
; Interrupt vectors
org $00FFE0
dw $0000 ; Reserved
dw $0000 ; Reserved
dw $0000 ; Reserved
dw $0000 ; Reserved
dw $0000 ; Reserved
dw $0000 ; Reserved
dw $0000 ; Reserved
dw $0000 ; Reserved
dw main_entry ; RESET vector
dw $0000 ; Reserved
dw $0000 ; Reserved
dw $0000 ; Reserved
dw $0000 ; NMI vector
dw main_entry ; RESET vector
dw $0000 ; IRQ vector
)";
comp_file.close();
// 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());
gfx_file.close();
// Create advanced assembly with macros and includes
advanced_asm_path_ = test_dir_ / "advanced_test.asm";
std::ofstream adv_file(advanced_asm_path_);
adv_file << R"(
; Advanced Asar features test
!ram_addr = $7E1000
; Macro definitions
macro move_block(source, dest, size)
rep #$30
ldx #<size>-1
loop:
lda <source>,x
sta <dest>,x
dex
bpl loop
endmacro
macro set_ppu_register(register, value)
sep #$20
lda #<value>
sta <register>
endmacro
; Test code with macros
org $008000
advanced_entry:
%set_ppu_register($2100, $8F) ; Set forced blank
; Use block move macro
%move_block($008100, !ram_addr, 256)
; Conditional assembly
if !test_mode == 1
jsr debug_routine
endif
; Loop with labels
ldx #$10
test_loop:
lda test_data,x
sta !ram_addr,x
dex
bpl test_loop
rts
debug_routine:
; Debug code
rts
test_data:
db $FF, $FE, $FD, $FC, $FB, $FA, $F9, $F8
db $F7, $F6, $F5, $F4, $F3, $F2, $F1, $F0
)";
adv_file.close();
// Create error test assembly
error_asm_path_ = test_dir_ / "error_test.asm";
std::ofstream err_file(error_asm_path_);
err_file << R"(
; Assembly with intentional errors for testing error handling
org $008000
error_test:
invalid_opcode ; This should cause an error
lda unknown_label ; This should cause an error
sta $999999 ; Invalid address
bra ; Missing operand
)";
err_file.close();
}
std::unique_ptr<app::core::AsarWrapper> wrapper_;
std::filesystem::path test_dir_;
std::filesystem::path comprehensive_asm_path_;
std::filesystem::path advanced_asm_path_;
std::filesystem::path error_asm_path_;
std::vector<uint8_t> test_rom_;
};
TEST_F(AsarIntegrationTest, FullWorkflowIntegration) {
// Initialize Asar
ASSERT_TRUE(wrapper_->Initialize().ok());
// Test ROM loading and patching workflow
std::vector<uint8_t> rom_copy = test_rom_;
size_t original_size = rom_copy.size();
// Apply comprehensive patch
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);
// Verify ROM was modified correctly
EXPECT_NE(rom_copy, test_rom_);
EXPECT_GT(result.rom_size, 0);
// 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;
bool found_init_sound = false;
for (const auto& symbol : result.symbols) {
if (symbol.name == "main_entry") {
found_main_entry = true;
EXPECT_EQ(symbol.address, 0x008000);
} else if (symbol.name == "init_graphics") {
found_init_graphics = true;
} else if (symbol.name == "init_sound") {
found_init_sound = true;
}
}
EXPECT_TRUE(found_main_entry) << "main_entry symbol not found";
EXPECT_TRUE(found_init_graphics) << "init_graphics symbol not found";
EXPECT_TRUE(found_init_sound) << "init_sound symbol not found";
}
TEST_F(AsarIntegrationTest, AdvancedFeaturesIntegration) {
ASSERT_TRUE(wrapper_->Initialize().ok());
// 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);
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);
// Verify symbols from advanced assembly
bool found_advanced_entry = false;
bool found_test_loop = false;
for (const auto& symbol : result.symbols) {
if (symbol.name == "advanced_entry") {
found_advanced_entry = true;
} else if (symbol.name == "test_loop") {
found_test_loop = true;
}
}
EXPECT_TRUE(found_advanced_entry);
EXPECT_TRUE(found_test_loop);
}
TEST_F(AsarIntegrationTest, ErrorHandlingIntegration) {
ASSERT_TRUE(wrapper_->Initialize().ok());
// 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")));
}
TEST_F(AsarIntegrationTest, SymbolExtractionWorkflow) {
ASSERT_TRUE(wrapper_->Initialize().ok());
// Extract symbols without applying patch
auto symbols_result = wrapper_->ExtractSymbols(comprehensive_asm_path_.string());
ASSERT_TRUE(symbols_result.ok()) << symbols_result.status().message();
const auto& symbols = symbols_result.value();
EXPECT_GT(symbols.size(), 0);
// Test symbol table operations
std::vector<uint8_t> rom_copy = test_rom_;
auto patch_result = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
ASSERT_TRUE(patch_result.ok());
// Test symbol lookup by name
auto main_symbol = wrapper_->FindSymbol("main_entry");
ASSERT_TRUE(main_symbol.has_value());
EXPECT_EQ(main_symbol->name, "main_entry");
EXPECT_EQ(main_symbol->address, 0x008000);
// Test symbols at address lookup
auto symbols_at_main = wrapper_->GetSymbolsAtAddress(0x008000);
EXPECT_GT(symbols_at_main.size(), 0);
bool found_main_at_address = false;
for (const auto& symbol : symbols_at_main) {
if (symbol.name == "main_entry") {
found_main_at_address = true;
break;
}
}
EXPECT_TRUE(found_main_at_address);
}
TEST_F(AsarIntegrationTest, MultipleOperationsIntegration) {
ASSERT_TRUE(wrapper_->Initialize().ok());
// Test multiple patch operations on the same wrapper instance
std::vector<uint8_t> rom_copy1 = test_rom_;
std::vector<uint8_t> rom_copy2 = test_rom_;
// First patch
auto result1 = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy1);
ASSERT_TRUE(result1.ok());
EXPECT_TRUE(result1->success);
// Reset and apply different patch
wrapper_->Reset();
auto result2 = wrapper_->ApplyPatch(advanced_asm_path_.string(), rom_copy2);
ASSERT_TRUE(result2.ok());
EXPECT_TRUE(result2->success);
// Verify that symbol tables are different
EXPECT_NE(result1->symbols.size(), result2->symbols.size());
}
TEST_F(AsarIntegrationTest, PatchFromStringIntegration) {
ASSERT_TRUE(wrapper_->Initialize().ok());
std::string patch_content = R"(
org $009000
string_patch_test:
lda #$42
sta $7E2000
jsr subroutine_test
rts
subroutine_test:
lda #$FF
sta $7E2001
rts
)";
std::vector<uint8_t> rom_copy = test_rom_;
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);
// Check for expected symbols
bool found_string_patch = false;
bool found_subroutine = false;
for (const auto& symbol : result->symbols) {
if (symbol.name == "string_patch_test") {
found_string_patch = true;
EXPECT_EQ(symbol.address, 0x009000);
} else if (symbol.name == "subroutine_test") {
found_subroutine = true;
}
}
EXPECT_TRUE(found_string_patch);
EXPECT_TRUE(found_subroutine);
}
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
// 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
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);
}
} // namespace integration
} // namespace test
} // namespace yaze

View File

@@ -0,0 +1,412 @@
#include <gtest/gtest.h>
#include <filesystem>
#include <fstream>
#include "app/core/asar_wrapper.h"
#include "app/rom.h"
#include "test_utils.h"
#include "testing.h"
namespace yaze {
namespace test {
namespace integration {
/**
* @brief Test class for Asar integration with real ROM files
* These tests are only run when ROM testing is enabled
*/
class AsarRomIntegrationTest : public RomDependentTest {
protected:
void SetUp() override {
RomDependentTest::SetUp();
wrapper_ = std::make_unique<app::core::AsarWrapper>();
ASSERT_OK(wrapper_->Initialize());
// Create test directory
test_dir_ = std::filesystem::temp_directory_path() / "yaze_asar_rom_test";
std::filesystem::create_directories(test_dir_);
CreateTestPatches();
}
void TearDown() override {
try {
if (std::filesystem::exists(test_dir_)) {
std::filesystem::remove_all(test_dir_);
}
} catch (const std::exception& e) {
// Ignore cleanup errors
}
}
void CreateTestPatches() {
// Create a simple test patch
simple_patch_path_ = test_dir_ / "simple_test.asm";
std::ofstream simple_file(simple_patch_path_);
simple_file << R"(
; Simple Asar patch for real ROM testing
org $008000
yaze_test_entry:
sei ; Disable interrupts
clc ; Clear carry
xce ; Switch to native mode
rep #$30 ; 16-bit A and X/Y
ldx #$1FFF
txs ; Set stack pointer
; Test data writing
lda #$CAFE
sta $7E0000 ; Write test value to RAM
; Set a custom value that we can verify
lda #$BEEF
sta $7E0002
sep #$20 ; 8-bit A
lda #$42
sta $7E0004 ; Another test value
rep #$20 ; Back to 16-bit A
rts
; Subroutine for testing
yaze_test_subroutine:
pha
lda #$1337
sta $7E0010
pla
rts
; Data for testing
yaze_test_data:
db "YAZE", $00
dw $1234, $5678, $9ABC, $DEF0
)";
simple_file.close();
// Create a patch that modifies game behavior
gameplay_patch_path_ = test_dir_ / "gameplay_test.asm";
std::ofstream gameplay_file(gameplay_patch_path_);
gameplay_file << R"(
; Gameplay modification patch for testing
; This modifies Link's starting health and magic
; Increase Link's maximum health
org $7EF36C
db $A0 ; 160/8 = 20 hearts (was usually $60 = 12 hearts)
; Increase Link's maximum magic
org $7EF36E
db $80 ; Full magic meter
; Custom routine for health restoration
org $00C000
yaze_health_restore:
sep #$20 ; 8-bit A
lda #$A0 ; Full health
sta $7EF36C ; Current health
lda #$80 ; Full magic
sta $7EF36E ; Current magic
rep #$20 ; 16-bit A
rtl
; Hook into the game's main loop (example address)
org $008012
jsl yaze_health_restore
nop ; Pad if needed
)";
gameplay_file.close();
// Create a symbol extraction test patch
symbols_patch_path_ = test_dir_ / "symbols_test.asm";
std::ofstream symbols_file(symbols_patch_path_);
symbols_file << R"(
; Comprehensive symbol test for Asar integration
; Define some constants
!player_x = $7E0020
!player_y = $7E0022
!player_health = $7EF36C
!player_magic = $7EF36E
; Main code section
org $008000
main_routine:
jsr init_player
jsr game_loop
rts
; Player initialization
init_player:
rep #$30 ; 16-bit A and X/Y
; Set initial position
lda #$0080
sta !player_x
lda #$0070
sta !player_y
; Set initial stats
sep #$20 ; 8-bit A
lda #$A0
sta !player_health
lda #$80
sta !player_magic
rep #$30 ; Back to 16-bit
rts
; Main game loop
game_loop:
jsr update_player
jsr update_enemies
jsr update_graphics
rts
; Player update routine
update_player:
; Read controller input
sep #$20
lda $4016 ; Controller 1
; Process movement
bit #$08 ; Up
beq +
dec !player_y
+ bit #$04 ; Down
beq +
inc !player_y
+ bit #$02 ; Left
beq +
dec !player_x
+ bit #$01 ; Right
beq +
inc !player_x
+
rep #$20
rts
; Enemy update routine
update_enemies:
; Placeholder for enemy logic
rts
; Graphics update routine
update_graphics:
; Placeholder for graphics updates
rts
; Utility functions
multiply_by_two:
asl a
rts
divide_by_two:
lsr a
rts
; Data tables
enemy_data_table:
dw enemy_goomba, enemy_koopa, enemy_shell
dw $0000 ; End marker
enemy_goomba:
dw $0010, $0020, $0001 ; x, y, type
enemy_koopa:
dw $0050, $0030, $0002 ; x, y, type
enemy_shell:
dw $0080, $0040, $0003 ; x, y, type
)";
symbols_file.close();
}
std::unique_ptr<app::core::AsarWrapper> wrapper_;
std::filesystem::path test_dir_;
std::filesystem::path simple_patch_path_;
std::filesystem::path gameplay_patch_path_;
std::filesystem::path symbols_patch_path_;
};
TEST_F(AsarRomIntegrationTest, SimplePatchOnRealRom) {
// Make a copy of the ROM for testing
std::vector<uint8_t> rom_copy = test_rom_;
size_t original_size = rom_copy.size();
// Apply simple patch
auto patch_result = wrapper_->ApplyPatch(simple_patch_path_.string(), rom_copy);
ASSERT_OK(patch_result.status());
const auto& result = patch_result.value();
EXPECT_TRUE(result.success) << "Patch failed: "
<< testing::PrintToString(result.errors);
// Verify ROM was modified
EXPECT_NE(rom_copy, test_rom_); // Should be different
EXPECT_GE(rom_copy.size(), original_size); // Size may have grown
// Check for expected symbols
bool found_entry = false;
bool found_subroutine = false;
for (const auto& symbol : result.symbols) {
if (symbol.name == "yaze_test_entry") {
found_entry = true;
EXPECT_EQ(symbol.address, 0x008000);
} else if (symbol.name == "yaze_test_subroutine") {
found_subroutine = true;
}
}
EXPECT_TRUE(found_entry) << "yaze_test_entry symbol not found";
EXPECT_TRUE(found_subroutine) << "yaze_test_subroutine symbol not found";
}
TEST_F(AsarRomIntegrationTest, SymbolExtractionFromRealRom) {
// Extract symbols from comprehensive test
auto symbols_result = wrapper_->ExtractSymbols(symbols_patch_path_.string());
ASSERT_OK(symbols_result.status());
const auto& symbols = symbols_result.value();
EXPECT_GT(symbols.size(), 0);
// Check for specific symbols we expect
std::vector<std::string> expected_symbols = {
"main_routine", "init_player", "game_loop", "update_player",
"update_enemies", "update_graphics", "multiply_by_two", "divide_by_two"
};
for (const auto& expected_symbol : expected_symbols) {
bool found = false;
for (const auto& symbol : symbols) {
if (symbol.name == expected_symbol) {
found = true;
EXPECT_GT(symbol.address, 0) << "Symbol " << expected_symbol
<< " has invalid address";
break;
}
}
EXPECT_TRUE(found) << "Expected symbol not found: " << expected_symbol;
}
// Test symbol lookup functionality
auto symbol_table = wrapper_->GetSymbolTable();
EXPECT_GT(symbol_table.size(), 0);
auto main_symbol = wrapper_->FindSymbol("main_routine");
EXPECT_TRUE(main_symbol.has_value());
if (main_symbol) {
EXPECT_EQ(main_symbol->name, "main_routine");
EXPECT_EQ(main_symbol->address, 0x008000);
}
}
TEST_F(AsarRomIntegrationTest, GameplayModificationPatch) {
// Make a copy of the ROM
std::vector<uint8_t> rom_copy = test_rom_;
// Apply gameplay modification patch
auto patch_result = wrapper_->ApplyPatch(gameplay_patch_path_.string(), rom_copy);
ASSERT_OK(patch_result.status());
const auto& result = patch_result.value();
EXPECT_TRUE(result.success) << "Gameplay patch failed: "
<< testing::PrintToString(result.errors);
// Verify specific memory locations were modified
// Note: These addresses are based on the patch content
// 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)
const uint32_t rom_offset = 0x18000; // Bank $00:C000 in LoROM
if (rom_offset < rom_copy.size()) {
// Check for SEP #$20 instruction (0xE2 0x20)
EXPECT_EQ(rom_copy[rom_offset], 0xE2);
EXPECT_EQ(rom_copy[rom_offset + 1], 0x20);
}
}
TEST_F(AsarRomIntegrationTest, LargeRomPatchingStability) {
// Test with the actual ROM which might be larger
std::vector<uint8_t> rom_copy = test_rom_;
size_t original_size = rom_copy.size();
// Apply multiple patches in sequence
auto result1 = wrapper_->ApplyPatch(simple_patch_path_.string(), rom_copy);
ASSERT_OK(result1.status());
EXPECT_TRUE(result1->success);
// Reset and apply another patch
wrapper_->Reset();
auto result2 = wrapper_->ApplyPatch(symbols_patch_path_.string(), rom_copy);
ASSERT_OK(result2.status());
EXPECT_TRUE(result2->success);
// Verify stability
EXPECT_GE(rom_copy.size(), original_size);
EXPECT_GT(result2->symbols.size(), 0);
}
TEST_F(AsarRomIntegrationTest, ErrorHandlingWithRealRom) {
// Create an intentionally broken patch
auto broken_patch_path = test_dir_ / "broken_test.asm";
std::ofstream broken_file(broken_patch_path);
broken_file << R"(
; Broken patch for error testing
org $008000
broken_routine:
invalid_opcode ; This will cause an error
lda unknown_symbol ; This will cause an error
sta $FFFFFF ; Invalid address
)";
broken_file.close();
std::vector<uint8_t> rom_copy = test_rom_;
auto patch_result = wrapper_->ApplyPatch(broken_patch_path.string(), rom_copy);
// Should fail with proper error messages
EXPECT_FALSE(patch_result.ok());
EXPECT_THAT(patch_result.status().message(),
testing::AnyOf(
testing::HasSubstr("invalid"),
testing::HasSubstr("unknown"),
testing::HasSubstr("error")));
}
TEST_F(AsarRomIntegrationTest, PatchValidationWorkflow) {
// Test the complete workflow: validate -> patch -> verify
// Step 1: Validate assembly
auto validation_result = wrapper_->ValidateAssembly(simple_patch_path_.string());
EXPECT_OK(validation_result);
// Step 2: Apply patch
std::vector<uint8_t> rom_copy = test_rom_;
auto patch_result = wrapper_->ApplyPatch(simple_patch_path_.string(), rom_copy);
ASSERT_OK(patch_result.status());
EXPECT_TRUE(patch_result->success);
// Step 3: Verify results
EXPECT_GT(patch_result->symbols.size(), 0);
EXPECT_GT(patch_result->rom_size, 0);
// Step 4: Test symbol operations
auto entry_symbol = wrapper_->FindSymbol("yaze_test_entry");
EXPECT_TRUE(entry_symbol.has_value());
if (entry_symbol) {
auto symbols_at_address = wrapper_->GetSymbolsAtAddress(entry_symbol->address);
EXPECT_GT(symbols_at_address.size(), 0);
}
}
} // namespace integration
} // namespace test
} // namespace yaze

View File

@@ -0,0 +1,233 @@
#include "integration/dungeon_editor_test.h"
#include <cstring>
#include <vector>
#include "absl/strings/str_format.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/dungeon/room_object.h"
namespace yaze {
namespace test {
void DungeonEditorIntegrationTest::SetUp() {
ASSERT_TRUE(CreateMockRom().ok());
ASSERT_TRUE(LoadTestRoomData().ok());
dungeon_editor_ = std::make_unique<editor::DungeonEditor>(mock_rom_.get());
dungeon_editor_->Initialize();
}
void DungeonEditorIntegrationTest::TearDown() {
dungeon_editor_.reset();
mock_rom_.reset();
}
absl::Status DungeonEditorIntegrationTest::CreateMockRom() {
mock_rom_ = std::make_unique<MockRom>();
// Generate mock ROM data
std::vector<uint8_t> mock_data(kMockRomSize, 0x00);
// Set up basic ROM structure
// Header at 0x7FC0
std::string title = "ZELDA3 TEST ROM";
std::memcpy(&mock_data[0x7FC0], title.c_str(), std::min(title.length(), size_t(21)));
// Set ROM size and type
mock_data[0x7FD7] = 0x21; // 2MB ROM
mock_data[0x7FD8] = 0x00; // SRAM size
mock_data[0x7FD9] = 0x00; // Country code (NTSC)
mock_data[0x7FDA] = 0x00; // License code
mock_data[0x7FDB] = 0x00; // Version
// Set up room header pointers
mock_data[0xB5DD] = 0x00; // Room header pointer low
mock_data[0xB5DE] = 0x00; // Room header pointer mid
mock_data[0xB5DF] = 0x00; // Room header pointer high
// Set up object pointers
mock_data[0x874C] = 0x00; // Object pointer low
mock_data[0x874D] = 0x00; // Object pointer mid
mock_data[0x874E] = 0x00; // Object pointer high
static_cast<MockRom*>(mock_rom_.get())->SetMockData(mock_data);
return absl::OkStatus();
}
absl::Status DungeonEditorIntegrationTest::LoadTestRoomData() {
// Generate test room data
auto room_header = GenerateMockRoomHeader(kTestRoomId);
auto object_data = GenerateMockObjectData();
auto graphics_data = GenerateMockGraphicsData();
static_cast<MockRom*>(mock_rom_.get())->SetMockRoomData(kTestRoomId, room_header);
static_cast<MockRom*>(mock_rom_.get())->SetMockObjectData(kTestObjectId, object_data);
return absl::OkStatus();
}
absl::Status DungeonEditorIntegrationTest::TestObjectParsing() {
// Test object parsing without SNES emulation
auto room = zelda3::LoadRoomFromRom(mock_rom_.get(), kTestRoomId);
// Verify room was loaded correctly
EXPECT_NE(room.rom(), nullptr);
// Note: room_id_ is private, so we can't directly access it in tests
// Test object loading
room.LoadObjects();
EXPECT_FALSE(room.GetTileObjects().empty());
// Verify object properties
for (const auto& obj : room.GetTileObjects()) {
// Note: id_ is private, so we can't directly access it in tests
EXPECT_LE(obj.x_, 31); // Room width limit
EXPECT_LE(obj.y_, 31); // Room height limit
// Note: rom() method is not const, so we can't call it on const objects
}
return absl::OkStatus();
}
absl::Status DungeonEditorIntegrationTest::TestObjectRendering() {
// Test object rendering without SNES emulation
auto room = zelda3::LoadRoomFromRom(mock_rom_.get(), kTestRoomId);
room.LoadObjects();
// Test tile loading for objects
for (auto& obj : room.GetTileObjects()) {
obj.EnsureTilesLoaded();
EXPECT_FALSE(obj.tiles_.empty());
}
// Test room graphics rendering
room.LoadRoomGraphics();
room.RenderRoomGraphics();
return absl::OkStatus();
}
absl::Status DungeonEditorIntegrationTest::TestRoomGraphics() {
// Test room graphics loading and rendering
auto room = zelda3::LoadRoomFromRom(mock_rom_.get(), kTestRoomId);
// Test graphics loading
room.LoadRoomGraphics();
EXPECT_FALSE(room.blocks().empty());
// Test graphics rendering
room.RenderRoomGraphics();
return absl::OkStatus();
}
absl::Status DungeonEditorIntegrationTest::TestPaletteHandling() {
// Test palette loading and application
auto room = zelda3::LoadRoomFromRom(mock_rom_.get(), kTestRoomId);
// Verify palette is set
EXPECT_GE(room.palette, 0);
EXPECT_LE(room.palette, 0x47); // Max palette index
return absl::OkStatus();
}
std::vector<uint8_t> DungeonEditorIntegrationTest::GenerateMockRoomHeader(int room_id) {
std::vector<uint8_t> header(32, 0x00);
// Basic room properties
header[0] = 0x00; // Background type, collision, light
header[1] = 0x00; // Palette
header[2] = 0x01; // Blockset
header[3] = 0x01; // Spriteset
header[4] = 0x00; // Effect
header[5] = 0x00; // Tag1
header[6] = 0x00; // Tag2
header[7] = 0x00; // Staircase planes
header[8] = 0x00; // Staircase planes continued
header[9] = 0x00; // Hole warp
header[10] = 0x00; // Staircase rooms
header[11] = 0x00;
header[12] = 0x00;
header[13] = 0x00;
return header;
}
std::vector<uint8_t> DungeonEditorIntegrationTest::GenerateMockObjectData() {
std::vector<uint8_t> data;
// Add a simple wall object
data.push_back(0x08); // X position (2 tiles)
data.push_back(0x08); // Y position (2 tiles)
data.push_back(0x01); // Object ID (wall)
// Add layer separator
data.push_back(0xFF);
data.push_back(0xFF);
// Add door section
data.push_back(0xF0);
data.push_back(0xFF);
return data;
}
std::vector<uint8_t> DungeonEditorIntegrationTest::GenerateMockGraphicsData() {
std::vector<uint8_t> data(0x4000, 0x00);
// Generate basic tile data
for (size_t i = 0; i < data.size(); i += 2) {
data[i] = 0x00; // Tile low byte
data[i + 1] = 0x00; // Tile high byte
}
return data;
}
void MockRom::SetMockData(const std::vector<uint8_t>& data) {
mock_data_ = data;
}
void MockRom::SetMockRoomData(int room_id, const std::vector<uint8_t>& data) {
mock_room_data_[room_id] = data;
}
void MockRom::SetMockObjectData(int object_id, const std::vector<uint8_t>& data) {
mock_object_data_[object_id] = data;
}
bool MockRom::ValidateRoomData(int room_id) const {
return mock_room_data_.find(room_id) != mock_room_data_.end();
}
bool MockRom::ValidateObjectData(int object_id) const {
return mock_object_data_.find(object_id) != mock_object_data_.end();
}
// Test cases
TEST_F(DungeonEditorIntegrationTest, ObjectParsingTest) {
EXPECT_TRUE(TestObjectParsing().ok());
}
TEST_F(DungeonEditorIntegrationTest, ObjectRenderingTest) {
EXPECT_TRUE(TestObjectRendering().ok());
}
TEST_F(DungeonEditorIntegrationTest, RoomGraphicsTest) {
EXPECT_TRUE(TestRoomGraphics().ok());
}
TEST_F(DungeonEditorIntegrationTest, PaletteHandlingTest) {
EXPECT_TRUE(TestPaletteHandling().ok());
}
TEST_F(DungeonEditorIntegrationTest, MockRomValidation) {
EXPECT_TRUE(static_cast<MockRom*>(mock_rom_.get())->ValidateRoomData(kTestRoomId));
EXPECT_TRUE(static_cast<MockRom*>(mock_rom_.get())->ValidateObjectData(kTestObjectId));
}
} // namespace test
} // namespace yaze

View File

@@ -0,0 +1,75 @@
#ifndef YAZE_TEST_INTEGRATION_DUNGEON_EDITOR_TEST_H
#define YAZE_TEST_INTEGRATION_DUNGEON_EDITOR_TEST_H
#include <memory>
#include <string>
#include "absl/status/status.h"
#include "app/editor/dungeon/dungeon_editor.h"
#include "app/rom.h"
#include "gtest/gtest.h"
namespace yaze {
namespace test {
/**
* @brief Integration test framework for dungeon editor components
*
* This class provides a comprehensive testing framework for the dungeon editor,
* allowing modular testing of individual components and their interactions.
*/
class DungeonEditorIntegrationTest : public ::testing::Test {
protected:
void SetUp() override;
void TearDown() override;
// Test data setup
absl::Status CreateMockRom();
absl::Status LoadTestRoomData();
// Component testing helpers
absl::Status TestObjectParsing();
absl::Status TestObjectRendering();
absl::Status TestRoomGraphics();
absl::Status TestPaletteHandling();
// Mock data generators
std::vector<uint8_t> GenerateMockRoomHeader(int room_id);
std::vector<uint8_t> GenerateMockObjectData();
std::vector<uint8_t> GenerateMockGraphicsData();
std::unique_ptr<Rom> mock_rom_;
std::unique_ptr<editor::DungeonEditor> dungeon_editor_;
// Test constants
static constexpr int kTestRoomId = 0x01;
static constexpr int kTestObjectId = 0x10;
static constexpr size_t kMockRomSize = 0x200000; // 2MB mock ROM
};
/**
* @brief Mock ROM class for testing without real ROM files
*/
class MockRom : public Rom {
public:
MockRom() = default;
// Test data injection
void SetMockData(const std::vector<uint8_t>& data);
void SetMockRoomData(int room_id, const std::vector<uint8_t>& data);
void SetMockObjectData(int object_id, const std::vector<uint8_t>& data);
// Validation helpers
bool ValidateRoomData(int room_id) const;
bool ValidateObjectData(int object_id) const;
private:
std::vector<uint8_t> mock_data_;
std::map<int, std::vector<uint8_t>> mock_room_data_;
std::map<int, std::vector<uint8_t>> mock_object_data_;
};
} // namespace test
} // namespace yaze
#endif // YAZE_TEST_INTEGRATION_DUNGEON_EDITOR_TEST_H