mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-24 03:10:14 +08:00
support target exist checking of mock method
This commit is contained in:
parent
7181b49211
commit
23af64475a
@ -4,6 +4,7 @@ import com.alibaba.testable.agent.constant.ByteCodeConst;
|
||||
import com.alibaba.testable.agent.constant.ConstPool;
|
||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
||||
import com.alibaba.testable.agent.util.*;
|
||||
import com.alibaba.testable.core.exception.TargetNotExistException;
|
||||
import com.alibaba.testable.core.model.MockScope;
|
||||
import com.alibaba.testable.core.util.MockAssociationUtil;
|
||||
import org.objectweb.asm.Label;
|
||||
@ -15,7 +16,6 @@ import java.util.List;
|
||||
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_ARRAY;
|
||||
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_CLASS;
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT;
|
||||
import static com.alibaba.testable.agent.util.ClassUtil.toJavaStyleClassName;
|
||||
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
||||
|
||||
/**
|
||||
@ -239,15 +239,77 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
||||
return false;
|
||||
}
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (isMockMethodAnnotation(an) && AnnotationUtil.isValidMockMethod(mn, an)) {
|
||||
if (isMockMethodAnnotation(an)) {
|
||||
checkTargetMethodExists(mn, an);
|
||||
return true;
|
||||
} else if (isMockConstructorAnnotation(an)) {
|
||||
checkTargetConstructorExists(mn);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkTargetMethodExists(MethodNode mn, AnnotationNode an) {
|
||||
String targetMethodName = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
|
||||
if (targetMethodName == null) {
|
||||
targetMethodName = mn.name;
|
||||
}
|
||||
String targetClassName;
|
||||
String targetMethodDesc;
|
||||
Type targetClass = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
||||
if (targetClass != null) {
|
||||
targetClassName = targetClass.getClassName();
|
||||
targetMethodDesc = mn.desc;
|
||||
checkMethodExists(mn.name, targetClassName, targetMethodName, targetMethodDesc);
|
||||
} else if (mn.desc.charAt(1) == TYPE_CLASS) {
|
||||
ImmutablePair<String, String> parameterPair = MethodUtil.splitFirstAndRestParameters(mn.desc);
|
||||
targetClassName = ClassUtil.toDotSeparatedName(parameterPair.left);
|
||||
targetMethodDesc = parameterPair.right;
|
||||
checkMethodExists(mn.name, targetClassName, targetMethodName,
|
||||
MethodUtil.removeFirstParameter(targetMethodDesc));
|
||||
} else {
|
||||
throw new TargetNotExistException("target class not exist", mn.name);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMethodExists(String mockMethodName, String targetClassName, String targetMethodName,
|
||||
String targetMethodDesc) {
|
||||
ClassNode targetClassNode = ClassUtil.getClassNode(targetClassName);
|
||||
if (targetClassNode == null) {
|
||||
throw new TargetNotExistException("target class not found", mockMethodName);
|
||||
}
|
||||
boolean targetFound = false;
|
||||
for (MethodNode targetMethodNode : targetClassNode.methods) {
|
||||
if (targetMethodNode.name.equals(targetMethodName)) {
|
||||
targetFound = true;
|
||||
if (targetMethodNode.desc.equals(targetMethodDesc)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new TargetNotExistException(targetFound ?
|
||||
"mock method does not match original method" : "no such method in target class", mockMethodName);
|
||||
}
|
||||
|
||||
private void checkTargetConstructorExists(MethodNode mn) {
|
||||
String returnType = MethodUtil.getReturnType(mn.desc);
|
||||
if (returnType.charAt(0) != TYPE_CLASS) {
|
||||
throw new TargetNotExistException("return type is not a class", mn.name);
|
||||
}
|
||||
ClassNode targetClassNode = ClassUtil.getClassNode(ClassUtil.toJavaStyleClassName(returnType));
|
||||
if (targetClassNode == null) {
|
||||
throw new TargetNotExistException("target class not found", mn.name);
|
||||
}
|
||||
for (MethodNode targetMethodNode : targetClassNode.methods) {
|
||||
if (CONSTRUCTOR.equals(targetMethodNode.name) &&
|
||||
MethodUtil.getParameters(targetMethodNode.desc).equals(MethodUtil.getParameters(mn.desc))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new TargetNotExistException("no such constructor in target class", mn.name);
|
||||
}
|
||||
|
||||
private boolean isMockConstructorAnnotation(AnnotationNode an) {
|
||||
return ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc);
|
||||
}
|
||||
@ -294,7 +356,7 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
||||
|
||||
private boolean isMockForConstructor(MethodNode mn) {
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
String annotationName = toJavaStyleClassName(an.desc);
|
||||
String annotationName = ClassUtil.toJavaStyleClassName(an.desc);
|
||||
if (ConstPool.MOCK_CONSTRUCTOR.equals(annotationName)) {
|
||||
return true;
|
||||
} else if (ConstPool.MOCK_METHOD.equals(annotationName)) {
|
||||
|
@ -16,6 +16,7 @@ import org.objectweb.asm.tree.MethodNode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_CLASS;
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT;
|
||||
import static com.alibaba.testable.agent.util.ClassUtil.toJavaStyleClassName;
|
||||
import static com.alibaba.testable.agent.util.MethodUtil.isStatic;
|
||||
@ -89,7 +90,7 @@ public class MockClassParser {
|
||||
ClassUtil.toJavaStyleClassName(MethodUtil.getReturnType(mn.desc)), mn.desc));
|
||||
}
|
||||
addMockConstructor(methodInfos, cn, mn);
|
||||
} else if (fullClassName.equals(ConstPool.MOCK_METHOD) && AnnotationUtil.isValidMockMethod(mn, an)) {
|
||||
} else if (fullClassName.equals(ConstPool.MOCK_METHOD) && isValidMockMethod(mn, an)) {
|
||||
if (LogUtil.isVerboseEnabled()) {
|
||||
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, MethodUtil.toJavaMethodDesc(
|
||||
getTargetMethodOwner(mn, an), getTargetMethodName(mn, an), getTargetMethodDesc(mn, an)));
|
||||
@ -132,8 +133,8 @@ public class MockClassParser {
|
||||
boolean isStatic = isStatic(mn);
|
||||
if (targetType == null) {
|
||||
// "targetClass" unset, use first parameter as target class type
|
||||
ImmutablePair<String, String> methodDescPair = extractFirstParameter(mn.desc);
|
||||
if (methodDescPair == null) {
|
||||
ImmutablePair<String, String> methodDescPair = MethodUtil.splitFirstAndRestParameters(mn.desc);
|
||||
if (methodDescPair.left.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new MethodInfo(methodDescPair.left, targetMethod, methodDescPair.right, cn.name, mn.name, mn.desc,
|
||||
@ -152,14 +153,18 @@ public class MockClassParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Split desc to "first parameter" and "desc of rest parameters"
|
||||
* @param desc method desc
|
||||
* Check is MockMethod annotation is used on a valid mock method
|
||||
* @param mn mock method
|
||||
* @param an MockMethod annotation
|
||||
* @return valid or not
|
||||
*/
|
||||
private ImmutablePair<String, String> extractFirstParameter(String desc) {
|
||||
// assume first parameter is a class
|
||||
int pos = desc.indexOf(";");
|
||||
return pos < 0 ? null : ImmutablePair.of(desc.substring(2, pos), "(" + desc.substring(pos + 1));
|
||||
private boolean isValidMockMethod(MethodNode mn, AnnotationNode an) {
|
||||
Type targetClass = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
||||
if (targetClass != null) {
|
||||
return true;
|
||||
}
|
||||
String firstParameter = MethodUtil.getFirstParameter(mn.desc);
|
||||
return !firstParameter.isEmpty() && firstParameter.charAt(0) == TYPE_CLASS;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import com.alibaba.testable.agent.handler.SourceClassHandler;
|
||||
import com.alibaba.testable.agent.handler.TestClassHandler;
|
||||
import com.alibaba.testable.agent.model.MethodInfo;
|
||||
import com.alibaba.testable.agent.util.*;
|
||||
import com.alibaba.testable.core.exception.TargetNotExistException;
|
||||
import com.alibaba.testable.core.model.ClassType;
|
||||
import com.alibaba.testable.core.util.LogUtil;
|
||||
import com.alibaba.testable.core.util.MockAssociationUtil;
|
||||
@ -85,6 +86,9 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes);
|
||||
}
|
||||
}
|
||||
} catch (TargetNotExistException e) {
|
||||
LogUtil.error("Invalid mock method %s::%s - %s", className, e.getMethodName(), e.getMessage());
|
||||
System.exit(0);
|
||||
} catch (Throwable t) {
|
||||
LogUtil.warn("Failed to transform class " + className);
|
||||
LogUtil.warn(t.toString());
|
||||
|
@ -1,11 +1,6 @@
|
||||
package com.alibaba.testable.agent.util;
|
||||
|
||||
import com.alibaba.testable.agent.constant.ConstPool;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_CLASS;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
@ -65,15 +60,4 @@ public class AnnotationUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is MockMethod annotation is used on a valid mock method
|
||||
* @param mn mock method
|
||||
* @param an MockMethod annotation
|
||||
* @return valid or not
|
||||
*/
|
||||
public static boolean isValidMockMethod(MethodNode mn, AnnotationNode an) {
|
||||
Type targetClass = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
||||
String firstParameter = MethodUtil.getFirstParameter(mn.desc);
|
||||
return targetClass != null || firstParameter.charAt(0) == TYPE_CLASS;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.alibaba.testable.agent.util;
|
||||
|
||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -11,6 +12,7 @@ import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
||||
public class MethodUtil {
|
||||
|
||||
private static final String COMMA_SPACE = ", ";
|
||||
private static final int MINIMAL_DESC_LENGTH_OF_TWO_PARAMETERS_METHOD = 4;
|
||||
|
||||
/**
|
||||
* Judge whether a method is static
|
||||
@ -65,7 +67,7 @@ public class MethodUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse method desc, fetch return value types
|
||||
* Parse method desc, fetch return value type
|
||||
* @param desc method description
|
||||
* @return types of return value
|
||||
*/
|
||||
@ -74,14 +76,41 @@ public class MethodUtil {
|
||||
return desc.substring(returnTypeEdge + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse method desc, fetch parameter types string
|
||||
* @param desc method description
|
||||
* @return parameter types
|
||||
*/
|
||||
public static Object getParameters(String desc) {
|
||||
int returnTypeEdge = desc.lastIndexOf(PARAM_END);
|
||||
return desc.substring(1, returnTypeEdge);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse method desc, fetch first parameter type (assume first parameter is an object type)
|
||||
* @param desc method description
|
||||
* @return types of first parameter
|
||||
*/
|
||||
public static String getFirstParameter(String desc) {
|
||||
int typeEdge = desc.indexOf(CLASS_END);
|
||||
return typeEdge > 0 ? desc.substring(1, typeEdge + 1) : "";
|
||||
// assume first parameter is class type
|
||||
return desc.substring(1, desc.indexOf(CLASS_END) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split desc to "first parameter" and "desc of rest parameters"
|
||||
* @param desc method desc
|
||||
* @return pair of [slash separated first parameter type, desc of rest parameters]
|
||||
*/
|
||||
public static ImmutablePair<String, String> splitFirstAndRestParameters(String desc) {
|
||||
if (desc.length() < MINIMAL_DESC_LENGTH_OF_TWO_PARAMETERS_METHOD) {
|
||||
return ImmutablePair.of("", "");
|
||||
}
|
||||
if (desc.charAt(1) != TYPE_CLASS) {
|
||||
return ImmutablePair.of(desc.substring(1, 2), "(" + desc.substring(2));
|
||||
}
|
||||
int pos = desc.indexOf(";");
|
||||
return pos < 0 ? ImmutablePair.of("", "")
|
||||
: ImmutablePair.of(desc.substring(2, pos), "(" + desc.substring(pos + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,23 +0,0 @@
|
||||
package com.alibaba.testable.agent.transformer;
|
||||
|
||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
||||
import com.alibaba.testable.core.tool.PrivateAccessor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class MockClassParserTest {
|
||||
|
||||
private MockClassParser mockClassParser = new MockClassParser();
|
||||
|
||||
@Test
|
||||
void should_split_parameters() {
|
||||
ImmutablePair<String, String> parameters =
|
||||
PrivateAccessor.invoke(mockClassParser, "extractFirstParameter", "()");
|
||||
assertNull(parameters);
|
||||
parameters = PrivateAccessor.invoke(mockClassParser, "extractFirstParameter", "(Lcom.alibaba.demo.Class;ILjava.lang.String;Z)");
|
||||
assertNotNull(parameters);
|
||||
assertEquals("com.alibaba.demo.Class", parameters.left);
|
||||
assertEquals("(ILjava.lang.String;Z)", parameters.right);
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
package com.alibaba.testable.agent.util;
|
||||
|
||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
||||
import com.alibaba.testable.core.tool.PrivateAccessor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class MethodUtilTest {
|
||||
|
||||
@ -31,11 +33,36 @@ class MethodUtilTest {
|
||||
assertEquals("[Ljava/lang/String;", MethodUtil.getReturnType("(Ljava/lang/String;)[Ljava/lang/String;"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_get_parameters() {
|
||||
assertEquals("Ljava/lang/String;", MethodUtil.getParameters("(Ljava/lang/String;)V"));
|
||||
assertEquals("I", MethodUtil.getParameters("(I)Ljava/lang/String;"));
|
||||
assertEquals("", MethodUtil.getParameters("()Ljava/lang/String;"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_split_parameters() {
|
||||
ImmutablePair<String, String> parameters = MethodUtil.splitFirstAndRestParameters("()");
|
||||
assertEquals("", parameters.left);
|
||||
assertEquals("", parameters.right);
|
||||
parameters = MethodUtil.splitFirstAndRestParameters("(ZZILjava/lang/String;Z)");
|
||||
assertEquals("Z", parameters.left);
|
||||
assertEquals("(ZILjava/lang/String;Z)", parameters.right);
|
||||
parameters = MethodUtil.splitFirstAndRestParameters("(Lcom/alibaba/demo/Class;ILjava/lang/String;Z)");
|
||||
assertEquals("com/alibaba/demo/Class", parameters.left);
|
||||
assertEquals("(ILjava/lang/String;Z)", parameters.right);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_get_first_parameter() {
|
||||
assertEquals("Ljava/lang/String;", MethodUtil.getFirstParameter("(Ljava/lang/String;Ljava/lang/Object;I)V"));
|
||||
assertEquals("Ljava/lang/String;", MethodUtil.getFirstParameter("(Ljava/lang/String;)V"));
|
||||
assertEquals("", MethodUtil.getFirstParameter("()V"));
|
||||
assertThrows(IndexOutOfBoundsException.class, new Executable() {
|
||||
@Override
|
||||
public void execute() {
|
||||
MethodUtil.getFirstParameter("()V");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -0,0 +1,18 @@
|
||||
package com.alibaba.testable.core.exception;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class TargetNotExistException extends RuntimeException {
|
||||
|
||||
private String methodName;
|
||||
|
||||
public TargetNotExistException(String message, String methodName) {
|
||||
super(message);
|
||||
this.methodName = methodName;
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user