/**
* Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de>
*
* 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 de.codesourcery.jasm16.emulator;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import junit.framework.TestCase;
import de.codesourcery.jasm16.Address;
import de.codesourcery.jasm16.OpCode;
import de.codesourcery.jasm16.Register;
import de.codesourcery.jasm16.compiler.CompilationUnit;
import de.codesourcery.jasm16.compiler.ICompilationUnit;
import de.codesourcery.jasm16.compiler.ICompiler;
import de.codesourcery.jasm16.compiler.ICompiler.CompilerOption;
import de.codesourcery.jasm16.compiler.ISymbol;
import de.codesourcery.jasm16.compiler.Label;
import de.codesourcery.jasm16.compiler.io.ByteArrayObjectCodeWriterFactory;
import de.codesourcery.jasm16.emulator.IEmulator.EmulationSpeed;
import de.codesourcery.jasm16.emulator.exceptions.UnknownOpcodeException;
import de.codesourcery.jasm16.exceptions.ParseException;
import de.codesourcery.jasm16.parser.Identifier;
import de.codesourcery.jasm16.utils.Misc;
public abstract class AbstractEmulatorTest extends TestCase
{
/**
* Max. time a single test may take before it is assumed
* to be stuck in an endless loop and forcefully aborted.
*/
public static final long MAX_TIME_PER_TEST_MILLIS = 2 * 1000; // 2 seconds
protected static final IEmulator emulator;
protected CompiledCode compiledCode;
static {
emulator = new Emulator();
emulator.calibrate();
}
@Override
protected final void setUp() throws Exception
{
setUpHook();
}
protected void setUpHook() throws Exception {
}
@Override
protected final void tearDown() throws Exception
{
try {
compiledCode = null;
emulator.reset(true);
emulator.removeAllEmulationListeners();
} finally {
tearDownHook();
}
}
protected void tearDownHook() throws Exception {
}
// ==================== helper code ====================
protected final void execute(String source) throws TimeoutException, InterruptedException {
execute(source,MAX_TIME_PER_TEST_MILLIS,true);
}
protected final void execute(String source,long maxWaitTimeMillis) throws TimeoutException, InterruptedException {
execute(source,maxWaitTimeMillis,true);
}
protected final void execute(String source,long maxWaitTimeMillis,boolean waitForEmulatorToStop) throws TimeoutException, InterruptedException {
compiledCode = compile(source);
final CountDownLatch stopped = new CountDownLatch(1);
final IEmulator emu;
if (waitForEmulatorToStop)
{
final IEmulationListener listener = new EmulationListener() {
@Override
protected void beforeContinuousExecutionHook()
{
// System.out.println("*** Emulator started. ***");
}
@Override
public void onStopHook(IEmulator emulator, Address previousPC, Throwable emulationError)
{
boolean suppressError = false;
if ( emulationError instanceof UnknownOpcodeException)
{
final UnknownOpcodeException ex = (UnknownOpcodeException) emulationError;
if ( OpCode.isHaltInstruction( ex.getInstructionWord() ) )
{
suppressError = true;
}
}
if ( ! suppressError ) {
System.out.println("*** Emulator stopped "+( emulationError != null ? "("+emulationError.getMessage() +")": "" )+" ***");
}
stopped.countDown();
}
};
emu = compiledCode.loadEmulator( listener );
} else {
emu = compiledCode.loadEmulator();
}
emu.start();
if ( ! waitForEmulatorToStop ) {
return;
}
while( true )
{
try
{
if ( ! stopped.await( maxWaitTimeMillis , TimeUnit.MILLISECONDS) ) {
emu.stop();
throw new TimeoutException("Emulator did not stop after "+MAX_TIME_PER_TEST_MILLIS+" milliseconds - maybe stuck in an infinite loop?");
}
break;
} catch (InterruptedException e) {
emu.stop();
throw e;
}
}
}
protected final class CompiledCode
{
public final byte[] objectCode;
public final ICompilationUnit compilationUnit;
protected CompiledCode(ICompilationUnit compilationUnit, byte[] objectCode)
{
this.compilationUnit = compilationUnit;
this.objectCode = objectCode;
}
public IEmulator loadEmulator() { return loadEmulator(null); }
public IEmulator loadEmulator(IEmulationListener l)
{
emulator.reset(true);
emulator.loadMemory(compilationUnit.getObjectCodeStartOffset() , objectCode);
emulator.setEmulationSpeed( EmulationSpeed.REAL_SPEED );
if ( l != null ) {
emulator.addEmulationListener( l );
}
return emulator;
}
}
private CompiledCode compile(String source)
{
final ICompiler c = new de.codesourcery.jasm16.compiler.Compiler();
c.setCompilerOption(CompilerOption.RELAXED_PARSING,true);
final ByteArrayObjectCodeWriterFactory factory = new ByteArrayObjectCodeWriterFactory();
c.setObjectCodeWriterFactory( factory );
final ICompilationUnit unit = CompilationUnit.createInstance("string" , source );
c.compile( Collections.singletonList( unit ) );
if ( unit.hasErrors() )
{
Misc.printCompilationErrors(unit, source, false );
fail("Failed to compile source");
}
final byte[] objectCode = factory.getBytes();
assertNotNull( "NULL object code" , objectCode );
assertTrue( "no object code generated?" , objectCode.length > 0 );
return new CompiledCode( unit , objectCode );
}
protected final void assertOnTopOfStack(int value) {
final int actualValue = emulator.getMemory().read( emulator.getCPU().getSP() );
assertEquals( "Expected a different value on top-of-stack" , value , actualValue );
}
protected final void assertMemoryValue(int value,String label)
{
final int actualValue = emulator.getMemory().read( getLabelAddress(label) );
assertEquals( "Expected a different value in memory at label '"+label+"'", value , actualValue );
}
protected final Address getLabelAddress(String label)
{
final ISymbol symbol;
try {
symbol = compiledCode.compilationUnit.getSymbolTable().getSymbol( new Identifier(label) , null );
} catch (ParseException e) {
throw new RuntimeException("Not a valid label: '"+label+"'",e);
}
assertNotNull("Unknown symbol '"+label+"'",symbol);
assertTrue("Symbol '"+label+"' is no label but a "+symbol,symbol instanceof Label);
final Label l = (Label) symbol;
assertNotNull( "Address of "+l+" has no been resolved?" , l.getAddress() );
return l.getAddress();
}
protected final void assertMemoryValue(Address expectedValue,Address address)
{
final Address actualValue = Address.wordAddress( emulator.getMemory().read( address ) );
assertEquals( "Expected a different value in memory at address "+address , expectedValue , actualValue );
}
protected final void assertMemoryValue(int value,Address address)
{
final int actualValue = emulator.getMemory().read( address );
assertEquals( "Expected a different value in memory at address "+address , value , actualValue );
}
protected final void assertRegIA(int value) {
assertEquals("Interrupt handler register has unexpected value" , Address.wordAddress( value ) , emulator.getCPU().getInterruptAddress());
}
protected final void assertRegA(int value) { assertRegister(Register.A,value) ; }
protected final void assertRegB(int value) { assertRegister(Register.B,value) ; }
protected final void assertRegC(int value) { assertRegister(Register.C,value) ; }
protected final void assertRegX(int value) { assertRegister(Register.X,value) ; }
protected final void assertRegY(int value) { assertRegister(Register.Y,value) ; }
protected final void assertRegZ(int value) { assertRegister(Register.Z,value) ; }
protected final void assertRegI(int value) { assertRegister(Register.I,value) ; }
protected final void assertRegJ(int value) { assertRegister(Register.J,value) ; }
protected final void assertRegSP(int value) { assertRegister(Register.SP,value) ; }
protected final void assertRegA(Address value) { assertRegister(Register.A,value) ; }
protected final void assertRegB(Address value) { assertRegister(Register.B,value) ; }
protected final void assertRegC(Address value) { assertRegister(Register.C,value) ; }
protected final void assertRegX(Address value) { assertRegister(Register.X,value) ; }
protected final void assertRegY(Address value) { assertRegister(Register.Y,value) ; }
protected final void assertRegZ(Address value) { assertRegister(Register.Z,value) ; }
protected final void assertRegI(Address value) { assertRegister(Register.I,value) ; }
protected final void assertRegJ(Address value) { assertRegister(Register.J,value) ; }
protected final void assertRegSP(Address value) { assertRegister(Register.SP,value) ; }
protected final void assertRegASigned(int value) { assertRegisterSigned(Register.A,value) ; }
protected final void assertRegBSigned(int value) { assertRegisterSigned(Register.B,value) ; }
protected final void assertRegCSigned(int value) { assertRegisterSigned(Register.C,value) ; }
protected final void assertRegXSigned(int value) { assertRegisterSigned(Register.X,value) ; }
protected final void assertRegYSigned(int value) { assertRegisterSigned(Register.Y,value) ; }
protected final void assertRegZSigned(int value) { assertRegisterSigned(Register.Z,value) ; }
protected final void assertRegISigned(int value) { assertRegisterSigned(Register.I,value) ; }
protected final void assertRegJSigned(int value) { assertRegisterSigned(Register.J,value) ; }
protected final void assertRegEX(int value) { assertRegister(Register.EX,value) ; }
protected final void assertRegister(Register reg,int expected) {
assertEquals( "Register "+reg+" has unexpected value", expected , emulator.getCPU().getRegisterValue(reg) );
}
protected final void assertRegister(Register reg,Address expected) {
assertEquals( "Register "+reg+" has unexpected value", expected , Address.wordAddress( emulator.getCPU().getRegisterValue(reg) ) );
}
protected final void assertRegisterSigned(Register reg,int expected)
{
final int actual = signExtend16( emulator.getCPU().getRegisterValue(reg) );
assertEquals( "Register "+reg+" has unexpected value", expected , actual );
}
protected final int signExtend16(int value) {
if ( (value & 1<<15) != 0 ) { // MSB set ?
return 0xffff0000 | value;
}
return value;
}
}