Files
yaze/docs/integration_test_guide.md
2025-09-25 00:02:38 -04:00

12 KiB

Integration Test Guide

This guide explains how to use yaze's integration test suite to validate ROM loading, overworld functionality, and ensure compatibility between vanilla and ZSCustomOverworld ROMs.

Table of Contents

  1. Overview
  2. Test Structure
  3. Running Tests
  4. Understanding Results
  5. Adding New Tests
  6. Debugging Failed Tests
  7. Best Practices

Overview

The integration test suite validates that yaze correctly loads and processes ROM data by comparing against known values from vanilla ROMs and ensuring that ZSCustomOverworld features work as expected.

Key Components

  • Vanilla ROM Tests: Validate loading of original Zelda 3 ROMs
  • ZSCustomOverworld Tests: Validate v2/v3 feature compatibility
  • Sprite Position Tests: Verify sprite coordinate systems
  • Overworld Map Tests: Test map loading and property access

Test Structure

Test Files

test/zelda3/
├── overworld_integration_test.cc     # Integration tests with real ROMs
├── comprehensive_integration_test.cc # Full ROM validation
├── dungeon_integration_test.cc       # Dungeon system integration tests
├── dungeon_editor_system_integration_test.cc # Dungeon editor system tests
└── extract_vanilla_values.cc         # Utility to extract test values

test/integration/
├── editor_integration_test.h/cc      # Base editor integration test framework
├── dungeon_editor_test.h             # Dungeon editor integration tests
└── test_editor.h/cc                  # Test editor framework

Test Categories

1. Integration Tests (overworld_integration_test.cc)

Test with real ROM files and validate overworld functionality:

TEST_F(OverworldIntegrationTest, VanillaROMLoading) {
  // Load vanilla ROM
  auto rom = LoadVanillaROM();
  Overworld overworld(rom.get());
  
  // Test specific map properties
  auto* map = overworld.overworld_map(0);
  EXPECT_EQ(map->area_graphics(), expected_graphics);
  EXPECT_EQ(map->area_palette(), expected_palette);
}

2. Comprehensive Integration Tests (comprehensive_integration_test.cc)

Full ROM validation with multiple ROM types:

TEST_F(ComprehensiveIntegrationTest, VanillaVsV3Comparison) {
  // Compare vanilla and v3 ROM features
  EXPECT_NE(vanilla_overworld_, nullptr);
  EXPECT_NE(v3_overworld_, nullptr);
  
  // Test feature differences
  TestFeatureDifferences();
}

3. Dungeon Integration Tests (dungeon_integration_test.cc)

Test dungeon system functionality:

TEST_F(DungeonIntegrationTest, DungeonRoomLoading) {
  // Test loading dungeon rooms
  for (int i = 0; i < kNumTestRooms; i++) {
    // Test room loading and properties
  }
}

4. Dungeon Editor System Tests (dungeon_editor_system_integration_test.cc)

Test the complete dungeon editor system:

TEST_F(DungeonEditorSystemIntegrationTest, BasicInitialization) {
  EXPECT_NE(dungeon_editor_system_, nullptr);
  EXPECT_EQ(dungeon_editor_system_->GetROM(), rom_.get());
  EXPECT_FALSE(dungeon_editor_system_->IsDirty());
}

Running Tests

Prerequisites

  1. Build yaze: Ensure yaze is built with test support
  2. ROM Files: Have test ROM files available
  3. Test Data: Generated test values from vanilla ROMs

Basic Test Execution

# Run all tests
cd /Users/scawful/Code/yaze/build
./bin/yaze_test

# Run specific test suite
./bin/yaze_test --gtest_filter="OverworldTest.*"

# Run specific test
./bin/yaze_test --gtest_filter="SpritePositionTest.SpriteCoordinateSystem"

# Run with verbose output
./bin/yaze_test --gtest_filter="OverworldIntegrationTest.*" --gtest_output=xml:test_results.xml

Test Filtering

Use Google Test filters to run specific tests:

# Run only overworld tests
./bin/yaze_test --gtest_filter="Overworld*"

# Run only integration tests
./bin/yaze_test --gtest_filter="*Integration*"

# Run tests matching pattern
./bin/yaze_test --gtest_filter="*Sprite*"

