Skip to content
Permalink
Browse files
Fixes #727 Use ByteBuddy to instrument classes instead Javassist: add…
… transforming static and final methods.
  • Loading branch information
Arthur Zagretdinov committed Apr 22, 2018
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 {

0 comments on commit cec406f

Please sign in to comment.