⚠️ Clean-Room / Educativo

Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.

Step 0422: Test Harness Policy - ROM Writes Fixtures + Security Test

Establecimiento de política oficial de test harness para MMU test mode

Objetivo

Establecer política oficial de test harness para MMU test mode, reducir deuda técnica de ROM-writes mediante fixtures centrales y crear test de seguridad para garantizar que ROM es read-only por defecto.

Contexto

Los Steps 0419-0421 introdujeron test_mode_allow_rom_writes para permitir tests unitarios que escriben en ROM (0x0000-0x7FFF). Sin embargo, esto generó 59 llamadas manuales a mmu.set_test_mode_allow_rom_writes(True) dispersas en 6 archivos de tests.

Esta deuda técnica requiere:

  • Centralización: Fixtures de pytest para evitar repetición
  • Restricción: ROM-writes solo en tests que realmente lo necesitan
  • Seguridad: Test que valide que ROM es read-only por defecto

Concepto de Hardware: Test Harness vs Comportamiento Real

En emuladores, el test harness es la infraestructura que permite tests unitarios sin depender de ROMs reales. El desafío es balancear:

  • Realismo: El MMU debe comportarse como hardware real (ROM read-only, MBC activo)
  • Testabilidad: Los tests unitarios necesitan escribir opcodes en memoria para validar CPU

Soluciones Comunes en Emuladores

Solución Ventajas Desventajas
Test Mode Flag (nuestra solución) ✅ Mantiene integración real CPU-MMU
✅ Permite tests atómicos
✅ No requiere ROMs externas
⚠️ Requiere disciplina (solo usar cuando sea necesario)
Test ROM Sintética ✅ Comportamiento 100% real ❌ Requiere ROM por test
❌ Difícil de mantener
Memory Mocking ✅ Flexibilidad total ❌ No valida integración MMU-CPU

Viboy Color usa Test Mode Flag porque mantiene la integración real CPU-MMU mientras permite tests atómicos sin ROMs externas.

Implementación

1. Auditoría Inicial (T1)

Identificar todos los usos de set_test_mode_allow_rom_writes(True):

grep -rn "set_test_mode_allow_rom_writes(True)" tests | wc -l
# Output: 59 hits en 6 archivos

Distribución:

  • test_core_cpu_loads.py: 18 hits
  • test_core_cpu_jumps.py: 14 hits
  • test_core_cpu_alu.py: 10 hits
  • test_core_cpu_interrupts.py: 8 hits
  • test_core_cpu_io.py: 5 hits
  • test_core_cpu_stack.py: 4 hits

2. Fixtures Centrales (T2)

Crear fixtures en tests/conftest.py:

@pytest.fixture
def mmu():
    """
    Fixture estándar para MMU sin ROM-writes habilitados.
    Uso: Tests que ejecutan desde WRAM (0xC000+) o no necesitan ROM.
    """
    try:
        from viboy_core import PyMMU
        return PyMMU()
    except ImportError:
        pytest.skip("Módulo viboy_core no compilado")

@pytest.fixture
def mmu_romw():
    """
    Fixture para MMU con ROM-writes habilitados (test mode).
    Uso: SOLO para tests que realmente necesitan escribir en ROM.
    ⚠️ ADVERTENCIA: Rompe comportamiento real del MMU (MBC).
    Preferir ejecutar desde WRAM cuando sea posible.
    """
    try:
        from viboy_core import PyMMU
        mmu = PyMMU()
        mmu.set_test_mode_allow_rom_writes(True)
        return mmu
    except ImportError:
        pytest.skip("Módulo viboy_core no compilado")

3. Test de Seguridad (T4)

Crear tests/test_mmu_rom_is_readonly_by_default.py con 4 validaciones:

  • test_rom_is_readonly_without_test_mode: ROM no es escribible sin test_mode
  • test_rom_is_writable_with_test_mode: fixture mmu_romw SÍ permite escrituras
  • test_rom_range_is_readonly: Todo el rango ROM (0x0000-0x7FFF) es read-only
  • test_wram_is_writable_without_test_mode: WRAM (0xC000+) es escribible sin test_mode

4. Migración Ejemplo (T3)

Migrar test_core_cpu_alu.py (10 tests) de ROM a WRAM:

# Antes (Step 0419):
def test_add_immediate_basic(self):
    mmu = PyMMU()
    mmu.set_test_mode_allow_rom_writes(True)  # Manual
    regs = PyRegisters()
    cpu = PyCPU(mmu, regs)
    regs.pc = 0x0100
    mmu.write(0x0100, 0x3E)  # LD A, d8
    mmu.write(0x0101, 0x0A)
    cpu.step()
    # ...

