From 2ce82e7d87e33fb94c6217860430e82716c353e6 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Mon, 9 Feb 2026 21:49:41 -0500 Subject: [PATCH] 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. --- src/mudlib/zmachine/zstackmanager.py | 1 + tests/test_zmachine_opcodes.py | 121 +++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/src/mudlib/zmachine/zstackmanager.py b/src/mudlib/zmachine/zstackmanager.py index 8730508..5e7577e 100644 --- a/src/mudlib/zmachine/zstackmanager.py +++ b/src/mudlib/zmachine/zstackmanager.py @@ -100,6 +100,7 @@ class ZStackBottom: stack can treat all frames uniformly without special-case checks for the bottom sentinel. """ + def __init__(self): self.program_counter = 0 # used as a cache only self.stack = [] diff --git a/tests/test_zmachine_opcodes.py b/tests/test_zmachine_opcodes.py index 524c669..db15c19 100644 --- a/tests/test_zmachine_opcodes.py +++ b/tests/test_zmachine_opcodes.py @@ -38,6 +38,11 @@ class MockMemory: def write_global(self, 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: """Mock stack manager for testing.""" @@ -291,6 +296,90 @@ class ZMachineOpcodeTests(TestCase): # Should just not raise an exception 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: """Mock object parser for testing CPU opcodes.""" @@ -319,6 +408,9 @@ class MockObjectParser: def get_sibling(self, objnum): return self.siblings.get(objnum, 0) + def get_child(self, objnum): + return self.children.get(objnum, 0) + def remove_object(self, objnum): # Simple implementation - just clear parent self.parents[objnum] = 0 @@ -519,6 +611,35 @@ class ZMachineObjectOpcodeTests(TestCase): # Should store 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): """Test suite for complex Z-machine opcodes (input, save/restore, restart)."""