diff --git a/tests/test_zmachine_opcodes.py b/tests/test_zmachine_opcodes.py index 1c6bf76..df5a003 100644 --- a/tests/test_zmachine_opcodes.py +++ b/tests/test_zmachine_opcodes.py @@ -834,6 +834,153 @@ class QuetzalWriterTests(TestCase): self.assertEqual(chunk_data[11], 0x34) self.assertEqual(chunk_data[12], 0x56) + def test_cmem_all_unchanged(self): + """Test CMem chunk with no changes (all zeros after XOR).""" + from mudlib.zmachine.quetzal import QuetzalWriter + from mudlib.zmachine.zmemory import ZMemory + + # Create a minimal z3 story file + story = bytearray(1024) + story[0] = 3 # version 3 + story[0x0E] = 0x04 # static memory starts at 0x0400 + story[0x0F] = 0x00 + + pristine = ZMemory(bytes(story)) + current = ZMemory(bytes(story)) + + zmachine = Mock() + zmachine._pristine_mem = pristine + zmachine._mem = current + + writer = QuetzalWriter(zmachine) + result = writer._generate_cmem_chunk() + + # All identical means no output (trailing zeros omitted) + self.assertIsInstance(result, bytes) + self.assertEqual(len(result), 0) + + def test_cmem_single_byte_change(self): + """Test CMem chunk with one byte changed.""" + from mudlib.zmachine.quetzal import QuetzalWriter + from mudlib.zmachine.zmemory import ZMemory + + story = bytearray(1024) + story[0] = 3 + story[0x0E] = 0x04 + story[0x0F] = 0x00 + + pristine = ZMemory(bytes(story)) + current = ZMemory(bytes(story)) + current[0x0100] = 0x42 + + zmachine = Mock() + zmachine._pristine_mem = pristine + zmachine._mem = current + + writer = QuetzalWriter(zmachine) + result = writer._generate_cmem_chunk() + + self.assertIsInstance(result, bytes) + self.assertIn(0x42, result) + + def test_cmem_multiple_scattered_changes(self): + """Test CMem chunk with multiple changes across memory.""" + from mudlib.zmachine.quetzal import QuetzalWriter + from mudlib.zmachine.zmemory import ZMemory + + story = bytearray(1024) + story[0] = 3 + story[0x0E] = 0x04 + story[0x0F] = 0x00 + + pristine = ZMemory(bytes(story)) + current = ZMemory(bytes(story)) + current[0x0010] = 0xAA + current[0x0100] = 0xBB + current[0x0200] = 0xCC + + zmachine = Mock() + zmachine._pristine_mem = pristine + zmachine._mem = current + + writer = QuetzalWriter(zmachine) + result = writer._generate_cmem_chunk() + + self.assertIsInstance(result, bytes) + self.assertIn(0xAA, result) + self.assertIn(0xBB, result) + self.assertIn(0xCC, result) + self.assertLess(len(result), 1024) + + def test_cmem_roundtrip_with_parser(self): + """Test that CMem output can be decoded by QuetzalParser._parse_cmem().""" + from mudlib.zmachine.quetzal import QuetzalParser, QuetzalWriter + from mudlib.zmachine.zmemory import ZMemory + + story = bytearray(1024) + story[0] = 3 + story[0x0E] = 0x04 + story[0x0F] = 0x00 + + pristine = ZMemory(bytes(story)) + current = ZMemory(bytes(story)) + current[0x0050] = 0x12 + current[0x0051] = 0x34 + current[0x0150] = 0xAB + current[0x0300] = 0xFF + + zmachine = Mock() + zmachine._pristine_mem = pristine + zmachine._mem = current + + writer = QuetzalWriter(zmachine) + compressed_bytes = writer._generate_cmem_chunk() + + # Create fresh memory for parsing into + restored = ZMemory(bytes(story)) + restored_zmachine = Mock() + restored_zmachine._pristine_mem = pristine + restored_zmachine._mem = restored + + parser = QuetzalParser(restored_zmachine) + parser._parse_cmem(compressed_bytes) + + # Verify restored memory matches current memory + for addr in [0x0050, 0x0051, 0x0150, 0x0300]: + self.assertEqual( + restored[addr], + current[addr], + f"Mismatch at address 0x{addr:04X}", + ) + + def test_cmem_consecutive_zeros(self): + """Test CMem encoding handles consecutive zero XOR results correctly.""" + from mudlib.zmachine.quetzal import QuetzalWriter + from mudlib.zmachine.zmemory import ZMemory + + story = bytearray(1024) + story[0] = 3 + story[0x0E] = 0x04 + story[0x0F] = 0x00 + + pristine = ZMemory(bytes(story)) + current = ZMemory(bytes(story)) + current[0x0040] = 0x11 + current[0x0045] = 0x22 + + zmachine = Mock() + zmachine._pristine_mem = pristine + zmachine._mem = current + + writer = QuetzalWriter(zmachine) + result = writer._generate_cmem_chunk() + + self.assertIsInstance(result, bytes) + idx_11 = result.index(0x11) + self.assertEqual(result[idx_11 + 1], 0x00) + self.assertEqual(result[idx_11 + 2], 0x03) + self.assertEqual(result[idx_11 + 3], 0x22) + # Note: ZObjectParser methods are tested through integration tests # with real story files, not unit tests with mock memory, as the