mirror of
https://github.com/badvision/jace.git
synced 2025-04-10 00:37:14 +00:00
Improved testing, UI terminal improvements
This commit is contained in:
parent
b70cb66630
commit
2f297a1c41
40
README.md
40
README.md
@ -51,6 +51,46 @@ All other native dependencies are automatically downloaded as needed by Maven fo
|
||||
### First time build note:
|
||||
Because Jace provides an annotation processor for compilation, there is a chicken-and-egg problem when building the first time. Currently, this means the first time you compile, run `mvn install` twice. You don't have to do this step again as long as Maven is able to find a previously build version of Jace to provide this annotation processor. I tried to set up the profiles in the pom.xml so that it disables the annotation processor the first time you compile to avoid any issues. If running in a CICD environment, keep in mind you will likely always need to run the "mvn install" step twice, but only if your goal is to build the entire application including the annotations (should not be needed for just running unit tests.)
|
||||
|
||||
## Development
|
||||
|
||||
### Running Tests
|
||||
Jace uses JUnit for testing. You can run tests using Maven:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
mvn test
|
||||
|
||||
# Run a specific test class
|
||||
mvn test -Dtest=ClassName
|
||||
|
||||
# Run a specific test method
|
||||
mvn test -Dtest=ClassName#methodName
|
||||
```
|
||||
|
||||
### Test Logging
|
||||
The test output is configured to be concise by default, showing only essential information. Two system properties can be used to control verbosity:
|
||||
|
||||
- `jace.test.debug` - Enables debug output for terminal-related tests
|
||||
```bash
|
||||
mvn test -Dtest=jace.terminal.MainModeTest -Djace.test.debug=true
|
||||
```
|
||||
|
||||
- `jace.test.verbose` - Enables verbose output for CPU and video initialization
|
||||
```bash
|
||||
mvn test -Djace.test.verbose=true
|
||||
```
|
||||
|
||||
These properties allow you to see detailed information about the test environment setup, mock initialization, and test execution when needed, while keeping the default output clean.
|
||||
|
||||
### Code Coverage
|
||||
JaCoCo is used for code coverage analysis. You can generate coverage reports with:
|
||||
|
||||
```bash
|
||||
mvn jacoco:report
|
||||
```
|
||||
|
||||
The coverage reports will be available in `target/site/jacoco/index.html` after running the above command.
|
||||
|
||||
## Support JACE:
|
||||
|
||||
JACE will always be free, and remain Apache-licensed, but it does take considerable time to refine and add new features. If you would like to show your support and encourage the author to keep maintaining this emulator, why not throw him some change to buy him a drink? (The emulator was named for the Jack and Cokes consumed during its inception.) Also, should you want to use Jace under the terms of the Apache-license for commercial works, you are under no obligation to contribute any source code modifications or royalties to me, but I would appreciate you credit and mention my project and let me know about it.
|
||||
|
19
pom.xml
19
pom.xml
@ -158,7 +158,7 @@
|
||||
<limit>
|
||||
<counter>COMPLEXITY</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.35</minimum>
|
||||
<minimum>0.31</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
@ -167,6 +167,19 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>resolve-mockito-agent</id>
|
||||
<goals>
|
||||
<goal>properties</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
@ -175,6 +188,8 @@
|
||||
<!-- Set a global timeout for all tests -->
|
||||
<forkedProcessTimeoutInSeconds>30</forkedProcessTimeoutInSeconds>
|
||||
<rerunFailingTestsCount>0</rerunFailingTestsCount>
|
||||
<!-- Configure Mockito as a Java agent to prevent "self-attaching" warnings -->
|
||||
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar}</argLine>
|
||||
<!-- Add additional configuration to fix ProgramException class loading issue -->
|
||||
<additionalClasspathElements>
|
||||
<additionalClasspathElement>${project.build.testOutputDirectory}</additionalClasspathElement>
|
||||
@ -349,7 +364,7 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
<version>3.13.0</version>
|
||||
</plugin>
|
||||
|
@ -20,6 +20,8 @@ import java.io.PrintStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.MOS65C02;
|
||||
@ -29,6 +31,8 @@ import jace.apple2e.SoftSwitches;
|
||||
* Main command mode for the Terminal
|
||||
*/
|
||||
public class MainMode implements TerminalMode {
|
||||
private static final Logger LOG = Logger.getLogger(MainMode.class.getName());
|
||||
|
||||
private final JaceTerminal terminal;
|
||||
private final PrintStream output;
|
||||
private final Map<String, Consumer<String[]>> commands = new HashMap<>();
|
||||
@ -40,6 +44,7 @@ public class MainMode implements TerminalMode {
|
||||
this.terminal = terminal;
|
||||
this.output = terminal.getOutput();
|
||||
initCommands();
|
||||
LOG.fine("MainMode initialized");
|
||||
}
|
||||
|
||||
private void initCommands() {
|
||||
@ -125,6 +130,8 @@ public class MainMode implements TerminalMode {
|
||||
"Saves a block of memory to a binary file.\nUsage: savebin <filename> <address> <size> (or sb <filename> <address> <size>)\n"
|
||||
+
|
||||
"Address and size can be decimal or hex with $ or 0x prefix.");
|
||||
|
||||
LOG.fine("Commands initialized");
|
||||
}
|
||||
|
||||
private void addAlias(String alias, String command) {
|
||||
@ -153,10 +160,12 @@ public class MainMode implements TerminalMode {
|
||||
|
||||
Consumer<String[]> handler = commands.get(cmd);
|
||||
if (handler != null) {
|
||||
LOG.fine("Processing command: " + cmd);
|
||||
handler.accept(args);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG.info("Unknown command received: " + cmd);
|
||||
output.println("Unknown command: " + cmd);
|
||||
return false;
|
||||
}
|
||||
@ -201,6 +210,7 @@ public class MainMode implements TerminalMode {
|
||||
|
||||
private void toggleSoftSwitchLogging(String[] args) {
|
||||
softSwitchLoggingEnabled = !softSwitchLoggingEnabled;
|
||||
LOG.info("SoftSwitch logging " + (softSwitchLoggingEnabled ? "enabled" : "disabled"));
|
||||
output.println("SoftSwitch logging " + (softSwitchLoggingEnabled ? "enabled" : "disabled"));
|
||||
|
||||
// TODO: Implement actual listener on SoftSwitch state changes when enabled
|
||||
@ -214,6 +224,7 @@ public class MainMode implements TerminalMode {
|
||||
SoftSwitches sw = SoftSwitches.valueOf(switchName);
|
||||
output.println(sw.toString() + " = " + (sw.isOn() ? "ON" : "OFF"));
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.info("Unknown softswitch requested: " + switchName);
|
||||
output.println("Unknown softswitch: " + switchName);
|
||||
}
|
||||
} else {
|
||||
@ -249,9 +260,11 @@ public class MainMode implements TerminalMode {
|
||||
|
||||
output.println(" Flags: " + flags.toString());
|
||||
} else {
|
||||
LOG.warning("CPU not available for register display");
|
||||
output.println("CPU not available");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Error displaying CPU registers", e);
|
||||
output.println("Error accessing CPU: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@ -269,6 +282,7 @@ public class MainMode implements TerminalMode {
|
||||
try {
|
||||
MOS65C02 cpu = getCPU();
|
||||
if (cpu == null) {
|
||||
LOG.warning("CPU not available for register setting");
|
||||
output.println("CPU not available");
|
||||
return;
|
||||
}
|
||||
@ -312,14 +326,18 @@ public class MainMode implements TerminalMode {
|
||||
cpu.C = parseBooleanValue(valueStr) ? 1 : 0;
|
||||
break;
|
||||
default:
|
||||
LOG.info("Unknown register requested: " + register);
|
||||
output.println("Unknown register: " + register);
|
||||
return;
|
||||
}
|
||||
LOG.fine("Register " + register + " set to " + valueStr);
|
||||
output.println("Register " + register + " set to " + valueStr);
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.info("Invalid value format for register: " + valueStr);
|
||||
output.println("Invalid value format: " + valueStr);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Error setting CPU register", e);
|
||||
output.println("Error accessing CPU: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@ -356,8 +374,10 @@ public class MainMode implements TerminalMode {
|
||||
Emulator.withComputer(computer -> {
|
||||
computer.coldStart();
|
||||
output.println("Apple II reset performed");
|
||||
LOG.info("Apple II reset performed");
|
||||
});
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Error performing system reset", e);
|
||||
output.println("Error accessing computer: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@ -368,6 +388,7 @@ public class MainMode implements TerminalMode {
|
||||
try {
|
||||
steps = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.info("Invalid step count: " + args[0]);
|
||||
output.println("Invalid step count: " + args[0]);
|
||||
return;
|
||||
}
|
||||
@ -377,6 +398,7 @@ public class MainMode implements TerminalMode {
|
||||
try {
|
||||
Emulator.withComputer(computer -> {
|
||||
output.println("Stepping CPU for " + stepCount + " cycles...");
|
||||
LOG.fine("Stepping CPU for " + stepCount + " cycles");
|
||||
computer.getMotherboard().whileSuspended(() -> {
|
||||
for (int i = 0; i < stepCount; i++) {
|
||||
computer.getCpu().tick();
|
||||
@ -386,6 +408,7 @@ public class MainMode implements TerminalMode {
|
||||
showRegisters();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Error stepping CPU", e);
|
||||
output.println("Error accessing computer: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@ -398,6 +421,7 @@ public class MainMode implements TerminalMode {
|
||||
try {
|
||||
cycles = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.info("Invalid cycle count: " + args[0]);
|
||||
output.println("Invalid cycle count: " + args[0]);
|
||||
return;
|
||||
}
|
||||
@ -411,6 +435,7 @@ public class MainMode implements TerminalMode {
|
||||
breakpoint = Integer.parseInt(args[1]) & 0xFFFF;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.info("Invalid breakpoint address: " + args[1]);
|
||||
output.println("Invalid breakpoint address: " + args[1]);
|
||||
return;
|
||||
}
|
||||
@ -419,8 +444,10 @@ public class MainMode implements TerminalMode {
|
||||
final int cycleCount = cycles;
|
||||
final int breakAddr = breakpoint;
|
||||
|
||||
output.println("Running CPU for " + (cycleCount == -1 ? "unlimited" : cycleCount) + " cycles" +
|
||||
(breakAddr != -1 ? " or until PC=$" + String.format("%04X", breakAddr) : ""));
|
||||
String cycleMsg = "Running CPU for " + (cycleCount == -1 ? "unlimited" : cycleCount) + " cycles" +
|
||||
(breakAddr != -1 ? " or until PC=$" + String.format("%04X", breakAddr) : "");
|
||||
LOG.info(cycleMsg);
|
||||
output.println(cycleMsg);
|
||||
|
||||
// TODO: Implement actual run logic with breakpoint support
|
||||
try {
|
||||
@ -431,6 +458,7 @@ public class MainMode implements TerminalMode {
|
||||
output.println("CPU resumed, press Ctrl+C to interrupt");
|
||||
});
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Error running CPU", e);
|
||||
output.println("Error accessing computer: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@ -444,6 +472,7 @@ public class MainMode implements TerminalMode {
|
||||
String drive = args[0];
|
||||
String filename = args[1];
|
||||
|
||||
LOG.info("Disk insertion requested for drive " + drive + ": " + filename);
|
||||
// TODO: Implement disk insertion
|
||||
output.println("Disk insertion not yet implemented");
|
||||
}
|
||||
@ -456,6 +485,7 @@ public class MainMode implements TerminalMode {
|
||||
|
||||
String drive = args[0];
|
||||
|
||||
LOG.info("Disk ejection requested for drive " + drive);
|
||||
// TODO: Implement disk ejection
|
||||
output.println("Disk ejection not yet implemented");
|
||||
}
|
||||
@ -476,10 +506,12 @@ public class MainMode implements TerminalMode {
|
||||
address = Integer.parseInt(args[1]) & 0xFFFF;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.info("Invalid address format: " + args[1]);
|
||||
output.println("Invalid address: " + args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Binary load requested: " + filename + " at $" + Integer.toHexString(address));
|
||||
// TODO: Implement binary loading
|
||||
output.println("Binary loading not yet implemented");
|
||||
}
|
||||
@ -506,10 +538,13 @@ public class MainMode implements TerminalMode {
|
||||
size = Integer.parseInt(args[2]) & 0xFFFF;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.info("Invalid address or size format");
|
||||
output.println("Invalid address or size");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Binary save requested: " + filename + " from $" +
|
||||
Integer.toHexString(address) + " size $" + Integer.toHexString(size));
|
||||
// TODO: Implement binary saving
|
||||
output.println("Binary saving not yet implemented");
|
||||
}
|
||||
@ -517,10 +552,11 @@ public class MainMode implements TerminalMode {
|
||||
/**
|
||||
* Helper method to get CPU from the emulator
|
||||
*/
|
||||
private MOS65C02 getCPU() {
|
||||
protected MOS65C02 getCPU() {
|
||||
try {
|
||||
return (MOS65C02) terminal.getEmulator().withComputer(c -> c.getCpu(), null);
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Error getting CPU: {0}", e.getMessage());
|
||||
output.println("Error getting CPU: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
@ -586,12 +586,25 @@ public class MonitorMode implements TerminalMode {
|
||||
|
||||
hexValues.append(String.format("%02X ", value & 0xFF));
|
||||
|
||||
// For ASCII representation, use '.' for non-printable characters
|
||||
char c = (char)(value & 0xFF);
|
||||
if (c >= 32 && c < 127) {
|
||||
asciiValues.append(c);
|
||||
// For ASCII representation, mask high bit for Apple II character set
|
||||
// Apple II text typically has high bit set (0x80-0xFF) for normal display
|
||||
int maskedValue = value & 0x7F; // Mask off high bit for ASCII display
|
||||
|
||||
// Special handling for 0x7F and 0xFF - use medium shade character
|
||||
if (value == 0x7F || value == 0xFF) {
|
||||
asciiValues.append('▒'); // Unicode U+2592 MEDIUM SHADE
|
||||
} else {
|
||||
asciiValues.append('.');
|
||||
// Apple II control characters (0x00-0x1F) should be displayed as uppercase letters (add 0x40)
|
||||
if (maskedValue < 0x20) {
|
||||
maskedValue += 0x40;
|
||||
}
|
||||
|
||||
char c = (char)maskedValue;
|
||||
if (c >= 32 && c < 127) {
|
||||
asciiValues.append(c);
|
||||
} else {
|
||||
asciiValues.append('.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,10 @@
|
||||
package jace.terminal;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.io.PrintStream;
|
||||
@ -46,6 +48,7 @@ public class TerminalUIController {
|
||||
|
||||
// Track current Terminal mode for UI display
|
||||
private static TerminalMode currentMode;
|
||||
private static javafx.scene.control.Label modeLabel;
|
||||
|
||||
/**
|
||||
* Set the current Terminal mode
|
||||
@ -55,6 +58,13 @@ public class TerminalUIController {
|
||||
*/
|
||||
public static void setCurrentMode(TerminalMode mode) {
|
||||
currentMode = mode;
|
||||
|
||||
// Update the mode label if available
|
||||
if (modeLabel != null && mode != null) {
|
||||
Platform.runLater(() -> {
|
||||
modeLabel.setText(mode.getPrompt());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,8 +99,12 @@ public class TerminalUIController {
|
||||
|
||||
Button sendButton = new Button("Send");
|
||||
|
||||
// Add a label to display the current mode
|
||||
modeLabel = new javafx.scene.control.Label("MAIN>");
|
||||
modeLabel.setStyle("-fx-font-family: 'monospace'; -fx-font-weight: bold;");
|
||||
|
||||
// Arrange input components horizontally
|
||||
HBox inputBox = new HBox(5, inputField, sendButton);
|
||||
HBox inputBox = new HBox(5, modeLabel, inputField, sendButton);
|
||||
inputBox.setAlignment(Pos.CENTER_LEFT);
|
||||
HBox.setHgrow(inputField, Priority.ALWAYS);
|
||||
|
||||
@ -101,7 +115,7 @@ public class TerminalUIController {
|
||||
layout.setPadding(new Insets(10));
|
||||
|
||||
// Set up the scene
|
||||
Scene scene = new Scene(layout, 600, 400);
|
||||
Scene scene = new Scene(layout, 650, 400);
|
||||
terminalStage.setScene(scene);
|
||||
|
||||
// Set up piped I/O - this allows communication between the UI and Terminal
|
||||
@ -115,7 +129,50 @@ public class TerminalUIController {
|
||||
|
||||
// Create readers/writers for the Terminal
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(terminalInput));
|
||||
PrintStream printStream = new PrintStream(terminalOutput, true);
|
||||
|
||||
// Create a PrintStream with a custom OutputStream that formats output
|
||||
PrintStream printStream = new PrintStream(new OutputStream() {
|
||||
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
// Pass through to the terminal output
|
||||
terminalOutput.write(b);
|
||||
|
||||
// If newline, flush our buffer and process the line
|
||||
if (b == '\n') {
|
||||
final String line = buffer.toString("UTF-8");
|
||||
buffer.reset();
|
||||
|
||||
// Format the output on the UI thread if needed
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
if (line.trim().endsWith(">")) {
|
||||
// If prompt, ensure it's on its own line
|
||||
if (!consoleOutput.getText().endsWith("\n\n")) {
|
||||
consoleOutput.appendText("\n");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error processing output: " + e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Add to our buffer
|
||||
buffer.write(b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
terminalOutput.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
terminalOutput.close();
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Initialize the Terminal in a background thread - not on the JavaFX thread!
|
||||
Thread terminalThread = new Thread(() -> {
|
||||
@ -166,9 +223,32 @@ public class TerminalUIController {
|
||||
// Update UI - must be on JavaFX thread
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
consoleOutput.appendText(finalLine + "\n");
|
||||
// Auto-scroll to bottom
|
||||
// Check for pure prompt lines first
|
||||
if (finalLine.trim().matches(".*>\\s*$") && !finalLine.contains(":")) {
|
||||
// Don't display pure prompt lines (they're shown in the input area)
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle lines containing both prompt and output
|
||||
String processedLine = finalLine;
|
||||
if (finalLine.contains("MONITOR>") || finalLine.contains("MAIN>") ||
|
||||
finalLine.contains("ASSEMBLER>") || finalLine.contains("DEBUGGER>")) {
|
||||
// Extract just the part after the prompt
|
||||
int promptEnd = Math.max(
|
||||
Math.max(finalLine.indexOf("MONITOR>") + 8, finalLine.indexOf("MAIN>") + 5),
|
||||
Math.max(finalLine.indexOf("ASSEMBLER>") + 10, finalLine.indexOf("DEBUGGER>") + 9)
|
||||
);
|
||||
if (promptEnd > 4) { // Ensure we found a prompt
|
||||
processedLine = finalLine.substring(promptEnd).trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Display the processed line (without the prompt)
|
||||
consoleOutput.appendText(processedLine + "\n");
|
||||
|
||||
// Force scroll to bottom with both methods to ensure it works
|
||||
consoleOutput.setScrollTop(Double.MAX_VALUE);
|
||||
consoleOutput.positionCaret(consoleOutput.getText().length());
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error updating console: " + e);
|
||||
}
|
||||
@ -185,6 +265,15 @@ public class TerminalUIController {
|
||||
String command = inputField.getText().trim();
|
||||
if (!command.isEmpty()) {
|
||||
try {
|
||||
// Echo command to console output
|
||||
Platform.runLater(() -> {
|
||||
// Add the user command with a newline
|
||||
consoleOutput.appendText("\n" + command + "\n");
|
||||
// Force scroll to bottom with both methods to ensure it works
|
||||
consoleOutput.setScrollTop(Double.MAX_VALUE);
|
||||
consoleOutput.positionCaret(consoleOutput.getText().length());
|
||||
});
|
||||
|
||||
// Send command to terminal
|
||||
uiToTerminal.write((command + "\n").getBytes());
|
||||
uiToTerminal.flush();
|
||||
|
@ -1,5 +1,7 @@
|
||||
package jace;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
@ -13,6 +15,8 @@ import javafx.application.Platform;
|
||||
*/
|
||||
public abstract class AbstractFXTest extends AbstractJaceTest {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(AbstractFXTest.class.getName());
|
||||
|
||||
// Flag to track if JavaFX runtime has been initialized
|
||||
protected static boolean fxInitialized = false;
|
||||
|
||||
@ -28,7 +32,7 @@ public abstract class AbstractFXTest extends AbstractJaceTest {
|
||||
|
||||
// Skip JavaFX initialization in test mode
|
||||
if (Utility.isTestMode()) {
|
||||
System.out.println("Skipping JavaFX initialization in test mode");
|
||||
LOG.fine("Skipping JavaFX initialization in test mode");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -37,9 +41,9 @@ public abstract class AbstractFXTest extends AbstractJaceTest {
|
||||
try {
|
||||
fxInitialized = true;
|
||||
Platform.startup(() -> {});
|
||||
System.out.println("JavaFX initialized successfully");
|
||||
LOG.fine("JavaFX initialized successfully");
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to initialize JavaFX: " + e.getMessage());
|
||||
LOG.log(Level.SEVERE, "Failed to initialize JavaFX: " + e.getMessage(), e);
|
||||
// Continue without JavaFX in test mode
|
||||
Utility.setTestMode(true);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package jace;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
@ -18,6 +20,8 @@ import jace.core.Utility;
|
||||
*/
|
||||
public abstract class AbstractJaceTest {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(AbstractJaceTest.class.getName());
|
||||
|
||||
// Common test resources
|
||||
protected static Computer computer;
|
||||
protected static MOS65C02 cpu;
|
||||
@ -51,11 +55,10 @@ public abstract class AbstractJaceTest {
|
||||
ram = (RAM128k) computer.getMemory();
|
||||
setupComplete = true;
|
||||
|
||||
System.out.println("Setup complete for test class: " +
|
||||
LOG.fine("Setup complete for test class: " +
|
||||
Thread.currentThread().getStackTrace()[2].getClassName());
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error in test class setup: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOG.log(Level.SEVERE, "Error in test class setup: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +85,7 @@ public abstract class AbstractJaceTest {
|
||||
// Disable sound
|
||||
SoundMixer.MUTE = true;
|
||||
|
||||
System.out.println("Test environment configured for headless mode");
|
||||
LOG.fine("Test environment configured for headless mode");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,11 +107,10 @@ public abstract class AbstractJaceTest {
|
||||
// Force garbage collection
|
||||
System.gc();
|
||||
|
||||
System.out.println("Teardown complete for test class: " +
|
||||
LOG.fine("Teardown complete for test class: " +
|
||||
Thread.currentThread().getStackTrace()[2].getClassName());
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error in test class teardown: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOG.log(Level.SEVERE, "Error in test class teardown: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,8 +158,7 @@ public abstract class AbstractJaceTest {
|
||||
// Make sure emulator is in a valid but suspended state
|
||||
Emulator.withComputer(c -> c.getMotherboard().suspend());
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error in test setup: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOG.log(Level.SEVERE, "Error in test setup: " + e.getMessage(), e);
|
||||
throw new RuntimeException("Test setup failed", e);
|
||||
}
|
||||
}
|
||||
@ -182,8 +183,7 @@ public abstract class AbstractJaceTest {
|
||||
// Clear all RAM
|
||||
clearRAM();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error in test teardown: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOG.log(Level.SEVERE, "Error in test teardown: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,8 @@ package jace;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.core.CPU;
|
||||
@ -40,6 +42,11 @@ import javafx.scene.image.WritableImage;
|
||||
* @author brobert
|
||||
*/
|
||||
public class TestUtils {
|
||||
private static final Logger LOG = Logger.getLogger(TestUtils.class.getName());
|
||||
|
||||
private static final String VERBOSE_PROPERTY = "jace.test.verbose";
|
||||
private static final boolean VERBOSE_MODE = Boolean.getBoolean(VERBOSE_PROPERTY);
|
||||
|
||||
private TestUtils() {
|
||||
// Utility class has no constructor
|
||||
}
|
||||
@ -399,39 +406,37 @@ public class TestUtils {
|
||||
|
||||
/**
|
||||
* Sets up a mock video device for tests to prevent NPEs when accessing the
|
||||
* floating bus or other video-related functionality.
|
||||
* floating bus.
|
||||
*
|
||||
* @param <T> The type of Video implementation
|
||||
* @param videoClass The class of the Video implementation to create and set up
|
||||
*/
|
||||
public static <T extends Video> void setupMockVideo(Class<T> videoClass) {
|
||||
try {
|
||||
Emulator.withComputer(c -> {
|
||||
// If video is null or not the right type, replace it
|
||||
if (c.getVideo() == null || !videoClass.isInstance(c.getVideo())) {
|
||||
// Create a new mock video instance
|
||||
Video mockVideo = null;
|
||||
|
||||
if (videoClass.equals(MockVideoNTSC.class)) {
|
||||
mockVideo = new MockVideoNTSC();
|
||||
} else if (videoClass.equals(MockVideoDHGR.class)) {
|
||||
mockVideo = new MockVideoDHGR();
|
||||
} else {
|
||||
mockVideo = new MockVideo();
|
||||
}
|
||||
|
||||
// Make sure the video is properly set up
|
||||
mockVideo.setMemory(c.getMemory());
|
||||
|
||||
// Set the video on the computer
|
||||
c.setVideo(mockVideo);
|
||||
|
||||
// Attach the video to the computer
|
||||
mockVideo.attach();
|
||||
|
||||
System.out.println("Mock video initialized successfully: " + videoClass.getSimpleName());
|
||||
// Create a new mock video instance
|
||||
T videoInstance = videoClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
// Configure and set the video in the computer
|
||||
Emulator.withComputer(computer -> {
|
||||
// Suspend the computer during setup
|
||||
computer.getMotherboard().suspend();
|
||||
try {
|
||||
// Attach the video
|
||||
computer.setVideo(videoInstance);
|
||||
videoInstance.attach();
|
||||
} finally {
|
||||
// Resume the computer
|
||||
computer.getMotherboard().resume();
|
||||
}
|
||||
});
|
||||
|
||||
// Log successful setup
|
||||
if (VERBOSE_MODE) {
|
||||
LOG.info("Mock video initialized successfully: " + videoClass.getSimpleName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error setting up mock video: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOG.log(Level.SEVERE, "Error setting up mock video: " + e.getMessage(), e);
|
||||
throw new RuntimeException("Failed to set up mock video", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -461,8 +466,9 @@ public class TestUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the test environment to ensure headless mode
|
||||
* and prevent JavaFX initialization.
|
||||
* Configure the test environment to ensure it's set up for headless operation.
|
||||
* This sets various system properties to prevent JavaFX initialization
|
||||
* and places the application in test mode.
|
||||
*/
|
||||
public static void configureTestEnvironment() {
|
||||
// Set system properties to disable JavaFX
|
||||
@ -481,12 +487,13 @@ public class TestUtils {
|
||||
// Prevent JaceApplication from initializing JavaFX toolkit
|
||||
JaceApplication.setupForTesting(true);
|
||||
|
||||
System.out.println("Test environment configured for headless mode");
|
||||
LOG.fine("Test environment configured for headless mode");
|
||||
}
|
||||
|
||||
/**
|
||||
* Special test setup for CpuUnitTest to avoid JavaFX dependencies.
|
||||
* This uses the headless mode and fake RAM to ensure reliable tests.
|
||||
* Sets up the computer for CPU tests.
|
||||
* This includes creating essential components, configuring the motherboard,
|
||||
* setting up mock video, and clearing any cards/peripherals.
|
||||
*/
|
||||
public static void setupForCpuTest() {
|
||||
// Configure test environment first
|
||||
@ -498,7 +505,9 @@ public class TestUtils {
|
||||
|
||||
// Create bare minimum computer setup for CPU testing
|
||||
Emulator.withComputer(c -> {
|
||||
System.out.println("CPU Test Setup - Creating essential components");
|
||||
if (VERBOSE_MODE) {
|
||||
LOG.info("CPU Test Setup - Creating essential components");
|
||||
}
|
||||
|
||||
// Replace any existing RAM with FakeRAM to avoid bank switching issues
|
||||
FakeRAM ram = new FakeRAM();
|
||||
@ -526,7 +535,9 @@ public class TestUtils {
|
||||
throw new IllegalStateException("Video is null after setting it");
|
||||
}
|
||||
|
||||
System.out.println("CPU Test Setup - Mock video initialized: " + c.getVideo().getClass().getSimpleName());
|
||||
if (VERBOSE_MODE) {
|
||||
LOG.info("CPU Test Setup - Mock video initialized: " + c.getVideo().getClass().getSimpleName());
|
||||
}
|
||||
|
||||
// Disable all cards and peripherals
|
||||
// In CPU tests we don't need any cards or peripherals
|
||||
@ -542,10 +553,11 @@ public class TestUtils {
|
||||
// Verify floating bus access works
|
||||
try {
|
||||
byte floatingBus = c.getVideo().getFloatingBus();
|
||||
System.out.println("CPU Test Setup - Floating bus test successful (value: " + floatingBus + ")");
|
||||
if (VERBOSE_MODE) {
|
||||
LOG.info("CPU Test Setup - Floating bus test successful (value: " + floatingBus + ")");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("CPU Test Setup - Floating bus access failed: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOG.log(Level.WARNING, "CPU Test Setup - Floating bus access failed: " + e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
@ -564,12 +576,13 @@ public class TestUtils {
|
||||
if (c.getVideo() == null) {
|
||||
throw new IllegalStateException("Video is null after CPU test setup - this should never happen");
|
||||
}
|
||||
System.out.println("CPU Test Setup - Final verification successful, video = " + c.getVideo().getClass().getSimpleName());
|
||||
if (VERBOSE_MODE) {
|
||||
LOG.info("CPU Test Setup - Final verification successful, video = " + c.getVideo().getClass().getSimpleName());
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("ERROR setting up CPU test environment: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOG.log(Level.SEVERE, "ERROR setting up CPU test environment: " + e.getMessage(), e);
|
||||
throw new RuntimeException("Failed to set up CPU test environment", e);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -32,6 +33,8 @@ public class CpuUnitTest extends AbstractJaceTest {
|
||||
// The goal is to produce an output report that shows the number of tests that passed and failed
|
||||
// The output should be reported in a format compatible with junit but also capture multiple potential failures, not just the first faliure
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(CpuUnitTest.class.getName());
|
||||
|
||||
TypeToken<Collection<TestRecord>> testCollectionType = new TypeToken<Collection<TestRecord>>(){};
|
||||
record TestResult(String source, String testName, boolean passed, String message) {}
|
||||
// Note cycles are a mix of int and string so the parser doesn't like to serialize that into well-formed objects
|
||||
@ -123,11 +126,11 @@ public class CpuUnitTest extends AbstractJaceTest {
|
||||
} else {
|
||||
failedTests.add(result.testName());
|
||||
if (failedTests.size() < 20) {
|
||||
System.err.println(result.source() + ";" + result.testName() + " " + "FAILED" + ": " + result.message());
|
||||
LOG.warning(result.source() + ";" + result.testName() + " " + "FAILED" + ": " + result.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
System.err.println("Passed: " + passed + " Failed: " + failedTests.size());
|
||||
LOG.info("Passed: " + passed + " Failed: " + failedTests.size());
|
||||
if (failedTests.size() > 0) {
|
||||
throw new RuntimeException("One or more tests failed, see log for details");
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import org.junit.Test;
|
||||
|
||||
import jace.AbstractFXTest;
|
||||
import jace.TestUtils;
|
||||
import jace.core.VideoWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
|
||||
// This is mostly to provide execution coverage to catch null pointer or index out of range exceptions
|
||||
@ -51,7 +52,9 @@ public class VideoDHGRTest extends AbstractFXTest {
|
||||
|
||||
@Test
|
||||
public void testGetYOffset() {
|
||||
// Run through all possible combinations of soft switches to ensure the correct Y offset is returned each time
|
||||
// Make sure _80STORE is OFF so PAGE2 works correctly
|
||||
SoftSwitches._80STORE.getSwitch().setState(false);
|
||||
|
||||
SoftSwitches[] switches = {SoftSwitches.HIRES, SoftSwitches.TEXT, SoftSwitches.PAGE2, SoftSwitches._80COL, SoftSwitches.DHIRES, SoftSwitches.MIXED};
|
||||
for (int i=0; i < Math.pow(2.0, switches.length); i++) {
|
||||
String state = "";
|
||||
@ -61,8 +64,29 @@ public class VideoDHGRTest extends AbstractFXTest {
|
||||
}
|
||||
video.configureVideoMode();
|
||||
int address = video.getCurrentWriter().getYOffset(0);
|
||||
int expected = SoftSwitches.TEXT.isOn() || SoftSwitches.HIRES.isOff() ? (SoftSwitches.PAGE2.isOn() ? 0x0800 : 0x0400)
|
||||
: (SoftSwitches.PAGE2.isOn() ? 0x04000 : 0x02000);
|
||||
|
||||
// Calculate expected address based on actual video mode logic
|
||||
boolean page2 = SoftSwitches.PAGE2.isOn() && SoftSwitches._80STORE.isOff();
|
||||
|
||||
int expected;
|
||||
if (SoftSwitches.TEXT.isOn()) {
|
||||
// Text mode (including 80-column text)
|
||||
expected = page2 ? 0x0800 : 0x0400;
|
||||
} else if (SoftSwitches.HIRES.isOff()) {
|
||||
// Lores mode (including double-lores when 80COL is ON)
|
||||
expected = page2 ? 0x0800 : 0x0400;
|
||||
} else {
|
||||
// Hires mode (including double-hires when 80COL and DHIRES are ON)
|
||||
expected = page2 ? 0x04000 : 0x02000;
|
||||
}
|
||||
|
||||
// To help debug the specific failure cases
|
||||
if (expected != address) {
|
||||
System.out.println("Failed case: " + state);
|
||||
System.out.println("Expected: " + expected + ", Actual: " + address);
|
||||
System.out.println("Current Writer: " + video.getCurrentWriter().getClass().getName());
|
||||
}
|
||||
|
||||
assertEquals("Address for mode not correct: " + state, expected, address);
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Test that memory listeners fire appropriately.
|
||||
@ -49,6 +50,7 @@ public class MemoryTest {
|
||||
static RAM128k ram;
|
||||
static String MEMORY_TEST_COMMONS;
|
||||
static String MACHINE_IDENTIFICATION;
|
||||
static final Logger LOG = Logger.getLogger(MemoryTest.class.getName());
|
||||
|
||||
@BeforeClass
|
||||
public static void setupClass() throws IOException, URISyntaxException {
|
||||
@ -536,7 +538,7 @@ public class MemoryTest {
|
||||
resetSoftSwitches();
|
||||
|
||||
for (int softswitch : testCase.softswitches) {
|
||||
System.out.println("Setting softswitch " + Integer.toHexString(softswitch));
|
||||
LOG.fine("Setting softswitch " + Integer.toHexString(softswitch));
|
||||
ram.write(softswitch, (byte) 0, true, false);
|
||||
}
|
||||
for (int i=0; i < testLocations.length; i++) {
|
||||
|
268
src/test/java/jace/terminal/MainModeTest.java
Normal file
268
src/test/java/jace/terminal/MainModeTest.java
Normal file
@ -0,0 +1,268 @@
|
||||
package jace.terminal;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.junit.*;
|
||||
import org.mockito.*;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.PagedMemory;
|
||||
|
||||
public class MainModeTest {
|
||||
// Logger setup
|
||||
private static final Logger LOG = Logger.getLogger(MainModeTest.class.getName());
|
||||
|
||||
// Control test output verbosity via system property:
|
||||
// -Djace.test.debug=true to enable debug logs
|
||||
private static final String DEBUG_PROPERTY = "jace.test.debug";
|
||||
private static final boolean DEBUG_MODE = Boolean.getBoolean(DEBUG_PROPERTY);
|
||||
|
||||
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
|
||||
private final PrintStream originalOut = System.out;
|
||||
|
||||
private TestableMainMode mainMode;
|
||||
private JaceTerminal mockTerminal;
|
||||
private EmulatorInterface mockEmulator;
|
||||
private Computer mockComputer;
|
||||
private RAM128k mockRam;
|
||||
private MOS65C02 mockCpu;
|
||||
private PagedMemory mockPagedMemory;
|
||||
|
||||
// Track memory writes
|
||||
private byte[] memoryValues = new byte[65536];
|
||||
|
||||
// Create a testable subclass that allows us to override the getCPU method
|
||||
static class TestableMainMode extends MainMode {
|
||||
private MOS65C02 testCpu;
|
||||
|
||||
public TestableMainMode(JaceTerminal terminal) {
|
||||
super(terminal);
|
||||
}
|
||||
|
||||
public void setTestCpu(MOS65C02 cpu) {
|
||||
this.testCpu = cpu;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MOS65C02 getCPU() {
|
||||
return testCpu != null ? testCpu : super.getCPU();
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setupLogging() {
|
||||
// Configure logging based on system property
|
||||
Level logLevel = DEBUG_MODE ? Level.FINE : Level.INFO;
|
||||
LOG.setLevel(logLevel);
|
||||
|
||||
// Ensure handlers use our level
|
||||
Logger rootLogger = Logger.getLogger("");
|
||||
for (Handler handler : rootLogger.getHandlers()) {
|
||||
if (handler instanceof ConsoleHandler) {
|
||||
handler.setLevel(logLevel);
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
LOG.info("Debug mode enabled - verbose output will be displayed");
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Setup mocks
|
||||
mockTerminal = mock(JaceTerminal.class);
|
||||
mockEmulator = mock(EmulatorInterface.class);
|
||||
mockComputer = mock(Computer.class);
|
||||
mockRam = mock(RAM128k.class);
|
||||
mockCpu = mock(MOS65C02.class);
|
||||
mockPagedMemory = mock(PagedMemory.class);
|
||||
|
||||
// Wire mocks together
|
||||
when(mockTerminal.getOutput()).thenReturn(new PrintStream(outContent));
|
||||
when(mockTerminal.getEmulator()).thenReturn(mockEmulator);
|
||||
|
||||
// Set up the computer mock to return RAM and CPU
|
||||
when(mockEmulator.withComputer(any(), any())).thenAnswer(invocation -> {
|
||||
Function<Computer, Object> function = invocation.getArgument(0);
|
||||
return function.apply(mockComputer);
|
||||
});
|
||||
when(mockComputer.getMemory()).thenReturn(mockRam);
|
||||
when(mockComputer.getCpu()).thenReturn(mockCpu);
|
||||
|
||||
// Create MainMode instance with mocked terminal
|
||||
mainMode = new TestableMainMode(mockTerminal);
|
||||
|
||||
LOG.fine("Test setup complete");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
outContent.reset();
|
||||
LOG.fine("Test cleaned up");
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to log test output when in debug mode
|
||||
*/
|
||||
private void logOutput(String output) {
|
||||
if (DEBUG_MODE) {
|
||||
LOG.fine("Command output:\n" + output);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMainModeName() {
|
||||
// Test that MainMode returns the correct name
|
||||
assertEquals("Main", mainMode.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMainModePrompt() {
|
||||
// Test that MainMode returns the correct prompt
|
||||
assertEquals("JACE> ", mainMode.getPrompt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHelpCommand() {
|
||||
// Test the help output
|
||||
mainMode.printHelp();
|
||||
|
||||
// Get the output
|
||||
String output = outContent.toString();
|
||||
logOutput(output);
|
||||
|
||||
// Verify the output contains expected help text
|
||||
assertTrue("Help text should list available commands",
|
||||
output.contains("Available commands:") &&
|
||||
output.contains("monitor") &&
|
||||
output.contains("assembler"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandHelp() {
|
||||
// Test displaying help for a specific command
|
||||
boolean result = mainMode.printCommandHelp("monitor");
|
||||
|
||||
// Get the output
|
||||
String output = outContent.toString();
|
||||
logOutput(output);
|
||||
|
||||
// Verify the output contains expected help for the monitor command
|
||||
assertTrue("Command help should be displayed",
|
||||
output.contains("monitor") &&
|
||||
result == true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMonitorCommand() {
|
||||
// Test the monitor command
|
||||
boolean result = mainMode.processCommand("monitor");
|
||||
|
||||
// Verify setMode was called with "monitor"
|
||||
verify(mockTerminal).setMode("monitor");
|
||||
assertTrue("Command should be processed successfully", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAssemblerCommand() {
|
||||
// Test the assembler command
|
||||
boolean result = mainMode.processCommand("assembler");
|
||||
|
||||
// Verify setMode was called with "assembler"
|
||||
verify(mockTerminal).setMode("assembler");
|
||||
assertTrue("Command should be processed successfully", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDebuggerCommand() {
|
||||
// Test the debugger command
|
||||
boolean result = mainMode.processCommand("debugger");
|
||||
|
||||
// Verify setMode was called with "debugger"
|
||||
verify(mockTerminal).setMode("debugger");
|
||||
assertTrue("Command should be processed successfully", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandAliases() {
|
||||
// Test the monitor command alias
|
||||
boolean result = mainMode.processCommand("m");
|
||||
|
||||
// Verify setMode was called with "monitor"
|
||||
verify(mockTerminal).setMode("monitor");
|
||||
assertTrue("Command should be processed successfully", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegistersCommand() {
|
||||
LOG.fine("Starting testRegistersCommand");
|
||||
|
||||
// Set up CPU with register values
|
||||
mockCpu.A = 0xAA;
|
||||
mockCpu.X = 0xBB;
|
||||
mockCpu.Y = 0xCC;
|
||||
mockCpu.STACK = 0xDD;
|
||||
when(mockCpu.getProgramCounter()).thenReturn(0xEEFF);
|
||||
|
||||
// Set up CPU flags
|
||||
mockCpu.Z = true;
|
||||
mockCpu.C = 1;
|
||||
mockCpu.I = true;
|
||||
mockCpu.D = true;
|
||||
|
||||
// Use our testable subclass to directly set the CPU
|
||||
mainMode.setTestCpu(mockCpu);
|
||||
|
||||
// Test the registers command
|
||||
boolean result = mainMode.processCommand("registers");
|
||||
|
||||
// Verify the result
|
||||
assertTrue("Command should be processed successfully", result);
|
||||
|
||||
// Get the output
|
||||
String output = outContent.toString();
|
||||
logOutput(output);
|
||||
|
||||
// Verify the output contains register values with the correct format
|
||||
assertTrue("Output should include register values",
|
||||
output.contains("CPU Registers:") &&
|
||||
output.contains("A: $") &&
|
||||
output.contains("X: $") &&
|
||||
output.contains("Y: $") &&
|
||||
output.contains("PC: $") &&
|
||||
output.contains("S: $") &&
|
||||
output.contains("Flags:"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownCommand() {
|
||||
// Test an unknown command
|
||||
boolean result = mainMode.processCommand("unknowncommand");
|
||||
|
||||
// Verify the result is false (command not recognized)
|
||||
assertFalse("Unknown command should return false", result);
|
||||
|
||||
// Verify error message is displayed
|
||||
String output = outContent.toString();
|
||||
logOutput(output);
|
||||
assertTrue("Output should include error message", output.contains("Unknown command"));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user