mirror of
https://github.com/badvision/jace.git
synced 2024-11-28 10:52:33 +00:00
restore test coverage
This commit is contained in:
parent
dafd4453eb
commit
7f598bcdc1
16
src/test/java/jace/AbstractFXTest.java
Normal file
16
src/test/java/jace/AbstractFXTest.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package jace;
|
||||||
|
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
|
||||||
|
public abstract class AbstractFXTest {
|
||||||
|
public static boolean fxInitialized = false;
|
||||||
|
@BeforeClass
|
||||||
|
public static void initJfxRuntime() {
|
||||||
|
if (!fxInitialized) {
|
||||||
|
fxInitialized = true;
|
||||||
|
Platform.startup(() -> {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
src/test/java/jace/ProgramException.java
Normal file
44
src/test/java/jace/ProgramException.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package jace;
|
||||||
|
|
||||||
|
import jace.apple2e.MOS65C02;
|
||||||
|
|
||||||
|
public class ProgramException extends Exception {
|
||||||
|
int breakpointNumber;
|
||||||
|
String processorStats;
|
||||||
|
String programLocation;
|
||||||
|
public ProgramException(String message, int breakpointNumber) {
|
||||||
|
super(message.replaceAll("<<.*>>", ""));
|
||||||
|
this.breakpointNumber = breakpointNumber;
|
||||||
|
this.processorStats = Emulator.withComputer(c-> ((MOS65C02) c.getCpu()).getState(), "N/A");
|
||||||
|
// Look for a string pattern <<programLocation>> in the message and extract if found
|
||||||
|
int start = message.indexOf("<<");
|
||||||
|
if (start != -1) {
|
||||||
|
int end = message.indexOf(">>", start);
|
||||||
|
if (end != -1) {
|
||||||
|
this.programLocation = message.substring(start + 2, end);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.programLocation = "N/A";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int getBreakpointNumber() {
|
||||||
|
return breakpointNumber;
|
||||||
|
}
|
||||||
|
public String getProcessorStats() {
|
||||||
|
return processorStats;
|
||||||
|
}
|
||||||
|
public String getProgramLocation() {
|
||||||
|
return programLocation;
|
||||||
|
}
|
||||||
|
public String getMessage() {
|
||||||
|
String message = super.getMessage();
|
||||||
|
if (getBreakpointNumber() >= 0) {
|
||||||
|
message += " at breakpoint " + getBreakpointNumber();
|
||||||
|
}
|
||||||
|
message += " \nStats: " + getProcessorStats();
|
||||||
|
if (getProgramLocation() != null) {
|
||||||
|
message += " \n at " + getProgramLocation();
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
465
src/test/java/jace/TestProgram.java
Normal file
465
src/test/java/jace/TestProgram.java
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
package jace;
|
||||||
|
|
||||||
|
import static jace.TestUtils.runAssemblyCode;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import jace.apple2e.Full65C02Test;
|
||||||
|
import jace.apple2e.MOS65C02;
|
||||||
|
import jace.core.Computer;
|
||||||
|
|
||||||
|
public class TestProgram {
|
||||||
|
// Tests could be run in any order so it is really important that all registers/flags are preserved!
|
||||||
|
public static enum Flag {
|
||||||
|
CARRY_SET("BCS +", "Carry should be set"),
|
||||||
|
CARRY_CLEAR("BCC +", "Carry should be clear"),
|
||||||
|
ZERO_SET("BEQ +", "Zero should be set"),
|
||||||
|
IS_ZERO("BEQ +", "Zero should be clear"),
|
||||||
|
ZERO_CLEAR("BNE +", "Zero should be clear"),
|
||||||
|
NOT_ZERO("BNE +", "Zero should be clear"),
|
||||||
|
NEGATIVE("BMI +", "Negative should be set"),
|
||||||
|
POSITIVE("BPL +", "Negative should be clear"),
|
||||||
|
OVERFLOW_SET("BVS +", "Overflow should be set"),
|
||||||
|
OVERFLOW_CLEAR("BVC +", "Overflow should be clear"),
|
||||||
|
DECIMAL_SET("""
|
||||||
|
PHP
|
||||||
|
PHA
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
BIT #%00001000
|
||||||
|
BEQ ++
|
||||||
|
PLA
|
||||||
|
PLP
|
||||||
|
BRA +
|
||||||
|
++ ; Error
|
||||||
|
""", "Decimal should be set"),
|
||||||
|
DECIMAL_CLEAR("""
|
||||||
|
PHP
|
||||||
|
PHA
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
BIT #%00001000
|
||||||
|
BNE ++
|
||||||
|
PLA
|
||||||
|
PLP
|
||||||
|
BRA +
|
||||||
|
++ ; Error
|
||||||
|
""", "Decimal should be clear"),
|
||||||
|
INTERRUPT_SET("""
|
||||||
|
PHP
|
||||||
|
PHA
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
BIT #%00000100
|
||||||
|
BEQ ++
|
||||||
|
PLA
|
||||||
|
PLP
|
||||||
|
BRA +
|
||||||
|
++ ; Error
|
||||||
|
""", "Interrupt should be set"),
|
||||||
|
INTERRUPT_CLEAR("""
|
||||||
|
PHP
|
||||||
|
PHA
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
BIT #%00000100
|
||||||
|
BNE ++
|
||||||
|
PLA
|
||||||
|
PLP
|
||||||
|
BRA +
|
||||||
|
++ ; Error
|
||||||
|
""", "Interrupt should be clear"),;
|
||||||
|
String code;
|
||||||
|
String condition;
|
||||||
|
Flag(String code, String condition) {
|
||||||
|
this.code = code;
|
||||||
|
this.condition = condition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<String> lines = new ArrayList<>();
|
||||||
|
Consumer<Byte> timerHandler;
|
||||||
|
Consumer<Byte> tickCountHandler;
|
||||||
|
Consumer<Byte> errorHandler;
|
||||||
|
Consumer<Byte> stopHandler;
|
||||||
|
Consumer<Byte> progressHandler;
|
||||||
|
Consumer<Byte> traceHandler;
|
||||||
|
|
||||||
|
public TestProgram() {
|
||||||
|
lines.add(TestProgram.UNIT_TEST_MACROS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestProgram(String line1) {
|
||||||
|
this();
|
||||||
|
lines.add(line1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestProgram(String line1, int tickCount) {
|
||||||
|
this();
|
||||||
|
assertTimed(line1, tickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
int tickCount = 0;
|
||||||
|
|
||||||
|
int timerStart = 0;
|
||||||
|
int timerLastMark = 0;
|
||||||
|
int timerEnd = 0;
|
||||||
|
int timerLastEllapsed = 0;
|
||||||
|
int lastBreakpoint = -1;
|
||||||
|
int maxTicks = 10000;
|
||||||
|
boolean programCompleted = false;
|
||||||
|
boolean programReportedError = false;
|
||||||
|
boolean programRunning = false;
|
||||||
|
List<String> errors = new ArrayList<>();
|
||||||
|
List<Integer> timings = new ArrayList<>();
|
||||||
|
|
||||||
|
ProgramException lastError = null;
|
||||||
|
public static String UNIT_TEST_MACROS = """
|
||||||
|
!cpu 65c02
|
||||||
|
!macro extendedOp .code, .val {!byte $FC, .code, .val}
|
||||||
|
!macro startTimer {+extendedOp $10, $80}
|
||||||
|
!macro markTimer {+extendedOp $10, $81}
|
||||||
|
!macro stopTimer {+extendedOp $10, $82}
|
||||||
|
!macro assertTicks .ticks {+extendedOp $11, .ticks}
|
||||||
|
!macro stop .p1, .p2 {
|
||||||
|
+extendedOp .p1, .p2
|
||||||
|
+extendedOp $13, $ff
|
||||||
|
+traceOff
|
||||||
|
-
|
||||||
|
JMP -
|
||||||
|
}
|
||||||
|
!macro throwError .errorCode {+stop $12, .errorCode}
|
||||||
|
!macro recordError .errorCode {+extendedOp $12, .errorCode}
|
||||||
|
!macro success {+stop $14, $ff}
|
||||||
|
!macro breakpoint .num {+extendedOp $14, .num}
|
||||||
|
!macro traceOn {+extendedOp $15, $01}
|
||||||
|
!macro traceOff {+extendedOp $15, $00}
|
||||||
|
!macro resetRegs {
|
||||||
|
LDA #0
|
||||||
|
LDX #0
|
||||||
|
LDY #0
|
||||||
|
PHA
|
||||||
|
PLP
|
||||||
|
TXS
|
||||||
|
PHP
|
||||||
|
}
|
||||||
|
+resetRegs
|
||||||
|
""";
|
||||||
|
public static String INDENT = " ";
|
||||||
|
|
||||||
|
public void attach() {
|
||||||
|
timerHandler = this::handleTimer;
|
||||||
|
tickCountHandler = this::countAndCompareTicks;
|
||||||
|
errorHandler = this::recordError;
|
||||||
|
stopHandler = b->stop();
|
||||||
|
progressHandler = this::recordProgress;
|
||||||
|
traceHandler = this::handleTrace;
|
||||||
|
|
||||||
|
Emulator.withComputer(c-> {
|
||||||
|
MOS65C02 cpu = (MOS65C02) c.getCpu();
|
||||||
|
cpu.registerExtendedCommandHandler(0x10, timerHandler);
|
||||||
|
cpu.registerExtendedCommandHandler(0x11, tickCountHandler);
|
||||||
|
cpu.registerExtendedCommandHandler(0x12, errorHandler);
|
||||||
|
cpu.registerExtendedCommandHandler(0x13, stopHandler);
|
||||||
|
cpu.registerExtendedCommandHandler(0x14, progressHandler);
|
||||||
|
cpu.registerExtendedCommandHandler(0x15, traceHandler);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void detach() {
|
||||||
|
Emulator.withComputer(c-> {
|
||||||
|
MOS65C02 cpu = (MOS65C02) c.getCpu();
|
||||||
|
cpu.unregisterExtendedCommandHandler(timerHandler);
|
||||||
|
cpu.unregisterExtendedCommandHandler(tickCountHandler);
|
||||||
|
cpu.unregisterExtendedCommandHandler(errorHandler);
|
||||||
|
cpu.unregisterExtendedCommandHandler(stopHandler);
|
||||||
|
cpu.unregisterExtendedCommandHandler(progressHandler);
|
||||||
|
cpu.unregisterExtendedCommandHandler(traceHandler);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTimer(byte val) {
|
||||||
|
switch (val) {
|
||||||
|
case (byte)0x80:
|
||||||
|
timerStart = tickCount;
|
||||||
|
timerLastMark = tickCount;
|
||||||
|
break;
|
||||||
|
case (byte)0x82:
|
||||||
|
timerEnd = tickCount;
|
||||||
|
// Fall through
|
||||||
|
case (byte)0x81:
|
||||||
|
// Don't count the time spent on the timer commands!
|
||||||
|
timerLastEllapsed = (tickCount - timerLastMark) - 4;
|
||||||
|
timerLastMark = tickCount;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
lastError = new ProgramException("Unknown timer command %s".formatted(val), lastBreakpoint);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void countAndCompareTicks(byte val) {
|
||||||
|
int expectedTickCountNum = val;
|
||||||
|
int expectedTickCount = timings.get(expectedTickCountNum);
|
||||||
|
String errorMessage = lastError != null ? lastError.getProgramLocation() : "";
|
||||||
|
if (timerLastEllapsed != expectedTickCount) {
|
||||||
|
lastError = new ProgramException("Expected %s ticks, instead counted %s <<%s>>".formatted(expectedTickCount, timerLastEllapsed, errorMessage), lastBreakpoint);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordError(byte v) {
|
||||||
|
int val = v & 0x0ff;
|
||||||
|
|
||||||
|
if (val >= 0 && val < errors.size()) {
|
||||||
|
lastError = new ProgramException(errors.get(val), lastBreakpoint);
|
||||||
|
} else if (val == 255) {
|
||||||
|
lastError = null;
|
||||||
|
} else {
|
||||||
|
lastError = new ProgramException("Error %s".formatted(val), lastBreakpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestProgram defineError(String error) {
|
||||||
|
errors.add(error);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stop() {
|
||||||
|
programReportedError = lastError != null;
|
||||||
|
programRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordProgress(byte val) {
|
||||||
|
if (val == (byte)0xff) {
|
||||||
|
programCompleted = true;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
lastBreakpoint = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTrace(byte val) {
|
||||||
|
if (val == (byte)0x01) {
|
||||||
|
System.out.println("Trace on");
|
||||||
|
Full65C02Test.cpu.setTraceEnabled(true);
|
||||||
|
} else {
|
||||||
|
System.out.println("Trace off");
|
||||||
|
Full65C02Test.cpu.setTraceEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestProgram assertTimed(String line, int ticks) {
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
String caller = stackTrace[2].toString();
|
||||||
|
int errNum = errors.size();
|
||||||
|
errors.add("Expected %s ticks for %s <<%s>>".formatted(ticks, line, caller));
|
||||||
|
int timingNum = timings.size();
|
||||||
|
timings.add(ticks);
|
||||||
|
lines.add(INDENT+"+startTimer");
|
||||||
|
lines.add(line);
|
||||||
|
lines.add(INDENT+"+markTimer");
|
||||||
|
lines.add(INDENT+"+recordError %s".formatted(errNum));
|
||||||
|
lines.add(INDENT+"+assertTicks %s ; Check for %s cycles".formatted(timingNum, ticks));
|
||||||
|
lines.add(INDENT+"+recordError %s".formatted(255));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestProgram add(String line) {
|
||||||
|
lines.add(line);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestProgram assertFlags(TestProgram.Flag... flags) {
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
String caller = stackTrace[2].toString();
|
||||||
|
for (TestProgram.Flag flag : flags)
|
||||||
|
_test(TestProgram.INDENT + flag.code, flag.condition + "<<" + caller + ">>");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test the A register for a specific value */
|
||||||
|
public TestProgram assertA(int val) {
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
String caller = stackTrace[2].toString();
|
||||||
|
String condition = "A != %s <<%s>>".formatted(Integer.toHexString(val), caller);
|
||||||
|
_test("""
|
||||||
|
PHP
|
||||||
|
CMP #%s
|
||||||
|
BNE ++
|
||||||
|
PLP
|
||||||
|
BRA +
|
||||||
|
++ ; Error
|
||||||
|
""".formatted(val), condition + " in " + caller);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test X register for a specific value */
|
||||||
|
public TestProgram assertX(int val) {
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
String caller = stackTrace[2].toString();
|
||||||
|
String condition = "X != %s <<%s>>".formatted(Integer.toHexString(val), caller);
|
||||||
|
_test("""
|
||||||
|
PHP
|
||||||
|
CPX #%s
|
||||||
|
BNE ++
|
||||||
|
PLP
|
||||||
|
BRA +
|
||||||
|
++ ; Error
|
||||||
|
""".formatted(val), condition);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test Y register for a specific value */
|
||||||
|
public TestProgram assertY(int val) {
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
String caller = stackTrace[2].toString();
|
||||||
|
String condition = "Y != %s <<%s>>".formatted(Integer.toHexString(val), caller);
|
||||||
|
_test("""
|
||||||
|
PHP
|
||||||
|
CPY #%s
|
||||||
|
BNE ++
|
||||||
|
PLP
|
||||||
|
BRA +
|
||||||
|
++ ; Error
|
||||||
|
""".formatted(val), condition);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test an address for a specific value. If the value is incorrect, leave it in A for inspection */
|
||||||
|
public TestProgram assertAddrVal(int address, int val) {
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
String caller = stackTrace[2].toString();
|
||||||
|
String condition = "$%s != %s <<%s>>".formatted(Integer.toHexString(address), Integer.toHexString(val), caller);
|
||||||
|
_test("""
|
||||||
|
PHP
|
||||||
|
PHA
|
||||||
|
LDA $%s
|
||||||
|
CMP #%s
|
||||||
|
BNE ++
|
||||||
|
PLA
|
||||||
|
PLP
|
||||||
|
BRA +
|
||||||
|
++ ; Error
|
||||||
|
LDA $%s
|
||||||
|
""".formatted(Integer.toHexString(address), val, val), condition);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use provided code to test a condition. If successful it should jump or branch to +
|
||||||
|
* If unsuccessful the error condition will be reported.
|
||||||
|
*
|
||||||
|
* @param code The code to test
|
||||||
|
* @param condition The condition to report if the test fails
|
||||||
|
*/
|
||||||
|
public TestProgram test(String code, String condition) {
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
String caller = stackTrace[2].toString();
|
||||||
|
_test(code, condition + "<<" + caller + ">>");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _test(String code, String condition) {
|
||||||
|
int errorNum = errors.size();
|
||||||
|
errors.add(condition);
|
||||||
|
lines.add("""
|
||||||
|
; << Test %s
|
||||||
|
%s
|
||||||
|
+throwError %s
|
||||||
|
+ ; >> Test """.formatted(condition, code, errorNum));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note the current breakpoint, helpful in understanding error locations
|
||||||
|
*
|
||||||
|
* @param num Error number to report in any error that occurs
|
||||||
|
*/
|
||||||
|
public TestProgram breakpoint(int num) {
|
||||||
|
lines.add(INDENT + "+breakpoint %s".formatted(num));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the registers to 0, clear the stack, and clear flags
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public TestProgram resetRegisters() {
|
||||||
|
lines.add(INDENT + "+resetRegs");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on or off tracing
|
||||||
|
*
|
||||||
|
* @param state True to turn on tracing, false to turn it off
|
||||||
|
*/
|
||||||
|
public TestProgram setTrace(boolean state) {
|
||||||
|
lines.add(INDENT +"+trace%s".formatted(state ? "On" : "Off"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the program as unassembled code
|
||||||
|
*
|
||||||
|
* @return The program as a string
|
||||||
|
*/
|
||||||
|
public String build() {
|
||||||
|
lines.add(INDENT +"+success");
|
||||||
|
return String.join("\n", lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the program for a specific number of ticks, or until it completes (whichever comes first)
|
||||||
|
*
|
||||||
|
* @param ticks The number of ticks to run the program
|
||||||
|
* @throws ProgramException If the program reports an error
|
||||||
|
*/
|
||||||
|
public void runForTicks(int ticks) throws ProgramException {
|
||||||
|
this.maxTicks = ticks;
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the program until it completes, reports an error, or reaches the maximum number of ticks
|
||||||
|
*
|
||||||
|
* @throws ProgramException If the program reports an error
|
||||||
|
*/
|
||||||
|
public void run() throws ProgramException {
|
||||||
|
Computer computer = Emulator.withComputer(c->c, null);
|
||||||
|
MOS65C02 cpu = (MOS65C02) computer.getCpu();
|
||||||
|
|
||||||
|
attach();
|
||||||
|
programRunning = true;
|
||||||
|
String program = build();
|
||||||
|
// We have to run the program more carefully so just load it first
|
||||||
|
try {
|
||||||
|
runAssemblyCode(program, 0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ProgramException(e.getMessage() + "\n" + program, -1);
|
||||||
|
}
|
||||||
|
cpu.resume();
|
||||||
|
try {
|
||||||
|
for (int i=0; i < maxTicks; i++) {
|
||||||
|
cpu.doTick();
|
||||||
|
tickCount++;
|
||||||
|
if (programReportedError) {
|
||||||
|
throw lastError;
|
||||||
|
}
|
||||||
|
if (!programRunning) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cpu.suspend();
|
||||||
|
detach();
|
||||||
|
}
|
||||||
|
assertFalse("Test reported an error", programReportedError);
|
||||||
|
assertFalse("Program never ended fully after " + tickCount + " ticks; got to breakpoint " + lastBreakpoint, programRunning);
|
||||||
|
assertTrue("Test did not complete fully", programCompleted);
|
||||||
|
}
|
||||||
|
}
|
83
src/test/java/jace/TestUtils.java
Normal file
83
src/test/java/jace/TestUtils.java
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 org.badvision.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package jace;
|
||||||
|
|
||||||
|
import jace.core.CPU;
|
||||||
|
import jace.core.Computer;
|
||||||
|
import jace.core.Device;
|
||||||
|
import jace.core.Utility;
|
||||||
|
import jace.ide.HeadlessProgram;
|
||||||
|
import jace.ide.Program;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author brobert
|
||||||
|
*/
|
||||||
|
public class TestUtils {
|
||||||
|
private TestUtils() {
|
||||||
|
// Utility class has no constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initComputer() {
|
||||||
|
Utility.setHeadlessMode(true);
|
||||||
|
Emulator.withComputer(Computer::reconfigure);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assemble(String code, int addr) throws Exception {
|
||||||
|
runAssemblyCode(code, addr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runAssemblyCode(String code, int ticks) throws Exception {
|
||||||
|
runAssemblyCode(code, 0x6000, ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runAssemblyCode(String code, int addr, int ticks) throws Exception {
|
||||||
|
CPU cpu = Emulator.withComputer(c->c.getCpu(), null);
|
||||||
|
HeadlessProgram program = new HeadlessProgram(Program.DocumentType.assembly);
|
||||||
|
program.setValue("*=$"+Integer.toHexString(addr)+"\n "+code+"\n NOP\n RTS");
|
||||||
|
program.execute();
|
||||||
|
if (ticks > 0) {
|
||||||
|
cpu.resume();
|
||||||
|
for (int i=0; i < ticks; i++) {
|
||||||
|
cpu.doTick();
|
||||||
|
}
|
||||||
|
cpu.suspend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Device createSimpleDevice(Runnable r, String name) {
|
||||||
|
return new Device() {
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
r.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getShortName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reconfigure() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDeviceName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
74
src/test/java/jace/apple2e/CycleCountTest.java
Normal file
74
src/test/java/jace/apple2e/CycleCountTest.java
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 org.badvision.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package jace.apple2e;
|
||||||
|
|
||||||
|
import static jace.TestUtils.initComputer;
|
||||||
|
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.ProgramException;
|
||||||
|
import jace.TestProgram;
|
||||||
|
import jace.core.SoundMixer;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* More advanced cycle counting tests. These help ensure CPU runs correctly so things
|
||||||
|
* like vapor lock and speaker sound work as expected.
|
||||||
|
* @author brobert
|
||||||
|
*/
|
||||||
|
public class CycleCountTest {
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setupClass() {
|
||||||
|
initComputer();
|
||||||
|
SoundMixer.MUTE = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composite test which ensures the speaker beep is the right pitch.
|
||||||
|
* Test that the wait routine for beep cycles correctly.
|
||||||
|
* Calling WAIT with A=#$c (12) should take 535 cycles
|
||||||
|
* according to the tech ref notes: =1/2*(26+27*A+5*A^2) where A = 12 (0x0c)
|
||||||
|
* The BELL routine has an additional 12 cycles per iteration plus 1 extra cycle in the first iteration.
|
||||||
|
* e.g. 2 iterations take 1093 cycles
|
||||||
|
*
|
||||||
|
* @throws ProgramException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDirectBeeperCycleCount() throws ProgramException {
|
||||||
|
new TestProgram("""
|
||||||
|
SPKR = $C030
|
||||||
|
jmp BELL
|
||||||
|
WAIT sec
|
||||||
|
WAIT2 pha
|
||||||
|
WAIT3 sbc #$01
|
||||||
|
bne WAIT3
|
||||||
|
pla
|
||||||
|
sbc #$01
|
||||||
|
bne WAIT2
|
||||||
|
rts
|
||||||
|
BELL +markTimer
|
||||||
|
ldy #$02
|
||||||
|
BELL2 lda #$0c
|
||||||
|
jsr WAIT
|
||||||
|
lda SPKR
|
||||||
|
dey
|
||||||
|
bne BELL2
|
||||||
|
""", 1093).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
804
src/test/java/jace/apple2e/Full65C02Test.java
Normal file
804
src/test/java/jace/apple2e/Full65C02Test.java
Normal file
@ -0,0 +1,804 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Brendan Robert
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package jace.apple2e;
|
||||||
|
|
||||||
|
import static jace.TestProgram.Flag.CARRY_CLEAR;
|
||||||
|
import static jace.TestProgram.Flag.CARRY_SET;
|
||||||
|
import static jace.TestProgram.Flag.DECIMAL_CLEAR;
|
||||||
|
import static jace.TestProgram.Flag.DECIMAL_SET;
|
||||||
|
import static jace.TestProgram.Flag.INTERRUPT_CLEAR;
|
||||||
|
import static jace.TestProgram.Flag.INTERRUPT_SET;
|
||||||
|
import static jace.TestProgram.Flag.IS_ZERO;
|
||||||
|
import static jace.TestProgram.Flag.NEGATIVE;
|
||||||
|
import static jace.TestProgram.Flag.NOT_ZERO;
|
||||||
|
import static jace.TestProgram.Flag.OVERFLOW_CLEAR;
|
||||||
|
import static jace.TestProgram.Flag.OVERFLOW_SET;
|
||||||
|
import static jace.TestProgram.Flag.POSITIVE;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.Emulator;
|
||||||
|
import jace.ProgramException;
|
||||||
|
import jace.TestProgram;
|
||||||
|
import jace.TestUtils;
|
||||||
|
import jace.core.Computer;
|
||||||
|
import jace.core.RAMEvent.TYPE;
|
||||||
|
import jace.core.SoundMixer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic test functionality to assert correct 6502 decode and execution.
|
||||||
|
*
|
||||||
|
* @author blurry
|
||||||
|
*/
|
||||||
|
public class Full65C02Test {
|
||||||
|
|
||||||
|
static Computer computer;
|
||||||
|
public static MOS65C02 cpu;
|
||||||
|
static RAM128k ram;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setupClass() {
|
||||||
|
TestUtils.initComputer();
|
||||||
|
SoundMixer.MUTE = true;
|
||||||
|
computer = Emulator.withComputer(c->c, null);
|
||||||
|
cpu = (MOS65C02) computer.getCpu();
|
||||||
|
ram = (RAM128k) computer.getMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void teardownClass() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
computer.pause();
|
||||||
|
cpu.clearState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
/* ADC: All CPU flags/modes */
|
||||||
|
public void testAdditionCPUFlags() throws ProgramException {
|
||||||
|
new TestProgram()
|
||||||
|
// Add 1 w/o carry; 1+1 = 2
|
||||||
|
.assertTimed("ADC #1", 2)
|
||||||
|
.assertA(1)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||||
|
// Add 1 w/ carry 1+0+c = 2
|
||||||
|
.add("SEC")
|
||||||
|
.assertTimed("ADC #0", 2)
|
||||||
|
.assertA(2)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||||
|
// Decimal: ADD 8 w/o carry; 2+8 = 10
|
||||||
|
.add("SED")
|
||||||
|
.assertTimed("ADC #8", 3)
|
||||||
|
.assertA(0x10)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||||
|
// Decimal: ADD 9 w/ carry; 10+9+c = 20
|
||||||
|
.add("SEC")
|
||||||
|
.assertTimed("ADC #09", 3)
|
||||||
|
.assertA(0x20)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||||
|
// Decimal: Overflow check; 20 + 99 + C = 20 (carry, no overflow)
|
||||||
|
.add("""
|
||||||
|
SED
|
||||||
|
SEC
|
||||||
|
LDA #$20
|
||||||
|
ADC #$99
|
||||||
|
""")
|
||||||
|
.assertA(0x20)
|
||||||
|
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||||
|
// Decimal: Overflow check; 20 + 64 + C = 85 (overflow)
|
||||||
|
.add("ADC #$64")
|
||||||
|
.assertA(0x85)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_SET, NEGATIVE)
|
||||||
|
// Overflow check; 0x7F + 0x01 = 0x80 (overflow)
|
||||||
|
.add("CLD")
|
||||||
|
.add("LDA #$7F")
|
||||||
|
.assertTimed("ADC #1", 2)
|
||||||
|
.assertA(0x80)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_SET, NEGATIVE)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
/* ADC: All addressing modes -- these are used in many opcodes so we mostly just need to test here */
|
||||||
|
public void testAdditionAddressingModes() throws ProgramException {
|
||||||
|
// Start test by filling zero page and $1000...$10FF with 0...255
|
||||||
|
new TestProgram("""
|
||||||
|
LDX #0
|
||||||
|
- TXA
|
||||||
|
STA $00,X
|
||||||
|
STA $1000,X
|
||||||
|
INX
|
||||||
|
BNE -
|
||||||
|
LDA #1
|
||||||
|
""")
|
||||||
|
// 1: AB,X
|
||||||
|
.add("LDX #$7F")
|
||||||
|
.assertTimed("ADC $1000,X", 4)
|
||||||
|
.assertA(0x80)
|
||||||
|
.assertFlags(OVERFLOW_SET, CARRY_CLEAR, NEGATIVE)
|
||||||
|
// 2: AB,Y
|
||||||
|
.add("LDY #$20")
|
||||||
|
.assertTimed("ADC $1000,Y", 4)
|
||||||
|
.assertA(0xA0)
|
||||||
|
.assertFlags(OVERFLOW_CLEAR, CARRY_CLEAR, NEGATIVE)
|
||||||
|
// 3: izp ($09) == 0x100f ==> A+=f
|
||||||
|
.assertTimed("ADC ($0f)", 5)
|
||||||
|
.assertA(0xAF)
|
||||||
|
// 4: izx ($00,x) where X=f = 0x100f ==> A+=f
|
||||||
|
.add("LDX #$0F")
|
||||||
|
.assertTimed("ADC ($00,x)", 6)
|
||||||
|
.assertA(0xBE)
|
||||||
|
// 5: izy ($00),y where Y=20 = 0x102F ==> A+=2F
|
||||||
|
.add("LDY #$21")
|
||||||
|
.assertTimed("ADC ($0F),y", 5)
|
||||||
|
.assertA(0xEE)
|
||||||
|
// 6: zpx $00,x where X=10 ==> A+=10
|
||||||
|
.add("LDX #$10")
|
||||||
|
.assertTimed("ADC $00,x", 4)
|
||||||
|
.assertA(0xFE)
|
||||||
|
// 7: zp $01 ==> A+=01
|
||||||
|
.assertTimed("ADC $01", 3)
|
||||||
|
.assertA(0xFF)
|
||||||
|
// 8: abs $1001 ==> A+=01
|
||||||
|
.assertTimed("ADC $1001", 4)
|
||||||
|
.assertA(0x00)
|
||||||
|
.assertFlags(IS_ZERO, OVERFLOW_CLEAR, CARRY_SET, POSITIVE)
|
||||||
|
// Now check boundary conditions on indexed addressing for timing differences
|
||||||
|
.add("LDX #$FF")
|
||||||
|
.assertTimed("ADC $10FF,X", 5)
|
||||||
|
.add("LDY #$FF")
|
||||||
|
.assertTimed("ADC $10FF,Y", 5)
|
||||||
|
.assertTimed("ADC ($0F),Y", 6)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
/* ABS: All CPU flags/modes */
|
||||||
|
public void testSubtraction() throws ProgramException {
|
||||||
|
new TestProgram("SEC")
|
||||||
|
// 0-1 = -1
|
||||||
|
.assertTimed("SBC #1", 2)
|
||||||
|
.assertA(0xFF)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE)
|
||||||
|
// 127 - -1 = 128 (Overflow)
|
||||||
|
.add("SEC")
|
||||||
|
.add("LDA #$7F")
|
||||||
|
.assertTimed("SBC #$FF", 2)
|
||||||
|
.assertA(0x80)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_SET, NEGATIVE)
|
||||||
|
// -128 - 1 = -129 (overflow)
|
||||||
|
.add("SEC")
|
||||||
|
.add("LDA #$80")
|
||||||
|
.assertTimed("SBC #$01", 2)
|
||||||
|
.assertA(0x7F)
|
||||||
|
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_SET, POSITIVE)
|
||||||
|
// 20-10=10 (no overflow)
|
||||||
|
.add("LDA #$30")
|
||||||
|
.assertTimed("SBC #$10", 2)
|
||||||
|
.assertA(0x20)
|
||||||
|
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||||
|
// Decimal: 20-5=15
|
||||||
|
.add("SED")
|
||||||
|
.assertTimed("SBC #$05", 3)
|
||||||
|
.assertA(0x15)
|
||||||
|
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||||
|
// Decimal: 0x15-0x05=0x10
|
||||||
|
.assertTimed("SBC #$05", 3)
|
||||||
|
.assertA(0x10)
|
||||||
|
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||||
|
// Decimal: 99-19=80
|
||||||
|
.add("LDA #$99")
|
||||||
|
.assertTimed("SBC #$19", 3)
|
||||||
|
.assertA(0x80)
|
||||||
|
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE)
|
||||||
|
// Decimal: 99-50=49 (unintuitively causes overflow)
|
||||||
|
.add("LDA #$99")
|
||||||
|
.assertTimed("SBC #$50", 3)
|
||||||
|
.assertA(0x49)
|
||||||
|
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_SET, POSITIVE)
|
||||||
|
// Decimal: 19 - 22 = 97 (arithmetic underflow clears carry)
|
||||||
|
.add("LDA #$19")
|
||||||
|
.assertTimed("SBC #$22", 3)
|
||||||
|
.assertA(0x97)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE)
|
||||||
|
// Test cycle counts for other addressing modes
|
||||||
|
.add("CLD")
|
||||||
|
.assertTimed("SBC $1000", 4)
|
||||||
|
.assertTimed("SBC $1000,x", 4)
|
||||||
|
.assertTimed("SBC $1000,y", 4)
|
||||||
|
.assertTimed("SBC ($00)", 5)
|
||||||
|
.assertTimed("SBC ($00,X)", 6)
|
||||||
|
.assertTimed("SBC ($00),Y", 5)
|
||||||
|
.assertTimed("SBC $00", 3)
|
||||||
|
.assertTimed("SBC $00,X", 4)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
/* Full test of ADC and SBC with binary coded decimal mode */
|
||||||
|
public void testBCD() throws ProgramException, URISyntaxException, IOException {
|
||||||
|
Path resource = Paths.get(getClass().getResource("/jace/bcd_test.asm").toURI());
|
||||||
|
String testCode = Files.readString(resource);
|
||||||
|
TestProgram test = new TestProgram(testCode);
|
||||||
|
test.defineError("Error when performing ADC operation");
|
||||||
|
test.defineError("Error when performing SBC operation");
|
||||||
|
try {
|
||||||
|
test.runForTicks(50000000);
|
||||||
|
} catch (ProgramException e) {
|
||||||
|
// Dump memory from 0x0006 to 0x0015
|
||||||
|
for (int i = 0x0006; i <= 0x0015; i++) {
|
||||||
|
System.out.printf("%04X: %02X\n", i, ram.read(i, TYPE.READ_DATA, false, false));
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
/* Test of the processor flags */
|
||||||
|
public void testFlags() throws ProgramException {
|
||||||
|
new TestProgram()
|
||||||
|
// Test explicit flag set/clear commands (and the tests by way of this cover all branch instructions)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("SEC", 2)
|
||||||
|
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("CLC", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("SED", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_SET, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("CLD", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("SEI", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_SET)
|
||||||
|
.assertTimed("CLI", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
// Set overflow flag by hacking the P register
|
||||||
|
.add("""
|
||||||
|
LDA #%01000000
|
||||||
|
PHA
|
||||||
|
PLP
|
||||||
|
""")
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_SET, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("CLV", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
// Test Zero and Negative flags (within reason, the ADC/SBC tests cover these more thoroughly)
|
||||||
|
.assertTimed("LDA #0",2 )
|
||||||
|
.assertFlags(CARRY_CLEAR, IS_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("LDA #$ff", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("LDY #0", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, IS_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("LDY #$ff", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("LDX #0", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, IS_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.assertTimed("LDX #$ff", 2)
|
||||||
|
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test stack operations */
|
||||||
|
@Test
|
||||||
|
public void testStack() throws ProgramException {
|
||||||
|
new TestProgram()
|
||||||
|
.assertTimed("TSX", 2)
|
||||||
|
.assertX(0xFF)
|
||||||
|
.assertTimed("LDA #255", 2)
|
||||||
|
.assertTimed("PHA", 3)
|
||||||
|
.assertTimed("LDX #11", 2)
|
||||||
|
.assertTimed("PHX", 3)
|
||||||
|
.assertTimed("LDY #12", 2)
|
||||||
|
.assertTimed("PHY", 3)
|
||||||
|
.assertTimed("PHP", 3)
|
||||||
|
.assertTimed("TSX", 2)
|
||||||
|
.assertX(0xFB)
|
||||||
|
.assertTimed("PLP", 4)
|
||||||
|
.add("LDA #0")
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.assertTimed("PLA", 4)
|
||||||
|
.assertA(12)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE)
|
||||||
|
.add("LDY #$FF")
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("PLY", 4)
|
||||||
|
.assertY(11)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE)
|
||||||
|
.assertTimed("PLX", 4)
|
||||||
|
.assertX(255)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test logic operations */
|
||||||
|
@Test
|
||||||
|
public void testLogic() throws ProgramException {
|
||||||
|
// OR, AND, EOR
|
||||||
|
new TestProgram()
|
||||||
|
.assertTimed("LDA #0x55", 2)
|
||||||
|
.assertTimed("AND #0xAA", 2)
|
||||||
|
.assertA(0x00)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.assertTimed("LDA #0x55", 2)
|
||||||
|
.assertTimed("ORA #0xAA", 2)
|
||||||
|
.assertA(0xFF)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("LDA #0x55", 2)
|
||||||
|
.assertTimed("EOR #0xFF", 2)
|
||||||
|
.assertA(0xAA)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.run();
|
||||||
|
TestProgram cycleCounts = new TestProgram();
|
||||||
|
for (String opcode : new String[] {"AND", "ORA", "EOR"}) {
|
||||||
|
cycleCounts.assertTimed(opcode + " $1000", 4)
|
||||||
|
.assertTimed(opcode + " $1000,x", 4)
|
||||||
|
.assertTimed(opcode + " $1000,y", 4)
|
||||||
|
.assertTimed(opcode + " ($00)", 5)
|
||||||
|
.assertTimed(opcode + " ($00,X)", 6)
|
||||||
|
.assertTimed(opcode + " ($00),Y", 5)
|
||||||
|
.assertTimed(opcode + " $00", 3)
|
||||||
|
.assertTimed(opcode + " $00,X", 4);
|
||||||
|
}
|
||||||
|
cycleCounts.run();
|
||||||
|
// ASL
|
||||||
|
new TestProgram()
|
||||||
|
.assertTimed("LDA #0x55", 2)
|
||||||
|
.add("STA $1000")
|
||||||
|
.assertTimed("ASL", 2)
|
||||||
|
.assertA(0xAA)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||||
|
.assertTimed("ASL", 2)
|
||||||
|
.assertA(0x54)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.assertTimed("ASL $1000", 6)
|
||||||
|
.assertAddrVal(0x1000, 0xAA)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||||
|
.assertTimed("ASL $1000,x", 7)
|
||||||
|
.assertTimed("ASL $00", 5)
|
||||||
|
.assertTimed("ASL $00,x", 6)
|
||||||
|
.run();
|
||||||
|
// LSR
|
||||||
|
new TestProgram()
|
||||||
|
.assertTimed("LDA #0x55", 2)
|
||||||
|
.add("STA $1000")
|
||||||
|
.assertTimed("LSR", 2)
|
||||||
|
.assertA(0x2A)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.assertTimed("LSR", 2)
|
||||||
|
.assertA(0x15)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE, CARRY_CLEAR)
|
||||||
|
.assertTimed("LSR $1000", 6)
|
||||||
|
.assertAddrVal(0x1000, 0x2A)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.assertTimed("LSR $1000,x", 7)
|
||||||
|
.assertTimed("LSR $00", 5)
|
||||||
|
.assertTimed("LSR $00,x", 6)
|
||||||
|
.run();
|
||||||
|
// BIT
|
||||||
|
new TestProgram()
|
||||||
|
.add("LDA #$FF")
|
||||||
|
.add("STA $FF")
|
||||||
|
.assertTimed("BIT #0", 2)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE, OVERFLOW_CLEAR)
|
||||||
|
.assertTimed("BIT #$FF", 2)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, OVERFLOW_CLEAR)
|
||||||
|
.assertTimed("BIT $FF", 3)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, OVERFLOW_SET)
|
||||||
|
.add("CLV")
|
||||||
|
.add("LDA #$40")
|
||||||
|
.assertTimed("BIT #$40", 2)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE, OVERFLOW_CLEAR)
|
||||||
|
.assertTimed("BIT #$80", 2)
|
||||||
|
.assertFlags(IS_ZERO, NEGATIVE, OVERFLOW_CLEAR)
|
||||||
|
.assertTimed("BIT $1000", 4)
|
||||||
|
.assertTimed("BIT $1000,x", 4)
|
||||||
|
.assertTimed("BIT $00,X", 4)
|
||||||
|
.run();
|
||||||
|
// ROL
|
||||||
|
new TestProgram()
|
||||||
|
.add("LDA #0x55")
|
||||||
|
.add("STA $1000")
|
||||||
|
.assertTimed("ROL", 2)
|
||||||
|
.assertA(0xAA)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||||
|
.assertTimed("ROL", 2)
|
||||||
|
.assertA(0x54)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.add("CLC")
|
||||||
|
.assertTimed("ROL $1000", 6)
|
||||||
|
.assertAddrVal(0x1000, 0xAA)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||||
|
.assertTimed("ROL $1000,x", 7)
|
||||||
|
.assertTimed("ROL $00", 5)
|
||||||
|
.assertTimed("ROL $00,x", 6)
|
||||||
|
.run();
|
||||||
|
// ROR
|
||||||
|
new TestProgram()
|
||||||
|
.add("LDA #0x55")
|
||||||
|
.add("STA $1000")
|
||||||
|
.assertTimed("ROR", 2)
|
||||||
|
.assertA(0x2A)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.assertTimed("ROR", 2)
|
||||||
|
.assertA(0x95)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||||
|
.assertTimed("ROR $1000", 6)
|
||||||
|
.assertAddrVal(0x1000, 0x2A)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.assertTimed("ROR $1000,x", 7)
|
||||||
|
.assertTimed("ROR $00", 5)
|
||||||
|
.assertTimed("ROR $00,x", 6)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Increment/Decrement instructions */
|
||||||
|
@Test
|
||||||
|
public void testIncDec() throws ProgramException {
|
||||||
|
new TestProgram()
|
||||||
|
.add("LDA #0")
|
||||||
|
.add("STA $1000")
|
||||||
|
.assertTimed("INC", 2)
|
||||||
|
.assertA(1)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE)
|
||||||
|
.assertTimed("DEC", 2)
|
||||||
|
.assertA(0)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.add("LDA #$FF")
|
||||||
|
.assertTimed("INC", 2)
|
||||||
|
.assertA(0)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.assertTimed("DEC", 2)
|
||||||
|
.assertA(0xFF)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("INC $1000", 6)
|
||||||
|
.assertAddrVal(0x1000, 1)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE)
|
||||||
|
.assertTimed("DEC $1000", 6)
|
||||||
|
.assertAddrVal(0x1000, 0)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.assertTimed("INC $1000,x", 7)
|
||||||
|
.assertTimed("DEC $1000,x", 7)
|
||||||
|
.assertTimed("INC $00", 5)
|
||||||
|
.assertTimed("DEC $00", 5)
|
||||||
|
.assertTimed("INC $00,x", 6)
|
||||||
|
.assertTimed("DEC $00,x", 6)
|
||||||
|
// INX/DEX/INY/DEY
|
||||||
|
.add("LDX #0")
|
||||||
|
.assertTimed("INX", 2)
|
||||||
|
.assertX(1)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE)
|
||||||
|
.assertTimed("DEX", 2)
|
||||||
|
.assertX(0)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.assertTimed("DEX", 2)
|
||||||
|
.assertX(0xFF)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("INX", 2)
|
||||||
|
.assertX(0)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.add("LDY #0")
|
||||||
|
.assertTimed("INY", 2)
|
||||||
|
.assertY(1)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE)
|
||||||
|
.assertTimed("DEY", 2)
|
||||||
|
.assertY(0)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.assertTimed("DEY", 2)
|
||||||
|
.assertY(0xFF)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("INY", 2)
|
||||||
|
.assertY(0)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All compare instructions CMP, CPX, CPY */
|
||||||
|
@Test
|
||||||
|
public void testComparisons() throws ProgramException {
|
||||||
|
new TestProgram()
|
||||||
|
.add("LDA #0")
|
||||||
|
.assertTimed("CMP #0", 2)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.assertTimed("CMP #1", 2)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||||
|
.add("LDA #$FF")
|
||||||
|
.assertTimed("CMP #0", 2)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_SET)
|
||||||
|
.assertTimed("CMP #$FF", 2)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.add("LDX #0")
|
||||||
|
.assertTimed("CPX #0", 2)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.assertTimed("CPX #1", 2)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||||
|
.add("LDX #$FF")
|
||||||
|
.assertTimed("CPX #0", 2)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_SET)
|
||||||
|
.assertTimed("CPX #$FF", 2)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.add("LDY #0")
|
||||||
|
.assertTimed("CPY #0", 2)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
.assertTimed("CPY #1", 2)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||||
|
.add("LDY #$FF")
|
||||||
|
.assertTimed("CPY #0", 2)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_SET)
|
||||||
|
.assertTimed("CPY #$FF", 2)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||||
|
// Cycle count other modes
|
||||||
|
.assertTimed("CMP $1000", 4)
|
||||||
|
.assertTimed("CMP $1000,x", 4)
|
||||||
|
.assertTimed("CMP $1000,y", 4)
|
||||||
|
.assertTimed("CMP ($00)", 5)
|
||||||
|
.assertTimed("CMP ($00,X)", 6)
|
||||||
|
.assertTimed("CMP ($00),Y", 5)
|
||||||
|
.assertTimed("CMP $00", 3)
|
||||||
|
.assertTimed("CMP $00,X", 4)
|
||||||
|
.assertTimed("CPX $1000", 4)
|
||||||
|
.assertTimed("CPX $10", 3)
|
||||||
|
.assertTimed("CPY $1000", 4)
|
||||||
|
.assertTimed("CPY $10", 3)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load/Store/Transfer operations */
|
||||||
|
@Test
|
||||||
|
public void testLoadStore() throws ProgramException {
|
||||||
|
new TestProgram()
|
||||||
|
.assertTimed("LDA #0", 2)
|
||||||
|
.assertA(0)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.add("LDA #$FF")
|
||||||
|
.assertA(0xFF)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("LDX #0", 2)
|
||||||
|
.assertX(0)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.add("LDX #$FE")
|
||||||
|
.assertX(0xFE)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("LDY #0", 2)
|
||||||
|
.assertY(0)
|
||||||
|
.assertFlags(IS_ZERO, POSITIVE)
|
||||||
|
.add("LDY #$FD")
|
||||||
|
.assertY(0xFD)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("STA $1000", 4)
|
||||||
|
.assertAddrVal(0x1000, 0xFF)
|
||||||
|
.assertTimed("STX $1001", 4)
|
||||||
|
.assertAddrVal(0x1001, 0xFE)
|
||||||
|
.assertTimed("STY $1002", 4)
|
||||||
|
.assertAddrVal(0x1002, 0xFD)
|
||||||
|
.assertTimed("LDA $1002", 4)
|
||||||
|
.assertA(0xFD)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("LDX $1000", 4)
|
||||||
|
.assertX(0xFF)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("LDY $1001", 4)
|
||||||
|
.assertY(0xFE)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
// Cycle count for other LDA modes
|
||||||
|
.assertTimed("LDA $1000,x", 4)
|
||||||
|
.assertTimed("LDA $1000,y", 4)
|
||||||
|
.assertTimed("LDA ($00)", 5)
|
||||||
|
.assertTimed("LDA ($00,X)", 6)
|
||||||
|
.assertTimed("LDA ($00),Y", 5)
|
||||||
|
.assertTimed("LDA $00", 3)
|
||||||
|
.assertTimed("LDA $00,X", 4)
|
||||||
|
// Cycle counts for other STA modes
|
||||||
|
.assertTimed("STA $1000,x", 5)
|
||||||
|
.assertTimed("STA $1000,y", 5)
|
||||||
|
.assertTimed("STA ($00)", 5)
|
||||||
|
.assertTimed("STA ($00,X)", 6)
|
||||||
|
.assertTimed("STA ($00),Y", 6)
|
||||||
|
.assertTimed("STA $00", 3)
|
||||||
|
.assertTimed("STA $00,X", 4)
|
||||||
|
// Cycle counts for other LDX and LDY modes
|
||||||
|
.assertTimed("LDX $1000", 4)
|
||||||
|
.assertTimed("LDX $1000,y", 4)
|
||||||
|
.assertTimed("LDX $00", 3)
|
||||||
|
.assertTimed("LDX $00,y", 4)
|
||||||
|
.assertTimed("LDY $1000", 4)
|
||||||
|
.assertTimed("LDY $1000,x", 4)
|
||||||
|
.assertTimed("LDY $00", 3)
|
||||||
|
.assertTimed("LDY $00,x", 4)
|
||||||
|
// Cycle counts for other STX and STY modes
|
||||||
|
.assertTimed("STX $1000", 4)
|
||||||
|
.assertTimed("STX $00", 3)
|
||||||
|
.assertTimed("STX $00, Y", 4)
|
||||||
|
.assertTimed("STY $1000", 4)
|
||||||
|
.assertTimed("STY $00", 3)
|
||||||
|
.assertTimed("STY $00, X", 4)
|
||||||
|
// STZ
|
||||||
|
.assertTimed("STZ $1000", 4)
|
||||||
|
.assertAddrVal(0x1000, 0)
|
||||||
|
.assertTimed("STZ $1000,x", 5)
|
||||||
|
.assertTimed("STZ $00", 3)
|
||||||
|
.assertTimed("STZ $00,x", 4)
|
||||||
|
.run();
|
||||||
|
// Now test the transfer instructions
|
||||||
|
new TestProgram()
|
||||||
|
.add("LDA #10")
|
||||||
|
.add("LDX #20")
|
||||||
|
.add("LDY #30")
|
||||||
|
.assertTimed("TAX", 2)
|
||||||
|
.assertX(10)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE)
|
||||||
|
.assertTimed("TAY", 2)
|
||||||
|
.assertY(10)
|
||||||
|
.assertFlags(NOT_ZERO, POSITIVE)
|
||||||
|
.add("LDA #$FF")
|
||||||
|
.assertTimed("TAX", 2)
|
||||||
|
.assertX(0xFF)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("TAY", 2)
|
||||||
|
.assertY(0xFF)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.add("LDA #0")
|
||||||
|
.assertTimed("TXA", 2)
|
||||||
|
.assertA(0xFF)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.add("LDA #0")
|
||||||
|
.assertTimed("TYA", 2)
|
||||||
|
.assertA(0xFF)
|
||||||
|
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||||
|
.assertTimed("TSX", 2)
|
||||||
|
.assertX(0xFF)
|
||||||
|
.assertTimed("TXS", 2)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test branch instructions */
|
||||||
|
@Test
|
||||||
|
public void testBranches() throws ProgramException {
|
||||||
|
new TestProgram()
|
||||||
|
// Zero and Negative flags
|
||||||
|
.add("LDA #0")
|
||||||
|
.assertTimed("BEQ *+2",3)
|
||||||
|
.assertTimed("BNE *+2",2)
|
||||||
|
.assertTimed("BPL *+2",3)
|
||||||
|
.assertTimed("BMI *+2",2)
|
||||||
|
.add("LDA #$FF")
|
||||||
|
.assertTimed("BEQ *+2",2)
|
||||||
|
.assertTimed("BNE *+2",3)
|
||||||
|
.assertTimed("BPL *+2",2)
|
||||||
|
.assertTimed("BMI *+2",3)
|
||||||
|
.assertTimed("BRA *+2", 3)
|
||||||
|
// Carry flag
|
||||||
|
.assertTimed("BCC *+2", 3)
|
||||||
|
.assertTimed("BCS *+2", 2)
|
||||||
|
.add("SEC")
|
||||||
|
.assertTimed("BCC *+2", 2)
|
||||||
|
.assertTimed("BCS *+2", 3)
|
||||||
|
// Overflow flag
|
||||||
|
.add("CLV")
|
||||||
|
.assertTimed("BVC *+2", 3)
|
||||||
|
.assertTimed("BVS *+2", 2)
|
||||||
|
.add("""
|
||||||
|
lda #$40
|
||||||
|
sta $1000
|
||||||
|
bit $1000
|
||||||
|
""")
|
||||||
|
.assertTimed("BVC *+2", 2)
|
||||||
|
.assertTimed("BVS *+2", 3)
|
||||||
|
.assertTimed("NOP", 2)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test JMP */
|
||||||
|
@Test
|
||||||
|
public void testJmp() throws ProgramException {
|
||||||
|
new TestProgram()
|
||||||
|
.add("LDA #0")
|
||||||
|
.assertTimed("""
|
||||||
|
JMP +
|
||||||
|
LDA #$FF
|
||||||
|
NOP
|
||||||
|
NOP
|
||||||
|
+
|
||||||
|
""", 3)
|
||||||
|
.assertA(0)
|
||||||
|
// Testing indirect jump using self-modifying code
|
||||||
|
// Load the address of jmp target and store at $300
|
||||||
|
.add("""
|
||||||
|
LDA #<jmpTarget
|
||||||
|
STA $300
|
||||||
|
LDA #>jmpTarget
|
||||||
|
STA $301
|
||||||
|
LDX #$FF
|
||||||
|
""")
|
||||||
|
.assertTimed("JMP ($300)", 6)
|
||||||
|
.add("""
|
||||||
|
LDX #0
|
||||||
|
jmpTarget LDA #$88
|
||||||
|
""")
|
||||||
|
.assertA(0x88)
|
||||||
|
.assertX(0xFF)
|
||||||
|
// Perform similar test using indirect,x addressing
|
||||||
|
.add("""
|
||||||
|
LDA #<(jmpTargetX)
|
||||||
|
STA $310
|
||||||
|
LDA #>(jmpTargetX)
|
||||||
|
STA $311
|
||||||
|
LDX #$10
|
||||||
|
LDY #$FF
|
||||||
|
""")
|
||||||
|
.assertTimed("JMP ($300,X)", 6)
|
||||||
|
.add("""
|
||||||
|
LDY #88
|
||||||
|
jmpTargetX LDA #$88
|
||||||
|
""")
|
||||||
|
.assertA(0x88)
|
||||||
|
.assertY(0xFF)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test JSR */
|
||||||
|
@Test
|
||||||
|
public void testJsr() throws ProgramException {
|
||||||
|
// "Easy" test that JSR + RTS work as expected and together take 12 cycles
|
||||||
|
new TestProgram()
|
||||||
|
.add("""
|
||||||
|
jmp +
|
||||||
|
sub1 rts
|
||||||
|
+throwError 69
|
||||||
|
+
|
||||||
|
""")
|
||||||
|
.assertTimed("""
|
||||||
|
JSR sub1
|
||||||
|
""", 12)
|
||||||
|
.run();
|
||||||
|
// Check that JSR pushes the expected PC values to the stack
|
||||||
|
new TestProgram()
|
||||||
|
.add("""
|
||||||
|
jmp start
|
||||||
|
test
|
||||||
|
plx
|
||||||
|
ply
|
||||||
|
phy
|
||||||
|
phx
|
||||||
|
rts
|
||||||
|
""")
|
||||||
|
.test("", "RTS did not return to the correct address")
|
||||||
|
.add("""
|
||||||
|
start
|
||||||
|
jsr test
|
||||||
|
ret
|
||||||
|
""")
|
||||||
|
.test("""
|
||||||
|
cpy #>ret
|
||||||
|
beq +
|
||||||
|
""", "Y = MSB of return address")
|
||||||
|
.test("""
|
||||||
|
inx
|
||||||
|
cpx #<ret
|
||||||
|
beq +
|
||||||
|
""", "X = LSB of return address-1")
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
}
|
80
src/test/java/jace/apple2e/VideoDHGRTest.java
Normal file
80
src/test/java/jace/apple2e/VideoDHGRTest.java
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package jace.apple2e;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import javafx.scene.image.WritableImage;
|
||||||
|
|
||||||
|
// This is mostly to provide execution coverage to catch null pointer or index out of range exceptions
|
||||||
|
public class VideoDHGRTest extends AbstractFXTest {
|
||||||
|
WritableImage image = new WritableImage(560, 192);
|
||||||
|
|
||||||
|
private VideoDHGR video;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
video = new VideoDHGR();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitHgrDhgrTables() {
|
||||||
|
// Test the initialization of HGR_TO_DHGR and HGR_TO_DHGR_BW tables
|
||||||
|
assertNotNull(video.HGR_TO_DHGR);
|
||||||
|
assertNotNull(video.HGR_TO_DHGR_BW);
|
||||||
|
// Add more assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitCharMap() {
|
||||||
|
// Test the initialization of CHAR_MAP1, CHAR_MAP2, and CHAR_MAP3 arrays
|
||||||
|
assertNotNull(video.CHAR_MAP1);
|
||||||
|
assertNotNull(video.CHAR_MAP2);
|
||||||
|
assertNotNull(video.CHAR_MAP3);
|
||||||
|
// Add more assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeToScreen() {
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 0, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 4, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 190, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, -1, 0, 0, 0);
|
||||||
|
video.getCurrentWriter().actualWriter().displayByte(image, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetYOffset() {
|
||||||
|
// Run through all possible combinations of soft switches to ensure the correct Y offset is returned each time
|
||||||
|
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 = "";
|
||||||
|
for (int j=0; j < switches.length; j++) {
|
||||||
|
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||||
|
state += switches[j].getSwitch().getName() + "=" + (switches[j].getSwitch().getState() ? "1" : "0") + " ";
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
assertEquals("Address for mode not correct: " + state, expected, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisplayByte() {
|
||||||
|
// Run through all possible combinations of soft switches to ensure the video writer executes without error
|
||||||
|
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++) {
|
||||||
|
for (int j=0; j < switches.length; j++) {
|
||||||
|
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||||
|
}
|
||||||
|
video.configureVideoMode();
|
||||||
|
writeToScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add more test cases for other methods in the VideoDHGR class
|
||||||
|
|
||||||
|
}
|
85
src/test/java/jace/apple2e/VideoNTSCTest.java
Normal file
85
src/test/java/jace/apple2e/VideoNTSCTest.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package jace.apple2e;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import javafx.scene.image.WritableImage;
|
||||||
|
|
||||||
|
// This is mostly to provide execution coverage to catch null pointer or index out of range exceptions
|
||||||
|
public class VideoNTSCTest extends AbstractFXTest {
|
||||||
|
WritableImage image = new WritableImage(560, 192);
|
||||||
|
|
||||||
|
private VideoNTSC video;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
video = new VideoNTSC();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitHgrDhgrTables() {
|
||||||
|
// Test the initialization of HGR_TO_DHGR and HGR_TO_DHGR_BW tables
|
||||||
|
assertNotNull(video.HGR_TO_DHGR);
|
||||||
|
assertNotNull(video.HGR_TO_DHGR_BW);
|
||||||
|
// Add more assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitCharMap() {
|
||||||
|
// Test the initialization of CHAR_MAP1, CHAR_MAP2, and CHAR_MAP3 arrays
|
||||||
|
assertNotNull(video.CHAR_MAP1);
|
||||||
|
assertNotNull(video.CHAR_MAP2);
|
||||||
|
assertNotNull(video.CHAR_MAP3);
|
||||||
|
// Add more assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeToScreen() {
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 0, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 4, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 190, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, -1, 0, 0, 0);
|
||||||
|
video.getCurrentWriter().actualWriter().displayByte(image, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetYOffset() {
|
||||||
|
// Run through all possible combinations of soft switches to ensure the correct Y offset is returned each time
|
||||||
|
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 = "";
|
||||||
|
for (int j=0; j < switches.length; j++) {
|
||||||
|
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||||
|
state += switches[j].getSwitch().getName() + "=" + (switches[j].getSwitch().getState() ? "1" : "0") + " ";
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
assertEquals("Address for mode not correct: " + state, expected, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisplayByte() {
|
||||||
|
// Run through all possible combinations of soft switches to ensure the video writer executes without error
|
||||||
|
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++) {
|
||||||
|
for (int j=0; j < switches.length; j++) {
|
||||||
|
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||||
|
}
|
||||||
|
video.configureVideoMode();
|
||||||
|
writeToScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisplayModes() {
|
||||||
|
for (VideoNTSC.VideoMode mode : VideoNTSC.VideoMode.values()) {
|
||||||
|
VideoNTSC.setVideoMode(mode, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
src/test/java/jace/applesoft/ApplesoftTest.java
Normal file
38
src/test/java/jace/applesoft/ApplesoftTest.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package jace.applesoft;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ApplesoftTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fromStringTest() {
|
||||||
|
String programSource = "10 PRINT \"Hello, World!\"\n20 PRINT \"Goodbye!\"";
|
||||||
|
ApplesoftProgram program = ApplesoftProgram.fromString(programSource);
|
||||||
|
assertNotNull(program);
|
||||||
|
assertEquals(2, program.lines.size());
|
||||||
|
Line line1 = program.lines.get(0);
|
||||||
|
assertEquals(10, line1.getNumber());
|
||||||
|
assertEquals(1, line1.getCommands().size());
|
||||||
|
Command command1 = line1.getCommands().get(0);
|
||||||
|
assertEquals(0xBA, command1.parts.get(0).getByte() & 0x0ff);
|
||||||
|
String match = "";
|
||||||
|
for (int idx=1; idx < command1.parts.size(); idx++) {
|
||||||
|
match += command1.parts.get(idx).toString();
|
||||||
|
}
|
||||||
|
assertEquals("\"Hello, World!\"", match);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toStringTest() {
|
||||||
|
Line line1 = Line.fromString("10 print \"Hello, world!\"");
|
||||||
|
Line line2 = Line.fromString("20 print \"Goodbye!\"");
|
||||||
|
ApplesoftProgram program = new ApplesoftProgram();
|
||||||
|
program.lines.add(line1);
|
||||||
|
program.lines.add(line2);
|
||||||
|
String programSource = program.toString();
|
||||||
|
assertEquals("10 PRINT \"Hello, world!\"\n20 PRINT \"Goodbye!\"\n", programSource);
|
||||||
|
}
|
||||||
|
}
|
156
src/test/java/jace/core/MemoryTest.java
Normal file
156
src/test/java/jace/core/MemoryTest.java
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 org.badvision.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package jace.core;
|
||||||
|
import static jace.TestUtils.initComputer;
|
||||||
|
import static jace.TestUtils.runAssemblyCode;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.Emulator;
|
||||||
|
import jace.apple2e.MOS65C02;
|
||||||
|
import jace.apple2e.RAM128k;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that memory listeners fire appropriately.
|
||||||
|
* @author brobert
|
||||||
|
*/
|
||||||
|
public class MemoryTest {
|
||||||
|
static Computer computer;
|
||||||
|
static MOS65C02 cpu;
|
||||||
|
static RAM128k ram;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setupClass() {
|
||||||
|
initComputer();
|
||||||
|
SoundMixer.MUTE = true;
|
||||||
|
computer = Emulator.withComputer(c->c, null);
|
||||||
|
cpu = (MOS65C02) computer.getCpu();
|
||||||
|
ram = (RAM128k) computer.getMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void assertMemoryConfiguredCorrectly() {
|
||||||
|
assertEquals("Active read bank 3 should be main memory page 3",
|
||||||
|
ram.mainMemory.getMemoryPage(3),
|
||||||
|
ram.activeRead.getMemoryPage(3));
|
||||||
|
|
||||||
|
assertEquals("Active write bank 3 should be main memory page 3",
|
||||||
|
ram.mainMemory.getMemoryPage(3),
|
||||||
|
ram.activeWrite.getMemoryPage(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testListenerRelevance() throws Exception {
|
||||||
|
AtomicInteger anyEventCaught = new AtomicInteger();
|
||||||
|
RAMListener anyListener = new RAMListener("Execution test", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||||
|
@Override
|
||||||
|
protected void doConfig() {
|
||||||
|
setScopeStart(0x0100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doEvent(RAMEvent e) {
|
||||||
|
anyEventCaught.incrementAndGet();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AtomicInteger readAnyEventCaught = new AtomicInteger();
|
||||||
|
RAMListener readAnyListener = new RAMListener("Execution test 1", RAMEvent.TYPE.READ, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||||
|
@Override
|
||||||
|
protected void doConfig() {
|
||||||
|
setScopeStart(0x0100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doEvent(RAMEvent e) {
|
||||||
|
readAnyEventCaught.incrementAndGet();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AtomicInteger writeEventCaught = new AtomicInteger();
|
||||||
|
RAMListener writeListener = new RAMListener("Execution test 2", RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||||
|
@Override
|
||||||
|
protected void doConfig() {
|
||||||
|
setScopeStart(0x0100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doEvent(RAMEvent e) {
|
||||||
|
writeEventCaught.incrementAndGet();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AtomicInteger executeEventCaught = new AtomicInteger();
|
||||||
|
RAMListener executeListener = new RAMListener("Execution test 3", RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||||
|
@Override
|
||||||
|
protected void doConfig() {
|
||||||
|
setScopeStart(0x0100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doEvent(RAMEvent e) {
|
||||||
|
executeEventCaught.incrementAndGet();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
RAMEvent readDataEvent = new RAMEvent(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, 0x100, 0, 0);
|
||||||
|
RAMEvent readOperandEvent = new RAMEvent(RAMEvent.TYPE.READ_OPERAND, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, 0x100, 0, 0);
|
||||||
|
RAMEvent executeEvent = new RAMEvent(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, 0x100, 0, 0);
|
||||||
|
RAMEvent writeEvent = new RAMEvent(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, 0x100, 0, 0);
|
||||||
|
|
||||||
|
// Any listener
|
||||||
|
assertTrue("Any listener should handle all events", anyListener.isRelevant(readDataEvent));
|
||||||
|
assertTrue("Any listener should handle all events", anyListener.isRelevant(readOperandEvent));
|
||||||
|
assertTrue("Any listener should handle all events", anyListener.isRelevant(executeEvent));
|
||||||
|
assertTrue("Any listener should handle all events", anyListener.isRelevant(writeEvent));
|
||||||
|
|
||||||
|
// Read listener
|
||||||
|
assertTrue("Read listener should handle all read events", readAnyListener.isRelevant(readDataEvent));
|
||||||
|
assertTrue("Read listener should handle all read events", readAnyListener.isRelevant(readOperandEvent));
|
||||||
|
assertTrue("Read listener should handle all read events", readAnyListener.isRelevant(executeEvent));
|
||||||
|
assertFalse("Read listener should ignore write events", readAnyListener.isRelevant(writeEvent));
|
||||||
|
|
||||||
|
// Write listener
|
||||||
|
assertFalse("Write listener should ignore all read events", writeListener.isRelevant(readDataEvent));
|
||||||
|
assertFalse("Write listener should ignore all read events", writeListener.isRelevant(readOperandEvent));
|
||||||
|
assertFalse("Write listener should ignore all read events", writeListener.isRelevant(executeEvent));
|
||||||
|
assertTrue("Write listener should handle write events", writeListener.isRelevant(writeEvent));
|
||||||
|
|
||||||
|
// Execution listener
|
||||||
|
assertTrue("Execute listener should only catch execution events", executeListener.isRelevant(executeEvent));
|
||||||
|
assertFalse("Execute listener should only catch execution events", executeListener.isRelevant(readDataEvent));
|
||||||
|
assertFalse("Execute listener should only catch execution events", executeListener.isRelevant(readOperandEvent));
|
||||||
|
assertFalse("Execute listener should only catch execution events", executeListener.isRelevant(writeEvent));
|
||||||
|
|
||||||
|
ram.addListener(anyListener);
|
||||||
|
ram.addListener(executeListener);
|
||||||
|
ram.addListener(readAnyListener);
|
||||||
|
ram.addListener(writeListener);
|
||||||
|
|
||||||
|
runAssemblyCode("NOP", 0x0100, 2);
|
||||||
|
|
||||||
|
assertEquals("Should have no writes for 0x0100", 0, writeEventCaught.get());
|
||||||
|
assertEquals("Should have read event for 0x0100", 1, readAnyEventCaught.get());
|
||||||
|
assertEquals("Should have execute for 0x0100", 1, executeEventCaught.get());
|
||||||
|
}
|
||||||
|
}
|
84
src/test/java/jace/core/SoundTest.java
Normal file
84
src/test/java/jace/core/SoundTest.java
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package jace.core;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import jace.core.SoundMixer.SoundBuffer;
|
||||||
|
import jace.core.SoundMixer.SoundError;
|
||||||
|
|
||||||
|
public class SoundTest extends AbstractFXTest {
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
System.out.println("Init sound");
|
||||||
|
Utility.setHeadlessMode(false);
|
||||||
|
SoundMixer.initSound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
Utility.setHeadlessMode(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
//(Only use this to ensure the sound engine produces audible output, it's otherwise annoying to hear all the time)
|
||||||
|
public void soundGenerationTest() throws SoundError {
|
||||||
|
try {
|
||||||
|
System.out.println("Performing sound test...");
|
||||||
|
SoundMixer mixer = new SoundMixer();
|
||||||
|
System.out.println("Attach mixer");
|
||||||
|
mixer.attach();
|
||||||
|
System.out.println("Allocate buffer");
|
||||||
|
SoundBuffer buffer = SoundMixer.createBuffer(false);
|
||||||
|
System.out.println("Generate sound");
|
||||||
|
// for (int i = 0; i < 100000; i++) {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
// Gerate a sin wave with a frequency sweep so we can tell if the buffer is being fully processed
|
||||||
|
double x = Math.sin(i*i * 0.0001);
|
||||||
|
buffer.playSample((short) (Short.MAX_VALUE * x));
|
||||||
|
}
|
||||||
|
System.out.println("Closing buffer");
|
||||||
|
buffer.shutdown();
|
||||||
|
System.out.println("Deactivating sound");
|
||||||
|
mixer.detach();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// Commented out because it's annoying to hear all the time, but it worked without issues
|
||||||
|
public void mixerTortureTest() throws SoundError, InterruptedException, ExecutionException {
|
||||||
|
System.out.println("Performing speaker tick test...");
|
||||||
|
SoundMixer.initSound();
|
||||||
|
System.out.println("Create mixer");
|
||||||
|
SoundMixer mixer = new SoundMixer();
|
||||||
|
System.out.println("Attach mixer");
|
||||||
|
mixer.attach();
|
||||||
|
// We want to create and destroy lots of buffers to make sure we don't have any memory leaks
|
||||||
|
// for (int i = 0; i < 10000; i++) {
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
// Print status every 1000 iterations
|
||||||
|
if (i % 1000 == 0) {
|
||||||
|
System.out.println("Iteration %d".formatted(i));
|
||||||
|
}
|
||||||
|
SoundBuffer buffer = SoundMixer.createBuffer(false);
|
||||||
|
for (int j = 0; j < SoundMixer.BUFFER_SIZE*2; j++) {
|
||||||
|
// Gerate a sin wave with a frequency sweep so we can tell if the buffer is being fully processed
|
||||||
|
double x = Math.sin(j*j * 0.0001);
|
||||||
|
buffer.playSample((short) (Short.MAX_VALUE * x));
|
||||||
|
}
|
||||||
|
buffer.flush();
|
||||||
|
buffer.shutdown();
|
||||||
|
}
|
||||||
|
// Assert buffers are empty
|
||||||
|
assertEquals("All buffers should be empty", 0, mixer.getActiveBuffers());
|
||||||
|
System.out.println("Deactivating sound");
|
||||||
|
mixer.detach();
|
||||||
|
}
|
||||||
|
}
|
42
src/test/java/jace/core/UtilityTest.java
Normal file
42
src/test/java/jace/core/UtilityTest.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package jace.core;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class UtilityTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLevenshteinDistance() {
|
||||||
|
String s1 = "kitten";
|
||||||
|
String s2 = "sitting";
|
||||||
|
int distance = Utility.levenshteinDistance(s1, s2);
|
||||||
|
assertEquals(3, distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAdjustedLevenshteinDistance() {
|
||||||
|
String s1 = "kitten";
|
||||||
|
String s2 = "sitting";
|
||||||
|
int adjustedDistance = Utility.adjustedLevenshteinDistance(s1, s2);
|
||||||
|
assertEquals(4, adjustedDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRankMatch() {
|
||||||
|
String s1 = "apple";
|
||||||
|
String s2 = "banana";
|
||||||
|
double score = Utility.rankMatch(s1, s2, 3);
|
||||||
|
assertEquals(0, score, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindBestMatch() {
|
||||||
|
String match = "apple";
|
||||||
|
Collection<String> search = Arrays.asList("banana", "orange", "apple pie");
|
||||||
|
String bestMatch = Utility.findBestMatch(match, search);
|
||||||
|
assertEquals("apple pie", bestMatch);
|
||||||
|
}
|
||||||
|
}
|
121
src/test/java/jace/hardware/CardAppleMouseTest.java
Normal file
121
src/test/java/jace/hardware/CardAppleMouseTest.java
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package jace.hardware;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import jace.core.RAMEvent;
|
||||||
|
import jace.core.RAMEvent.SCOPE;
|
||||||
|
import jace.core.RAMEvent.TYPE;
|
||||||
|
import jace.core.RAMEvent.VALUE;
|
||||||
|
import javafx.geometry.Rectangle2D;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
|
||||||
|
public class CardAppleMouseTest extends AbstractFXTest {
|
||||||
|
|
||||||
|
private CardAppleMouse cardAppleMouse;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
cardAppleMouse = new CardAppleMouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDeviceName() {
|
||||||
|
assertEquals("Apple Mouse", cardAppleMouse.getDeviceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReset() {
|
||||||
|
cardAppleMouse.mode = 1;
|
||||||
|
cardAppleMouse.clampWindow = new Rectangle2D(10, 10, 100, 100);
|
||||||
|
cardAppleMouse.detach();
|
||||||
|
|
||||||
|
cardAppleMouse.reset();
|
||||||
|
|
||||||
|
assertEquals(0, cardAppleMouse.mode);
|
||||||
|
assertEquals(new Rectangle2D(0, 0, 0x03ff, 0x03ff), cardAppleMouse.clampWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// Test mouseHandler responses to mouse events
|
||||||
|
public void testMouseHandler() {
|
||||||
|
MouseEvent clickEvent = new MouseEvent(MouseEvent.MOUSE_CLICKED,0,0,0,0,MouseButton.PRIMARY,1,false,false,false, false, false, false, false, false, false, false, null);
|
||||||
|
MouseEvent releaseEvent = new MouseEvent(MouseEvent.MOUSE_RELEASED,0,0,0,0,MouseButton.PRIMARY,1,false,false,false, false, false, false, false, false, false, false, null);
|
||||||
|
MouseEvent dragEvent = new MouseEvent(MouseEvent.MOUSE_DRAGGED,0,0,0,0,MouseButton.PRIMARY,1,false,false,false, false, false, false, false, false, false, false, null);
|
||||||
|
|
||||||
|
cardAppleMouse.mode = 1;
|
||||||
|
cardAppleMouse.mouseHandler.handle(clickEvent);
|
||||||
|
cardAppleMouse.mouseHandler.handle(dragEvent);
|
||||||
|
cardAppleMouse.mouseHandler.handle(releaseEvent);
|
||||||
|
assertEquals(1, cardAppleMouse.mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// Test firmware entry points
|
||||||
|
public void testFirmware() {
|
||||||
|
// Test reads
|
||||||
|
RAMEvent event = new RAMEvent(TYPE.READ, SCOPE.ANY, VALUE.ANY, 0, 0, 0);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x80, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x81, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x82, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x83, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x84, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x85, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x86, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x87, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x88, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x05, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x38, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x07, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x18, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x08, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x01, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x0B, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x01, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x0C, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x20, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x11, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x00, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x12, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x080, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x13, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x081, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x14, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x082, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x15, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x083, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x16, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x084, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x17, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x085, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x18, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x086, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x19, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x087, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x1A, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x088, event.getNewValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
54
src/test/java/jace/hardware/CardSSCTest.java
Normal file
54
src/test/java/jace/hardware/CardSSCTest.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package jace.hardware;
|
||||||
|
|
||||||
|
import static jace.hardware.CardSSC.ACIA_Command;
|
||||||
|
import static jace.hardware.CardSSC.ACIA_Control;
|
||||||
|
import static jace.hardware.CardSSC.ACIA_Data;
|
||||||
|
import static jace.hardware.CardSSC.ACIA_Status;
|
||||||
|
import static jace.hardware.CardSSC.SW1;
|
||||||
|
import static jace.hardware.CardSSC.SW2_CTS;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import jace.core.RAMEvent;
|
||||||
|
import jace.core.RAMEvent.SCOPE;
|
||||||
|
import jace.core.RAMEvent.TYPE;
|
||||||
|
import jace.core.RAMEvent.VALUE;
|
||||||
|
|
||||||
|
public class CardSSCTest extends AbstractFXTest {
|
||||||
|
|
||||||
|
private CardSSC cardSSC;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
cardSSC = new CardSSC();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDeviceName() {
|
||||||
|
assertEquals("Super Serial Card", cardSSC.getDeviceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetSlot() {
|
||||||
|
cardSSC.setSlot(1);
|
||||||
|
// assertEquals("Slot 1", cardSSC.activityIndicator.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReset() {
|
||||||
|
cardSSC.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIOAccess() {
|
||||||
|
RAMEvent event = new RAMEvent(TYPE.READ_DATA, SCOPE.ANY, VALUE.ANY, 0, 0, 0);
|
||||||
|
int[] registers = {SW1, SW2_CTS, ACIA_Data, ACIA_Control, ACIA_Status, ACIA_Command};
|
||||||
|
for (int register : registers) {
|
||||||
|
cardSSC.handleIOAccess(register, TYPE.READ_DATA, 0, event);
|
||||||
|
cardSSC.handleIOAccess(register, TYPE.WRITE, 0, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
src/test/java/jace/hardware/FloppyDiskTest.java
Normal file
62
src/test/java/jace/hardware/FloppyDiskTest.java
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package jace.hardware;
|
||||||
|
import static jace.hardware.FloppyDisk.PRODOS_SECTOR_ORDER;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import jace.hardware.FloppyDisk.SectorOrder;
|
||||||
|
|
||||||
|
public class FloppyDiskTest extends AbstractFXTest {
|
||||||
|
|
||||||
|
private FloppyDisk floppyDisk;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws IOException {
|
||||||
|
floppyDisk = new FloppyDisk();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readDisk_ValidDiskFile_Success() throws IOException {
|
||||||
|
// Create a sample disk file
|
||||||
|
byte[] diskData = new byte[232960];
|
||||||
|
File diskFile = File.createTempFile("test_disk", ".dsk");
|
||||||
|
diskFile.deleteOnExit();
|
||||||
|
ByteArrayInputStream diskInputStream = new ByteArrayInputStream(diskData);
|
||||||
|
|
||||||
|
// Read the disk file
|
||||||
|
floppyDisk.readDisk(diskInputStream, SectorOrder.DOS);
|
||||||
|
|
||||||
|
// Verify the disk properties
|
||||||
|
assert(floppyDisk.isNibblizedImage);
|
||||||
|
assertEquals(254, floppyDisk.volumeNumber);
|
||||||
|
assertEquals(0, floppyDisk.headerLength);
|
||||||
|
assertEquals(232960, floppyDisk.nibbles.length);
|
||||||
|
assertEquals("Sector order not null", true, null != floppyDisk.currentSectorOrder);
|
||||||
|
assertNull(floppyDisk.diskPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nibblize_ValidNibbles_Success() throws IOException {
|
||||||
|
// Create a sample nibbles array
|
||||||
|
byte[] nibbles = new byte[FloppyDisk.DISK_NIBBLE_LENGTH];
|
||||||
|
for (int i = 0; i < nibbles.length; i++) {
|
||||||
|
nibbles[i] = (byte) (i % 256);
|
||||||
|
}
|
||||||
|
floppyDisk.currentSectorOrder = PRODOS_SECTOR_ORDER;
|
||||||
|
// Nibblize the nibbles array
|
||||||
|
byte[] nibblizedData = floppyDisk.nibblize(nibbles);
|
||||||
|
|
||||||
|
// Verify the nibblized data
|
||||||
|
assertEquals(FloppyDisk.DISK_NIBBLE_LENGTH, nibblizedData.length);
|
||||||
|
// for (int i = 0; i < nibblizedData.length; i++) {
|
||||||
|
// assertEquals((i % 256) >> 2, nibblizedData[i]);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
44
src/test/java/jace/hardware/PassportMidiInterfaceTest.java
Normal file
44
src/test/java/jace/hardware/PassportMidiInterfaceTest.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package jace.hardware;
|
||||||
|
|
||||||
|
import static jace.hardware.PassportMidiInterface.ACIA_RECV;
|
||||||
|
import static jace.hardware.PassportMidiInterface.ACIA_STATUS;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER1_LSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER1_MSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER2_LSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER2_MSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER3_LSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER3_MSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER_CONTROL_1;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER_CONTROL_2;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import jace.core.RAMEvent;
|
||||||
|
import jace.core.RAMEvent.SCOPE;
|
||||||
|
import jace.core.RAMEvent.TYPE;
|
||||||
|
import jace.core.RAMEvent.VALUE;
|
||||||
|
|
||||||
|
|
||||||
|
public class PassportMidiInterfaceTest extends AbstractFXTest {
|
||||||
|
PassportMidiInterface midi = new PassportMidiInterface();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeviceSelection() {
|
||||||
|
assertNotNull(PassportMidiInterface.preferredMidiDevice.getSelections());
|
||||||
|
assertNotEquals(0, PassportMidiInterface.preferredMidiDevice.getSelections().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIOAccess() {
|
||||||
|
RAMEvent event = new RAMEvent(TYPE.READ_DATA, SCOPE.ANY, VALUE.ANY, 0, 0, 0);
|
||||||
|
int[] registers = {ACIA_STATUS, ACIA_RECV, TIMER_CONTROL_1, TIMER_CONTROL_2, TIMER1_LSB, TIMER1_MSB, TIMER2_LSB, TIMER2_MSB, TIMER3_LSB, TIMER3_MSB};
|
||||||
|
for (int register : registers) {
|
||||||
|
midi.handleIOAccess(register, TYPE.READ_DATA, 0, event);
|
||||||
|
midi.handleIOAccess(register, TYPE.WRITE, 0, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
50
src/test/java/jace/hardware/mockingboard/PSGTest.java
Normal file
50
src/test/java/jace/hardware/mockingboard/PSGTest.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package jace.hardware.mockingboard;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class PSGTest {
|
||||||
|
|
||||||
|
private PSG psg;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
psg = new PSG(0, 100, 44100, "name", 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setControl_InactiveCommand_NoAction() {
|
||||||
|
psg.setControl(0); // Set control to inactive
|
||||||
|
// Assert that no action is taken
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setControl_LatchCommand_SelectedRegUpdated() {
|
||||||
|
psg.setControl(1); // Set control to latch
|
||||||
|
// Assert that selectedReg is updated correctly
|
||||||
|
// Add your assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setControl_ReadCommand_BusUpdated() {
|
||||||
|
psg.setControl(2); // Set control to read
|
||||||
|
// Assert that bus is updated correctly
|
||||||
|
// Add your assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setControl_WriteCommand_RegUpdated() {
|
||||||
|
psg.setControl(3); // Set control to write
|
||||||
|
// Assert that the corresponding register is updated correctly
|
||||||
|
// Add your assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateTest() {
|
||||||
|
AtomicInteger out = new AtomicInteger();
|
||||||
|
psg.update(out, false, out, false, out, false);
|
||||||
|
psg.update(out, true, out, true, out, true);
|
||||||
|
}
|
||||||
|
}
|
49
src/test/java/jace/hardware/mockingboard/R6522Test.java
Normal file
49
src/test/java/jace/hardware/mockingboard/R6522Test.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package jace.hardware.mockingboard;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class R6522Test {
|
||||||
|
R6522 r6522 = new R6522() {
|
||||||
|
@Override
|
||||||
|
public String getShortName() {
|
||||||
|
return "name";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendOutputA(int value) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendOutputB(int value) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int receiveOutputA() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int receiveOutputB() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteRegs() {
|
||||||
|
for (R6522.Register reg : R6522.Register.values()) {
|
||||||
|
r6522.writeRegister(reg.val, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadRegs() {
|
||||||
|
for (R6522.Register reg : R6522.Register.values()) {
|
||||||
|
r6522.readRegister(reg.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
83
src/test/java/jace/ide/ApplesoftTest.java
Normal file
83
src/test/java/jace/ide/ApplesoftTest.java
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package jace.ide;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNotSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static jace.TestUtils.initComputer;
|
||||||
|
import jace.applesoft.ApplesoftProgram;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author blurry
|
||||||
|
*/
|
||||||
|
public class ApplesoftTest {
|
||||||
|
|
||||||
|
public ApplesoftTest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static Byte[] lemonadeStandBinary;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setUpClass() throws URISyntaxException, IOException {
|
||||||
|
initComputer();
|
||||||
|
byte[] lemonadeStand = readBinary("/jace/lemonade_stand.bin");
|
||||||
|
lemonadeStandBinary = ApplesoftProgram.toObjects(lemonadeStand);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] readBinary(String path) throws IOException, URISyntaxException {
|
||||||
|
Path resource = Paths.get(ApplesoftTest.class.getResource(path).toURI());
|
||||||
|
return Files.readAllBytes(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void tearDownClass() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deserializeBinaryTest() {
|
||||||
|
ApplesoftProgram program = ApplesoftProgram.fromBinary(Arrays.asList(lemonadeStandBinary), 0x0801);
|
||||||
|
assertNotNull(program);
|
||||||
|
assertNotSame("", program.toString());
|
||||||
|
assertEquals("Lemonade stand has 380 lines", 380, program.getLength());
|
||||||
|
assertTrue("Should have last line 31114", program.toString().contains("31114 "));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundTripStringComparisonTest() {
|
||||||
|
ApplesoftProgram program = ApplesoftProgram.fromBinary(Arrays.asList(lemonadeStandBinary), 0x0801);
|
||||||
|
String serialized = program.toString();
|
||||||
|
ApplesoftProgram deserialized = ApplesoftProgram.fromString(serialized);
|
||||||
|
String[] serializedLines = serialized.split("\\n");
|
||||||
|
String[] researializedLines = deserialized.toString().split("\\n");
|
||||||
|
assertEquals("Lemonade stand has 380 lines", 380, deserialized.getLength());
|
||||||
|
assertArrayEquals("Program listing should be not change if re-keyed in as printed", serializedLines, researializedLines);
|
||||||
|
}
|
||||||
|
}
|
301
src/test/resources/jace/bcd_test.asm
Normal file
301
src/test/resources/jace/bcd_test.asm
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
; Modified and adapted from http://www.6502.org/tutorials/decimal_mode.html#B
|
||||||
|
;
|
||||||
|
; Verify decimal mode behavior
|
||||||
|
; Written by Bruce Clark. This code is public domain.
|
||||||
|
;
|
||||||
|
; Returns:
|
||||||
|
; ERROR = 0 if the test passed
|
||||||
|
; ERROR = 1 if the test failed
|
||||||
|
;
|
||||||
|
; This routine requires 17 bytes of RAM -- 1 byte each for:
|
||||||
|
; AR, CF, DA, DNVZC, ERROR, HA, HNVZC, N1, N1H, N1L, N2, N2L, NF, VF, and ZF
|
||||||
|
; and 2 bytes for N2H
|
||||||
|
;
|
||||||
|
; Variables:
|
||||||
|
; N1 and N2 are the two numbers to be added or subtracted
|
||||||
|
; N1H, N1L, N2H, and N2L are the upper 4 bits and lower 4 bits of N1 and N2
|
||||||
|
; DA and DNVZC are the actual accumulator and flag results in decimal mode
|
||||||
|
; HA and HNVZC are the accumulator and flag results when N1 and N2 are
|
||||||
|
; added or subtracted using binary arithmetic
|
||||||
|
; AR, NF, VF, ZF, and CF are the predicted decimal mode accumulator and
|
||||||
|
; flag results, calculated using binary arithmetic
|
||||||
|
;
|
||||||
|
; This program takes approximately 1 minute at 1 MHz (a few seconds more on
|
||||||
|
; a 65C02 than a 6502 or 65816)
|
||||||
|
;
|
||||||
|
AR = $06
|
||||||
|
NF = $07
|
||||||
|
VF = $08
|
||||||
|
CF = $09
|
||||||
|
ZF = $0a
|
||||||
|
DA = $0b
|
||||||
|
DNVZC = $0c
|
||||||
|
HA = $0d
|
||||||
|
HNVZC = $0e
|
||||||
|
N1 = $0f
|
||||||
|
N1H = $10
|
||||||
|
N1L = $11
|
||||||
|
N2 = $12
|
||||||
|
N2H = $13
|
||||||
|
N2L = $15
|
||||||
|
|
||||||
|
TEST ;LDY #1 ; initialize Y (used to loop through carry flag values)
|
||||||
|
;STY ERROR ; store 1 in ERROR until the test passes
|
||||||
|
LDA #0 ; initialize N1 and N2
|
||||||
|
STA N1
|
||||||
|
STA N2
|
||||||
|
LOOP1 LDA N2 ; N2L = N2 & $0F
|
||||||
|
AND #$0F ; [1] see text
|
||||||
|
STA N2L
|
||||||
|
LDA N2 ; N2H = N2 & $F0
|
||||||
|
AND #$F0 ; [2] see text
|
||||||
|
STA N2H
|
||||||
|
ORA #$0F ; N2H+1 = (N2 & $F0) + $0F
|
||||||
|
STA N2H+1
|
||||||
|
LOOP2 LDA N1 ; N1L = N1 & $0F
|
||||||
|
AND #$0F ; [3] see text
|
||||||
|
STA N1L
|
||||||
|
LDA N1 ; N1H = N1 & $F0
|
||||||
|
AND #$F0 ; [4] see text
|
||||||
|
STA N1H
|
||||||
|
JSR ADD
|
||||||
|
JSR A65C02
|
||||||
|
JSR COMPARE
|
||||||
|
BNE ADD_ERROR
|
||||||
|
JSR SUB
|
||||||
|
JSR S65C02
|
||||||
|
JSR COMPARE
|
||||||
|
BNE SUB_ERROR
|
||||||
|
INC N1 ; [5] see text
|
||||||
|
BNE LOOP2 ; loop through all 256 values of N1
|
||||||
|
INC N2 ; [6] see text
|
||||||
|
BNE LOOP1 ; loop through all 256 values of N2
|
||||||
|
DEY
|
||||||
|
BPL LOOP1 ; loop through both values of the carry flag
|
||||||
|
SUCCESS +success
|
||||||
|
ADD_ERROR
|
||||||
|
CPY #1 ; Set carry based on Y reg
|
||||||
|
LDA DA
|
||||||
|
LDX N1
|
||||||
|
LDY N2
|
||||||
|
+throwError 0
|
||||||
|
SUB_ERROR
|
||||||
|
CPY #1 ; Set carry based on Y reg
|
||||||
|
LDA DA
|
||||||
|
LDX N1
|
||||||
|
LDY N2
|
||||||
|
+throwError 1
|
||||||
|
|
||||||
|
; Calculate the actual decimal mode accumulator and flags, the accumulator
|
||||||
|
; and flag results when N1 is added to N2 using binary arithmetic, the
|
||||||
|
; predicted accumulator result, the predicted carry flag, and the predicted
|
||||||
|
; V flag
|
||||||
|
;
|
||||||
|
ADD SED ; decimal mode
|
||||||
|
CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||||
|
LDA N1
|
||||||
|
ADC N2
|
||||||
|
STA DA ; actual accumulator result in decimal mode
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
STA DNVZC ; actual flags result in decimal mode
|
||||||
|
CLD ; binary mode
|
||||||
|
CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||||
|
LDA N1
|
||||||
|
ADC N2
|
||||||
|
STA HA ; accumulator result of N1+N2 using binary arithmetic
|
||||||
|
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
STA HNVZC ; flags result of N1+N2 using binary arithmetic
|
||||||
|
CPY #1
|
||||||
|
LDA N1L
|
||||||
|
ADC N2L
|
||||||
|
CMP #$0A
|
||||||
|
LDX #0
|
||||||
|
BCC A1
|
||||||
|
INX
|
||||||
|
ADC #5 ; add 6 (carry is set)
|
||||||
|
AND #$0F
|
||||||
|
SEC
|
||||||
|
A1 ORA N1H
|
||||||
|
;
|
||||||
|
; if N1L + N2L < $0A, then add N2 & $F0
|
||||||
|
; if N1L + N2L >= $0A, then add (N2 & $F0) + $0F + 1 (carry is set)
|
||||||
|
;
|
||||||
|
ADC N2H,X
|
||||||
|
PHP
|
||||||
|
BCS A2
|
||||||
|
CMP #$A0
|
||||||
|
BCC A3
|
||||||
|
A2 ADC #$5F ; add $60 (carry is set)
|
||||||
|
SEC
|
||||||
|
A3 STA AR ; predicted accumulator result
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
STA CF ; predicted carry result
|
||||||
|
PLA
|
||||||
|
;
|
||||||
|
; note that all 8 bits of the P register are stored in VF
|
||||||
|
;
|
||||||
|
STA VF ; predicted V flags
|
||||||
|
RTS
|
||||||
|
|
||||||
|
; Calculate the actual decimal mode accumulator and flags, and the
|
||||||
|
; accumulator and flag results when N2 is subtracted from N1 using binary
|
||||||
|
; arithmetic
|
||||||
|
;
|
||||||
|
SUB SED ; decimal mode
|
||||||
|
CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||||
|
LDA N1
|
||||||
|
SBC N2
|
||||||
|
STA DA ; actual accumulator result in decimal mode
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
STA DNVZC ; actual flags result in decimal mode
|
||||||
|
CLD ; binary mode
|
||||||
|
CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||||
|
LDA N1
|
||||||
|
SBC N2
|
||||||
|
STA HA ; accumulator result of N1-N2 using binary arithmetic
|
||||||
|
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
STA HNVZC ; flags result of N1-N2 using binary arithmetic
|
||||||
|
RTS
|
||||||
|
|
||||||
|
; Calculate the predicted SBC accumulator result for the 6502 and 65816
|
||||||
|
|
||||||
|
;
|
||||||
|
SUB1 CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||||
|
LDA N1L
|
||||||
|
SBC N2L
|
||||||
|
LDX #0
|
||||||
|
BCS S11
|
||||||
|
INX
|
||||||
|
SBC #5 ; subtract 6 (carry is clear)
|
||||||
|
AND #$0F
|
||||||
|
CLC
|
||||||
|
S11 ORA N1H
|
||||||
|
;
|
||||||
|
; if N1L - N2L >= 0, then subtract N2 & $F0
|
||||||
|
; if N1L - N2L < 0, then subtract (N2 & $F0) + $0F + 1 (carry is clear)
|
||||||
|
;
|
||||||
|
SBC N2H,X
|
||||||
|
BCS S12
|
||||||
|
SBC #$5F ; subtract $60 (carry is clear)
|
||||||
|
S12 STA AR
|
||||||
|
RTS
|
||||||
|
|
||||||
|
; Calculate the predicted SBC accumulator result for the 6502 and 65C02
|
||||||
|
|
||||||
|
;
|
||||||
|
SUB2 CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||||
|
LDA N1L
|
||||||
|
SBC N2L
|
||||||
|
LDX #0
|
||||||
|
BCS S21
|
||||||
|
INX
|
||||||
|
AND #$0F
|
||||||
|
CLC
|
||||||
|
S21 ORA N1H
|
||||||
|
;
|
||||||
|
; if N1L - N2L >= 0, then subtract N2 & $F0
|
||||||
|
; if N1L - N2L < 0, then subtract (N2 & $F0) + $0F + 1 (carry is clear)
|
||||||
|
;
|
||||||
|
SBC N2H,X
|
||||||
|
BCS S22
|
||||||
|
SBC #$5F ; subtract $60 (carry is clear)
|
||||||
|
S22 CPX #0
|
||||||
|
BEQ S23
|
||||||
|
SBC #6
|
||||||
|
S23 STA AR ; predicted accumulator result
|
||||||
|
RTS
|
||||||
|
|
||||||
|
; Compare accumulator actual results to predicted results
|
||||||
|
;
|
||||||
|
; Return:
|
||||||
|
; Z flag = 1 (BEQ branch) if same
|
||||||
|
; Z flag = 0 (BNE branch) if different
|
||||||
|
;
|
||||||
|
COMPARE LDA DA
|
||||||
|
CMP AR
|
||||||
|
+breakpoint 1
|
||||||
|
BNE C1
|
||||||
|
LDA DNVZC ; [7] see text
|
||||||
|
EOR NF
|
||||||
|
AND #$80 ; mask off N flag
|
||||||
|
+breakpoint 2
|
||||||
|
BNE C1
|
||||||
|
LDA DNVZC ; [8] see text
|
||||||
|
EOR VF
|
||||||
|
AND #$40 ; mask off V flag
|
||||||
|
+breakpoint 3
|
||||||
|
BNE C1 ; [9] see text
|
||||||
|
LDA DNVZC
|
||||||
|
EOR ZF ; mask off Z flag
|
||||||
|
AND #2
|
||||||
|
+breakpoint 4
|
||||||
|
BNE C1 ; [10] see text
|
||||||
|
LDA DNVZC
|
||||||
|
EOR CF
|
||||||
|
AND #1 ; mask off C flag
|
||||||
|
+breakpoint 5
|
||||||
|
C1 RTS
|
||||||
|
|
||||||
|
; These routines store the predicted values for ADC and SBC for the 6502,
|
||||||
|
; 65C02, and 65816 in AR, CF, NF, VF, and ZF
|
||||||
|
|
||||||
|
A6502 LDA VF
|
||||||
|
;
|
||||||
|
; since all 8 bits of the P register were stored in VF, bit 7 of VF contains
|
||||||
|
; the N flag for NF
|
||||||
|
;
|
||||||
|
STA NF
|
||||||
|
LDA HNVZC
|
||||||
|
STA ZF
|
||||||
|
RTS
|
||||||
|
|
||||||
|
S6502 JSR SUB1
|
||||||
|
LDA HNVZC
|
||||||
|
STA NF
|
||||||
|
STA VF
|
||||||
|
STA ZF
|
||||||
|
STA CF
|
||||||
|
RTS
|
||||||
|
|
||||||
|
A65C02 LDA AR
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
STA NF
|
||||||
|
STA ZF
|
||||||
|
RTS
|
||||||
|
|
||||||
|
S65C02 JSR SUB2
|
||||||
|
LDA AR
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
STA NF
|
||||||
|
STA ZF
|
||||||
|
LDA HNVZC
|
||||||
|
STA VF
|
||||||
|
STA CF
|
||||||
|
RTS
|
||||||
|
|
||||||
|
A65816 LDA AR
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
STA NF
|
||||||
|
STA ZF
|
||||||
|
RTS
|
||||||
|
|
||||||
|
S65816 JSR SUB1
|
||||||
|
LDA AR
|
||||||
|
PHP
|
||||||
|
PLA
|
||||||
|
STA NF
|
||||||
|
STA ZF
|
||||||
|
LDA HNVZC
|
||||||
|
STA VF
|
||||||
|
STA CF
|
||||||
|
RTS
|
Loading…
Reference in New Issue
Block a user