# Después (Step 0422):
def test_add_immediate_basic(self, mmu):  # Fixture sin ROM-writes
    regs = PyRegisters()
    cpu = PyCPU(mmu, regs)
    program = [0x3E, 0x0A, 0xC6, 0x02]  # LD A, 10; ADD A, 2
    load_program(mmu, regs, program)  # Carga en WRAM (0xC000)
    cpu.step()
    cpu.step()
    # ...

Tests migrados de test_core_cpu_alu.py:

  1. test_add_immediate_basic: ADD A, d8 (10 + 2 = 12)
  2. test_sub_immediate_zero_flag: SUB d8 (10 - 10 = 0, Z=1)
  3. test_add_half_carry: ADD con half-carry (0x0F + 0x01 = 0x10, H=1)
  4. test_xor_a_optimization: XOR A (limpia A a 0)
  5. test_inc_a: INC A (0x0F → 0x10, H=1)
  6. test_dec_a: DEC A (0x10 → 0x0F, H=1)
  7. test_add_full_carry: ADD con carry completo (0xFF + 0x01 = 0x00, C=1)
  8. test_sub_a_b: SUB B (0x3E - 0x3E = 0x00, Z=1)
  9. test_sbc_a_b_with_borrow: SBC A, B con borrow
  10. test_sbc_a_b_with_full_borrow: SBC A, B con underflow

Política Oficial de Test Harness

Cuándo usar mmu (fixture estándar)

  • ✅ Tests que ejecutan desde WRAM (0xC000-0xDFFF)
  • ✅ Tests de ALU, loads, jumps, stack que no dependen de ROM específico
  • Preferido por defecto (comportamiento real del MMU)

Cuándo usar mmu_romw (fixture con ROM-writes)

  • ⚠️ Tests que verifican vectores de interrupción (0x0040, 0x0048, etc.)
  • ⚠️ Tests que validan wrap-around de direcciones ROM
  • ⚠️ Tests legacy que aún no han migrado a WRAM
  • ⚠️ Uso excepcional (rompe comportamiento MBC)

Tests y Verificación

Comandos Ejecutados

# Build
python3 setup.py build_ext --inplace
# EXIT: 0 ✅

# Test Build
python3 test_build.py
# EXIT: 0 ✅

# Tests ALU + Seguridad
pytest tests/test_core_cpu_alu.py tests/test_mmu_rom_is_readonly_by_default.py -v
# 14 passed (10 ALU + 4 seguridad) ✅

# Tests Completos
pytest -q
# 118 passed, 10 failed (pre-existentes: joypad/MMU) ✅

Validación de Módulo Compilado C++

✅ Todos los tests ejecutan contra el módulo nativo viboy_core compilado desde C++.

Resultados

Auditoría Final

  • Hits ROM-writes ANTES: 59
  • Hits ROM-writes DESPUÉS: 49 (eliminados 10 de ALU)
  • Reducción: 16.9%

Tests que requieren ROM-writes (justificados)

  • test_core_cpu_loads.py (18): Pueden migrar a WRAM (Step futuro)
  • test_core_cpu_jumps.py (14): Pueden migrar a WRAM (Step futuro)
  • test_core_cpu_interrupts.py (8): Algunos requieren vectores ROM (revisar)
  • test_core_cpu_io.py (5): Pueden migrar a WRAM (Step futuro)
  • test_core_cpu_stack.py (4): Pueden migrar a WRAM (Step futuro)

Archivos Modificados

  • tests/conftest.py: Fixtures mmu y mmu_romw
  • tests/test_mmu_rom_is_readonly_by_default.py: Test de seguridad (nuevo)
  • tests/test_core_cpu_alu.py: Migración de 10 tests a WRAM
  • docs/bitacora/entries/2026-01-02__0422__test-harness-policy-rom-writes-fixtures.html: Esta entrada
  • docs/bitacora/index.html: Actualizado con Step 0422
  • docs/informe_fase_2/parte_01_steps_0412_0450.md: Entrada en informe dividido

Próximos Steps

  • Step 0423: Migración masiva de tests CPU a WRAM (49 tests restantes)
  • Step 0424: Marker pytest @pytest.mark.rom_writes para tests excepcionales
  • Step 0425: Documentación de política en CONTRIBUTING.md

Conclusión

Política de test harness establecida con fixtures centrales, test de seguridad y ejemplo de migración. Reducción de 16.9% en ROM-writes (59 → 49). Base sólida para migración masiva en Steps futuros.