mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-27 12:51:00 +08:00
refactor transformer
This commit is contained in:
parent
01bd676df7
commit
dab1d36a81
@ -0,0 +1,151 @@
|
|||||||
|
package com.alibaba.testable.agent.transformer;
|
||||||
|
|
||||||
|
import com.alibaba.testable.agent.constant.ConstPool;
|
||||||
|
import com.alibaba.testable.agent.model.MethodInfo;
|
||||||
|
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.DiagnoseUtil;
|
||||||
|
import com.alibaba.testable.core.util.LogUtil;
|
||||||
|
import com.alibaba.testable.core.util.MockContextUtil;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassName;
|
||||||
|
|
||||||
|
public class MockClassParser {
|
||||||
|
|
||||||
|
private static final String CLASS_OBJECT = "java/lang/Object";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get information of all mock methods
|
||||||
|
* @param className mock class name
|
||||||
|
* @return list of mock methods
|
||||||
|
*/
|
||||||
|
public List<MethodInfo> getTestableMockMethods(String className) {
|
||||||
|
List<MethodInfo> methodInfos = new ArrayList<MethodInfo>();
|
||||||
|
ClassNode cn = ClassUtil.getClassNode(className);
|
||||||
|
if (cn == null) {
|
||||||
|
return new ArrayList<MethodInfo>();
|
||||||
|
}
|
||||||
|
for (MethodNode mn : getAllMethods(cn)) {
|
||||||
|
checkMethodAnnotation(cn, methodInfos, mn);
|
||||||
|
}
|
||||||
|
LogUtil.diagnose(" Found %d mock methods", methodInfos.size());
|
||||||
|
return methodInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether any method in specified class has mock-related annotation
|
||||||
|
*
|
||||||
|
* @param className class that need to explore
|
||||||
|
* @return found annotation or not
|
||||||
|
*/
|
||||||
|
public boolean isMockClass(String className) {
|
||||||
|
return MockContextUtil.mockToTests.containsKey(ClassUtil.toDotSeparatedName(className)) ||
|
||||||
|
hasMockMethod(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasMockMethod(String className) {
|
||||||
|
ClassNode cn = ClassUtil.getClassNode(className);
|
||||||
|
if (cn == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DiagnoseUtil.setupByClass(cn);
|
||||||
|
for (MethodNode mn : cn.methods) {
|
||||||
|
if (mn.visibleAnnotations != null) {
|
||||||
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
|
String fullClassName = toDotSeparateFullClassName(an.desc);
|
||||||
|
if (fullClassName.equals(ConstPool.MOCK_METHOD) ||
|
||||||
|
fullClassName.equals(ConstPool.MOCK_CONSTRUCTOR)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MethodNode> getAllMethods(ClassNode cn) {
|
||||||
|
List<MethodNode> mns = new ArrayList<MethodNode>(cn.methods);
|
||||||
|
if (cn.superName != null && !cn.superName.equals(CLASS_OBJECT)) {
|
||||||
|
ClassNode scn = ClassUtil.getClassNode(cn.superName);
|
||||||
|
if (scn != null) {
|
||||||
|
mns.addAll(getAllMethods(scn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mns;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMethodAnnotation(ClassNode cn, List<MethodInfo> methodInfos, MethodNode mn) {
|
||||||
|
if (mn.visibleAnnotations == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
|
String fullClassName = toDotSeparateFullClassName(an.desc);
|
||||||
|
if (fullClassName.equals(ConstPool.MOCK_CONSTRUCTOR)) {
|
||||||
|
LogUtil.verbose(" Mock constructor \"%s\" as \"(%s)V\" for \"%s\"", mn.name,
|
||||||
|
ClassUtil.extractParameters(mn.desc), ClassUtil.getReturnType(mn.desc));
|
||||||
|
addMockConstructor(methodInfos, cn, mn);
|
||||||
|
} else if (fullClassName.equals(ConstPool.MOCK_METHOD)) {
|
||||||
|
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, getTargetMethodDesc(mn, an));
|
||||||
|
String targetMethod = AnnotationUtil.getAnnotationParameter(
|
||||||
|
an, ConstPool.FIELD_TARGET_METHOD, mn.name, String.class);
|
||||||
|
if (ConstPool.CONSTRUCTOR.equals(targetMethod)) {
|
||||||
|
addMockConstructor(methodInfos, cn, mn);
|
||||||
|
} else {
|
||||||
|
MethodInfo mi = getMethodInfo(mn, an, targetMethod);
|
||||||
|
if (mi != null) {
|
||||||
|
methodInfos.add(mi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTargetMethodDesc(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
||||||
|
Type type = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_CLASS,
|
||||||
|
null, Type.class);
|
||||||
|
return type == null ? ClassUtil.removeFirstParameter(mn.desc) : mn.desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodInfo getMethodInfo(MethodNode mn, AnnotationNode an, String targetMethod) {
|
||||||
|
Type targetType = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
||||||
|
if (targetType == null) {
|
||||||
|
// "targetClass" unset, use first parameter as target class type
|
||||||
|
ImmutablePair<String, String> methodDescPair = extractFirstParameter(mn.desc);
|
||||||
|
if (methodDescPair == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new MethodInfo(methodDescPair.left, targetMethod, methodDescPair.right, mn.name, mn.desc);
|
||||||
|
} else {
|
||||||
|
// "targetClass" found, use it as target class type
|
||||||
|
String slashSeparatedName = ClassUtil.toSlashSeparatedName(targetType.getClassName());
|
||||||
|
return new MethodInfo(slashSeparatedName, targetMethod, mn.desc, mn.name,
|
||||||
|
ClassUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(slashSeparatedName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMockConstructor(List<MethodInfo> methodInfos, ClassNode cn, MethodNode mn) {
|
||||||
|
String sourceClassName = ClassUtil.getSourceClassName(cn.name);
|
||||||
|
methodInfos.add(new MethodInfo(sourceClassName, ConstPool.CONSTRUCTOR, mn.desc, mn.name, mn.desc));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split desc to "first parameter" and "desc of rest parameters"
|
||||||
|
* @param desc method desc
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -5,29 +5,22 @@ import com.alibaba.testable.agent.handler.MockClassHandler;
|
|||||||
import com.alibaba.testable.agent.handler.SourceClassHandler;
|
import com.alibaba.testable.agent.handler.SourceClassHandler;
|
||||||
import com.alibaba.testable.agent.handler.TestClassHandler;
|
import com.alibaba.testable.agent.handler.TestClassHandler;
|
||||||
import com.alibaba.testable.agent.model.MethodInfo;
|
import com.alibaba.testable.agent.model.MethodInfo;
|
||||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
import com.alibaba.testable.agent.util.*;
|
||||||
import com.alibaba.testable.agent.util.AnnotationUtil;
|
|
||||||
import com.alibaba.testable.agent.util.ClassUtil;
|
|
||||||
import com.alibaba.testable.agent.util.GlobalConfig;
|
|
||||||
import com.alibaba.testable.agent.util.StringUtil;
|
|
||||||
import com.alibaba.testable.core.model.ClassType;
|
import com.alibaba.testable.core.model.ClassType;
|
||||||
import com.alibaba.testable.core.model.LogLevel;
|
import com.alibaba.testable.core.model.LogLevel;
|
||||||
import com.alibaba.testable.core.util.LogUtil;
|
import com.alibaba.testable.core.util.LogUtil;
|
||||||
import com.alibaba.testable.core.util.MockContextUtil;
|
import com.alibaba.testable.core.util.MockContextUtil;
|
||||||
import org.objectweb.asm.ClassReader;
|
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.AnnotationNode;
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
import org.objectweb.asm.tree.ClassNode;
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
import org.objectweb.asm.tree.InnerClassNode;
|
import org.objectweb.asm.tree.InnerClassNode;
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
|
||||||
import javax.lang.model.type.NullType;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.instrument.ClassFileTransformer;
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.alibaba.testable.agent.constant.ConstPool.*;
|
import static com.alibaba.testable.agent.constant.ConstPool.*;
|
||||||
@ -42,10 +35,8 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
|
|
||||||
private static final String FIELD_VALUE = "value";
|
private static final String FIELD_VALUE = "value";
|
||||||
private static final String FIELD_TREAT_AS = "treatAs";
|
private static final String FIELD_TREAT_AS = "treatAs";
|
||||||
private static final String FIELD_DIAGNOSE = "diagnose";
|
|
||||||
private static final String COMMA = ",";
|
private static final String COMMA = ",";
|
||||||
private static final String CLASS_NAME_MOCK = "Mock";
|
private static final String CLASS_NAME_MOCK = "Mock";
|
||||||
private static final String CLASS_OBJECT = "java/lang/Object";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Just avoid spend time to scan those surely non-user classes Should keep these lists as tiny as possible
|
* Just avoid spend time to scan those surely non-user classes Should keep these lists as tiny as possible
|
||||||
@ -54,6 +45,8 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
private final String[] BLACKLIST_PREFIXES = new String[] {"jdk/", "java/", "javax/", "com/sun/",
|
private final String[] BLACKLIST_PREFIXES = new String[] {"jdk/", "java/", "javax/", "com/sun/",
|
||||||
"org/apache/maven/", "com/alibaba/testable/", "junit/", "org/junit/", "org/testng/"};
|
"org/apache/maven/", "com/alibaba/testable/", "junit/", "org/junit/", "org/testng/"};
|
||||||
|
|
||||||
|
public MockClassParser mockClassParser = new MockClassParser();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
|
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
|
||||||
ProtectionDomain protectionDomain, byte[] classFileBuffer) {
|
ProtectionDomain protectionDomain, byte[] classFileBuffer) {
|
||||||
@ -64,7 +57,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
LogUtil.verbose("Handle class: " + className);
|
LogUtil.verbose("Handle class: " + className);
|
||||||
byte[] bytes = null;
|
byte[] bytes = null;
|
||||||
try {
|
try {
|
||||||
if (isMockClass(className)) {
|
if (mockClassParser.isMockClass(className)) {
|
||||||
// it's a mock class
|
// it's a mock class
|
||||||
LogUtil.diagnose("Handling mock class %s", className);
|
LogUtil.diagnose("Handling mock class %s", className);
|
||||||
bytes = new MockClassHandler(className).getBytes(classFileBuffer);
|
bytes = new MockClassHandler(className).getBytes(classFileBuffer);
|
||||||
@ -80,7 +73,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
mockClass = foundMockForSourceClass(className);
|
mockClass = foundMockForSourceClass(className);
|
||||||
if (mockClass != null) {
|
if (mockClass != null) {
|
||||||
// it's a source class with testable enabled
|
// it's a source class with testable enabled
|
||||||
List<MethodInfo> injectMethods = getTestableMockMethods(mockClass);
|
List<MethodInfo> injectMethods = mockClassParser.getTestableMockMethods(mockClass);
|
||||||
LogUtil.diagnose("Handling source class %s", className);
|
LogUtil.diagnose("Handling source class %s", className);
|
||||||
bytes = new SourceClassHandler(injectMethods, mockClass).getBytes(classFileBuffer);
|
bytes = new SourceClassHandler(injectMethods, mockClass).getBytes(classFileBuffer);
|
||||||
dumpByte(className, bytes);
|
dumpByte(className, bytes);
|
||||||
@ -127,17 +120,12 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
return mockClass;
|
return mockClass;
|
||||||
}
|
}
|
||||||
mockClass = ClassUtil.getMockClassName(ClassUtil.getSourceClassName(className));
|
mockClass = ClassUtil.getMockClassName(ClassUtil.getSourceClassName(className));
|
||||||
if (isMockClass(mockClass)) {
|
if (mockClassParser.isMockClass(mockClass)) {
|
||||||
return mockClass;
|
return mockClass;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMockClass(String className) {
|
|
||||||
return MockContextUtil.mockToTests.containsKey(ClassUtil.toDotSeparatedName(className)) ||
|
|
||||||
hasMockMethod(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSystemClass(String className) {
|
private boolean isSystemClass(String className) {
|
||||||
// className can be null for Java 8 lambdas
|
// className can be null for Java 8 lambdas
|
||||||
if (null == className) {
|
if (null == className) {
|
||||||
@ -167,85 +155,6 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MethodInfo> getTestableMockMethods(String className) {
|
|
||||||
List<MethodInfo> methodInfos = new ArrayList<MethodInfo>();
|
|
||||||
ClassNode cn = getClassNode(className);
|
|
||||||
if (cn == null) {
|
|
||||||
return new ArrayList<MethodInfo>();
|
|
||||||
}
|
|
||||||
for (MethodNode mn : getAllMethods(cn)) {
|
|
||||||
checkMethodAnnotation(cn, methodInfos, mn);
|
|
||||||
}
|
|
||||||
LogUtil.diagnose(" Found %d mock methods", methodInfos.size());
|
|
||||||
return methodInfos;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<MethodNode> getAllMethods(ClassNode cn) {
|
|
||||||
List<MethodNode> mns = new ArrayList<MethodNode>(cn.methods);
|
|
||||||
if (cn.superName != null && !cn.superName.equals(CLASS_OBJECT)) {
|
|
||||||
ClassNode scn = getClassNode(cn.superName);
|
|
||||||
if (scn != null) {
|
|
||||||
mns.addAll(getAllMethods(scn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mns;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkMethodAnnotation(ClassNode cn, List<MethodInfo> methodInfos, MethodNode mn) {
|
|
||||||
if (mn.visibleAnnotations == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
|
||||||
String fullClassName = toDotSeparateFullClassName(an.desc);
|
|
||||||
if (fullClassName.equals(ConstPool.MOCK_CONSTRUCTOR)) {
|
|
||||||
LogUtil.verbose(" Mock constructor \"%s\" as \"(%s)V\" for \"%s\"", mn.name,
|
|
||||||
ClassUtil.extractParameters(mn.desc), ClassUtil.getReturnType(mn.desc));
|
|
||||||
addMockConstructor(methodInfos, cn, mn);
|
|
||||||
} else if (fullClassName.equals(ConstPool.MOCK_METHOD)) {
|
|
||||||
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, getTargetMethodDesc(mn, an));
|
|
||||||
String targetMethod = AnnotationUtil.getAnnotationParameter(
|
|
||||||
an, ConstPool.FIELD_TARGET_METHOD, mn.name, String.class);
|
|
||||||
if (ConstPool.CONSTRUCTOR.equals(targetMethod)) {
|
|
||||||
addMockConstructor(methodInfos, cn, mn);
|
|
||||||
} else {
|
|
||||||
MethodInfo mi = getMethodInfo(mn, an, targetMethod);
|
|
||||||
if (mi != null) {
|
|
||||||
methodInfos.add(mi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTargetMethodDesc(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
|
||||||
Type type = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_CLASS,
|
|
||||||
null, Type.class);
|
|
||||||
return type == null ? ClassUtil.removeFirstParameter(mn.desc) : mn.desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodInfo getMethodInfo(MethodNode mn, AnnotationNode an, String targetMethod) {
|
|
||||||
Type targetType = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
|
||||||
if (targetType == null) {
|
|
||||||
// "targetClass" unset, use first parameter as target class type
|
|
||||||
ImmutablePair<String, String> methodDescPair = extractFirstParameter(mn.desc);
|
|
||||||
if (methodDescPair == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new MethodInfo(methodDescPair.left, targetMethod, methodDescPair.right, mn.name, mn.desc);
|
|
||||||
} else {
|
|
||||||
// "targetClass" found, use it as target class type
|
|
||||||
String slashSeparatedName = ClassUtil.toSlashSeparatedName(targetType.getClassName());
|
|
||||||
return new MethodInfo(slashSeparatedName, targetMethod, mn.desc, mn.name,
|
|
||||||
ClassUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(slashSeparatedName)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addMockConstructor(List<MethodInfo> methodInfos, ClassNode cn, MethodNode mn) {
|
|
||||||
String sourceClassName = ClassUtil.getSourceClassName(cn.name);
|
|
||||||
methodInfos.add(new MethodInfo(sourceClassName, ConstPool.CONSTRUCTOR, mn.desc, mn.name, mn.desc));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read @MockWith annotation upon class to fetch mock class
|
* Read @MockWith annotation upon class to fetch mock class
|
||||||
*
|
*
|
||||||
@ -253,7 +162,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
* @return name of mock class, null for not found
|
* @return name of mock class, null for not found
|
||||||
*/
|
*/
|
||||||
private String readMockWithAnnotationAsSourceClass(String className) {
|
private String readMockWithAnnotationAsSourceClass(String className) {
|
||||||
ClassNode cn = getClassNode(className);
|
ClassNode cn = ClassUtil.getClassNode(className);
|
||||||
if (cn == null) {
|
if (cn == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -267,7 +176,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
* @return name of mock class, null for not found
|
* @return name of mock class, null for not found
|
||||||
*/
|
*/
|
||||||
private String readMockWithAnnotationAndInnerClassAsTestClass(String className) {
|
private String readMockWithAnnotationAndInnerClassAsTestClass(String className) {
|
||||||
ClassNode cn = getClassNode(className);
|
ClassNode cn = ClassUtil.getClassNode(className);
|
||||||
if (cn == null) {
|
if (cn == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -299,7 +208,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
private String parseMockWithAnnotation(ClassNode cn, ClassType expectedType) {
|
private String parseMockWithAnnotation(ClassNode cn, ClassType expectedType) {
|
||||||
if (cn.visibleAnnotations != null) {
|
if (cn.visibleAnnotations != null) {
|
||||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||||
setupDiagnose(an);
|
DiagnoseUtil.setupByAnnotation(an);
|
||||||
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.MOCK_WITH)) {
|
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.MOCK_WITH)) {
|
||||||
ClassType type = AnnotationUtil.getAnnotationParameter(an, FIELD_TREAT_AS, ClassType.GuessByName,
|
ClassType type = AnnotationUtil.getAnnotationParameter(an, FIELD_TREAT_AS, ClassType.GuessByName,
|
||||||
ClassType.class);
|
ClassType.class);
|
||||||
@ -324,80 +233,8 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether any method in specified class has mock-related annotation
|
|
||||||
*
|
|
||||||
* @param className class that need to explore
|
|
||||||
* @return found annotation or not
|
|
||||||
*/
|
|
||||||
private boolean hasMockMethod(String className) {
|
|
||||||
ClassNode cn = getClassNode(className);
|
|
||||||
if (cn == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
setupDiagnose(cn);
|
|
||||||
for (MethodNode mn : cn.methods) {
|
|
||||||
if (mn.visibleAnnotations != null) {
|
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
|
||||||
String fullClassName = toDotSeparateFullClassName(an.desc);
|
|
||||||
if (fullClassName.equals(ConstPool.MOCK_METHOD) ||
|
|
||||||
fullClassName.equals(ConstPool.MOCK_CONSTRUCTOR)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getInnerMockClassName(String className) {
|
private String getInnerMockClassName(String className) {
|
||||||
return className + DOLLAR + CLASS_NAME_MOCK;
|
return className + DOLLAR + CLASS_NAME_MOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClassNode getClassNode(String className) {
|
|
||||||
ClassNode cn = new ClassNode();
|
|
||||||
try {
|
|
||||||
new ClassReader(className).accept(cn, 0);
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return cn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupDiagnose(ClassNode cn) {
|
|
||||||
if (cn.visibleAnnotations == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
|
||||||
setupDiagnose(an);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupDiagnose(AnnotationNode an) {
|
|
||||||
if (toDotSeparateFullClassName(an.desc).equals(MOCK_WITH)) {
|
|
||||||
setupDianose(an, FIELD_DIAGNOSE);
|
|
||||||
}
|
|
||||||
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.MOCK_DIAGNOSE)) {
|
|
||||||
setupDianose(an, FIELD_VALUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupDianose(AnnotationNode an, String fieldDiagnose) {
|
|
||||||
LogLevel level = AnnotationUtil.getAnnotationParameter(an, fieldDiagnose, null, LogLevel.class);
|
|
||||||
if (level != null) {
|
|
||||||
LogUtil.setLevel(level == LogLevel.ENABLE ? LogUtil.LogLevel.LEVEL_DIAGNOSE :
|
|
||||||
(level == LogLevel.VERBOSE ? LogUtil.LogLevel.LEVEL_VERBOSE : LogUtil.LogLevel.LEVEL_MUTE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split desc to "first parameter" and "desc of rest parameters"
|
|
||||||
* @param desc method desc
|
|
||||||
*/
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package com.alibaba.testable.agent.util;
|
package com.alibaba.testable.agent.util;
|
||||||
|
|
||||||
import com.alibaba.testable.agent.constant.ConstPool;
|
import com.alibaba.testable.agent.constant.ConstPool;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
import org.objectweb.asm.tree.MethodInsnNode;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -220,15 +223,6 @@ public class ClassUtil {
|
|||||||
return toDotSeparatedName(className).substring(1, className.length() - 1);
|
return toDotSeparatedName(className).substring(1, className.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* convert byte code class name to slash separated human readable name
|
|
||||||
* @param className original name
|
|
||||||
* @return converted name
|
|
||||||
*/
|
|
||||||
public static String toSlashSeparateFullClassName(String className) {
|
|
||||||
return toSlashSeparatedName(className).substring(1, className.length() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* remove first parameter from method descriptor
|
* remove first parameter from method descriptor
|
||||||
* @param desc original descriptor
|
* @param desc original descriptor
|
||||||
@ -248,6 +242,21 @@ public class ClassUtil {
|
|||||||
return "(" + type + desc.substring(1);
|
return "(" + type + desc.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read class from current context
|
||||||
|
* @param className class name
|
||||||
|
* @return loaded class
|
||||||
|
*/
|
||||||
|
public static ClassNode getClassNode(String className) {
|
||||||
|
ClassNode cn = new ClassNode();
|
||||||
|
try {
|
||||||
|
new ClassReader(className).accept(cn, 0);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return cn;
|
||||||
|
}
|
||||||
|
|
||||||
private static String toDescriptor(Byte type, String objectType) {
|
private static String toDescriptor(Byte type, String objectType) {
|
||||||
return "(" + (char)type.byteValue() + ")L" + objectType + ";";
|
return "(" + (char)type.byteValue() + ")L" + objectType + ";";
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.alibaba.testable.agent.util;
|
||||||
|
|
||||||
|
import com.alibaba.testable.agent.constant.ConstPool;
|
||||||
|
import com.alibaba.testable.core.model.LogLevel;
|
||||||
|
import com.alibaba.testable.core.util.LogUtil;
|
||||||
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.agent.constant.ConstPool.MOCK_WITH;
|
||||||
|
import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassName;
|
||||||
|
|
||||||
|
public class DiagnoseUtil {
|
||||||
|
|
||||||
|
private static final String FIELD_VALUE = "value";
|
||||||
|
private static final String FIELD_DIAGNOSE = "diagnose";
|
||||||
|
|
||||||
|
public static void setupByClass(ClassNode cn) {
|
||||||
|
if (cn.visibleAnnotations == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||||
|
setupByAnnotation(an);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setupByAnnotation(AnnotationNode an) {
|
||||||
|
if (toDotSeparateFullClassName(an.desc).equals(MOCK_WITH)) {
|
||||||
|
setupDiagnose(an, FIELD_DIAGNOSE);
|
||||||
|
}
|
||||||
|
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.MOCK_DIAGNOSE)) {
|
||||||
|
setupDiagnose(an, FIELD_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setupDiagnose(AnnotationNode an, String fieldDiagnose) {
|
||||||
|
LogLevel level = AnnotationUtil.getAnnotationParameter(an, fieldDiagnose, null, LogLevel.class);
|
||||||
|
if (level != null) {
|
||||||
|
LogUtil.setLevel(level == LogLevel.ENABLE ? LogUtil.LogLevel.LEVEL_DIAGNOSE :
|
||||||
|
(level == LogLevel.VERBOSE ? LogUtil.LogLevel.LEVEL_VERBOSE : LogUtil.LogLevel.LEVEL_MUTE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,16 +6,16 @@ import org.junit.jupiter.api.Test;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
class TestableClassTransformerTest {
|
class MockClassParserTest {
|
||||||
|
|
||||||
private TestableClassTransformer testableClassTransformer = new TestableClassTransformer();
|
private MockClassParser mockClassParser = new MockClassParser();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_split_parameters() {
|
void should_split_parameters() {
|
||||||
ImmutablePair<String, String> parameters =
|
ImmutablePair<String, String> parameters =
|
||||||
PrivateAccessor.invoke(testableClassTransformer, "extractFirstParameter", "()");
|
PrivateAccessor.invoke(mockClassParser, "extractFirstParameter", "()");
|
||||||
assertNull(parameters);
|
assertNull(parameters);
|
||||||
parameters = PrivateAccessor.invoke(testableClassTransformer, "extractFirstParameter", "(Lcom.alibaba.demo.Class;ILjava.lang.String;Z)");
|
parameters = PrivateAccessor.invoke(mockClassParser, "extractFirstParameter", "(Lcom.alibaba.demo.Class;ILjava.lang.String;Z)");
|
||||||
assertNotNull(parameters);
|
assertNotNull(parameters);
|
||||||
assertEquals("com.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);
|
Loading…
Reference in New Issue
Block a user