// $Id: HotSwapTest.java,v 1.2 2008/11/18 10:33:25 anicoara Exp $
// =====================================================================
package ch.ethz.prose;
// used packages
import java.lang.reflect.*;
import junit.framework.*;
import ch.ethz.inf.iks.jvmai.jvmdi.HotSwapAspectInterfaceImpl;
import ch.ethz.inf.iks.jvmai.jvmdi.HotSwapClassWeaver;
import ch.ethz.inf.iks.jvmai.jvmdi.HotSwapProvider;
import ch.ethz.jvmai.*;
import java.io.InputStream;
//import org.apache.bcel.classfile.*;
import org.apache.bcel.classfile.ClassParser;
//import org.apache.bcel.verifier.*;
//import org.apache.bcel.classfile.ClassFormatException; // could not resolve this class
import org.apache.bcel.classfile.JavaClass; // not yet used
import org.apache.bcel.generic.*;
/**
* JUnit testcase for class HotSwapProvider.
*
* @version $Revision: 1.2 $
* @author Angela Nicoara
* @author Gerald Linhofer
*/
public class HotSwapTest extends TestCase {
HotSwapAspectInterfaceImpl aspectInterface;
//----------------------------------------------------------------------------------------------------------------------
private static void staticMethod() { return; }
public static double staticField = 3;
class MyDummy {
private int privateField = 1;
public int publicField = 2;
public int publicMethod() { return 1; }
private void privateMethod() { return; }
}
class MyDummy2 {
private int privateField = 1;
public int publicField = 2;
public int publicMethod() { return privateField; }
private void privateMethod() { return; }
}
class MyAspectInterface extends HotSwapAspectInterfaceImpl {
private int dummy;
/**
* Tests {@link ch.ethz.inf.iks.jvmai.jvmdi.HotSwapAspectInterfaceImpl#getMethodFromString
* getMethodFromString(java.lang.String)}.
*
*/
void testGetMethodFromString() {
// do some calls with invalid parameters
int catcheExceptions = 0;
try{
try{ getMethodFromString(null); }
catch(NullPointerException e) { catcheExceptions++; }
try{ getMethodFromString("class"); }
catch(InvalidIdException e) { catcheExceptions++; }
try{ getMethodFromString("class#method"); }
catch(InvalidIdException e) { catcheExceptions++; }
try{ getMethodFromString("class#method#(I)Z"); }
catch(ClassNotFoundException e) { catcheExceptions++; }
try{ getMethodFromString(this.getClass().getName() + "#method#(I)Z"); }
catch(NoSuchMethodError e) { catcheExceptions++; }
try{ getMethodFromString(this.getClass().getName() + "#testGetMethodFromString#(I)Z"); }
catch(NoSuchMethodError e) { catcheExceptions++; }
} catch(Exception e) { fail("throws wrong assertion" + e.getClass().getName()); }
assertEquals("number of catched assertions", 6, catcheExceptions);
// try to get this method.
Member thisMethod = this.getClass().getDeclaredMethods()[0];
Member fetchedMethod = null;
try { fetchedMethod = getMethodFromString( this.getClass().getName() + '#' + thisMethod.getName() + "#()V" ); }
catch(Exception e) { fail("exception " + e.getClass().getName() + " @getMethodString(): " + e.getMessage() ); }
assertEquals("could not resolve method id string", thisMethod, fetchedMethod);
// try to get a method from a not yet loaded class
try { getMethodFromString( "ch.ethz.prose.HotSwapTest$MyDummy#publicMethod#()I" ); }
catch(Exception e) { fail("exception " + e.getClass().getName() + " @getMethodString() for a not loaded class: " + e.getMessage() ); }
// try to get a private method
try { getMethodFromString( "ch.ethz.prose.HotSwapTest$MyDummy#privateMethod#()V" ); }
catch(Exception e) { fail("exception " + e.getClass().getName() + " @getMethodString() for a private method: " + e.getMessage() ); }
// try to get a static method
try { getMethodFromString( "ch.ethz.prose.HotSwapTest#suite#()Ljunit/framework/Test;#" ); }
catch(Throwable e) { fail("exception " + e.getClass().getName() + " @getMethodString() for a static method: " + e.getMessage() ); }
}
/**
* Tests {@link ch.ethz.inf.iks.jvmai.jvmdi.HotSwapAspectInterfaceImpl#getMethodFromString
* getMethodFromString(java.lang.String)}.
*
*/
void testGetFieldFromString() {
// do some calls with invalid parameters
int catcheExceptions = 0;
try{
try{ getFieldFromString(null, true); }
catch(NullPointerException e) { catcheExceptions++; }
try{ getFieldFromString("class", false); }
catch(InvalidIdException e) { catcheExceptions++; }
try{ getFieldFromString("class#field", true); }
catch(InvalidIdException e) { catcheExceptions++; }
try{ getFieldFromString("class#method#I", false); }
catch(ClassNotFoundException e) { catcheExceptions++; }
try{ getFieldFromString(this.getClass().getName() + "#field#I", false); }
catch(NoSuchFieldError e) { catcheExceptions++; }
try{ getFieldFromString(this.getClass().getName() + "#dummy#Z", true); }
catch(NoSuchFieldError e) { catcheExceptions++; }
} catch(Exception e) { fail("throws wrong assertion" + e.getClass().getName()); }
assertEquals("number of catched assertions", 6, catcheExceptions);
// try to get 'dummy'.
java.lang.reflect.Field thisField = this.getClass().getDeclaredFields()[0];
java.lang.reflect.Field fetchedField = null;
try { fetchedField = getFieldFromString( this.getClass().getName() + "#dummy#I", false ); }
catch(Exception e) { fail("exception " + e.getClass().getName() + " @getFieldString(): " + e.getMessage() ); }
assertEquals("could not resolve field id string", thisField, fetchedField);
// try to get a field from a not yet loaded class
try { getFieldFromString( "ch.ethz.prose.HotSwapTest$MyDummy2#publicField#I", false ); }
catch(Exception e) { fail("exception " + e.getClass().getName() + " @getFieldString() for a not loaded class: " + e.getMessage() ); }
// try to get a private field
try { getFieldFromString( "ch.ethz.prose.HotSwapTest$MyDummy2#privateField#I", false ); }
catch(Exception e) { fail("exception " + e.getClass().getName() + " @getFieldString() for a private field: " + e.getMessage() ); }
// try to get a static field
try { getFieldFromString( "ch.ethz.prose.HotSwapTest#staticField#I", true ); }
catch(Throwable e) { fail("exception " + e.getClass().getName() + " @getFieldString() for a static field: " + e.getMessage() ); }
}
/**
* Tests {@link ch.ethz.inf.iks.jvmai.jvmdi.HotSwapAspectInterfaceImpl#getBCELClassDefinition
* getBCELClassDefinition(java.lang.Class)}.
*/
void testGetBCELClassDefinition() {
int exceptionCount = 0;
try {
try{ getBCELClassDefinition( null ); }
catch(NullPointerException e) { exceptionCount++; }
// cannot test for NotInitializedException here, because once initialized,
// it's not possible to uninitialize the aspectInterface any more
// except by forced unloading of the class (this was done to be compatible
// with the debugging implementation of prose).
} catch(Exception e) { fail("wrong exception type"); }
assertEquals( "exception test", 1, exceptionCount );
Class thisClass = this.getClass();
JavaClass jc = null;
try{ jc = getBCELClassDefinition( this.getClass() ); }
catch(ClassNotFoundException e) { fail("class not found"); }
// class name
assertEquals("different class name", thisClass.getName(), jc.getClassName() );
// access flags. Note: the ACC_SUPER flag (0x020 or 32) may be set in class files
// but newer VMs ignores it and wont return it, when quering the flags with the
// reflection API.
int accessMask = 0x0611; // no ACC_SUPER flag, which is ignored by newer JVMs
int jvmAccessFlags = thisClass.getModifiers();
int bcelAccessFlags = jc.getModifiers();
assertEquals("different modifier (access flags)", jvmAccessFlags & accessMask, bcelAccessFlags & accessMask );
// methods
java.lang.reflect.Method[] methods = thisClass.getDeclaredMethods();
for( int i = 0; i < methods.length; i++ ) {
java.lang.reflect.Method method = methods[i];
assertNotNull("class file has no definition for " + method.getName(), jc.getMethod( method ) );
}
}
void testRedefineClass() { }
void testRedefineClasses() { }
}
interface TestRedefineClasses {
public int method();
}
class TestRedefineClasses1 {
public int method() { return 1; }
// static public int statMethod() { return 1; }
}
class TestRedefineClasses2 {
public int method() { return 2; }
// static public int StatMethod() { return 2; }
}
//--------------------------------------------------------------------------------------------------------------------------------------------
/**
* Construct test with given name.
* @param name test name
*/
public HotSwapTest(String name) {
super(name);
}
/**
* Set up fixture.
*/
protected void setUp() {
// String providerClassName = System.getProperty("ch.ethz.prose.JVMAIProvider","ch.ethz.inf.iks.jvmai.jvmdi.HotSwapProvider");
// Class providerClass = Class.forName(providerClassName);
// Provider provider = (Provider)providerClass.newInstance();
Provider provider = new HotSwapProvider();
aspectInterface = (HotSwapAspectInterfaceImpl) provider.getAspectInterface();
aspectInterface.startup(new String[0], true);
}
protected void teardown() {
aspectInterface.teardown();
}
public void test_010_AspectInterface() {
MyAspectInterface ai = new MyAspectInterface();
ai.startup( new String[0], true );
ai.testGetMethodFromString();
ai.testGetBCELClassDefinition();
ai.teardown();
}
public void test_020_RedefineClasses() {
Class cl1=null, cl2=null;
// InputStream is1=null, is2=null;//, is3=null, is4=null;
JavaClass jc1 = null, jc2 = null;
// get classes
try {
cl1 = Class.forName( this.getClass().getName() + "$TestRedefineClasses1" );
cl2 = Class.forName( this.getClass().getName() + "$TestRedefineClasses2" );
}
catch ( ClassNotFoundException nfe ) {
fail( "Class not found: " + nfe.getMessage() + " (internal error in the test case)" );
}
Class[] cls1 = { cl1 };
Class[] cls2 = { cl2 };
// Check if could get unmodified class definitions (contents of class files)
try { jc1 = HotSwapAspectInterfaceImpl.getBCELClassDefinition( cl1 ); }
catch( ClassNotFoundException ie ) { fail( "getOriginalClassDefinition could not read class file (IOException)" ); }
catch( Exception fe ) { fail( "getOriginalClassDefinition malformed class file" ); }
/*
// full verification
Verifier veri1 = VerifierFactory.getVerifier("ch.ethz.prose.JVMAspectInterfaceTest$TestRedefineClasses2");
VerificationResult vr1 = veri1.doPass1();
assertEquals( VerificationResult.VERIFIED_OK, vr1.getStatus() );
VerificationResult vr2 = veri1.doPass2();
assertEquals( VerificationResult.VERIFIED_OK, vr2.getStatus() );
VerificationResult vr3a = veri1.doPass3a(1);
assertEquals( VerificationResult.VERIFIED_OK, vr3a.getStatus() );
VerificationResult vr3b = veri1.doPass3b(1);
assertEquals( VerificationResult.VERIFIED_OK, vr3b.getStatus() );
*/
try { jc2 = HotSwapAspectInterfaceImpl.getBCELClassDefinition( cl2 ); }
catch( ClassNotFoundException ie ) { fail( "getClassDefinition could not read unmodified class file (IOException)" ); }
catch( Exception fe ) { fail( "getClassDefinition malformed unmodified class file" ); }
// test original methods
TestRedefineClasses1 test1 = new TestRedefineClasses1();
assertEquals( "unmodified test1.method() failed (internal error in the test)", 1, test1.method() );
//
// Redefine cl1 using cl2
//
// change the behavour of cl1
ClassGen cgen = new ClassGen( jc1 );
ConstantPoolGen cpgen = cgen.getConstantPool();
org.apache.bcel.classfile.Method meth = cgen.getMethodAt( 1 );
MethodGen mgen = new MethodGen( meth, cgen.getClassName(), cpgen );
cgen.removeMethod( meth );
InstructionList il = mgen.getInstructionList();
InstructionHandle ih = il.findHandle( 0 );
Instruction in = new ICONST( 2 );
ih.setInstruction( in );
cgen.addMethod( mgen.getMethod() );
il.dispose();
byte[] klassDef = cgen.getJavaClass().getBytes();
byte[][] defs = { klassDef };
// redefine cls1 using cls2
try { HotSwapClassWeaver.redefineClasses( cls1, defs ); }
catch ( RuntimeException re ) { fail( "redefineClasses could not redefine class (RuntimeException) " + re.getMessage() ); }
// test replaced methods
assertEquals( "redefineClasses modified test1.method() failed", 2, test1.method() );
}
/**
* Test suite.
* @return test instance
*/
public static Test suite() {
return new TestSuite(HotSwapTest.class);
}
}