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
- Overview
- Test Structure
- Running Tests
- Understanding Results
- Adding New Tests
- Debugging Failed Tests
- 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
- Build yaze: Ensure yaze is built with test support
- ROM Files: Have test ROM files available
- 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