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_METHOD = "targetMethod";
public static final String FIELD_TARGET_CLASS = "targetClass"; public static final String FIELD_TARGET_CLASS = "targetClass";
public static final String FIELD_SCOPE = "scope"; 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_USER_DIR = "user.dir";
public static final String PROPERTY_TEMP_DIR = "java.io.tmpdir"; 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.security.ProtectionDomain;
import java.util.List; 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.DOLLAR;
import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX; 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; 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 { 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_TREAT_AS = "treatAs";
private static final String CLASS_JUNIT_5_NESTED = "Lorg/junit/jupiter/api/Nested;"; 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)) { if (mockClassParser.isMockClass(cn)) {
// it's a mock class // it's a mock class
bytes = new MockClassHandler(className).getBytes(bytes); bytes = new MockClassHandler(className).getBytes(bytes);
BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes); BytecodeUtil.dumpByte(cn, GlobalConfig.getDumpPath(), bytes);
return bytes; return bytes;
} }
String mockClass = foundMockForSourceClass(className); String mockClass = foundMockForSourceClass(className);
@ -79,14 +78,14 @@ public class TestableClassTransformer implements ClassFileTransformer {
// it's a source class with testable enabled // it's a source class with testable enabled
List<MethodInfo> injectMethods = mockClassParser.getTestableMockMethods(mockClass); List<MethodInfo> injectMethods = mockClassParser.getTestableMockMethods(mockClass);
bytes = new SourceClassHandler(injectMethods, mockClass).getBytes(bytes); bytes = new SourceClassHandler(injectMethods, mockClass).getBytes(bytes);
BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes); BytecodeUtil.dumpByte(cn, GlobalConfig.getDumpPath(), bytes);
return bytes; return bytes;
} }
Framework framework = testClassChecker.checkFramework(cn); Framework framework = testClassChecker.checkFramework(cn);
if (framework != null) { if (framework != null) {
// it's a test class // it's a test class
bytes = new TestClassHandler(framework).getBytes(bytes); bytes = new TestClassHandler(framework).getBytes(bytes);
BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes); BytecodeUtil.dumpByte(cn, GlobalConfig.getDumpPath(), bytes);
return bytes; return bytes;
} else if (cn.name.endsWith(TEST_POSTFIX)) { } else if (cn.name.endsWith(TEST_POSTFIX)) {
LogUtil.verbose("Failed to detect test framework for %s", cn.name); LogUtil.verbose("Failed to detect test framework for %s", cn.name);
@ -101,7 +100,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
} finally { } finally {
LogUtil.resetLogLevel(); LogUtil.resetLogLevel();
} }
BytecodeUtil.dumpByte(className, getDumpPathByAnnotation(cn), bytes); BytecodeUtil.dumpByte(cn, null, bytes);
return bytes; return bytes;
} }
@ -257,7 +256,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
ClassType type = AnnotationUtil.getAnnotationParameter(an, FIELD_TREAT_AS, ClassType.GuessByName, ClassType type = AnnotationUtil.getAnnotationParameter(an, FIELD_TREAT_AS, ClassType.GuessByName,
ClassType.class); ClassType.class);
if (isExpectedType(cn.name, type, expectedType)) { 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); Type.getType(NullType.class), Type.class);
DiagnoseUtil.setupByClass(ClassUtil.getClassNode(clazz.getClassName())); DiagnoseUtil.setupByClass(ClassUtil.getClassNode(clazz.getClassName()));
return clazz.getClassName(); return clazz.getClassName();
@ -268,21 +267,6 @@ public class TestableClassTransformer implements ClassFileTransformer {
return null; 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) { private boolean isExpectedType(String className, ClassType type, ClassType expectedType) {
if (type.equals(ClassType.GuessByName)) { if (type.equals(ClassType.GuessByName)) {
return expectedType.equals(ClassType.TestClass) == className.endsWith(TEST_POSTFIX); return expectedType.equals(ClassType.TestClass) == className.endsWith(TEST_POSTFIX);

View File

@ -1,19 +1,20 @@
package com.alibaba.testable.agent.util; package com.alibaba.testable.agent.util;
import com.alibaba.testable.agent.constant.ByteCodeConst; 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.agent.tool.ImmutablePair;
import com.alibaba.testable.core.util.LogUtil; import com.alibaba.testable.core.util.LogUtil;
import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.*;
import static com.alibaba.testable.core.constant.ConstPool.UNDERLINE; import static com.alibaba.testable.core.util.PathUtil.createFolder;
import static org.objectweb.asm.Opcodes.*; import static org.objectweb.asm.Opcodes.*;
/** /**
@ -21,6 +22,8 @@ import static org.objectweb.asm.Opcodes.*;
*/ */
public class BytecodeUtil { public class BytecodeUtil {
private static final String POSTFIX_CLASS = ".class";
/** /**
* refer to https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings * refer to https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
* positive for push stack, negative for pop stack * positive for push stack, negative for pop stack
@ -208,17 +211,20 @@ public class BytecodeUtil {
/** /**
* Dump byte code to specified class file * 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 dumpPath folder to store class file
* @param bytes original class bytes * @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) { if (dumpPath == null) {
return; dumpPath = getDumpPathByAnnotation(cn);
if (dumpPath == null) {
return;
}
} }
try { try {
String dumpFile = PathUtil.join(dumpPath, 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); LogUtil.verbose("Dump class: " + dumpFile);
FileOutputStream stream = new FileOutputStream(dumpFile); FileOutputStream stream = new FileOutputStream(dumpFile);
stream.write(bytes); stream.write(bytes);
@ -275,4 +281,19 @@ public class BytecodeUtil {
return new IntInsnNode(BIPUSH, num); 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 { public class DiagnoseUtil {
private static final String FIELD_VALUE = "value";
public static void setupByClass(ClassNode cn) { public static void setupByClass(ClassNode cn) {
if (cn == null || cn.visibleAnnotations == null) { if (cn == null || cn.visibleAnnotations == null) {
return; return;
} }
for (AnnotationNode an : cn.visibleAnnotations) { for (AnnotationNode an : cn.visibleAnnotations) {
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_DIAGNOSE).equals(an.desc)) { if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_DIAGNOSE).equals(an.desc)) {
setupDiagnose(an, FIELD_VALUE); setupDiagnose(an, ConstPool.FIELD_VALUE);
} }
} }
} }