mirror of
https://github.com/alibaba/testable-mock.git
synced 2026-08-19 09:43:29 +08:00
feat: always consider class with MockContainer annotation as mock class
This commit is contained in:
@@ -77,13 +77,10 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
||||
*/
|
||||
private void injectInheritedMockMethods(ClassNode cn) {
|
||||
List<Type> inheritedTypes = new ArrayList<Type>();
|
||||
if (cn.visibleAnnotations != null) {
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
if ((ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONTAINER)).equals(an.desc)) {
|
||||
inheritedTypes.addAll(AnnotationUtil.getAnnotationParameter(an, FIELD_INHERITS,
|
||||
Collections.<Type>emptyList(), List.class));
|
||||
}
|
||||
}
|
||||
AnnotationNode an = AnnotationUtil.getClassAnnotation(cn, MOCK_CONTAINER);
|
||||
if (an != null) {
|
||||
inheritedTypes.addAll(AnnotationUtil.getAnnotationParameter(an, FIELD_INHERITS,
|
||||
Collections.<Type>emptyList(), List.class));
|
||||
}
|
||||
for (Type inheritedType : inheritedTypes) {
|
||||
String className = inheritedType.getClassName();
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.alibaba.testable.agent.handler;
|
||||
|
||||
import com.alibaba.testable.agent.handler.test.JUnit4Framework;
|
||||
import com.alibaba.testable.agent.handler.test.JUnit5Framework;
|
||||
import com.alibaba.testable.agent.util.AnnotationUtil;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
import com.alibaba.testable.core.util.CollectionUtil;
|
||||
import org.objectweb.asm.Label;
|
||||
@@ -24,7 +25,7 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
private static final String METHOD_START = "(";
|
||||
private static final String VOID_METHOD_END = ")V";
|
||||
private static final String VOID_METHOD = "()V";
|
||||
private static final String ENABLE_CONFIGURATION = "Lorg/springframework/context/annotation/Configuration;";
|
||||
private static final String ENABLE_CONFIGURATION = "org.springframework.context.annotation.Configuration";
|
||||
private static final String CLASS_ABSTRACT_COLLECTION = "java/util/AbstractCollection";
|
||||
private static final String CLASS_NUMBER = "java/lang/Number";
|
||||
private static final String CLASS_HASH_SET = "java/util/HashSet";
|
||||
@@ -38,7 +39,8 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
|
||||
@Override
|
||||
protected void transform(ClassNode cn) {
|
||||
if (isInterfaceOrAtom(cn) || isUniqueConstructorClass(cn) || isUninstantiableClass(cn) || hasSpecialAnnotation(cn)) {
|
||||
if (isInterfaceOrAtom(cn) || isUniqueConstructorClass(cn) || isUninstantiableClass(cn) ||
|
||||
AnnotationUtil.getClassAnnotation(cn, ENABLE_CONFIGURATION) != null) {
|
||||
return;
|
||||
}
|
||||
addConstructorWithVoidTypeParameter(cn);
|
||||
@@ -62,18 +64,6 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
cn.methods.add(constructor);
|
||||
}
|
||||
|
||||
private boolean hasSpecialAnnotation(ClassNode cn) {
|
||||
if (cn.visibleAnnotations == null) {
|
||||
return false;
|
||||
}
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
if (an.desc.equals(ENABLE_CONFIGURATION)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isUninstantiableClass(ClassNode cn) {
|
||||
// if the class has no even default constructor, skip it
|
||||
for (MethodNode mn : cn.methods) {
|
||||
|
||||
@@ -44,9 +44,9 @@ public class MockClassParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether any method in specified class has mock-related annotation
|
||||
* Check whether specified class is declared as mock container or has any method with mock-related annotation
|
||||
*
|
||||
* @param cn class that need to explore
|
||||
* @param cn class to explore
|
||||
* @return found annotation or not
|
||||
*/
|
||||
public boolean isMockClass(ClassNode cn) {
|
||||
@@ -54,6 +54,9 @@ public class MockClassParser {
|
||||
return false;
|
||||
}
|
||||
DiagnoseUtil.setupByClass(cn);
|
||||
if (AnnotationUtil.getClassAnnotation(cn, MOCK_CONTAINER) != null) {
|
||||
return true;
|
||||
}
|
||||
for (MethodNode mn : cn.name.endsWith(MOCK_POSTFIX) ? getAllMethods(cn) : cn.methods) {
|
||||
if (mn.visibleAnnotations != null) {
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
@@ -97,21 +100,18 @@ public class MockClassParser {
|
||||
* Take care of @MockContainer annotation
|
||||
*/
|
||||
private void handleMockContainerInherits(List<MethodInfo> methodInfos, ClassNode cn) {
|
||||
if (cn.visibleAnnotations != null) {
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
if ((ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONTAINER)).equals(an.desc)) {
|
||||
for (Object st : AnnotationUtil.getAnnotationParameter(an, FIELD_INHERITS,
|
||||
Collections.<Type>emptyList(), List.class)) {
|
||||
String superClassName = ((Type)st).getClassName();
|
||||
ClassNode superCn = ClassUtil.getClassNode(superClassName);
|
||||
if (superCn == null) {
|
||||
LogUtil.warn("failed to load class '%s' inherited by '%s'", superClassName, cn.name);
|
||||
continue;
|
||||
}
|
||||
for (MethodNode mn : getAllMethods(superCn)) {
|
||||
addMethodWithAnnotationCheck(methodInfos, cn, mn);
|
||||
}
|
||||
}
|
||||
AnnotationNode an = AnnotationUtil.getClassAnnotation(cn, MOCK_CONTAINER);
|
||||
if (an != null) {
|
||||
for (Object st : AnnotationUtil.getAnnotationParameter(an, FIELD_INHERITS,
|
||||
Collections.<Type>emptyList(), List.class)) {
|
||||
String superClassName = ((Type)st).getClassName();
|
||||
ClassNode superCn = ClassUtil.getClassNode(superClassName);
|
||||
if (superCn == null) {
|
||||
LogUtil.warn("failed to load class '%s' inherited by '%s'", superClassName, cn.name);
|
||||
continue;
|
||||
}
|
||||
for (MethodNode mn : getAllMethods(superCn)) {
|
||||
addMethodWithAnnotationCheck(methodInfos, cn, mn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ import java.lang.instrument.ClassFileTransformer;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.List;
|
||||
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.CGLIB_CLASS_PATTERN;
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.KOTLIN_POSTFIX_COMPANION;
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.*;
|
||||
import static com.alibaba.testable.core.constant.ConstPool.DOLLAR;
|
||||
import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX;
|
||||
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
||||
@@ -31,7 +30,7 @@ import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
||||
public class TestableClassTransformer implements ClassFileTransformer {
|
||||
|
||||
private static final String FIELD_TREAT_AS = "treatAs";
|
||||
private static final String CLASS_JUNIT_5_NESTED = "Lorg/junit/jupiter/api/Nested;";
|
||||
private static final String CLASS_JUNIT_5_NESTED = "org.junit.jupiter.api.Nested";
|
||||
|
||||
/**
|
||||
* Just avoid spend time to scan those surely non-user classes, should keep these lists as tiny as possible
|
||||
@@ -103,14 +102,17 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
|
||||
private String foundMockForSourceClass(String name) {
|
||||
String className = (GlobalConfig.getMockPackageMapping() == null) ? name : mapPackage(name);
|
||||
// handle @MockWith annotation on source class
|
||||
String mockClass = lookForMockWithAnnotationAsSourceClass(className);
|
||||
if (mockClass != null) {
|
||||
return mockClass;
|
||||
}
|
||||
mockClass = foundMockForTestClass(ClassUtil.getTestClassName(className));
|
||||
// look for [ThisClass]Test.Mock and [ThisClass]Mock
|
||||
mockClass = foundMockForStandardClass(className);
|
||||
if (mockClass != null) {
|
||||
return mockClass;
|
||||
}
|
||||
// inner class should also look for mock class in the test class of its outer class
|
||||
return foundMockForInnerSourceClass(className);
|
||||
}
|
||||
|
||||
@@ -127,32 +129,30 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
|
||||
private String foundMockForInnerSourceClass(String className) {
|
||||
return (className.contains(DOLLAR) && !className.endsWith(KOTLIN_POSTFIX_COMPANION)) ?
|
||||
foundMockForTestClass(ClassUtil.getTestClassName(className.substring(0, className.indexOf(DOLLAR)))) : null;
|
||||
foundMockForStandardClass(className.substring(0, className.indexOf(DOLLAR))) : null;
|
||||
}
|
||||
|
||||
private String foundMockForTestClass(String className) {
|
||||
ClassNode cn = adaptInnerClass(ClassUtil.getClassNode(className));
|
||||
private String foundMockForStandardClass(String className) {
|
||||
ClassNode cn = adaptInnerClass(ClassUtil.getClassNode(ClassUtil.getTestClassName(className)));
|
||||
if (cn != null) {
|
||||
// handle @MockWith annotation on test class
|
||||
String mockClass = lookForMockWithAnnotationAsTestClass(cn);
|
||||
if (mockClass != null) {
|
||||
return mockClass;
|
||||
}
|
||||
// look for [ThisClass]Test.Mock
|
||||
mockClass = lookForInnerMockClass(cn);
|
||||
if (mockClass != null) {
|
||||
return mockClass;
|
||||
}
|
||||
}
|
||||
// look for [ThisClass]Mock
|
||||
return lookForOuterMockClass(className);
|
||||
}
|
||||
|
||||
private ClassNode adaptInnerClass(ClassNode cn) {
|
||||
if (cn == null || cn.visibleAnnotations == null) {
|
||||
return cn;
|
||||
}
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
if (an.desc.equals(CLASS_JUNIT_5_NESTED)) {
|
||||
return ClassUtil.getClassNode(ClassUtil.toOuterClassName(cn.name));
|
||||
}
|
||||
if (AnnotationUtil.getClassAnnotation(cn, CLASS_JUNIT_5_NESTED) != null) {
|
||||
return ClassUtil.getClassNode(ClassUtil.toOuterClassName(cn.name));
|
||||
}
|
||||
return cn;
|
||||
}
|
||||
@@ -214,7 +214,9 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
private String lookForInnerMockClass(ClassNode cn) {
|
||||
for (InnerClassNode ic : cn.innerClasses) {
|
||||
ClassNode innerClassNode = ClassUtil.getClassNode(ic.name);
|
||||
if (ic.name.equals(getInnerMockClassName(cn.name)) && mockClassParser.isMockClass(innerClassNode)) {
|
||||
boolean isNameMatched = ic.name.equals(getInnerMockClassName(cn.name)) ||
|
||||
AnnotationUtil.getClassAnnotation(innerClassNode, MOCK_CONTAINER) != null;
|
||||
if (isNameMatched && mockClassParser.isMockClass(innerClassNode)) {
|
||||
if ((ic.access & ACC_STATIC) == 0) {
|
||||
LogUtil.warn("Mock class in \"%s\" is not declared as static", cn.name);
|
||||
} else {
|
||||
@@ -248,18 +250,15 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
* @return mock class name
|
||||
*/
|
||||
private String parseMockWithAnnotation(ClassNode cn, ClassType expectedType) {
|
||||
if (cn.visibleAnnotations != null) {
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
if ((ClassUtil.toByteCodeClassName(ConstPool.MOCK_WITH)).equals(an.desc)) {
|
||||
ClassType type = AnnotationUtil.getAnnotationParameter(an, FIELD_TREAT_AS, ClassType.GuessByName,
|
||||
ClassType.class);
|
||||
if (isExpectedType(cn.name, type, expectedType)) {
|
||||
Type clazz = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_VALUE,
|
||||
Type.getType(NullType.class), Type.class);
|
||||
DiagnoseUtil.setupByClass(ClassUtil.getClassNode(clazz.getClassName()));
|
||||
return clazz.getClassName();
|
||||
}
|
||||
}
|
||||
AnnotationNode an = AnnotationUtil.getClassAnnotation(cn, MOCK_WITH);
|
||||
if (an != null) {
|
||||
ClassType type = AnnotationUtil.getAnnotationParameter(an, FIELD_TREAT_AS, ClassType.GuessByName,
|
||||
ClassType.class);
|
||||
if (isExpectedType(cn.name, type, expectedType)) {
|
||||
Type clazz = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_VALUE,
|
||||
Type.getType(NullType.class), Type.class);
|
||||
DiagnoseUtil.setupByClass(ClassUtil.getClassNode(clazz.getClassName()));
|
||||
return clazz.getClassName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.alibaba.testable.agent.util;
|
||||
|
||||
import com.alibaba.testable.agent.constant.ConstPool;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
@@ -60,4 +62,20 @@ public class AnnotationUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specified annotation node from specified class, or null if the annotation not exist
|
||||
* @param cn class to explore
|
||||
* @param annotation name of annotation to look for
|
||||
* @return the annotation instance or null
|
||||
*/
|
||||
public static AnnotationNode getClassAnnotation(ClassNode cn, String annotation) {
|
||||
if (cn != null && cn.visibleAnnotations != null) {
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
if (ClassUtil.toByteCodeClassName(annotation).equals(an.desc)) {
|
||||
return an;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.alibaba.testable.agent.util;
|
||||
|
||||
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.core.util.LogUtil;
|
||||
import org.objectweb.asm.tree.*;
|
||||
@@ -11,8 +10,7 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.FIELD_VALUE;
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.PROPERTY_USER_DIR;
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.*;
|
||||
import static com.alibaba.testable.core.constant.ConstPool.*;
|
||||
import static com.alibaba.testable.core.util.PathUtil.createFolder;
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
@@ -283,15 +281,12 @@ public class BytecodeUtil {
|
||||
}
|
||||
|
||||
private static String getDumpPathByAnnotation(ClassNode cn) {
|
||||
if (cn.visibleAnnotations != null) {
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
if ((ClassUtil.toByteCodeClassName(ConstPool.DUMP_TO)).equals(an.desc)) {
|
||||
String path = AnnotationUtil.getAnnotationParameter(an, FIELD_VALUE, null, String.class);
|
||||
String fullPath = PathUtil.join(System.getProperty(PROPERTY_USER_DIR), path);
|
||||
if (createFolder(fullPath)) {
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
AnnotationNode an = AnnotationUtil.getClassAnnotation(cn, DUMP_TO);
|
||||
if (an != null) {
|
||||
String path = AnnotationUtil.getAnnotationParameter(an, FIELD_VALUE, null, String.class);
|
||||
String fullPath = PathUtil.join(System.getProperty(PROPERTY_USER_DIR), path);
|
||||
if (createFolder(fullPath)) {
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -6,16 +6,14 @@ 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_DIAGNOSE;
|
||||
|
||||
public class DiagnoseUtil {
|
||||
|
||||
public static void setupByClass(ClassNode cn) {
|
||||
if (cn == null || cn.visibleAnnotations == null) {
|
||||
return;
|
||||
}
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_DIAGNOSE).equals(an.desc)) {
|
||||
setupDiagnose(an, ConstPool.FIELD_VALUE);
|
||||
}
|
||||
AnnotationNode an = AnnotationUtil.getClassAnnotation(cn, MOCK_DIAGNOSE);
|
||||
if (an != null) {
|
||||
setupDiagnose(an, ConstPool.FIELD_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user