Skip to content
Permalink
Browse files

Fixes #727 Use ByteBuddy to instrument classes instead Javassist: add…

… transforming static and final methods.
  • Loading branch information...
thekingnothing committed Oct 21, 2017
1 parent c8876d7 commit cec406f45e56e86ea71a24e936d0d885ab293063
Showing with 1,019 additions and 295 deletions.
  1. +9 −3 gradle/modules.gradle
  2. +1 −1 powermock-core/src/main/java/org/powermock/core/classloader/MockClassLoader.java
  3. +63 −0 powermock-core/src/main/java/org/powermock/core/transformers/bytebuddy/MethodMockTransformer.java
  4. +7 −0 powermock-core/src/main/java/org/powermock/core/transformers/bytebuddy/advice/MethodDispatcher.java
  5. +27 −0 ...e/src/main/java/org/powermock/core/transformers/bytebuddy/advice/MockGatewayMethodDispatcher.java
  6. +52 −0 powermock-core/src/main/java/org/powermock/core/transformers/bytebuddy/advice/MockMethodAdvice.java
  7. +20 −0 ...ck-core/src/main/java/org/powermock/core/transformers/bytebuddy/advice/MockMethodDispatchers.java
  8. +46 −0 ...k-core/src/main/java/org/powermock/core/transformers/bytebuddy/advice/MockStaticMethodAdvice.java
  9. +7 −2 powermock-core/src/main/java/org/powermock/core/transformers/javassist/MethodMockTransformer.java
  10. +1 −1 ...re/src/main/java/org/powermock/core/transformers/javassist/support/PowerMockExpressionEditor.java
  11. +5 −0 ...rmock-core/src/main/java/org/powermock/core/transformers/support/DefaultMockTransformerChain.java
  12. +25 −7 powermock-core/src/test/java/org/powermock/core/test/ClassLoaderTestHelper.java
  13. +6 −22 powermock-core/src/test/java/org/powermock/core/transformers/AbstractBaseMockTransformerTest.java
  14. +28 −42 powermock-core/src/test/java/org/powermock/core/transformers/ConstructorCallMockTransformerTest.java
  15. +4 −8 powermock-core/src/test/java/org/powermock/core/transformers/InstrumentMockTransformerTest.java
  16. +290 −0 powermock-core/src/test/java/org/powermock/core/transformers/MethodsMockTransformerTest.java
  17. +20 −0 powermock-core/src/test/java/org/powermock/core/transformers/MockTransformerTestHelper.java
  18. +0 −169 ...ore/src/test/java/org/powermock/core/transformers/StaticFinalNativeMethodMockTransformerTest.java
  19. +95 −0 powermock-core/src/test/java/org/powermock/core/transformers/StaticMethodsMockTransformerTest.java
  20. +1 −1 ...e/src/test/java/org/powermock/core/transformers/javassist/TestClassTransformerJavaAssistTest.java
  21. +149 −0 powermock-core/src/test/java/org/powermock/core/transformers/mock/MockGatewaySpy.java
  22. +10 −0 ...rmock-core/src/test/java/org/powermock/core/transformers/mock/MockGatewaySpyMethodDispatcher.java
  23. +125 −24 powermock-core/src/test/java/powermock/test/support/MainMockTransformerTestSupport.java
  24. +8 −2 powermock-release/build.gradle
  25. +15 −7 tests/java8/build.gradle
  26. +5 −6 tests/mockito/build.gradle
@@ -85,7 +85,10 @@ project(":powermock-core") {
testCompile("org.assertj:assertj-core:${assertjVersion}")
testCompile(project(":tests:utils"))

testCompile("org.mockito:mockito-core:${mockitoVersion}")
testCompile("org.mockito:mockito-core:${mockitoVersion}"){
exclude group: 'net.bytebuddy', module: 'byte-buddy'
exclude group: 'net.bytebuddy', module: 'byte-buddy-agent'
}
}
}

