feat: always consider class with MockContainer annotation as mock class

This commit is contained in:
金戟
2022-11-23 14:05:10 +08:00
parent e52538acf3
commit 47871d0eb6
7 changed files with 81 additions and 84 deletions

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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);
}
}