/* * Copyright 2011 the original author or authors. * * 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 org.powermock.core.transformers.impl; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import javassist.CtMethod; import javassist.CtNewMethod; import javassist.Loader; import javassist.Modifier; import javassist.NotFoundException; import javassist.bytecode.AccessFlag; import org.junit.Test; import org.powermock.core.IndicateReloadClass; import org.powermock.core.MockGateway; import org.powermock.core.classloader.MockClassLoader; import org.powermock.core.transformers.MockTransformer; import powermock.test.support.ClassWithLargeMethods; import powermock.test.support.MainMockTransformerTestSupport.CallSpy; import powermock.test.support.MainMockTransformerTestSupport.SubclassWithBridgeMethod; import powermock.test.support.MainMockTransformerTestSupport.SuperClassWithObjectMethod; import powermock.test.support.MainMockTransformerTestSupport.SupportClasses; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; public class ClassMockTransformerTest { public static final String SYNTHETIC_METHOD_NAME = "$synth"; public static final String SYNTH_FIELD = "$_synthField"; public static final String SYNTHETIC_FIELD_VALUE = "Synthetic Field Value"; /** * This tests that a inner 'public static final class' can be modified to drop the final modifier. Fixes <a * href="http://code.google.com/p/powermock/issues/detail?id=95">Issue 95</a>. */ @Test public void staticFinalInnerClassesShouldBecomeNonFinal() throws Exception { Class<?> clazz = loadWithMockClassLoader(SupportClasses.StaticFinalInnerClass.class.getName()); assertFalse(Modifier.isFinal(clazz.getModifiers())); } /** * This tests that a inner 'public final class' can be modified to drop the final modifier. Fixes <a * href="http://code.google.com/p/powermock/issues/detail?id=95">Issue 95</a>. */ @Test public void finalInnerClassesShouldBecomeNonFinal() throws Exception { Class<?> clazz = loadWithMockClassLoader(SupportClasses.FinalInnerClass.class.getName()); assertFalse(Modifier.isFinal(clazz.getModifiers())); } /** * This tests that a inner 'enum' can be modified to drop the final modifier. Fixes <a * href="http://code.google.com/p/powermock/issues/detail?id=95">Issue 95</a>. */ @Test public void enumClassesShouldBecomeNonFinal() throws Exception { Class<?> clazz = loadWithMockClassLoader(SupportClasses.EnumClass.class.getName()); assertFalse(Modifier.isFinal(clazz.getModifiers())); } @Test public void privateInnerClassesShouldBecomeNonFinal() throws Exception { Class<?> clazz = loadWithMockClassLoader(SupportClasses.class.getName() + "$PrivateStaticFinalInnerClass"); assertFalse(Modifier.isFinal(clazz.getModifiers())); } @Test public void subclassShouldNormallyGetAnAdditionalDeferConstructor() throws Exception { Class<?> clazz = loadWithMockClassLoader(SupportClasses.SubClass.class.getName()); assertEquals("Original number of constructoprs", 1, SupportClasses.SubClass.class.getConstructors().length); assertEquals("Number of constructors in modified class", 2, clazz.getConstructors().length); assertNotNull("Defer-constructor expected", clazz.getConstructor(IndicateReloadClass.class)); } @Test public void shouldLoadClassWithMethodLowerThanJvmLimit() throws Exception { Class<?> clazz = loadWithMockClassLoader(ClassWithLargeMethods.MethodLowerThanLimit.class.getName()); assertNotNull("Class has been loaded", clazz); // There should be no exception since method was not overridden clazz.getMethod("init").invoke(clazz); } @Test public void shouldLoadClassAndOverrideMethodGreaterThanJvmLimit() throws Exception { Class<?> clazz = loadWithMockClassLoader(ClassWithLargeMethods.MethodGreaterThanLimit.class.getName()); assertNotNull("Class has been loaded", clazz); // There should be exception since method was overridden to satisfy JVM limit try { clazz.getMethod("init").invoke(clazz); fail("Overridden method should throw exception"); } catch (Exception e) { Throwable cause = e.getCause(); assertThat(cause).isInstanceOf(IllegalAccessException.class); assertThat(cause.getMessage()).contains("Method was too large and after instrumentation exceeded JVM limit"); } } @Test public void shouldIgnoreSyntheticNonBridgeMethods() throws Throwable { final ClassPool classPool = new ClassPool(true); CtClass ctClass = prepareClassesForTest(classPool, "return;"); new ClassMockTransformer().transform(ctClass); runTestWithNewClassLoader(classPool, ShouldIgnoreSyntheticNonBridgeMethods.class.getName()); } @Test public void shouldIgnoreCallToSyntheticNonBridgeMethods() throws Throwable { final ClassPool classPool = new ClassPool(true); CtClass ctClass = prepareClassesForTest(classPool, "powermock.test.support.MainMockTransformerTestSupport.CallSpy.registerMethodCall($1);"); new ClassMockTransformer().transform(ctClass); runTestWithNewClassLoader(classPool, ShouldIgnoreCallToSyntheticNonBridgeMethods.class.getName()); } @Test public void shouldIgnoreCallToSyntheticField() throws Throwable { final ClassPool classPool = new ClassPool(true); CtClass ctClass = prepareClassesForFieldTests(classPool); new ClassMockTransformer().transform(ctClass); runTestWithNewClassLoader(classPool, ShouldIgnoreCallToSyntheticField.class.getName()); } @Test public void shouldModifyBridgeMethods() throws Throwable { final ClassPool classPool = new ClassPool(true); addCallInterceptorToMockGateway(classPool); CtClass ctClass = classPool.get(SubclassWithBridgeMethod.class.getName()); new ClassMockTransformer().transform(ctClass); runTestWithNewClassLoader(classPool, ShouldModifyBridgeMethods.class.getName()); } private CtClass prepareClassesForFieldTests(ClassPool classPool) throws NotFoundException, CannotCompileException { addCallInterceptorToMockGateway(classPool); CtClass ctClass = classPool.getCtClass(SuperClassWithObjectMethod.class.getName()); addSyntheticField(classPool, ctClass); insertCallSyntheticMethod(ctClass); return ctClass; } private void insertCallSyntheticMethod(CtClass ctClass) throws CannotCompileException { for (CtMethod method : ctClass.getDeclaredMethods()) { method.insertBefore( "String v = " + SYNTH_FIELD + ";" + SYNTH_FIELD + " = \"" + method.getName() + "\";" ); } } private void addSyntheticField(ClassPool classPool, CtClass ctClass) throws CannotCompileException, NotFoundException { CtField field = new CtField(classPool.get(String.class.getName()), SYNTH_FIELD, ctClass); field.setModifiers(AccessFlag.SYNTHETIC); ctClass.addField(field, CtField.Initializer.constant(SYNTHETIC_FIELD_VALUE)); } private Class<?> loadWithMockClassLoader(String className) throws ClassNotFoundException { MockClassLoader loader = new MockClassLoader(new String[]{MockClassLoader.MODIFY_ALL_CLASSES}); loader.setMockTransformerChain(Collections.<MockTransformer>singletonList(new ClassMockTransformer())); return Class.forName(className, true, loader); } private CtClass prepareClassesForTest(ClassPool classPool, String body) throws NotFoundException, CannotCompileException { addCallInterceptorToMockGateway(classPool); CtClass ctClass = classPool.getCtClass(SuperClassWithObjectMethod.class.getName()); addSyntheticMethod(classPool, ctClass, body); return ctClass; } private void runTestWithNewClassLoader(ClassPool classPool, String name) throws Throwable { Loader loader = new Loader(classPool); loader.run(name, new String[0]); } private void addCallInterceptorToMockGateway(ClassPool classPool) throws NotFoundException, CannotCompileException { CtClass mockGetawayClass = classPool.get(MockGateway.class.getName()); for (CtMethod method : mockGetawayClass.getMethods()) { String methodName = method.getName(); if (methodName.equals("methodCall")) { method.insertBefore( "powermock.test.support.MainMockTransformerTestSupport.CallSpy.registerMethodCall(" + "methodName" + ");" ); }else if(methodName.equals("fieldCall")){ method.insertBefore( "powermock.test.support.MainMockTransformerTestSupport.CallSpy.registerFieldCall(" + "fieldName" + ");" ); } } } private void addSyntheticMethod(ClassPool classPool, CtClass ctClass, String body) throws NotFoundException, CannotCompileException { CtMethod ctMethod = CtNewMethod.make(AccessFlag.SYNTHETIC, CtClass.voidType, SYNTHETIC_METHOD_NAME, new CtClass[]{classPool.get(String.class.getName())}, null, body, ctClass); ctClass.addMethod(ctMethod); for (CtMethod method : ctClass.getDeclaredMethods()) { if (!method.getName().equals(SYNTHETIC_METHOD_NAME)) { method.insertBefore("$synth(\"" + method.getLongName() + "\");"); } } } public static class ShouldIgnoreSyntheticNonBridgeMethods { public static void main(String[] args) throws Exception { Class clazz = SuperClassWithObjectMethod.class; Method method = null; for (Method m : clazz.getDeclaredMethods()) { if (m.getName().equals(SYNTHETIC_METHOD_NAME)) { method = m; break; } } Object instance = clazz.newInstance(); if (method != null) { method.setAccessible(true); } method.invoke(instance, ""); assertThat(CallSpy.getMethodCalls()).isEmpty(); } } @SuppressWarnings("unchecked") public static class ShouldIgnoreCallToSyntheticNonBridgeMethods { public static void main(String[] args) throws Exception { Class clazz = SuperClassWithObjectMethod.class; Object instance = clazz.newInstance(); clazz.getMethod("doSomething", Object.class).invoke(instance, new Object()); List<String> calls = CallSpy.getMethodCalls(); assertThat(calls).contains("doSomething").doesNotContain(SYNTHETIC_METHOD_NAME); } } public static class ShouldIgnoreCallToSyntheticField { public static void main(String[] args) throws Exception { Class clazz = SuperClassWithObjectMethod.class; Object instance = clazz.newInstance(); clazz.getMethod("doSomething", Object.class).invoke(instance, new Object()); assertThat(CallSpy.getFieldCalls()).doesNotContain(SYNTH_FIELD); Field field = clazz.getDeclaredField(SYNTH_FIELD); field.setAccessible(true); String fieldValue = (String) field.get(instance); assertThat(fieldValue).isEqualTo("doSomething"); } } public static class ShouldModifyBridgeMethods { public static void main(String[] args) throws Exception { Class clazz = SubclassWithBridgeMethod.class; Object instance = clazz.newInstance(); clazz.getMethod("doSomething", String.class).invoke(instance, "value"); List<String> calls = CallSpy.getMethodCalls(); assertThat(calls).contains("doSomething"); } } }