@@ -115,7 +118,7 @@ project(":powermock-api:powermock-api-easymock") {
dependencies {
compile(project(":powermock-api:powermock-api-support"))
compile("cglib:cglib-nodep:${cglibVersion}")
provided("org.easymock:easymock:${easymockVersion}")
compile("org.easymock:easymock:${easymockVersion}")

testCompile("junit:junit:${junitVersion}") {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
@@ -133,7 +136,10 @@ project(":powermock-api:powermock-api-mockito2"){
dependencies {

compile(project(":powermock-api:powermock-api-support"))
compile("org.mockito:mockito-core:${mockitoVersion}")
compile("org.mockito:mockito-core:${mockitoVersion}"){
exclude group: 'net.bytebuddy', module: 'byte-buddy'
exclude group: 'net.bytebuddy', module: 'byte-buddy-agent'
}

testCompile("junit:junit:${junitVersion}") {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
@@ -117,7 +117,7 @@ public void setMockTransformerChain(MockTransformerChain mockTransformerChain) {
return defineClass(name, protectionDomain, clazz);
}

protected Class<?> defineClass(final String name, final ProtectionDomain protectionDomain, final byte[] clazz) {
public Class<?> defineClass(final String name, final ProtectionDomain protectionDomain, final byte[] clazz) {
return defineClass(name, clazz, 0, clazz.length, protectionDomain);
}

@@ -0,0 +1,63 @@
package org.powermock.core.transformers.bytebuddy;

import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.RandomString;
import org.powermock.core.transformers.TransformStrategy;
import org.powermock.core.transformers.bytebuddy.advice.MockMethodAdvice;
import org.powermock.core.transformers.bytebuddy.advice.MockGatewayMethodDispatcher;
import org.powermock.core.transformers.bytebuddy.advice.MockMethodDispatchers;
import org.powermock.core.transformers.bytebuddy.advice.MockStaticMethodAdvice;
import org.powermock.core.transformers.bytebuddy.support.ByteBuddyClass;

import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isVirtual;

public class MethodMockTransformer extends AbstractByteBuddyMockTransformer {

private final String identifier;

public MethodMockTransformer(final TransformStrategy strategy) {
super(strategy);
identifier = RandomString.make();
MockMethodDispatchers.set(identifier, new MockGatewayMethodDispatcher());
}

public String getIdentifier() {
return identifier;
}

@Override
protected boolean classShouldTransformed(final TypeDescription typeDefinitions) {
return true;
}

@Override
public ByteBuddyClass transform(final ByteBuddyClass clazz) throws Exception {
final Builder builder = clazz.getBuilder()
.visit(virtualMethods())
.visit(staticMethods());
return new ByteBuddyClass(clazz.getTypeDefinitions(), builder);
}

private ForDeclaredMethods staticMethods() {
return Advice.withCustomMapping()
.bind(MockMethodAdvice.Identifier.class, identifier)
.to(MockStaticMethodAdvice.class)
.on(
isMethod().and(ElementMatchers.<MethodDescription>isStatic())
);
}

private ForDeclaredMethods virtualMethods() {
return Advice.withCustomMapping()
.bind(MockMethodAdvice.Identifier.class, identifier)
.to(MockMethodAdvice.class)
.on(isVirtual());
}
}
@@ -0,0 +1,7 @@
package org.powermock.core.transformers.bytebuddy.advice;

import java.util.concurrent.Callable;

public interface MethodDispatcher {
Callable<Object> methodCall(Object instance, String methodName, Object[] args, Class<?>[] sig, String returnTypeAsString) throws Throwable;
}
@@ -0,0 +1,27 @@
package org.powermock.core.transformers.bytebuddy.advice;

import org.powermock.core.MockGateway;

import java.util.concurrent.Callable;

public class MockGatewayMethodDispatcher implements MethodDispatcher {
public Callable<Object> methodCall(Object instance, String methodName, Object[] args, Class<?>[] sig,
String returnTypeAsString) throws Throwable {
final Object result = handleMethodCall(instance, methodName, args, sig, returnTypeAsString);
if (result == MockGateway.PROCEED) {
return null;
} else {
return new Callable<Object>() {
@Override
public Object call() throws Exception {
return result;
}
};
}
}

protected Object handleMethodCall(final Object instance, final String methodName, final Object[] args,
final Class<?>[] sig, final String returnTypeAsString) throws Throwable {
return MockGateway.methodCall(instance, methodName, args, sig, returnTypeAsString);
}
}
@@ -0,0 +1,52 @@
package org.powermock.core.transformers.bytebuddy.advice;

import net.bytebuddy.asm.Advice;
import net.bytebuddy.implementation.bytecode.assign.Assigner;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;

public class MockMethodAdvice {

public static final String VOID = "";

@SuppressWarnings("unused")
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
private static Callable<?> enter(@Identifier String identifier,
@Advice.This Object instance,
@Advice.Origin Method origin,
@Advice.AllArguments Object[] arguments) throws Throwable {
final Class<?> returnType = origin.getReturnType();
final String returnTypeAsString;
if (!returnType.equals(Void.class)) {
returnTypeAsString = returnType.getName();
} else {
returnTypeAsString = VOID;
}

final MethodDispatcher methodDispatcher = MockMethodDispatchers.get(identifier, instance);

if (methodDispatcher == null){
return null;
}else {
return methodDispatcher.methodCall(instance, origin.getName(), arguments, origin.getParameterTypes(), returnTypeAsString);
}
}

@SuppressWarnings({"unused", "UnusedAssignment"})
@Advice.OnMethodExit
private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object returned,
@Advice.Enter Callable<?> mocked) throws Throwable {
if (mocked != null) {
returned = mocked.call();
}
}

@Retention(RetentionPolicy.RUNTIME)
public @interface Identifier {

}

}
@@ -0,0 +1,20 @@
package org.powermock.core.transformers.bytebuddy.advice;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class MockMethodDispatchers {
private static final ConcurrentMap<String, MethodDispatcher> INSTANCE = new ConcurrentHashMap<String, MethodDispatcher>();

public static MethodDispatcher get(String identifier, Object mock) {
if (mock == INSTANCE) { // Avoid endless loop if ConcurrentHashMap was redefined to check for being a mock.
return null;
} else {
return INSTANCE.get(identifier);
}
}

public static void set(String identifier, MethodDispatcher dispatcher) {
INSTANCE.put(identifier, dispatcher);
}
}
@@ -0,0 +1,46 @@
package org.powermock.core.transformers.bytebuddy.advice;

import net.bytebuddy.asm.Advice;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import org.powermock.core.transformers.bytebuddy.advice.MockMethodAdvice.Identifier;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

import static org.powermock.core.transformers.bytebuddy.advice.MockMethodAdvice.VOID;

public class MockStaticMethodAdvice {

@SuppressWarnings("unused")
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
private static Callable<?> enter(@Identifier String identifier,
@Advice.Origin Method origin,
@Advice.AllArguments Object[] arguments) throws Throwable {
final Class<?> returnType = origin.getReturnType();
final String returnTypeAsString;
if (!returnType.equals(Void.class)) {
returnTypeAsString = returnType.getName();
} else {
returnTypeAsString = VOID;
}

final Class<?> mock = origin.getDeclaringClass();
final MethodDispatcher methodDispatcher = MockMethodDispatchers.get(identifier, mock);

if (methodDispatcher == null){
return null;
}else {
return methodDispatcher.methodCall(mock, origin.getName(), arguments, origin.getParameterTypes(), returnTypeAsString);
}
}

@SuppressWarnings({"unused", "UnusedAssignment"})
@Advice.OnMethodExit
private static void exit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object returned,
@Advice.Enter Callable<?> mocked) throws Throwable {
if (mocked != null) {
returned = mocked.call();
}
}

}
@@ -33,7 +33,12 @@

abstract class MethodMockTransformer extends AbstractJavaAssistMockTransformer {

MethodMockTransformer(final TransformStrategy strategy) {super(strategy);}
private Class<?> mockGetawayClass;

MethodMockTransformer(final TransformStrategy strategy) {
super(strategy);
this.mockGetawayClass = MockGateway.class;
}

void modifyMethod(final CtMethod method) throws NotFoundException, CannotCompileException {

@@ -96,7 +101,7 @@ private void modifyMethod(CtMethod method, CtClass returnTypeAsCtClass,
String classOrInstance = classOrInstance(method);

String code = "Object value = "
+ MockGateway.class.getName()
+ mockGetawayClass.getName()
+ ".methodCall("
+ classOrInstance + ", \""
+ method.getName()
@@ -144,7 +144,7 @@ public void edit(ConstructorCall c) throws CannotCompileException {
code.append("{Object value =")
.append(mockGetawayClass.getName())
.append(".constructorCall($class, $args, $sig);");
code.append("if (value != ").append(mockGetawayClass.getName()).append(".PROCEED){");
code.append("if (value != ").append(MockGateway.class.getName()).append(".PROCEED){");

/*
* TODO Suppress and lazy inject field (when this feature is ready).
@@ -43,6 +43,11 @@ private DefaultMockTransformerChain(final List<MockTransformer> transformers) {
return classWrapper;
}

@Override
public String toString() {
return "MockTransformerChain{" + "transformers=" + transformers + '}';
}

public static MockTransformerChainBuilder newBuilder() {
return new MockTransformerChainBuilder();
}
@@ -23,6 +23,7 @@
import org.powermock.core.classloader.MockClassLoader;
import org.powermock.core.transformers.MockTransformerChain;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

@@ -38,6 +39,28 @@

public static Class<?> loadWithMockClassLoader(final String className, final MockTransformerChain mockTransformerChain,
final MockClassLoaderFactory mockClassloaderFactory) throws Exception {
MockClassLoader loader = getMockClassLoader(mockTransformerChain, mockClassloaderFactory);

Class<?> clazz = Class.forName(className, true, loader);

assertNotNull("Class has been loaded", clazz);

return clazz;
}


public static Class<?> loadWithMockClassLoader(final String className, final byte[] klass, final MockTransformerChain mockTransformerChain,
final MockClassLoaderFactory mockClassloaderFactory) throws Exception {
MockClassLoader loader = getMockClassLoader(mockTransformerChain, mockClassloaderFactory);
final Class<?> definedClass = loader.defineClass(className, ClassLoaderTestHelper.class.getProtectionDomain(), klass);

assertNotNull("Class has been loaded", definedClass);

return definedClass;
}

private static MockClassLoader getMockClassLoader(final MockTransformerChain mockTransformerChain,
final MockClassLoaderFactory mockClassloaderFactory) throws IllegalAccessException, InvocationTargetException, InstantiationException {
MockClassLoader loader = null;

Map<MockTransformerChain, MockClassLoader> classloaders = cache.get(mockClassloaderFactory);
@@ -48,18 +71,13 @@
classloaders = new HashMap<MockTransformerChain, MockClassLoader>();
cache.put(mockClassloaderFactory, classloaders);
}

if (loader == null) {
loader = mockClassloaderFactory.getInstance(new String[]{MockClassLoader.MODIFY_ALL_CLASSES});
loader.setMockTransformerChain(mockTransformerChain);
classloaders.put(mockTransformerChain, loader);
}

Class<?> clazz = Class.forName(className, true, loader);

assertNotNull("Class has been loaded", clazz);

return clazz;
return loader;
}

public static void runTestWithNewClassLoader(ClassPool classPool, String name) throws Throwable {
Oops, something went wrong.

0 comments on commit cec406f

Please sign in to comment.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.