3
0
mirror of https://github.com/Qortal/AT.git synced 2025-01-30 19:02:14 +00:00
AT/Java/tests/CallStackOpCodeTests.java
catbref 454d4bed35 OpCode refactoring, versioned constants, refactored tests
API is now an abstract class instead of an Interface. This is to
allow protected methods that give access to package-scoped methods
and variables inside MachineState.

MachineState now supports a different set of constants based on AT
version.

MachineState's methods and variables have had their scopes tightened
up and getters/setters added where appropriate.

MachineState.parseHeader() inlined into the constructor that calls it
so it can set public final variables.

Some reordering and additional comments in MachineState.

OpCode enum entries refactored so that:
a) variables provided via MachineState "state" are not explicitly
passed as well

b) args to each opcode are pre-fetched from the codeByteBuffer before
calling and only need to be cast before use. this comes almost
"for free" thanks to OpCodeParam.fetch

Calling functions from OpCode now cleaner as there's no write-access
to programCounter, only changing codeByteBuffer's position.

Also in OpCode, state.getProgramCounter() now provides a consistent
before-opcode position for branches, jumps, etc.

Lots of repeated code refactored out of unit tests into ExecutableTest
class. ExecutableTest also provides helper methods for examining
post-execution data values, stack positions and entries.
2018-10-05 15:56:32 +01:00

183 lines
6.1 KiB
Java

import static common.TestUtils.*;
import static org.junit.Assert.*;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.Test;
import common.ExecutableTest;
public class CallStackOpCodeTests extends ExecutableTest {
@Test
public void testJMP_SUB() throws ExecutionException {
int subAddr = 0x06;
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr);
int returnAddress = codeByteBuffer.position();
codeByteBuffer.put(OpCode.FIN_IMD.value);
// subAddr:
assertEquals(subAddr, codeByteBuffer.position());
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1) * CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address does not match", returnAddress, getCallStackEntry(expectedCallStackPosition));
assertEquals("Data does not match", 4444L, getData(0));
}
@Test
public void testJMP_SUB2() throws ExecutionException {
int subAddr1 = 0x06;
int subAddr2 = 0x19;
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr1);
int returnAddress1 = codeByteBuffer.position();
codeByteBuffer.put(OpCode.FIN_IMD.value);
// subAddr1:
assertEquals(subAddr1, codeByteBuffer.position());
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr2);
int returnAddress2 = codeByteBuffer.position();
codeByteBuffer.put(OpCode.FIN_IMD.value);
// subAddr2:
assertEquals(subAddr2, codeByteBuffer.position());
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(5555L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1) * CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address does not match", returnAddress2, getCallStackEntry(expectedCallStackPosition));
assertEquals("Return address does not match", returnAddress1, getCallStackEntry(expectedCallStackPosition + MachineState.ADDRESS_SIZE));
assertEquals("Data does not match", 4444L, getData(0));
assertEquals("Data does not match", 5555L, getData(1));
}
@Test
public void testJMP_SUBoverflow() throws ExecutionException {
// Call stack is 0x0010 entries in size, so exceed this to test overflow
for (int i = 0; i < 20; ++i) {
// sub address is next opcode!
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(i * (1 + 4));
}
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
public void testRET_SUB() throws ExecutionException {
int subAddr = 0x13;
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr);
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(7777L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
// subAddr:
assertEquals(subAddr, codeByteBuffer.position());
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
codeByteBuffer.put(OpCode.RET_SUB.value);
codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
execute(true);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1 + 1) * CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address not cleared", 0L, getCallStackEntry(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
assertEquals("Data does not match", 4444L, getData(0));
assertEquals("Data does not match", 7777L, getData(1));
}
@Test
public void testRET_SUB2() throws ExecutionException {
int subAddr1 = 0x13;
int subAddr2 = 0x34;
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr1);
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(7777L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
// subAddr1:
assertEquals(subAddr1, codeByteBuffer.position());
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(subAddr2);
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
codeByteBuffer.put(OpCode.RET_SUB.value);
codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
// subAddr2:
assertEquals(subAddr2, codeByteBuffer.position());
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
codeByteBuffer.put(OpCode.RET_SUB.value);
codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
execute(true);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1 + 1 + 1) * CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address not cleared", 0L, getCallStackEntry(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
assertEquals("Data does not match", 4444L, getData(0));
assertEquals("Data does not match", 7777L, getData(1));
assertEquals("Data does not match", 2222L, getData(2));
assertEquals("Data does not match", 3333L, getData(3));
}
@Test
public void testRET_SUBoverflow() throws ExecutionException {
codeByteBuffer.put(OpCode.RET_SUB.value);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
public void testRET_SUBoverflow2() throws ExecutionException {
// sub address is next opcode!
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(1 + 4);
// this is return address too
codeByteBuffer.put(OpCode.RET_SUB.value);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
}