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 hitstest_core_cpu_jumps.py: 14 hitstest_core_cpu_alu.py: 10 hitstest_core_cpu_interrupts.py: 8 hitstest_core_cpu_io.py: 5 hitstest_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_modetest_rom_is_writable_with_test_mode: fixturemmu_romwYES allows writingtest_rom_range_is_readonly: Entire ROM range (0x0000-0x7FFF) is read-onlytest_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:
test_add_immediate_basic: ADD A, d8 (10 + 2 = 12)test_sub_immediate_zero_flag: SUB d8 (10 - 10 = 0, Z=1)test_add_half_carry: ADD with half-carry (0x0F + 0x01 = 0x10, H=1)test_xor_a_optimization: XOR A (clean A to 0)test_inc_a: INC A (0x0F → 0x10, H=1)test_dec_a: DEC A (0x10 → 0x0F, H=1)test_add_full_carry: ADD with full carry (0xFF + 0x01 = 0x00, C=1)test_sub_a_b: SUB B (0x3E - 0x3E = 0x00, Z=1)test_sbc_a_b_with_borrow: SBC A, B with borrowtest_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_romwtests/test_mmu_rom_is_readonly_by_default.py: Security test (new)tests/test_core_cpu_alu.py: Migration of 10 tests to WRAMdocs/bitacora/entries/2026-01-02__0422__test-harness-policy-rom-writes-fixtures.html: This entrydocs/bitacora/index.html: Updated with Step 0422docs/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 in
CONTRIBUTING.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.