# Exclude specific tests
./bin/yaze_test --gtest_filter="OverworldTest.*:-OverworldTest.OverworldMapDestroy"

Parallel Execution

# Run tests in parallel (faster)
./bin/yaze_test --gtest_parallel=4

# Run with specific number of workers
./bin/yaze_test --gtest_workers=8

Understanding Results

Test Output Format

[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from OverworldTest
[ RUN      ] OverworldTest.OverworldMapInitialization
[       OK ] OverworldTest.OverworldMapInitialization (2 ms)
[ RUN      ] OverworldTest.AreaSizeEnumValues
[       OK ] OverworldTest.AreaSizeEnumValues (1 ms)
[ RUN      ] OverworldTest.OverworldMapDestroy
[       OK ] OverworldTest.OverworldMapDestroy (1 ms)
[----------] 3 tests from OverworldTest (4 ms total)
[----------] Global test environment tear-down.
[==========] 3 tests from 1 test suite ran. (4 ms total)
[  PASSED  ] 3 tests.

Success Indicators

  • [ OK ]: Test passed
  • [ PASSED ]: All tests in suite passed
  • No error messages or stack traces

Failure Indicators

  • [ FAILED ]: Test failed
  • Error messages with expected vs actual values
  • Stack traces showing failure location

Example Failure Output

[ RUN      ] OverworldTest.OverworldMapInitialization
test/zelda3/overworld_test.cc:45: Failure
Expected equality of these values:
  map.area_graphics()
    Which is: 5
  0
[  FAILED  ] OverworldTest.OverworldMapInitialization (2 ms)

Adding New Tests

1. Unit Test Example

// Add to overworld_test.cc
TEST_F(OverworldTest, VanillaOverlayLoading) {
  OverworldMap map(0, rom_.get());
  
  // Test vanilla overlay loading
  RETURN_IF_ERROR(map.LoadVanillaOverlay());
  
  // Verify overlay data
  EXPECT_TRUE(map.has_vanilla_overlay());
  EXPECT_GT(map.vanilla_overlay_data().size(), 0);
}

2. Integration Test Example

// Add to overworld_integration_test.cc
TEST_F(OverworldIntegrationTest, ZSCustomOverworldV3Features) {
  // Load ZSCustomOverworld v3 ROM
  auto rom = LoadZSCustomOverworldV3ROM();
  Overworld overworld(rom.get());
  
  // Test v3 features
  auto* map = overworld.overworld_map(0);
  EXPECT_GT(map->subscreen_overlay(), 0);
  EXPECT_GT(map->animated_gfx(), 0);
  
  // Test custom tile graphics
  for (int i = 0; i < 8; i++) {
    EXPECT_GE(map->custom_tileset(i), 0);
  }
}

3. Performance Test Example

TEST_F(OverworldPerformanceTest, LargeMapLoading) {
  auto start = std::chrono::high_resolution_clock::now();
  
  // Load large number of maps
  for (int i = 0; i < 100; i++) {
    OverworldMap map(i % 160, rom_.get());
  }
  
  auto end = std::chrono::high_resolution_clock::now();
  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
  
  // Ensure loading is reasonably fast
  EXPECT_LT(duration.count(), 1000); // Less than 1 second
}

Debugging Failed Tests

1. Enable Debug Output

// Add debug output to tests
TEST_F(OverworldTest, DebugTest) {
  OverworldMap map(0, rom_.get());
  
  // Print debug information
  std::cout << "Map index: " << map.index() << std::endl;
  std::cout << "Area graphics: " << static_cast<int>(map.area_graphics()) << std::endl;
  std::cout << "Area palette: " << static_cast<int>(map.area_palette()) << std::endl;
  
  // Your assertions here
}

2. Use GDB for Debugging

# Run test with GDB
gdb --args ./bin/yaze_test --gtest_filter="OverworldTest.DebugTest"

# Set breakpoints
(gdb) break overworld_test.cc:45
(gdb) run

# Inspect variables
(gdb) print map.area_graphics()
(gdb) print map.area_palette()

3. Memory Debugging

# Run with Valgrind (Linux)
valgrind --leak-check=full ./bin/yaze_test --gtest_filter="OverworldTest.*"

# Run with AddressSanitizer
export ASAN_OPTIONS=detect_leaks=1
./bin/yaze_test --gtest_filter="OverworldTest.*"

4. Common Debugging Scenarios

ROM Loading Issues

// Check ROM version detection
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
std::cout << "ASM Version: 0x" << std::hex << static_cast<int>(asm_version) << std::endl;

// Verify ROM size
std::cout << "ROM Size: " << rom_->size() << " bytes" << std::endl;

Map Property Issues

// Check map loading
auto* map = overworld.overworld_map(current_map_);
std::cout << "Map " << current_map_ << " properties:" << std::endl;
std::cout << "  Area Graphics: 0x" << std::hex << static_cast<int>(map->area_graphics()) << std::endl;
std::cout << "  Area Palette: 0x" << std::hex << static_cast<int>(map->area_palette()) << std::endl;
std::cout << "  Main Palette: 0x" << std::hex << static_cast<int>(map->main_palette()) << std::endl;

Sprite Issues

// Check sprite loading
for (int game_state = 0; game_state < 3; game_state++) {
  auto& sprites = *overworld.mutable_sprites(game_state);
  std::cout << "Game State " << game_state << ": " << sprites.size() << " sprites" << std::endl;
  
  for (size_t i = 0; i < std::min(sprites.size(), size_t(5)); i++) {
    auto& sprite = sprites[i];
    std::cout << "  Sprite " << i << ": Map=" << sprite.map_id() 
              << ", X=" << sprite.x_ << ", Y=" << sprite.y_ << std::endl;
  }
}

Best Practices

1. Test Organization

  • Group related tests: Use descriptive test suite names
  • One concept per test: Each test should verify one specific behavior
  • Descriptive names: Test names should clearly indicate what they're testing
// Good
TEST_F(OverworldTest, VanillaOverlayLoadingForMap00) {
  // Test specific map's overlay loading
}

// Bad
TEST_F(OverworldTest, Test1) {
  // Unclear what this tests
}

2. Test Data Management

class OverworldTest : public ::testing::Test {
protected:
  void SetUp() override {
    // Initialize test data once
    rom_ = std::make_unique<Rom>();
    // ... setup code
  }
  
  void TearDown() override {
    // Clean up after each test
    rom_.reset();
  }
  
  std::unique_ptr<Rom> rom_;
};

3. Error Handling in Tests

TEST_F(OverworldTest, ErrorHandling) {
  // Test error conditions
  OverworldMap map(999, rom_.get()); // Invalid map index
  
  // Verify error handling
  EXPECT_FALSE(map.is_initialized());
}

4. Performance Considerations

// Use fixtures for expensive setup
class ExpensiveTest : public ::testing::Test {
protected:
  void SetUp() override {
    // Expensive setup here
    LoadLargeROM();
    ProcessAllMaps();
  }
};

// Run expensive tests separately
TEST_F(ExpensiveTest, FullOverworldProcessing) {
  // Test that requires expensive setup
}

5. Continuous Integration

Add tests to CI pipeline:

# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Build and Test
        run: |
          cd build
          make -j4 yaze_test
          ./bin/yaze_test --gtest_output=xml:test_results.xml
      - name: Upload Test Results
        uses: actions/upload-artifact@v2
        with:
          name: test-results
          path: build/test_results.xml

Test Results Interpretation

Viewing Results

Test results are typically displayed in the terminal, but you can also generate XML reports:

# Generate XML report
./bin/yaze_test --gtest_output=xml:test_results.xml

# View in browser (if you have a test result viewer)
open test_results.xml

Key Metrics

  • Test Count: Number of tests run
  • Pass Rate: Percentage of tests that passed
  • Execution Time: How long tests took to run
  • Memory Usage: Peak memory consumption during tests

Performance Benchmarks

Track performance over time:

# Run with timing
time ./bin/yaze_test --gtest_filter="OverworldPerformanceTest.*"

# Profile with gprof
gprof ./bin/yaze_test gmon.out > profile.txt

Conclusion

The integration test suite is essential for maintaining yaze's reliability and ensuring compatibility with different ROM types. By following this guide, you can effectively run tests, debug issues, and add new test cases to improve the overall quality of the codebase.

Remember to:

  • Run tests regularly during development
  • Add tests for new features
  • Debug failures promptly
  • Keep tests fast and focused
  • Use appropriate test data and fixtures