⚠️ Clean-Room / Educational

This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.

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

Establishment of official test harness policy for MMU test mode

Aim

Establish official test harness policy for MMU test mode, reduce technical debt of ROM-writes through central fixtures and create security tests to guarantee that ROM is read-only by default.

Context

Steps 0419-0421 introducedtest_mode_allow_rom_writesto allow unit tests that write to ROM (0x0000-0x7FFF). However, this generated59 manual callstommu.set_test_mode_allow_rom_writes(True)dispersed in 6 test files.

This technical debt requires:

  • Centralization: pytest fixtures to avoid repetition
  • Restriction: ROM-writes only in tests that really need it
  • Security: Test that validates that ROM is read-only by default

Hardware Concept: Test Harness vs Real Behavior

In emulators, thetest harnessIt is the infrastructure that allows unit testing without depending on real ROMs. The challenge is to balance:

  • Realism: The MMU must behave like real hardware (ROM read-only, MBC active)
  • Testability: Unit tests need to write opcodes in memory to validate CPU

Common Solutions in Emulators

Solution Advantages Disadvantages
Test Mode Flag(our solution) ✅ Maintains real CPU-MMU integration
✅ Allows atomic tests
✅ Does not require external ROMs
⚠️ Requires discipline (only use when necessary)
Synthetic ROM Test ✅ 100% real behavior ❌ Requires ROM per test
❌ Difficult to maintain
Memory Mocking ✅ Total flexibility ❌ Does not validate MMU-CPU integration

Viboy Color uses Test Mode Flagbecause it maintains true CPU-MMU integration while allowing atomic tests without external ROMs.

Implementation

1. Initial Audit (T1)

Identify all uses ofset_test_mode_allow_rom_writes(True):

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

Distribution:

  • 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. Central Fixtures (T2)

Create fixtures intests/conftest.py:

@pytest.fixture
def mmu():
    """
    Standard fixture for MMU without ROM-writes enabled.
    Use: Tests that run from WRAM (0xC000+) or do not need ROM.
    """
    try:
        from viboy_core import PyMMU
        return PyMMU()
    exceptImportError:
        pytest.skip("viboy_core module not compiled")

@pytest.fixture
def mmu_romw():
    """
    Fixture for MMU with ROM-writes enabled (test mode).
    Use: ONLY for tests that really need to write to ROM.
    ⚠️ WARNING: Breaks real behavior of the MMU (MBC).
    Prefer to run from WRAM when possible.
    """
    try:
        from viboy_core import PyMMU
        mmu = PyMMU()
        mmu.set_test_mode_allow_rom_writes(True)
        return mmu
    exceptImportError:
        pytest.skip("viboy_core module not compiled")

3. Security Test (T4)

Createtests/test_mmu_rom_is_readonly_by_default.pywith 4 validations:

  • test_rom_is_readonly_without_test_mode: ROM is not writable without test_mode
  • test_rom_is_writable_with_test_mode: fixturemmu_romwYES allows writing
  • test_rom_range_is_readonly: Entire ROM range (0x0000-0x7FFF) is read-only
  • test_wram_is_writable_without_test_mode: WRAM (0xC000+) is writable without test_mode

4. Migration Example (T3)

Migratetest_core_cpu_alu.py(10 tests) from ROM to WRAM:

# Before (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()
    #...

# After (Step 0422):
def test_add_immediate_basic(self, mmu): # Fixture without ROM-writes
    regs = PyRegisters()
    cpu = PyCPU(mmu, regs)
    program = [0x3E, 0x0A, 0xC6, 0x02] # LD A, 10; ADD A, 2
    load_program(mmu, regs, program) # Load into WRAM (0xC000)
    cpu.step()
    cpu.step()
    #...

Tests migrated from 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 with half-carry (0x0F + 0x01 = 0x10, H=1)
  4. test_xor_a_optimization: XOR A (clean A to 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 with full carry (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 with borrow
  10. test_sbc_a_b_with_full_borrow: SBC A, B with underflow

Official Test Harness Policy

When to usemmu(standard fixture)

  • ✅ Tests that run from WRAM (0xC000-0xDFFF)
  • ✅ ALU tests, loads, jumps, stack that do not depend on specific ROM
  • Default preferred(actual MMU behavior)

When to usemmu_romw(fixture with ROM-writes)

  • ⚠️ Tests that verify interruption vectors (0x0040, 0x0048, etc.)
  • ⚠️ Tests that validate wrap-around of ROM addresses
  • ⚠️ Legacy tests that have not yet migrated to WRAM
  • ⚠️ Exceptional use(breaks MBC behavior)

Tests and Verification

Commands Executed

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

#TestBuild
python3 test_build.py
# EXIT: 0 ✅

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

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

C++ Compiled Module Validation

✅ All tests run against the native moduleviboy_corecompiled from C++.

Results

Final Audit

  • Hits ROM-writes BEFORE: 59
  • Hits ROM-writes AFTER: 49 (10 removed from ALU)
  • Reduction: 16.9%

Tests that require ROM-writes (justified)

  • test_core_cpu_loads.py(18): They can migrate to WRAM (Future Step)
  • test_core_cpu_jumps.py(14): They can migrate to WRAM (Future Step)
  • test_core_cpu_interrupts.py(8): Some require ROM vectors (review)
  • test_core_cpu_io.py(5): They can migrate to WRAM (Future Step)
  • test_core_cpu_stack.py(4): They can migrate to WRAM (Future Step)

Modified Files

  • tests/conftest.py: Fixturesmmuandmmu_romw
  • tests/test_mmu_rom_is_readonly_by_default.py: Security test (new)
  • tests/test_core_cpu_alu.py: Migration of 10 tests to WRAM
  • docs/bitacora/entries/2026-01-02__0422__test-harness-policy-rom-writes-fixtures.html: This entry
  • docs/bitacora/index.html: Updated with Step 0422
  • docs/report_phase_2/part_01_steps_0412_0450.md: Split report entry

Next Steps

  • Step 0423: Mass migration of CPU tests to WRAM (49 tests remaining)
  • Step 0424: Marker pytest@pytest.mark.rom_writesfor exceptional tests
  • Step 0425: Policy documentation inCONTRIBUTING.md

Conclusion

Established test harness policywith central fixtures, security test and migration example. 16.9% reduction in ROM-writes (59 → 49). Solid foundation for mass migration in future Steps.