mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-02-10 11:51:09 +08:00
inject singleton field and method to mock class
This commit is contained in:
parent
948878687c
commit
cefc5fb1df
@ -7,9 +7,12 @@ public class ConstPool {
|
||||
|
||||
public static final String DOT = ".";
|
||||
public static final String SLASH = "/";
|
||||
public static final String DOLLAR = "$";
|
||||
public static final String UNDERLINE = "_";
|
||||
|
||||
public static final String TEST_POSTFIX = "Test";
|
||||
public static final String MOCK_POSTFIX = "Mock";
|
||||
public static final String INNER_MOCK_CLASS = "$Mock";
|
||||
|
||||
public static final String FIELD_TARGET_METHOD = "targetMethod";
|
||||
public static final String FIELD_TARGET_CLASS = "targetClass";
|
||||
@ -18,8 +21,6 @@ public class ConstPool {
|
||||
public static final String MOCK_METHOD = "com.alibaba.testable.core.annotation.MockMethod";
|
||||
public static final String MOCK_CONSTRUCTOR = "com.alibaba.testable.core.annotation.MockConstructor";
|
||||
|
||||
public static final String CGLIB_CLASS_INFIX = "$$EnhancerBy";
|
||||
|
||||
public static final String KOTLIN_POSTFIX_COMPANION = "$Companion";
|
||||
public static final String KOTLIN_PREFIX_ACCESS = "access$";
|
||||
|
||||
|
@ -15,6 +15,8 @@ import java.util.Iterator;
|
||||
*/
|
||||
abstract public class BaseClassHandler implements Opcodes {
|
||||
|
||||
protected String mockClassName;
|
||||
|
||||
protected boolean wasTransformed(ClassNode cn, String refName, String refDescriptor) {
|
||||
Iterator<FieldNode> iterator = cn.fields.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
@ -24,7 +26,6 @@ abstract public class BaseClassHandler implements Opcodes {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// TODO: `ACC_STATIC` should be removed in v0.5 to shorten the life cycle of this variable
|
||||
cn.fields.add(new FieldNode(ACC_PRIVATE | ACC_STATIC, refName, refDescriptor, null, null));
|
||||
return false;
|
||||
}
|
||||
|
@ -1,15 +1,52 @@
|
||||
package com.alibaba.testable.agent.handler;
|
||||
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
import org.objectweb.asm.tree.*;
|
||||
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.CONSTRUCTOR;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class MockClassHandler extends BaseClassHandler {
|
||||
|
||||
private static final String REF_INSTANCE = "_instance";
|
||||
private static final String REF_GET_INSTANCE = "getInstance";
|
||||
private static final String VOID_ARGS = "()";
|
||||
private static final String VOID_RES = "V";
|
||||
|
||||
public MockClassHandler(String className) {
|
||||
this.mockClassName = className;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void transform(ClassNode cn) {
|
||||
if (wasTransformed(cn, REF_INSTANCE, ClassUtil.toByteCodeClassName(mockClassName))) {
|
||||
return;
|
||||
}
|
||||
addGetInstanceMethod(cn);
|
||||
}
|
||||
|
||||
private void addGetInstanceMethod(ClassNode cn) {
|
||||
MethodNode getInstanceMethod = new MethodNode(ACC_PUBLIC | ACC_STATIC, REF_GET_INSTANCE,
|
||||
VOID_ARGS + ClassUtil.toByteCodeClassName(mockClassName), null, null);
|
||||
InsnList il = new InsnList();
|
||||
il.add(new FieldInsnNode(GETSTATIC, mockClassName, REF_INSTANCE,
|
||||
ClassUtil.toByteCodeClassName(mockClassName)));
|
||||
LabelNode label = new LabelNode();
|
||||
il.add(new JumpInsnNode(IFNONNULL, label));
|
||||
il.add(new TypeInsnNode(NEW, mockClassName));
|
||||
il.add(new InsnNode(DUP));
|
||||
il.add(new MethodInsnNode(INVOKESPECIAL, mockClassName, CONSTRUCTOR, VOID_ARGS + VOID_RES, false));
|
||||
il.add(new FieldInsnNode(PUTSTATIC, mockClassName, REF_INSTANCE, ClassUtil.toByteCodeClassName(mockClassName)));
|
||||
il.add(label);
|
||||
il.add(new FrameNode(F_SAME, 0, null, 0, null));
|
||||
il.add(new FieldInsnNode(GETSTATIC, mockClassName, REF_INSTANCE, ClassUtil.toByteCodeClassName(mockClassName)));
|
||||
il.add(new InsnNode(ARETURN));
|
||||
getInstanceMethod.instructions = il;
|
||||
getInstanceMethod.maxStack = 2;
|
||||
getInstanceMethod.maxLocals = 0;
|
||||
cn.methods.add(getInstanceMethod);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,8 +29,9 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
add(Opcodes.INVOKEINTERFACE);
|
||||
}};
|
||||
|
||||
public SourceClassHandler(List<MethodInfo> injectMethods) {
|
||||
public SourceClassHandler(List<MethodInfo> injectMethods, String mockClassName) {
|
||||
this.injectMethods = injectMethods;
|
||||
this.mockClassName = mockClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,7 @@ import org.objectweb.asm.tree.*;
|
||||
import javax.lang.model.type.NullType;
|
||||
import java.util.List;
|
||||
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.CONSTRUCTOR;
|
||||
import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassName;
|
||||
|
||||
/**
|
||||
@ -35,13 +36,17 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
private static final String SIGNATURE_INVOKE_RECORDER_METHOD = "([Ljava/lang/Object;ZZ)V";
|
||||
private static final String SIGNATURE_PARAMETERS = "Ljava/util/Map;";
|
||||
|
||||
public TestClassHandler(String mockClassName) {
|
||||
this.mockClassName = mockClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bytecode of test class
|
||||
* @param cn original class node
|
||||
*/
|
||||
@Override
|
||||
protected void transform(ClassNode cn) {
|
||||
if (wasTransformed(cn, REF_TESTABLE_CONTEXT, ClassUtil.toByteCodeClassName(CLASS_MOCK_CONTEXT))) {
|
||||
if (wasTransformed(cn, REF_TESTABLE_CONTEXT, ClassUtil.toByteCodeClassName(mockClassName))) {
|
||||
return;
|
||||
}
|
||||
for (MethodNode mn : cn.methods) {
|
||||
@ -58,7 +63,7 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
InsnList il = new InsnList();
|
||||
il.add(new TypeInsnNode(NEW, CLASS_MOCK_CONTEXT));
|
||||
il.add(new InsnNode(DUP));
|
||||
il.add(new MethodInsnNode(INVOKESPECIAL, CLASS_MOCK_CONTEXT, "<init>", "()V", false));
|
||||
il.add(new MethodInsnNode(INVOKESPECIAL, CLASS_MOCK_CONTEXT, CONSTRUCTOR, "()V", false));
|
||||
il.add(new FieldInsnNode(PUTSTATIC, cn.name, REF_TESTABLE_CONTEXT,
|
||||
ClassUtil.toByteCodeClassName(CLASS_MOCK_CONTEXT)));
|
||||
mn.instructions.insertBefore(mn.instructions.get(0), il);
|
||||
|
@ -28,8 +28,7 @@ import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.DOT;
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.SLASH;
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.*;
|
||||
import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassName;
|
||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
||||
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
||||
@ -68,25 +67,27 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
LogUtil.verbose("Handle class: " + className);
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
String mockClass = foundMockForSourceClass(className);
|
||||
if (mockClass != null) {
|
||||
// it's a source class with testable enabled
|
||||
List<MethodInfo> injectMethods = getTestableMockMethods(mockClass);
|
||||
LogUtil.diagnose("Handling source class %s", className);
|
||||
bytes = new SourceClassHandler(injectMethods).getBytes(classFileBuffer);
|
||||
if (isMockClass(className)) {
|
||||
// it's a mock class
|
||||
LogUtil.diagnose("Handling mock class %s", className);
|
||||
bytes = new MockClassHandler(className).getBytes(classFileBuffer);
|
||||
dumpByte(className, bytes);
|
||||
} else {
|
||||
mockClass = foundMockForTestClass(className);
|
||||
String mockClass = foundMockForTestClass(className);
|
||||
if (mockClass != null) {
|
||||
// it's a test class with testable enabled
|
||||
LogUtil.diagnose("Handling test class %s", className);
|
||||
bytes = new TestClassHandler().getBytes(classFileBuffer);
|
||||
dumpByte(className, bytes);
|
||||
} else if (isMockClass(className)) {
|
||||
// it's a mock class
|
||||
LogUtil.diagnose("Handling mock class %s", className);
|
||||
bytes = new MockClassHandler().getBytes(classFileBuffer);
|
||||
bytes = new TestClassHandler(mockClass).getBytes(classFileBuffer);
|
||||
dumpByte(className, bytes);
|
||||
} else {
|
||||
mockClass = foundMockForSourceClass(className);
|
||||
if (mockClass != null) {
|
||||
// it's a source class with testable enabled
|
||||
List<MethodInfo> injectMethods = getTestableMockMethods(mockClass);
|
||||
LogUtil.diagnose("Handling source class %s", className);
|
||||
bytes = new SourceClassHandler(injectMethods, mockClass).getBytes(classFileBuffer);
|
||||
dumpByte(className, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
@ -104,7 +105,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String dumpFile = StringUtil.joinPath(dumpDir, className.replaceAll(SLASH, DOT) + ".class");
|
||||
String dumpFile = StringUtil.joinPath(dumpDir, className.replace(SLASH, DOT).replace(DOLLAR, UNDERLINE) + ".class");
|
||||
LogUtil.verbose("Dump class: " + dumpFile);
|
||||
FileOutputStream stream = new FileOutputStream(dumpFile);
|
||||
stream.write(bytes);
|
||||
@ -127,6 +128,10 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
if (mockClass != null) {
|
||||
return mockClass;
|
||||
}
|
||||
mockClass = ClassUtil.getInnerMockClassName(className);
|
||||
if (isMockClass(mockClass)) {
|
||||
return mockClass;
|
||||
}
|
||||
mockClass = ClassUtil.getMockClassName(ClassUtil.getSourceClassName(className));
|
||||
if (isMockClass(mockClass)) {
|
||||
return mockClass;
|
||||
|
@ -84,6 +84,15 @@ public class ClassUtil {
|
||||
name.substring(ConstPool.KOTLIN_PREFIX_ACCESS.length()) : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* get inner mock class name from test class name
|
||||
* @param testClassName test class name
|
||||
* @return mock class name
|
||||
*/
|
||||
public static String getInnerMockClassName(String testClassName) {
|
||||
return testClassName + ConstPool.INNER_MOCK_CLASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* get mock class name from source class name
|
||||
* @param sourceClassName source class name
|
||||
|
@ -15,7 +15,7 @@ import static org.objectweb.asm.Opcodes.*;
|
||||
|
||||
class SourceClassHandlerTest {
|
||||
|
||||
private final SourceClassHandler handler = new SourceClassHandler(new ArrayList<MethodInfo>());
|
||||
private final SourceClassHandler handler = new SourceClassHandler(new ArrayList<MethodInfo>(), "");
|
||||
|
||||
@Test
|
||||
void should_get_member_method_start() {
|
||||
|
Loading…
Reference in New Issue
Block a user