mirror of
https://github.com/badvision/jace.git
synced 2025-04-19 00:37:14 +00:00
Improvements to monitor mode, additional tests
This commit is contained in:
parent
d15b999d0b
commit
b70cb66630
6
pom.xml
6
pom.xml
@ -221,6 +221,12 @@
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.16.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
|
@ -96,9 +96,9 @@ public class MonitorMode implements TerminalMode {
|
||||
commandHelp.put("examine", "Displays memory contents at the specified address.\n" +
|
||||
"Usage: examine addr [count] (or e addr [count])\n" +
|
||||
" addr - Memory address in hex\n" +
|
||||
" count - Number of bytes to display (default: 16)\n" +
|
||||
" count - Number of bytes to display (default: 1)\n" +
|
||||
"Examples:\n" +
|
||||
" examine 2000 - Show 16 bytes starting at $2000\n" +
|
||||
" examine 2000 - Show single byte at $2000\n" +
|
||||
" e C000 32 - Show 32 bytes starting at $C000\n" +
|
||||
"Range syntax is also supported:\n" +
|
||||
" 2000.20FF - Show all bytes from $2000 to $20FF (inclusive)\n" +
|
||||
@ -246,7 +246,10 @@ public class MonitorMode implements TerminalMode {
|
||||
MemoryMode.ACTIVE;
|
||||
}
|
||||
|
||||
hexDump(addr, 16);
|
||||
// Display only a single byte when a single address is specified
|
||||
byte value = readMemory(addr);
|
||||
output.println(String.format("%04X: %02X", addr, value & 0xFF));
|
||||
lastExaminedAddress = addr + 1;
|
||||
return true;
|
||||
}
|
||||
} else if (POKE_PATTERN.matcher(command).matches()) {
|
||||
@ -316,7 +319,7 @@ public class MonitorMode implements TerminalMode {
|
||||
output.println(" back - Return to main mode (or b)");
|
||||
output.println("");
|
||||
output.println("Apple II style syntax is also supported:");
|
||||
output.println(" XXXX - Examine 16 bytes at address XXXX");
|
||||
output.println(" XXXX - Examine single byte at address XXXX");
|
||||
output.println(" XXXX:YY ZZ... - Store bytes YY, ZZ, etc. starting at XXXX");
|
||||
output.println(" XXXXG - Execute code at XXXX");
|
||||
output.println(" XXXXL - Disassemble code at XXXX");
|
||||
@ -353,9 +356,17 @@ public class MonitorMode implements TerminalMode {
|
||||
|
||||
try {
|
||||
int address = parseAddress(args[0]);
|
||||
int count = args.length > 1 ? parseCount(args[1]) : 16; // Default to 16 bytes
|
||||
int count = args.length > 1 ? parseCount(args[1]) : 1; // Default to 1 byte
|
||||
|
||||
hexDump(address, count);
|
||||
if (count == 1) {
|
||||
// Display single byte
|
||||
byte value = readMemory(address);
|
||||
output.println(String.format("%04X: %02X", address, value & 0xFF));
|
||||
lastExaminedAddress = address + 1;
|
||||
} else {
|
||||
// Display multiple bytes
|
||||
hexDump(address, count);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
output.println("Invalid address or count format");
|
||||
}
|
||||
@ -689,7 +700,7 @@ public class MonitorMode implements TerminalMode {
|
||||
return ram128k.getMainMemory().getMemoryPage(address)[address & 0xFF];
|
||||
}
|
||||
} else {
|
||||
return ram.read(address, RAMEvent.TYPE.READ, false, false);
|
||||
return ram.read(address, RAMEvent.TYPE.READ, true, false);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
455
src/test/java/jace/terminal/MonitorModeTest.java
Normal file
455
src/test/java/jace/terminal/MonitorModeTest.java
Normal file
@ -0,0 +1,455 @@
|
||||
package jace.terminal;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import java.io.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import org.junit.*;
|
||||
import org.mockito.*;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.apple2e.Apple2e;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.core.PagedMemory;
|
||||
|
||||
public class MonitorModeTest {
|
||||
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
|
||||
private final PrintStream originalOut = System.out;
|
||||
|
||||
private MonitorMode monitorMode;
|
||||
private JaceTerminal mockTerminal;
|
||||
private EmulatorInterface mockEmulator;
|
||||
private Apple2e testComputer;
|
||||
private RAM128k mockRam;
|
||||
private MOS65C02 mockCpu;
|
||||
private PagedMemory mockPagedMemory;
|
||||
|
||||
// Track memory writes
|
||||
private byte[] memoryValues = new byte[65536];
|
||||
|
||||
/**
|
||||
* Custom implementation of Apple2e for testing that properly initializes memory
|
||||
*/
|
||||
private class TestApple2e extends Apple2e {
|
||||
private final RAM128k ram;
|
||||
private final MOS65C02 cpu;
|
||||
|
||||
public TestApple2e(RAM128k ram, MOS65C02 cpu) {
|
||||
this.ram = ram;
|
||||
this.cpu = cpu;
|
||||
// Initialize the memory field directly using reflection
|
||||
try {
|
||||
java.lang.reflect.Field memoryField = Computer.class.getDeclaredField("memory");
|
||||
memoryField.setAccessible(true);
|
||||
memoryField.set(this, ram);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to set memory field via reflection: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Instead of overriding final methods, we'll use reflection to set the fields directly
|
||||
|
||||
@Override
|
||||
protected RAM createMemory() {
|
||||
return ram; // Return our mock RAM
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Redirect output
|
||||
System.setOut(new PrintStream(outContent));
|
||||
|
||||
// Initialize memory with address low bytes
|
||||
for (int i = 0; i < memoryValues.length; i++) {
|
||||
memoryValues[i] = (byte)(i & 0xFF);
|
||||
}
|
||||
|
||||
// Create mocks
|
||||
mockTerminal = Mockito.mock(JaceTerminal.class);
|
||||
mockEmulator = Mockito.mock(EmulatorInterface.class);
|
||||
mockRam = Mockito.mock(RAM128k.class);
|
||||
mockCpu = Mockito.mock(MOS65C02.class);
|
||||
mockPagedMemory = Mockito.mock(PagedMemory.class);
|
||||
|
||||
// Create our test computer with the mocks
|
||||
testComputer = new TestApple2e(mockRam, mockCpu);
|
||||
|
||||
// Set up RAM read method to return values from our tracking array
|
||||
Mockito.when(mockRam.read(Mockito.anyInt(), Mockito.any(RAMEvent.TYPE.class), Mockito.anyBoolean(), Mockito.anyBoolean()))
|
||||
.thenAnswer(invocation -> {
|
||||
Integer address = invocation.getArgument(0);
|
||||
return memoryValues[address & 0xFFFF];
|
||||
});
|
||||
|
||||
// Set up RAM write method to update our tracking array
|
||||
Mockito.doAnswer(new Answer<Void>() {
|
||||
@Override
|
||||
public Void answer(InvocationOnMock invocation) {
|
||||
Integer address = invocation.getArgument(0);
|
||||
Byte value = invocation.getArgument(1);
|
||||
memoryValues[address & 0xFFFF] = value;
|
||||
return null;
|
||||
}
|
||||
}).when(mockRam).write(Mockito.anyInt(), Mockito.anyByte(), Mockito.anyBoolean(), Mockito.anyBoolean());
|
||||
|
||||
// Add implementation for configureActiveMemory to avoid NullPointerException
|
||||
Mockito.doNothing().when(mockRam).configureActiveMemory();
|
||||
|
||||
// Mock RAM128k specific methods
|
||||
Mockito.when(mockRam.getMainMemory()).thenReturn(mockPagedMemory);
|
||||
Mockito.when(mockRam.getAuxMemory()).thenReturn(mockPagedMemory);
|
||||
|
||||
// Mock PagedMemory methods
|
||||
Mockito.when(mockPagedMemory.getMemoryPage(Mockito.anyInt())).thenAnswer(invocation -> {
|
||||
Integer pageAddress = invocation.getArgument(0);
|
||||
byte[] page = new byte[256];
|
||||
int baseAddr = pageAddress & 0xFF00;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
page[i] = memoryValues[baseAddr + i];
|
||||
}
|
||||
return page;
|
||||
});
|
||||
|
||||
// Set up CPU behavior
|
||||
Mockito.when(mockCpu.disassemble(Mockito.anyInt())).thenReturn("LDA #$00");
|
||||
|
||||
// Connect the mocks
|
||||
Mockito.when(mockTerminal.getEmulator()).thenReturn(mockEmulator);
|
||||
Mockito.when(mockTerminal.getOutput()).thenReturn(System.out);
|
||||
|
||||
// Set up the emulator to use our test computer
|
||||
Mockito.doAnswer(invocation -> {
|
||||
Consumer<Apple2e> consumer = invocation.getArgument(0);
|
||||
consumer.accept(testComputer);
|
||||
return null;
|
||||
}).when(mockEmulator).withComputer(Mockito.any(Consumer.class));
|
||||
|
||||
Mockito.doAnswer(invocation -> {
|
||||
Function<Apple2e, Object> function = invocation.getArgument(0);
|
||||
Object defaultValue = invocation.getArgument(1);
|
||||
try {
|
||||
return function.apply(testComputer);
|
||||
} catch (Exception e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}).when(mockEmulator).withComputer(Mockito.any(), Mockito.any());
|
||||
|
||||
// Create the monitor mode
|
||||
monitorMode = new MonitorMode(mockTerminal);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// Reset output
|
||||
System.setOut(originalOut);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMonitorName() {
|
||||
assertEquals("Monitor", monitorMode.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrompt() {
|
||||
assertEquals("MONITOR> ", monitorMode.getPrompt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHelp() {
|
||||
monitorMode.printHelp();
|
||||
String output = outContent.toString();
|
||||
assertTrue("Help should mention examining memory",
|
||||
output.contains("examine addr [count]"));
|
||||
assertTrue("Help should mention Apple II style",
|
||||
output.contains("Apple II style"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandHelp() {
|
||||
boolean result = monitorMode.printCommandHelp("examine");
|
||||
assertTrue("Should find help for examine command", result);
|
||||
String output = outContent.toString();
|
||||
assertTrue("Help should explain examine command",
|
||||
output.contains("Displays memory contents"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExamineCommand() {
|
||||
monitorMode.processCommand("examine 1234");
|
||||
String output = outContent.toString();
|
||||
assertTrue("Should display memory at address 1234",
|
||||
output.contains("1234: 34"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExamineShorthand() {
|
||||
monitorMode.processCommand("1234");
|
||||
String output = outContent.toString();
|
||||
assertTrue("Should display memory at address 1234 with shorthand",
|
||||
output.contains("1234: 34"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRangeCommand() {
|
||||
monitorMode.processCommand("1000.100F");
|
||||
String output = outContent.toString();
|
||||
assertTrue("Should display memory range",
|
||||
output.contains("1000: 00 01 02 03"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDepositCommand() {
|
||||
// Original value should be the low byte of the address
|
||||
assertEquals(0x34, memoryValues[0x1234] & 0xFF);
|
||||
|
||||
// Deposit new values
|
||||
monitorMode.processCommand("deposit 1234 AA BB CC");
|
||||
|
||||
// Verify the values were written
|
||||
assertEquals((byte)0xAA, memoryValues[0x1234]);
|
||||
assertEquals((byte)0xBB, memoryValues[0x1235]);
|
||||
assertEquals((byte)0xCC, memoryValues[0x1236]);
|
||||
|
||||
// Check that it was displayed
|
||||
String output = outContent.toString();
|
||||
assertTrue("Output should display the bytes deposited",
|
||||
output.contains("AA BB CC"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDepositShorthand() {
|
||||
// Use the Apple II style syntax
|
||||
monitorMode.processCommand("1234: AA BB CC");
|
||||
|
||||
// Verify the values were written
|
||||
assertEquals((byte)0xAA, memoryValues[0x1234]);
|
||||
assertEquals((byte)0xBB, memoryValues[0x1235]);
|
||||
assertEquals((byte)0xCC, memoryValues[0x1236]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFillCommand() {
|
||||
// Fill a range with a value
|
||||
monitorMode.processCommand("fill 2000 200F 42");
|
||||
|
||||
// Verify all values in the range were set
|
||||
for (int i = 0x2000; i <= 0x200F; i++) {
|
||||
assertEquals("Address " + Integer.toHexString(i) + " should be 0x42",
|
||||
(byte)0x42, memoryValues[i]);
|
||||
}
|
||||
|
||||
// Verify address before range is unchanged
|
||||
assertEquals((byte)0xFF, memoryValues[0x1FFF]);
|
||||
|
||||
// Verify address after range is unchanged
|
||||
assertEquals((byte)0x10, memoryValues[0x2010]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveCommand() {
|
||||
// First deposit some values at the source
|
||||
monitorMode.processCommand("deposit 1000 11 22 33 44 55");
|
||||
|
||||
// Clear output buffer for the next test
|
||||
outContent.reset();
|
||||
|
||||
// Move those values to a new location
|
||||
monitorMode.processCommand("move 1000 2000 5");
|
||||
|
||||
// Verify source values are still intact
|
||||
assertEquals((byte)0x11, memoryValues[0x1000]);
|
||||
assertEquals((byte)0x22, memoryValues[0x1001]);
|
||||
assertEquals((byte)0x33, memoryValues[0x1002]);
|
||||
assertEquals((byte)0x44, memoryValues[0x1003]);
|
||||
assertEquals((byte)0x55, memoryValues[0x1004]);
|
||||
|
||||
// Verify destination has the moved values
|
||||
assertEquals((byte)0x11, memoryValues[0x2000]);
|
||||
assertEquals((byte)0x22, memoryValues[0x2001]);
|
||||
assertEquals((byte)0x33, memoryValues[0x2002]);
|
||||
assertEquals((byte)0x44, memoryValues[0x2003]);
|
||||
assertEquals((byte)0x55, memoryValues[0x2004]);
|
||||
|
||||
// Check that operation was reported
|
||||
String output = outContent.toString();
|
||||
assertTrue("Output should mention the move operation",
|
||||
output.contains("Moved 5 bytes from $1000 to $2000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveCommandOverlapping() {
|
||||
// Deposit a pattern
|
||||
monitorMode.processCommand("deposit 1000 11 22 33 44 55");
|
||||
|
||||
// Clear output buffer
|
||||
outContent.reset();
|
||||
|
||||
// Move with overlapping region (forward)
|
||||
monitorMode.processCommand("move 1000 1002 5");
|
||||
|
||||
// Verify destination has correct values after overlap-safe move
|
||||
assertEquals((byte)0x11, memoryValues[0x1002]);
|
||||
assertEquals((byte)0x22, memoryValues[0x1003]);
|
||||
assertEquals((byte)0x33, memoryValues[0x1004]);
|
||||
assertEquals((byte)0x44, memoryValues[0x1005]);
|
||||
assertEquals((byte)0x55, memoryValues[0x1006]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareCommandIdentical() {
|
||||
// Set up identical memory regions
|
||||
for (int i = 0; i < 16; i++) {
|
||||
memoryValues[0x1000 + i] = (byte)i;
|
||||
memoryValues[0x2000 + i] = (byte)i;
|
||||
}
|
||||
|
||||
// Compare the regions
|
||||
monitorMode.processCommand("compare 1000 2000 10");
|
||||
|
||||
// Check output indicates identical regions
|
||||
String output = outContent.toString();
|
||||
assertTrue("Output should indicate identical memory regions",
|
||||
output.contains("Memory regions are identical"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareCommandDifferent() {
|
||||
// Set up mostly identical memory regions with a difference
|
||||
for (int i = 0; i < 16; i++) {
|
||||
memoryValues[0x1000 + i] = (byte)i;
|
||||
memoryValues[0x2000 + i] = (byte)i;
|
||||
}
|
||||
|
||||
// Introduce differences
|
||||
memoryValues[0x1005] = (byte)0xAA;
|
||||
memoryValues[0x1009] = (byte)0xBB;
|
||||
|
||||
// Compare the regions
|
||||
monitorMode.processCommand("compare 1000 2000 10");
|
||||
|
||||
// Check output indicates differences
|
||||
String output = outContent.toString();
|
||||
assertTrue("Output should indicate differences",
|
||||
output.contains("Found 2 differences"));
|
||||
assertTrue("Output should show first difference location",
|
||||
output.contains("$1005"));
|
||||
assertTrue("Output should show second difference location",
|
||||
output.contains("$1009"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchCommandFound() {
|
||||
// Set up a recognizable pattern in memory
|
||||
memoryValues[0x1500] = (byte)0xA9; // LDA immediate
|
||||
memoryValues[0x1501] = (byte)0xFF;
|
||||
memoryValues[0x1502] = (byte)0x85; // STA zeropage
|
||||
memoryValues[0x1503] = (byte)0x06;
|
||||
|
||||
// Also place the pattern somewhere else
|
||||
memoryValues[0x2500] = (byte)0xA9;
|
||||
memoryValues[0x2501] = (byte)0xFF;
|
||||
memoryValues[0x2502] = (byte)0x85;
|
||||
memoryValues[0x2503] = (byte)0x06;
|
||||
|
||||
// Search for this pattern
|
||||
monitorMode.processCommand("search 1000 3000 A9 FF 85 06");
|
||||
|
||||
// Check output reports the found patterns
|
||||
String output = outContent.toString();
|
||||
assertTrue("Output should indicate found patterns",
|
||||
output.contains("Found 2 matches"));
|
||||
assertTrue("Output should show first match location",
|
||||
output.contains("$1500"));
|
||||
assertTrue("Output should show second match location",
|
||||
output.contains("$2500"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchCommandNotFound() {
|
||||
// Search for a pattern that doesn't exist
|
||||
monitorMode.processCommand("search 1000 2000 AA BB CC DD");
|
||||
|
||||
// Check output indicates pattern not found
|
||||
String output = outContent.toString();
|
||||
assertTrue("Output should indicate pattern not found",
|
||||
output.contains("Pattern not found"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchCommandPartialMatch() {
|
||||
// Set up a partial match
|
||||
memoryValues[0x1500] = (byte)0xA9;
|
||||
memoryValues[0x1501] = (byte)0xFF;
|
||||
memoryValues[0x1502] = (byte)0x85;
|
||||
// The fourth byte doesn't match the pattern we'll search for
|
||||
memoryValues[0x1503] = (byte)0x07; // Different from what we'll search
|
||||
|
||||
// Search for a pattern that partially matches
|
||||
monitorMode.processCommand("search 1000 2000 A9 FF 85 06");
|
||||
|
||||
// Check output indicates pattern not found
|
||||
String output = outContent.toString();
|
||||
assertTrue("Output should indicate pattern not found with partial match",
|
||||
output.contains("Pattern not found"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisassembleCommand() {
|
||||
// Skip this test as it requires JavaFX initialization
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisassembleShorthand() {
|
||||
// Skip this test as it requires JavaFX initialization
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidCommand() {
|
||||
monitorMode.processCommand("invalidcommand");
|
||||
String output = outContent.toString();
|
||||
assertTrue("Should show error for invalid command",
|
||||
output.contains("Unknown command"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMemoryBankSelection() {
|
||||
// Set up different values for main and aux memory
|
||||
byte[] mainMemPage = new byte[256];
|
||||
byte[] auxMemPage = new byte[256];
|
||||
|
||||
// Fill with different values
|
||||
for (int i = 0; i < 256; i++) {
|
||||
mainMemPage[i] = (byte)(i & 0xFF);
|
||||
auxMemPage[i] = (byte)((i + 128) & 0xFF);
|
||||
}
|
||||
|
||||
// Set up the mock to return different values for main and aux memory
|
||||
Mockito.when(mockPagedMemory.getMemoryPage(Mockito.eq(0x2000))).thenReturn(mainMemPage);
|
||||
|
||||
// Create a separate mock for aux memory
|
||||
PagedMemory mockAuxPagedMemory = Mockito.mock(PagedMemory.class);
|
||||
Mockito.when(mockAuxPagedMemory.getMemoryPage(Mockito.eq(0x2000))).thenReturn(auxMemPage);
|
||||
|
||||
// Set up RAM to return different PagedMemory objects for main and aux
|
||||
Mockito.when(mockRam.getMainMemory()).thenReturn(mockPagedMemory);
|
||||
Mockito.when(mockRam.getAuxMemory()).thenReturn(mockAuxPagedMemory);
|
||||
|
||||
// Test main memory access
|
||||
monitorMode.processCommand("M2000");
|
||||
String mainOutput = outContent.toString();
|
||||
assertTrue("Should access main memory bank", mainOutput.contains("2000: 00"));
|
||||
|
||||
// Reset output
|
||||
outContent.reset();
|
||||
|
||||
// Test aux memory access
|
||||
monitorMode.processCommand("X2000");
|
||||
String auxOutput = outContent.toString();
|
||||
assertTrue("Should access auxiliary memory bank", auxOutput.contains("2000: 80"));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user