mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-10 20:30:11 +08:00
fit mock invocation recorder for targetClass parameter
This commit is contained in:
parent
0255ffa911
commit
070aeb6559
@ -3,7 +3,6 @@ package com.alibaba.testable.agent.handler;
|
|||||||
import com.alibaba.testable.agent.constant.ConstPool;
|
import com.alibaba.testable.agent.constant.ConstPool;
|
||||||
import com.alibaba.testable.agent.model.MethodInfo;
|
import com.alibaba.testable.agent.model.MethodInfo;
|
||||||
import com.alibaba.testable.agent.model.ModifiedInsnNodes;
|
import com.alibaba.testable.agent.model.ModifiedInsnNodes;
|
||||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
|
||||||
import com.alibaba.testable.agent.util.BytecodeUtil;
|
import com.alibaba.testable.agent.util.BytecodeUtil;
|
||||||
import com.alibaba.testable.agent.util.ClassUtil;
|
import com.alibaba.testable.agent.util.ClassUtil;
|
||||||
import com.alibaba.testable.core.util.LogUtil;
|
import com.alibaba.testable.core.util.LogUtil;
|
||||||
@ -65,7 +64,7 @@ public class SourceClassHandler extends BaseClassHandler {
|
|||||||
do {
|
do {
|
||||||
if (invokeOps.contains(instructions[i].getOpcode())) {
|
if (invokeOps.contains(instructions[i].getOpcode())) {
|
||||||
MethodInsnNode node = (MethodInsnNode)instructions[i];
|
MethodInsnNode node = (MethodInsnNode)instructions[i];
|
||||||
ImmutablePair<String, String> mockMethod = getMemberInjectMethodName(memberInjectMethods, node);
|
MethodInfo mockMethod = getMemberInjectMethodName(memberInjectMethods, node);
|
||||||
if (mockMethod != null) {
|
if (mockMethod != null) {
|
||||||
// it's a member or static method and an inject method for it exist
|
// it's a member or static method and an inject method for it exist
|
||||||
int rangeStart = getMemberMethodStart(instructions, i);
|
int rangeStart = getMemberMethodStart(instructions, i);
|
||||||
@ -103,14 +102,13 @@ public class SourceClassHandler extends BaseClassHandler {
|
|||||||
* find the mock method fit for specified method node
|
* find the mock method fit for specified method node
|
||||||
* @param memberInjectMethods mock methods available
|
* @param memberInjectMethods mock methods available
|
||||||
* @param node method node to match for
|
* @param node method node to match for
|
||||||
* @return pair of <mock-method-name, mock-method-desc>
|
* @return mock method info
|
||||||
*/
|
*/
|
||||||
private ImmutablePair<String, String> getMemberInjectMethodName(Set<MethodInfo> memberInjectMethods,
|
private MethodInfo getMemberInjectMethodName(Set<MethodInfo> memberInjectMethods, MethodInsnNode node) {
|
||||||
MethodInsnNode node) {
|
|
||||||
for (MethodInfo m : memberInjectMethods) {
|
for (MethodInfo m : memberInjectMethods) {
|
||||||
String nodeOwner = ClassUtil.fitCompanionClassName(node.owner);
|
String nodeOwner = ClassUtil.fitCompanionClassName(node.owner);
|
||||||
if (m.getClazz().equals(nodeOwner) && m.getName().equals(node.name) && m.getDesc().equals(node.desc)) {
|
if (m.getClazz().equals(nodeOwner) && m.getName().equals(node.name) && m.getDesc().equals(node.desc)) {
|
||||||
return ImmutablePair.of(m.getMockName(), m.getMockDesc());
|
return m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -219,23 +217,29 @@ public class SourceClassHandler extends BaseClassHandler {
|
|||||||
ClassUtil.toByteCodeClassName(classType);
|
ClassUtil.toByteCodeClassName(classType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModifiedInsnNodes replaceMemberCallOps(ClassNode cn, MethodNode mn, ImmutablePair<String, String> mockMethod,
|
private ModifiedInsnNodes replaceMemberCallOps(ClassNode cn, MethodNode mn, MethodInfo mockMethod,
|
||||||
AbstractInsnNode[] instructions, String ownerClass,
|
AbstractInsnNode[] instructions, String ownerClass,
|
||||||
int opcode, int start, int end) {
|
int opcode, int start, int end) {
|
||||||
LogUtil.diagnose(" Line %d, mock method %s used", getLineNum(instructions, start), mockMethod.left);
|
LogUtil.diagnose(" Line %d, mock method %s used", getLineNum(instructions, start),
|
||||||
MethodInsnNode method = (MethodInsnNode)instructions[end];
|
mockMethod.getMockName());
|
||||||
|
boolean shouldAppendTypeParameter = !mockMethod.getDesc().equals(mockMethod.getMockDesc());
|
||||||
String testClassName = ClassUtil.getTestClassName(cn.name);
|
String testClassName = ClassUtil.getTestClassName(cn.name);
|
||||||
if (Opcodes.INVOKESTATIC == opcode || isCompanionMethod(ownerClass, opcode)) {
|
if (Opcodes.INVOKESTATIC == opcode || isCompanionMethod(ownerClass, opcode)) {
|
||||||
// append a null value if it was a static invoke or in kotlin companion class
|
if (shouldAppendTypeParameter) {
|
||||||
mn.instructions.insertBefore(instructions[start], new InsnNode(ACONST_NULL));
|
// append a null value if it was a static invoke or in kotlin companion class
|
||||||
|
mn.instructions.insertBefore(instructions[start], new InsnNode(ACONST_NULL));
|
||||||
|
}
|
||||||
if (ClassUtil.isCompanionClassName(ownerClass)) {
|
if (ClassUtil.isCompanionClassName(ownerClass)) {
|
||||||
// for kotlin companion class, remove the byte code of reference to "companion" static field
|
// for kotlin companion class, remove the byte code of reference to "companion" static field
|
||||||
mn.instructions.remove(instructions[end - 1]);
|
mn.instructions.remove(instructions[end - 1]);
|
||||||
}
|
}
|
||||||
|
} else if (!shouldAppendTypeParameter) {
|
||||||
|
// remove extra target ops code
|
||||||
|
mn.instructions.remove(instructions[start]);
|
||||||
}
|
}
|
||||||
// method with @MockMethod will be modified as public static access, so INVOKESTATIC is used
|
// method with @MockMethod will be modified as public static access, so INVOKESTATIC is used
|
||||||
mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKESTATIC, testClassName,
|
mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKESTATIC, testClassName,
|
||||||
mockMethod.left, mockMethod.right, false));
|
mockMethod.getMockName(), mockMethod.getMockDesc(), false));
|
||||||
mn.instructions.remove(instructions[end]);
|
mn.instructions.remove(instructions[end]);
|
||||||
return new ModifiedInsnNodes(mn.instructions.toArray(), 1);
|
return new ModifiedInsnNodes(mn.instructions.toArray(), 1);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ import com.alibaba.testable.agent.constant.ConstPool;
|
|||||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
import com.alibaba.testable.agent.tool.ImmutablePair;
|
||||||
import com.alibaba.testable.agent.util.AnnotationUtil;
|
import com.alibaba.testable.agent.util.AnnotationUtil;
|
||||||
import com.alibaba.testable.agent.util.ClassUtil;
|
import com.alibaba.testable.agent.util.ClassUtil;
|
||||||
|
import com.alibaba.testable.core.model.NullType;
|
||||||
import com.alibaba.testable.core.util.LogUtil;
|
import com.alibaba.testable.core.util.LogUtil;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.*;
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -30,7 +32,7 @@ public class TestClassHandler extends BaseClassHandler {
|
|||||||
private static final String METHOD_RECORD_MOCK_INVOKE = "recordMockInvoke";
|
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;Z)V";
|
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;";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,6 +195,11 @@ public class TestClassHandler extends BaseClassHandler {
|
|||||||
} else {
|
} else {
|
||||||
il.add(new InsnNode(ICONST_0));
|
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,
|
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_INVOKE_RECORD_UTIL, METHOD_RECORD_MOCK_INVOKE,
|
||||||
SIGNATURE_INVOKE_RECORDER_METHOD, false));
|
SIGNATURE_INVOKE_RECORDER_METHOD, false));
|
||||||
mn.instructions.insertBefore(mn.instructions.get(0), il);
|
mn.instructions.insertBefore(mn.instructions.get(0), il);
|
||||||
@ -200,18 +207,32 @@ public class TestClassHandler extends BaseClassHandler {
|
|||||||
|
|
||||||
private boolean isMockForConstructor(MethodNode mn) {
|
private boolean isMockForConstructor(MethodNode mn) {
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.MOCK_CONSTRUCTOR)) {
|
String annotationName = toDotSeparateFullClassName(an.desc);
|
||||||
return true;
|
if (ConstPool.MOCK_CONSTRUCTOR.equals(annotationName)) {
|
||||||
}
|
|
||||||
String method = AnnotationUtil.getAnnotationParameter
|
|
||||||
(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
|
|
||||||
if (ConstPool.CONSTRUCTOR.equals(method)) {
|
|
||||||
return true;
|
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;
|
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) {
|
private static ImmutablePair<Integer, Integer> getLoadParameterByteCode(Byte type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ClassUtil.TYPE_BYTE:
|
case ClassUtil.TYPE_BYTE:
|
||||||
|
@ -17,7 +17,7 @@ class TestableClassTransformerTest {
|
|||||||
assertNull(parameters);
|
assertNull(parameters);
|
||||||
parameters = PrivateAccessor.invoke(testableClassTransformer, "extractFirstParameter", "(Lcom.alibaba.demo.Class;ILjava.lang.String;Z)");
|
parameters = PrivateAccessor.invoke(testableClassTransformer, "extractFirstParameter", "(Lcom.alibaba.demo.Class;ILjava.lang.String;Z)");
|
||||||
assertNotNull(parameters);
|
assertNotNull(parameters);
|
||||||
assertEquals("Lcom.alibaba.demo.Class;", parameters.left);
|
assertEquals("com.alibaba.demo.Class", parameters.left);
|
||||||
assertEquals("(ILjava.lang.String;Z)", parameters.right);
|
assertEquals("(ILjava.lang.String;Z)", parameters.right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ public class InvokeRecordUtil {
|
|||||||
* @param args invocation parameters
|
* @param args invocation parameters
|
||||||
* @param isConstructor whether mocked method is constructor
|
* @param isConstructor whether mocked method is constructor
|
||||||
*/
|
*/
|
||||||
public static void recordMockInvoke(Object[] args, boolean isConstructor) {
|
public static void recordMockInvoke(Object[] args, boolean isConstructor, boolean isTargetClassInParameter) {
|
||||||
StackTraceElement mockMethodTraceElement = Thread.currentThread().getStackTrace()[INDEX_OF_TEST_CLASS];
|
StackTraceElement mockMethodTraceElement = Thread.currentThread().getStackTrace()[INDEX_OF_TEST_CLASS];
|
||||||
String mockMethodName = mockMethodTraceElement.getMethodName();
|
String mockMethodName = mockMethodTraceElement.getMethodName();
|
||||||
String testClass = mockMethodTraceElement.getClassName();
|
String testClass = mockMethodTraceElement.getClassName();
|
||||||
@ -37,7 +37,7 @@ public class InvokeRecordUtil {
|
|||||||
records.add(args);
|
records.add(args);
|
||||||
LogUtil.verbose(" Mock constructor invoked \"%s\"", identify);
|
LogUtil.verbose(" Mock constructor invoked \"%s\"", identify);
|
||||||
} else {
|
} else {
|
||||||
records.add(slice(args, 1));
|
records.add(isTargetClassInParameter ? slice(args, 1) : args);
|
||||||
LogUtil.verbose(" Mock method invoked \"%s\"", identify);
|
LogUtil.verbose(" Mock method invoked \"%s\"", identify);
|
||||||
}
|
}
|
||||||
INVOKE_RECORDS.put(identify, records);
|
INVOKE_RECORDS.put(identify, records);
|
||||||
|
Loading…
Reference in New Issue
Block a user