support set dump path for single class

This commit is contained in:
金戟 2021-03-27 00:23:19 +08:00
parent 55ccc385d5
commit 26d3178840
6 changed files with 61 additions and 19 deletions

View File

@ -47,7 +47,6 @@
| --- | --- | --- | ---- | --- |
| N/A | Class | 否 | NullType.class | 指定使用的Mock容器类 |
| treatAs | ClassType | 否 | ClassType.GuessByName | 指定当前类是测试类或被测类 |
| diagnose | LogLevel | 否 | N/A | (**deprecated**)指定Mock诊断日志级别 |
#### @MockDiagnose

View File

@ -10,6 +10,7 @@ public class ConstPool {
public static final String FIELD_SCOPE = "scope";
public static final String MOCK_WITH = "com.alibaba.testable.core.annotation.MockWith";
public static final String DUMP_TO = "com.alibaba.testable.core.annotation.DumpTo";
public static final String MOCK_DIAGNOSE = "com.alibaba.testable.core.annotation.MockDiagnose";
public static final String MOCK_METHOD = "com.alibaba.testable.core.annotation.MockMethod";
public static final String MOCK_CONSTRUCTOR = "com.alibaba.testable.core.annotation.MockConstructor";

View File

@ -45,11 +45,10 @@ public class MockClassParser {
/**
* Check whether any method in specified class has mock-related annotation
*
* @param className class that need to explore
* @param cn class that need to explore
* @return found annotation or not
*/
public boolean isMockClass(String className) {
ClassNode cn = ClassUtil.getClassNode(className);
public boolean isMockClass(ClassNode cn) {
if (cn == null) {
return false;
}

View File

@ -16,7 +16,6 @@ import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InnerClassNode;
import javax.lang.model.type.NullType;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
@ -36,6 +35,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
private static final String FIELD_VALUE = "value";
private static final String FIELD_TREAT_AS = "treatAs";
private static final String FIELD_PATH = "path";
private static final String COMMA = ",";
private static final String CLASS_NAME_MOCK = "Mock";
@ -55,19 +55,28 @@ public class TestableClassTransformer implements ClassFileTransformer {
}
LogUtil.verbose("Handle class: " + className);
byte[] bytes = new OmniClassHandler().getBytes(classFileBuffer);
ClassNode cn = ClassUtil.getClassNode(className);
if (cn != null) {
return transformMock(bytes, cn);
}
return bytes;
}
private byte[] transformMock(byte[] bytes, ClassNode cn) {
String className = cn.name;
try {
if (mockClassParser.isMockClass(className)) {
if (mockClassParser.isMockClass(cn)) {
// it's a mock class
LogUtil.diagnose("Handling mock class %s", className);
bytes = new MockClassHandler(className).getBytes(bytes);
dumpByte(className, bytes);
dumpByte(className, GlobalConfig.getDumpPath(), bytes);
} else {
String mockClass = foundMockForTestClass(className);
if (mockClass != null) {
// it's a test class with testable enabled
LogUtil.diagnose("Handling test class %s", className);
bytes = new TestClassHandler().getBytes(bytes);
dumpByte(className, bytes);
dumpByte(className, GlobalConfig.getDumpPath(), bytes);
} else {
mockClass = foundMockForSourceClass(className);
if (mockClass != null) {
@ -75,7 +84,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
List<MethodInfo> injectMethods = mockClassParser.getTestableMockMethods(mockClass);
LogUtil.diagnose("Handling source class %s", className);
bytes = new SourceClassHandler(injectMethods, mockClass).getBytes(bytes);
dumpByte(className, bytes);
dumpByte(className, GlobalConfig.getDumpPath(), bytes);
}
}
}
@ -86,16 +95,16 @@ public class TestableClassTransformer implements ClassFileTransformer {
} finally {
LogUtil.resetLogLevel();
}
dumpByte(className, getDumpPathByAnnotation(cn), bytes);
return bytes;
}
private void dumpByte(String className, byte[] bytes) {
String dumpDir = GlobalConfig.getDumpPath();
if (dumpDir == null || dumpDir.isEmpty() || !new File(dumpDir).isDirectory()) {
private void dumpByte(String className, String dumpPath, byte[] bytes) {
if (dumpPath == null) {
return;
}
try {
String dumpFile = StringUtil.joinPath(dumpDir,
String dumpFile = StringUtil.joinPath(dumpPath,
className.replace(SLASH, DOT).replace(DOLLAR, UNDERLINE) + ".class");
LogUtil.verbose("Dump class: " + dumpFile);
FileOutputStream stream = new FileOutputStream(dumpFile);
@ -139,10 +148,9 @@ public class TestableClassTransformer implements ClassFileTransformer {
}
private String lookForOuterMockClass(String className) {
String mockClass;
mockClass = ClassUtil.getMockClassName(ClassUtil.getSourceClassName(className));
if (mockClassParser.isMockClass(mockClass)) {
return mockClass;
String mockClassName = ClassUtil.getMockClassName(ClassUtil.getSourceClassName(className));
if (mockClassParser.isMockClass(ClassUtil.getClassNode(mockClassName))) {
return mockClassName;
}
return null;
}
@ -193,7 +201,8 @@ public class TestableClassTransformer implements ClassFileTransformer {
*/
private String lookForInnerMockClass(ClassNode cn) {
for (InnerClassNode ic : cn.innerClasses) {
if (ic.name.equals(getInnerMockClassName(cn.name)) && mockClassParser.isMockClass(ic.name)) {
ClassNode innerClassNode = ClassUtil.getClassNode(ic.name);
if (ic.name.equals(getInnerMockClassName(cn.name)) && mockClassParser.isMockClass(innerClassNode)) {
if ((ic.access & ACC_STATIC) == 0) {
LogUtil.warn(String.format("Mock class in \"%s\" is not declared as static", cn.name));
} else {
@ -244,6 +253,17 @@ public class TestableClassTransformer implements ClassFileTransformer {
return null;
}
private String getDumpPathByAnnotation(ClassNode cn) {
if (cn.visibleAnnotations != null) {
for (AnnotationNode an : cn.visibleAnnotations) {
if (toJavaStyleClassName(an.desc).equals(ConstPool.DUMP_TO)) {
return AnnotationUtil.getAnnotationParameter(an, FIELD_PATH, null, String.class);
}
}
}
return null;
}
private boolean isExpectedType(String className, ClassType type, ClassType expectedType) {
if (type.equals(ClassType.GuessByName)) {
return expectedType.equals(ClassType.TestClass) == className.endsWith(TEST_POSTFIX);

View File

@ -3,6 +3,8 @@ package com.alibaba.testable.agent.util;
import com.alibaba.testable.core.model.MockScope;
import com.alibaba.testable.core.util.LogUtil;
import java.io.File;
/**
* @author flin
*/
@ -31,7 +33,7 @@ public class GlobalConfig {
}
public static String getDumpPath() {
return dumpPath;
return (dumpPath == null || dumpPath.isEmpty() || !new File(dumpPath).isDirectory()) ? null : dumpPath;
}
public static void setDumpPath(String path) {

View File

@ -0,0 +1,21 @@
package com.alibaba.testable.core.annotation;
import java.lang.annotation.*;
/**
* Dump byte code for single class
*
* @author flin
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface DumpTo {
/**
* dump class byte code to specified folder
* @return an exist folder
*/
String path() default "";
}