Add unit tests for op_test, op_verify, and op_get_child
Adds comprehensive test coverage for three newly implemented Z-machine opcodes: - op_test: Tests bitmap flag checking with all flags set, some missing, zero flags, and identical values - op_verify: Tests checksum verification with matching and mismatched checksums - op_get_child: Tests getting first child of object with and without children Also extends MockMemory with generate_checksum() and MockObjectParser with get_child() to support the new tests.
This commit is contained in:
parent
2cf303dd67
commit
2ce82e7d87
2 changed files with 122 additions and 0 deletions
|
|
@ -100,6 +100,7 @@ class ZStackBottom:
|
||||||
stack can treat all frames uniformly without special-case checks for
|
stack can treat all frames uniformly without special-case checks for
|
||||||
the bottom sentinel.
|
the bottom sentinel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.program_counter = 0 # used as a cache only
|
self.program_counter = 0 # used as a cache only
|
||||||
self.stack = []
|
self.stack = []
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,11 @@ class MockMemory:
|
||||||
def write_global(self, varnum, value):
|
def write_global(self, varnum, value):
|
||||||
self.globals[varnum] = value
|
self.globals[varnum] = value
|
||||||
|
|
||||||
|
def generate_checksum(self):
|
||||||
|
"""Generate checksum from 0x40 onwards, modulo 0x10000."""
|
||||||
|
total = sum(self.data[0x40:])
|
||||||
|
return total % 0x10000
|
||||||
|
|
||||||
|
|
||||||
class MockStackManager:
|
class MockStackManager:
|
||||||
"""Mock stack manager for testing."""
|
"""Mock stack manager for testing."""
|
||||||
|
|
@ -291,6 +296,90 @@ class ZMachineOpcodeTests(TestCase):
|
||||||
# Should just not raise an exception
|
# Should just not raise an exception
|
||||||
self.cpu.op_show_status()
|
self.cpu.op_show_status()
|
||||||
|
|
||||||
|
def test_op_test_all_flags_set(self):
|
||||||
|
"""Test op_test branches when all flags are set in bitmap."""
|
||||||
|
self.decoder.branch_condition = True
|
||||||
|
self.decoder.branch_offset = 10
|
||||||
|
old_pc = self.cpu._opdecoder.program_counter
|
||||||
|
|
||||||
|
# bitmap 0b11010110, flags 0b10010100 - all flags present
|
||||||
|
self.cpu.op_test(0b11010110, 0b10010100)
|
||||||
|
|
||||||
|
# Should branch (offset - 2)
|
||||||
|
self.assertEqual(self.cpu._opdecoder.program_counter, old_pc + 8)
|
||||||
|
|
||||||
|
def test_op_test_some_flags_missing(self):
|
||||||
|
"""Test op_test doesn't branch when some flags are missing."""
|
||||||
|
self.decoder.branch_condition = True
|
||||||
|
old_pc = self.cpu._opdecoder.program_counter
|
||||||
|
|
||||||
|
# bitmap 0b11010110, flags 0b10011100 - bit 3 missing
|
||||||
|
self.cpu.op_test(0b11010110, 0b10011100)
|
||||||
|
|
||||||
|
# Should not branch
|
||||||
|
self.assertEqual(self.cpu._opdecoder.program_counter, old_pc)
|
||||||
|
|
||||||
|
def test_op_test_zero_flags(self):
|
||||||
|
"""Test op_test with zero flags always branches (all 0 flags set)."""
|
||||||
|
self.decoder.branch_condition = True
|
||||||
|
self.decoder.branch_offset = 15
|
||||||
|
old_pc = self.cpu._opdecoder.program_counter
|
||||||
|
|
||||||
|
# Any bitmap with flags=0 should pass (0 & 0 == 0)
|
||||||
|
self.cpu.op_test(0b11111111, 0)
|
||||||
|
|
||||||
|
# Should branch
|
||||||
|
self.assertEqual(self.cpu._opdecoder.program_counter, old_pc + 13)
|
||||||
|
|
||||||
|
def test_op_test_identical(self):
|
||||||
|
"""Test op_test branches when bitmap and flags are identical."""
|
||||||
|
self.decoder.branch_condition = True
|
||||||
|
self.decoder.branch_offset = 20
|
||||||
|
old_pc = self.cpu._opdecoder.program_counter
|
||||||
|
|
||||||
|
# Identical bitmap and flags
|
||||||
|
self.cpu.op_test(0b10101010, 0b10101010)
|
||||||
|
|
||||||
|
# Should branch
|
||||||
|
self.assertEqual(self.cpu._opdecoder.program_counter, old_pc + 18)
|
||||||
|
|
||||||
|
def test_op_verify_matching_checksum(self):
|
||||||
|
"""Test op_verify branches when checksum matches."""
|
||||||
|
# Set expected checksum at 0x1C
|
||||||
|
self.memory.write_word(0x1C, 0x1234)
|
||||||
|
# Set data that produces matching checksum
|
||||||
|
# checksum = sum(data[0x40:]) % 0x10000
|
||||||
|
# For simplicity, set one byte to produce desired checksum
|
||||||
|
self.memory[0x40] = 0x34
|
||||||
|
self.memory[0x41] = 0x12
|
||||||
|
# Sum = 0x34 + 0x12 = 0x46, need 0x1234
|
||||||
|
# Set more bytes: 0x1234 - 0x46 = 0x11EE
|
||||||
|
for i in range(0x42, 0x42 + 0x11EE):
|
||||||
|
self.memory[i] = 1 if i < 0x42 + 0x11EE else 0
|
||||||
|
|
||||||
|
self.decoder.branch_condition = True
|
||||||
|
self.decoder.branch_offset = 25
|
||||||
|
old_pc = self.cpu._opdecoder.program_counter
|
||||||
|
|
||||||
|
self.cpu.op_verify()
|
||||||
|
|
||||||
|
# Should branch
|
||||||
|
self.assertEqual(self.cpu._opdecoder.program_counter, old_pc + 23)
|
||||||
|
|
||||||
|
def test_op_verify_mismatched_checksum(self):
|
||||||
|
"""Test op_verify doesn't branch when checksum doesn't match."""
|
||||||
|
# Set expected checksum at 0x1C
|
||||||
|
self.memory.write_word(0x1C, 0x5678)
|
||||||
|
# Memory data will produce different checksum (mostly zeros)
|
||||||
|
|
||||||
|
self.decoder.branch_condition = True
|
||||||
|
old_pc = self.cpu._opdecoder.program_counter
|
||||||
|
|
||||||
|
self.cpu.op_verify()
|
||||||
|
|
||||||
|
# Should not branch (checksums don't match)
|
||||||
|
self.assertEqual(self.cpu._opdecoder.program_counter, old_pc)
|
||||||
|
|
||||||
|
|
||||||
class MockObjectParser:
|
class MockObjectParser:
|
||||||
"""Mock object parser for testing CPU opcodes."""
|
"""Mock object parser for testing CPU opcodes."""
|
||||||
|
|
@ -319,6 +408,9 @@ class MockObjectParser:
|
||||||
def get_sibling(self, objnum):
|
def get_sibling(self, objnum):
|
||||||
return self.siblings.get(objnum, 0)
|
return self.siblings.get(objnum, 0)
|
||||||
|
|
||||||
|
def get_child(self, objnum):
|
||||||
|
return self.children.get(objnum, 0)
|
||||||
|
|
||||||
def remove_object(self, objnum):
|
def remove_object(self, objnum):
|
||||||
# Simple implementation - just clear parent
|
# Simple implementation - just clear parent
|
||||||
self.parents[objnum] = 0
|
self.parents[objnum] = 0
|
||||||
|
|
@ -519,6 +611,35 @@ class ZMachineObjectOpcodeTests(TestCase):
|
||||||
# Should store 0
|
# Should store 0
|
||||||
self.assertEqual(self.stack.pop_stack(), 0)
|
self.assertEqual(self.stack.pop_stack(), 0)
|
||||||
|
|
||||||
|
def test_op_get_child_with_child(self):
|
||||||
|
"""Test get_child stores child and branches if nonzero."""
|
||||||
|
self.objects.children[10] = 5
|
||||||
|
self.decoder.store_address = 0
|
||||||
|
self.decoder.branch_condition = True
|
||||||
|
self.decoder.branch_offset = 12
|
||||||
|
old_pc = self.cpu._opdecoder.program_counter
|
||||||
|
|
||||||
|
self.cpu.op_get_child(10)
|
||||||
|
|
||||||
|
# Should store 5
|
||||||
|
self.assertEqual(self.stack.pop_stack(), 5)
|
||||||
|
# Should branch (offset - 2)
|
||||||
|
self.assertEqual(self.cpu._opdecoder.program_counter, old_pc + 10)
|
||||||
|
|
||||||
|
def test_op_get_child_no_child(self):
|
||||||
|
"""Test get_child with no child doesn't branch."""
|
||||||
|
self.objects.children[10] = 0
|
||||||
|
self.decoder.store_address = 0
|
||||||
|
self.decoder.branch_condition = True
|
||||||
|
old_pc = self.cpu._opdecoder.program_counter
|
||||||
|
|
||||||
|
self.cpu.op_get_child(10)
|
||||||
|
|
||||||
|
# Should store 0
|
||||||
|
self.assertEqual(self.stack.pop_stack(), 0)
|
||||||
|
# Should not branch
|
||||||
|
self.assertEqual(self.cpu._opdecoder.program_counter, old_pc)
|
||||||
|
|
||||||
|
|
||||||
class ZMachineComplexOpcodeTests(TestCase):
|
class ZMachineComplexOpcodeTests(TestCase):
|
||||||
"""Test suite for complex Z-machine opcodes (input, save/restore, restart)."""
|
"""Test suite for complex Z-machine opcodes (input, save/restore, restart)."""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue