allow to dump single mock class

This commit is contained in:
金戟 2021-07-12 21:41:31 +08:00
parent 2780ccc6fa
commit 93ad1fa94f
4 changed files with 38 additions and 34 deletions

View File

@ -8,6 +8,7 @@ public class ConstPool {
public static final String FIELD_TARGET_METHOD = "targetMethod";
public static final String FIELD_TARGET_CLASS = "targetClass";
public static final String FIELD_SCOPE = "scope";
public static final String FIELD_VALUE = "value";
public static final String PROPERTY_USER_DIR = "user.dir";
public static final String PROPERTY_TEMP_DIR = "java.io.tmpdir";

View File

@ -22,10 +22,10 @@ import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import java.util.List;
import static com.alibaba.testable.agent.constant.ConstPool.*;
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.core.constant.ConstPool.DOLLAR;
import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX;
import static com.alibaba.testable.core.util.PathUtil.createFolder;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
/**
@ -33,7 +33,6 @@ import static org.objectweb.asm.Opcodes.ACC_STATIC;
*/
public class TestableClassTransformer implements ClassFileTransformer {
private static final String FIELD_VALUE = "value";
private static final String FIELD_TREAT_AS = "treatAs";
private static final String CLASS_JUNIT_5_NESTED = "Lorg/junit/jupiter/api/Nested;";
@ -71,7 +70,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
if (mockClassParser.isMockClass(cn)) {
// it's a mock class
bytes = new MockClassHandler(className).getBytes(bytes);
BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes);
BytecodeUtil.dumpByte(cn, GlobalConfig.getDumpPath(), bytes);
return bytes;
}
String mockClass = foundMockForSourceClass(className);
@ -79,14 +78,14 @@ public class TestableClassTransformer implements ClassFileTransformer {
// it's a source class with testable enabled
List<MethodInfo> injectMethods = mockClassParser.getTestableMockMethods(mockClass);
bytes = new SourceClassHandler(injectMethods, mockClass).getBytes(bytes);
BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes);
BytecodeUtil.dumpByte(cn, GlobalConfig.getDumpPath(), bytes);
return bytes;
}
Framework framework = testClassChecker.checkFramework(cn);
if (framework != null) {
// it's a test class
bytes = new TestClassHandler(framework).getBytes(bytes);
BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes);
BytecodeUtil.dumpByte(cn, GlobalConfig.getDumpPath(), bytes);
return bytes;
} else if (cn.name.endsWith(TEST_POSTFIX)) {
LogUtil.verbose("Failed to detect test framework for %s", cn.name);
@ -101,7 +100,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
} finally {
LogUtil.resetLogLevel();
}
BytecodeUtil.dumpByte(className, getDumpPathByAnnotation(cn), bytes);
BytecodeUtil.dumpByte(cn, null, bytes);
return bytes;
}
@ -257,7 +256,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
ClassType type = AnnotationUtil.getAnnotationParameter(an, FIELD_TREAT_AS, ClassType.GuessByName,
ClassType.class);
if (isExpectedType(cn.name, type, expectedType)) {
Type clazz = AnnotationUtil.getAnnotationParameter(an, FIELD_VALUE,
Type clazz = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_VALUE,
Type.getType(NullType.class), Type.class);
DiagnoseUtil.setupByClass(ClassUtil.getClassNode(clazz.getClassName()));
return clazz.getClassName();
@ -268,21 +267,6 @@ public class TestableClassTransformer implements ClassFileTransformer {
return null;
}
private 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;
}
}
}
}
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

@ -1,19 +1,20 @@
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.AbstractInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.*;
import java.io.FileOutputStream;
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.core.constant.ConstPool.*;
import static com.alibaba.testable.core.constant.ConstPool.UNDERLINE;
import static com.alibaba.testable.core.util.PathUtil.createFolder;
import static org.objectweb.asm.Opcodes.*;
/**
@ -21,6 +22,8 @@ import static org.objectweb.asm.Opcodes.*;
*/
public class BytecodeUtil {
private static final String POSTFIX_CLASS = ".class";
/**
* refer to https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
* positive for push stack, negative for pop stack
@ -208,17 +211,20 @@ public class BytecodeUtil {
/**
* Dump byte code to specified class file
* @param className original class name
* @param cn original class node
* @param dumpPath folder to store class file
* @param bytes original class bytes
*/
public static void dumpByte(String className, String dumpPath, byte[] bytes) {
public static void dumpByte(ClassNode cn, String dumpPath, byte[] bytes) {
if (dumpPath == null) {
return;
dumpPath = getDumpPathByAnnotation(cn);
if (dumpPath == null) {
return;
}
}
try {
String dumpFile = PathUtil.join(dumpPath,
className.replace(SLASH, DOT).replace(DOLLAR, UNDERLINE) + ".class");
cn.name.replace(SLASH, DOT).replace(DOLLAR, UNDERLINE) + POSTFIX_CLASS);
LogUtil.verbose("Dump class: " + dumpFile);
FileOutputStream stream = new FileOutputStream(dumpFile);
stream.write(bytes);
@ -275,4 +281,19 @@ public class BytecodeUtil {
return new IntInsnNode(BIPUSH, num);
}
}
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;
}
}
}
}
return null;
}
}

View File

@ -8,15 +8,13 @@ import org.objectweb.asm.tree.ClassNode;
public class DiagnoseUtil {
private static final String FIELD_VALUE = "value";
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, FIELD_VALUE);
setupDiagnose(an, ConstPool.FIELD_VALUE);
}
}
}