mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-03-23 16:20:26 +08:00
move invoke recorder injection to mock class
This commit is contained in:
parent
6f9accf319
commit
f1edf92626
@ -1,9 +1,17 @@
|
|||||||
package com.alibaba.testable.agent.handler;
|
package com.alibaba.testable.agent.handler;
|
||||||
|
|
||||||
|
import com.alibaba.testable.agent.constant.ConstPool;
|
||||||
|
import com.alibaba.testable.agent.tool.ImmutablePair;
|
||||||
|
import com.alibaba.testable.agent.util.AnnotationUtil;
|
||||||
import com.alibaba.testable.agent.util.ClassUtil;
|
import com.alibaba.testable.agent.util.ClassUtil;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.*;
|
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.constant.ConstPool.CONSTRUCTOR;
|
||||||
|
import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author flin
|
* @author flin
|
||||||
@ -11,6 +19,9 @@ import static com.alibaba.testable.agent.constant.ConstPool.CONSTRUCTOR;
|
|||||||
public class MockClassHandler extends BaseClassHandler {
|
public class MockClassHandler extends BaseClassHandler {
|
||||||
|
|
||||||
private static final String REF_INSTANCE = "_instance";
|
private static final String REF_INSTANCE = "_instance";
|
||||||
|
private static final String CLASS_INVOKE_RECORD_UTIL = "com/alibaba/testable/core/util/InvokeRecordUtil";
|
||||||
|
private static final String METHOD_RECORD_MOCK_INVOKE = "recordMockInvoke";
|
||||||
|
private static final String SIGNATURE_INVOKE_RECORDER_METHOD = "([Ljava/lang/Object;ZZ)V";
|
||||||
|
|
||||||
public MockClassHandler(String className) {
|
public MockClassHandler(String className) {
|
||||||
this.mockClassName = className;
|
this.mockClassName = className;
|
||||||
@ -22,6 +33,14 @@ public class MockClassHandler extends BaseClassHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addGetInstanceMethod(cn);
|
addGetInstanceMethod(cn);
|
||||||
|
for (MethodNode mn : cn.methods) {
|
||||||
|
if (isMockMethod(mn)) {
|
||||||
|
mn.access &= ~ACC_PRIVATE;
|
||||||
|
mn.access &= ~ACC_PROTECTED;
|
||||||
|
mn.access |= ACC_PUBLIC;
|
||||||
|
injectInvokeRecorder(mn);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addGetInstanceMethod(ClassNode cn) {
|
private void addGetInstanceMethod(ClassNode cn) {
|
||||||
@ -46,4 +65,119 @@ public class MockClassHandler extends BaseClassHandler {
|
|||||||
cn.methods.add(getInstanceMethod);
|
cn.methods.add(getInstanceMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isMockMethod(MethodNode mn) {
|
||||||
|
if (mn.visibleAnnotations == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
|
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc) ||
|
||||||
|
ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void injectInvokeRecorder(MethodNode mn) {
|
||||||
|
InsnList il = new InsnList();
|
||||||
|
List<Byte> types = ClassUtil.getParameterTypes(mn.desc);
|
||||||
|
int size = types.size();
|
||||||
|
int parameterOffset = 1;
|
||||||
|
mn.maxStack += 2;
|
||||||
|
il.add(getIntInsn(size));
|
||||||
|
il.add(new TypeInsnNode(ANEWARRAY, ClassUtil.CLASS_OBJECT));
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
mn.maxStack += 3;
|
||||||
|
il.add(new InsnNode(DUP));
|
||||||
|
il.add(getIntInsn(i));
|
||||||
|
ImmutablePair<Integer, Integer> code = getLoadParameterByteCode(types.get(i));
|
||||||
|
il.add(new VarInsnNode(code.left, parameterOffset));
|
||||||
|
parameterOffset += code.right;
|
||||||
|
MethodInsnNode typeConvertMethodNode = ClassUtil.getPrimaryTypeConvertMethod(types.get(i));
|
||||||
|
if (typeConvertMethodNode != null) {
|
||||||
|
il.add(typeConvertMethodNode);
|
||||||
|
}
|
||||||
|
il.add(new InsnNode(AASTORE));
|
||||||
|
}
|
||||||
|
if (isMockForConstructor(mn)) {
|
||||||
|
il.add(new InsnNode(ICONST_1));
|
||||||
|
} else {
|
||||||
|
il.add(new InsnNode(ICONST_0));
|
||||||
|
}
|
||||||
|
if (isTargetClassInParameter(mn)) {
|
||||||
|
il.add(new InsnNode(ICONST_1));
|
||||||
|
} else {
|
||||||
|
il.add(new InsnNode(ICONST_0));
|
||||||
|
}
|
||||||
|
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_INVOKE_RECORD_UTIL, METHOD_RECORD_MOCK_INVOKE,
|
||||||
|
SIGNATURE_INVOKE_RECORDER_METHOD, false));
|
||||||
|
mn.instructions.insertBefore(mn.instructions.get(0), il);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMockForConstructor(MethodNode mn) {
|
||||||
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
|
String annotationName = toDotSeparateFullClassName(an.desc);
|
||||||
|
if (ConstPool.MOCK_CONSTRUCTOR.equals(annotationName)) {
|
||||||
|
return true;
|
||||||
|
} else if (ConstPool.MOCK_METHOD.equals(annotationName)) {
|
||||||
|
String method = AnnotationUtil.getAnnotationParameter
|
||||||
|
(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
|
||||||
|
if (ConstPool.CONSTRUCTOR.equals(method)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTargetClassInParameter(MethodNode mn) {
|
||||||
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
|
if (ConstPool.MOCK_METHOD.equals(toDotSeparateFullClassName(an.desc))) {
|
||||||
|
Type type = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
||||||
|
if (type != null && !type.getClassName().equals(NullType.class.getName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImmutablePair<Integer, Integer> getLoadParameterByteCode(Byte type) {
|
||||||
|
switch (type) {
|
||||||
|
case ClassUtil.TYPE_BYTE:
|
||||||
|
case ClassUtil.TYPE_CHAR:
|
||||||
|
case ClassUtil.TYPE_SHORT:
|
||||||
|
case ClassUtil.TYPE_INT:
|
||||||
|
case ClassUtil.TYPE_BOOL:
|
||||||
|
return ImmutablePair.of(ILOAD, 1);
|
||||||
|
case ClassUtil.TYPE_DOUBLE:
|
||||||
|
return ImmutablePair.of(DLOAD, 2);
|
||||||
|
case ClassUtil.TYPE_FLOAT:
|
||||||
|
return ImmutablePair.of(FLOAD, 1);
|
||||||
|
case ClassUtil.TYPE_LONG:
|
||||||
|
return ImmutablePair.of(LLOAD, 2);
|
||||||
|
default:
|
||||||
|
return ImmutablePair.of(ALOAD, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractInsnNode getIntInsn(int num) {
|
||||||
|
switch (num) {
|
||||||
|
case 0:
|
||||||
|
return new InsnNode(ICONST_0);
|
||||||
|
case 1:
|
||||||
|
return new InsnNode(ICONST_1);
|
||||||
|
case 2:
|
||||||
|
return new InsnNode(ICONST_2);
|
||||||
|
case 3:
|
||||||
|
return new InsnNode(ICONST_3);
|
||||||
|
case 4:
|
||||||
|
return new InsnNode(ICONST_4);
|
||||||
|
case 5:
|
||||||
|
return new InsnNode(ICONST_5);
|
||||||
|
default:
|
||||||
|
return new IntInsnNode(BIPUSH, num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ public class TestClassHandler extends BaseClassHandler {
|
|||||||
|
|
||||||
private static final String CLASS_TESTABLE_TOOL = "com/alibaba/testable/core/tool/TestableTool";
|
private static final String CLASS_TESTABLE_TOOL = "com/alibaba/testable/core/tool/TestableTool";
|
||||||
private static final String CLASS_TESTABLE_UTIL = "com/alibaba/testable/core/util/TestableUtil";
|
private static final String CLASS_TESTABLE_UTIL = "com/alibaba/testable/core/util/TestableUtil";
|
||||||
private static final String CLASS_INVOKE_RECORD_UTIL = "com/alibaba/testable/core/util/InvokeRecordUtil";
|
|
||||||
private static final String CLASS_MOCK_CONTEXT = "com/alibaba/testable/agent/model/MockContext";
|
private static final String CLASS_MOCK_CONTEXT = "com/alibaba/testable/agent/model/MockContext";
|
||||||
private static final String REF_TESTABLE_CONTEXT = "_testableContextReference";
|
private static final String REF_TESTABLE_CONTEXT = "_testableContextReference";
|
||||||
private static final String FIELD_TEST_CASE = "TEST_CASE";
|
private static final String FIELD_TEST_CASE = "TEST_CASE";
|
||||||
@ -30,10 +29,8 @@ public class TestClassHandler extends BaseClassHandler {
|
|||||||
private static final String FIELD_PARAMETERS = "parameters";
|
private static final String FIELD_PARAMETERS = "parameters";
|
||||||
private static final String METHOD_CURRENT_TEST_CASE_NAME = "currentTestCaseName";
|
private static final String METHOD_CURRENT_TEST_CASE_NAME = "currentTestCaseName";
|
||||||
private static final String METHOD_CURRENT_SOURCE_METHOD_NAME = "currentSourceMethodName";
|
private static final String METHOD_CURRENT_SOURCE_METHOD_NAME = "currentSourceMethodName";
|
||||||
private static final String METHOD_RECORD_MOCK_INVOKE = "recordMockInvoke";
|
|
||||||
private static final String SIGNATURE_CURRENT_TEST_CASE_NAME = "(Ljava/lang/String;)Ljava/lang/String;";
|
private static final String SIGNATURE_CURRENT_TEST_CASE_NAME = "(Ljava/lang/String;)Ljava/lang/String;";
|
||||||
private static final String SIGNATURE_CURRENT_SOURCE_METHOD_NAME = "()Ljava/lang/String;";
|
private static final String SIGNATURE_CURRENT_SOURCE_METHOD_NAME = "()Ljava/lang/String;";
|
||||||
private static final String SIGNATURE_INVOKE_RECORDER_METHOD = "([Ljava/lang/Object;ZZ)V";
|
|
||||||
private static final String SIGNATURE_PARAMETERS = "Ljava/util/Map;";
|
private static final String SIGNATURE_PARAMETERS = "Ljava/util/Map;";
|
||||||
|
|
||||||
public TestClassHandler(String mockClassName) {
|
public TestClassHandler(String mockClassName) {
|
||||||
@ -53,7 +50,6 @@ public class TestClassHandler extends BaseClassHandler {
|
|||||||
if (mn.name.equals(ConstPool.CONSTRUCTOR)) {
|
if (mn.name.equals(ConstPool.CONSTRUCTOR)) {
|
||||||
initMockContextReference(cn, mn);
|
initMockContextReference(cn, mn);
|
||||||
} else {
|
} else {
|
||||||
handleMockMethod(cn, mn);
|
|
||||||
handleInstruction(cn, mn);
|
handleInstruction(cn, mn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,67 +66,6 @@ public class TestClassHandler extends BaseClassHandler {
|
|||||||
mn.maxStack++;
|
mn.maxStack++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMockMethod(ClassNode cn, MethodNode mn) {
|
|
||||||
if (isMockMethod(mn)) {
|
|
||||||
toPublicStatic(cn, mn);
|
|
||||||
injectInvokeRecorder(mn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toPublicStatic(ClassNode cn, MethodNode mn) {
|
|
||||||
mn.access &= ~ACC_PRIVATE;
|
|
||||||
mn.access &= ~ACC_PROTECTED;
|
|
||||||
mn.access |= ACC_PUBLIC;
|
|
||||||
if ((mn.access & ACC_STATIC) == 0) {
|
|
||||||
mn.access |= ACC_STATIC;
|
|
||||||
// remove `this` reference
|
|
||||||
LocalVariableNode thisRef = null;
|
|
||||||
for (LocalVariableNode vn : mn.localVariables) {
|
|
||||||
if (vn.index == 0) {
|
|
||||||
thisRef = vn;
|
|
||||||
} else {
|
|
||||||
vn.index--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (thisRef != null) {
|
|
||||||
mn.localVariables.remove(thisRef);
|
|
||||||
} else {
|
|
||||||
LogUtil.error("Fail to find `this` reference in non-static method " + getName(cn, mn));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (AbstractInsnNode in : mn.instructions) {
|
|
||||||
if (in instanceof IincInsnNode) {
|
|
||||||
((IincInsnNode)in).var--;
|
|
||||||
} else if (in instanceof VarInsnNode) {
|
|
||||||
if (((VarInsnNode)in).var > 0) {
|
|
||||||
((VarInsnNode)in).var--;
|
|
||||||
} else if (in.getOpcode() == ALOAD) {
|
|
||||||
LogUtil.error("Attempt to access non-static member in mock method " + getName(cn, mn));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mn.maxLocals--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getName(ClassNode cn, MethodNode mn) {
|
|
||||||
return cn.name + ":" + mn.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMockMethod(MethodNode mn) {
|
|
||||||
if (mn.visibleAnnotations == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
|
||||||
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc) ||
|
|
||||||
ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleInstruction(ClassNode cn, MethodNode mn) {
|
private void handleInstruction(ClassNode cn, MethodNode mn) {
|
||||||
AbstractInsnNode[] instructions = mn.instructions.toArray();
|
AbstractInsnNode[] instructions = mn.instructions.toArray();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@ -173,106 +108,4 @@ public class TestClassHandler extends BaseClassHandler {
|
|||||||
return mn.instructions.toArray();
|
return mn.instructions.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void injectInvokeRecorder(MethodNode mn) {
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
List<Byte> types = ClassUtil.getParameterTypes(mn.desc);
|
|
||||||
int size = types.size();
|
|
||||||
int parameterOffset = 0;
|
|
||||||
mn.maxStack += 2;
|
|
||||||
il.add(getIntInsn(size));
|
|
||||||
il.add(new TypeInsnNode(ANEWARRAY, ClassUtil.CLASS_OBJECT));
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
mn.maxStack += 3;
|
|
||||||
il.add(new InsnNode(DUP));
|
|
||||||
il.add(getIntInsn(i));
|
|
||||||
ImmutablePair<Integer, Integer> code = getLoadParameterByteCode(types.get(i));
|
|
||||||
il.add(new VarInsnNode(code.left, parameterOffset));
|
|
||||||
parameterOffset += code.right;
|
|
||||||
MethodInsnNode typeConvertMethodNode = ClassUtil.getPrimaryTypeConvertMethod(types.get(i));
|
|
||||||
if (typeConvertMethodNode != null) {
|
|
||||||
il.add(typeConvertMethodNode);
|
|
||||||
}
|
|
||||||
il.add(new InsnNode(AASTORE));
|
|
||||||
}
|
|
||||||
if (isMockForConstructor(mn)) {
|
|
||||||
il.add(new InsnNode(ICONST_1));
|
|
||||||
} else {
|
|
||||||
il.add(new InsnNode(ICONST_0));
|
|
||||||
}
|
|
||||||
if (isTargetClassInParameter(mn)) {
|
|
||||||
il.add(new InsnNode(ICONST_1));
|
|
||||||
} else {
|
|
||||||
il.add(new InsnNode(ICONST_0));
|
|
||||||
}
|
|
||||||
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_INVOKE_RECORD_UTIL, METHOD_RECORD_MOCK_INVOKE,
|
|
||||||
SIGNATURE_INVOKE_RECORDER_METHOD, false));
|
|
||||||
mn.instructions.insertBefore(mn.instructions.get(0), il);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMockForConstructor(MethodNode mn) {
|
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
|
||||||
String annotationName = toDotSeparateFullClassName(an.desc);
|
|
||||||
if (ConstPool.MOCK_CONSTRUCTOR.equals(annotationName)) {
|
|
||||||
return true;
|
|
||||||
} else if (ConstPool.MOCK_METHOD.equals(annotationName)) {
|
|
||||||
String method = AnnotationUtil.getAnnotationParameter
|
|
||||||
(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
|
|
||||||
if (ConstPool.CONSTRUCTOR.equals(method)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isTargetClassInParameter(MethodNode mn) {
|
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
|
||||||
if (ConstPool.MOCK_METHOD.equals(toDotSeparateFullClassName(an.desc))) {
|
|
||||||
Type type = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
|
||||||
if (type != null && !type.getClassName().equals(NullType.class.getName())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImmutablePair<Integer, Integer> getLoadParameterByteCode(Byte type) {
|
|
||||||
switch (type) {
|
|
||||||
case ClassUtil.TYPE_BYTE:
|
|
||||||
case ClassUtil.TYPE_CHAR:
|
|
||||||
case ClassUtil.TYPE_SHORT:
|
|
||||||
case ClassUtil.TYPE_INT:
|
|
||||||
case ClassUtil.TYPE_BOOL:
|
|
||||||
return ImmutablePair.of(ILOAD, 1);
|
|
||||||
case ClassUtil.TYPE_DOUBLE:
|
|
||||||
return ImmutablePair.of(DLOAD, 2);
|
|
||||||
case ClassUtil.TYPE_FLOAT:
|
|
||||||
return ImmutablePair.of(FLOAD, 1);
|
|
||||||
case ClassUtil.TYPE_LONG:
|
|
||||||
return ImmutablePair.of(LLOAD, 2);
|
|
||||||
default:
|
|
||||||
return ImmutablePair.of(ALOAD, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AbstractInsnNode getIntInsn(int num) {
|
|
||||||
switch (num) {
|
|
||||||
case 0:
|
|
||||||
return new InsnNode(ICONST_0);
|
|
||||||
case 1:
|
|
||||||
return new InsnNode(ICONST_1);
|
|
||||||
case 2:
|
|
||||||
return new InsnNode(ICONST_2);
|
|
||||||
case 3:
|
|
||||||
return new InsnNode(ICONST_3);
|
|
||||||
case 4:
|
|
||||||
return new InsnNode(ICONST_4);
|
|
||||||
case 5:
|
|
||||||
return new InsnNode(ICONST_5);
|
|
||||||
default:
|
|
||||||
return new IntInsnNode(BIPUSH, num);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user