mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-25 20:00:17 +08:00
rename transformer to handler
This commit is contained in:
parent
d91d972aa7
commit
b75e3f5291
@ -0,0 +1,93 @@
|
|||||||
|
package com.alibaba.testable.transformer;
|
||||||
|
|
||||||
|
import com.alibaba.testable.model.TravelStatus;
|
||||||
|
import com.alibaba.testable.util.ClassUtil;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.constant.Const.SYS_CLASSES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author flin
|
||||||
|
*/
|
||||||
|
public class TestableClassTransformer implements Opcodes {
|
||||||
|
|
||||||
|
private static final String CONSTRUCTOR = "<init>";
|
||||||
|
private static final String TESTABLE_NE = "n/e";
|
||||||
|
private final ClassNode cn = new ClassNode();
|
||||||
|
|
||||||
|
public TestableClassTransformer(String className) throws IOException {
|
||||||
|
ClassReader cr = new ClassReader(className);
|
||||||
|
cr.accept(cn, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes() {
|
||||||
|
transform();
|
||||||
|
ClassWriter cw = new ClassWriter( 0);
|
||||||
|
cn.accept(cw);
|
||||||
|
return cw.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transform() {
|
||||||
|
List<String> methodNames = new ArrayList<String>();
|
||||||
|
for (MethodNode m : cn.methods) {
|
||||||
|
if (!CONSTRUCTOR.equals(m.name)) {
|
||||||
|
methodNames.add(m.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (MethodNode m : cn.methods) {
|
||||||
|
transformMethod(m, methodNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transformMethod(MethodNode mn, List<String> methodNames) {
|
||||||
|
AbstractInsnNode[] instructions = mn.instructions.toArray();
|
||||||
|
TravelStatus status = TravelStatus.INIT;
|
||||||
|
String target = "";
|
||||||
|
int rangeStart = 0;
|
||||||
|
int i = 0;
|
||||||
|
do {
|
||||||
|
if (instructions[i].getOpcode() == Opcodes.NEW) {
|
||||||
|
TypeInsnNode node = (TypeInsnNode)instructions[i];
|
||||||
|
if (!SYS_CLASSES.contains(node.desc)) {
|
||||||
|
target = node.desc;
|
||||||
|
status = TravelStatus.NEW_REP;
|
||||||
|
rangeStart = i;
|
||||||
|
}
|
||||||
|
} else if (instructions[i].getOpcode() == Opcodes.INVOKESPECIAL) {
|
||||||
|
MethodInsnNode node = (MethodInsnNode)instructions[i];
|
||||||
|
if (methodNames.contains(node.name) && cn.name.equals(node.owner)) {
|
||||||
|
status = TravelStatus.MEM_REP;
|
||||||
|
} else if (TravelStatus.NEW_REP == status && CONSTRUCTOR.equals(node.name) && target.equals(node.owner)) {
|
||||||
|
instructions = replaceNewOps(mn, instructions, rangeStart, i);
|
||||||
|
i = rangeStart;
|
||||||
|
status = TravelStatus.INIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
} while (i < instructions.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractInsnNode[] replaceNewOps(MethodNode mn, AbstractInsnNode[] instructions, int start, int end) {
|
||||||
|
String classType = ((TypeInsnNode)instructions[start]).desc;
|
||||||
|
String paramTypes = ((MethodInsnNode)instructions[end]).desc;
|
||||||
|
mn.instructions.insertBefore(instructions[start], new LdcInsnNode(Type.getType("L" + classType + ";")));
|
||||||
|
InsnList il = new InsnList();
|
||||||
|
il.add(new MethodInsnNode(INVOKESTATIC, TESTABLE_NE, "w", ClassUtil.generateTargetDesc(paramTypes), false));
|
||||||
|
il.add(new TypeInsnNode(CHECKCAST, classType));
|
||||||
|
mn.instructions.insertBefore(instructions[end], il);
|
||||||
|
mn.instructions.remove(instructions[start]);
|
||||||
|
mn.instructions.remove(instructions[start + 1]);
|
||||||
|
mn.instructions.remove(instructions[end]);
|
||||||
|
mn.maxStack += 1;
|
||||||
|
return mn.instructions.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,93 +1,56 @@
|
|||||||
package com.alibaba.testable.transformer;
|
package com.alibaba.testable.transformer;
|
||||||
|
|
||||||
import com.alibaba.testable.model.TravelStatus;
|
|
||||||
import com.alibaba.testable.util.ClassUtil;
|
import com.alibaba.testable.util.ClassUtil;
|
||||||
import org.objectweb.asm.ClassReader;
|
|
||||||
import org.objectweb.asm.ClassWriter;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.security.ProtectionDomain;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import static com.alibaba.testable.constant.Const.SYS_CLASSES;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author flin
|
* @author flin
|
||||||
*/
|
*/
|
||||||
public class TestableClassTransformer implements Opcodes {
|
public class TestableFileTransformer implements ClassFileTransformer {
|
||||||
|
|
||||||
private static final String CONSTRUCTOR = "<init>";
|
private static final String ENABLE_TESTABLE = "com.alibaba.testable.annotation.EnableTestable";
|
||||||
private static final String TESTABLE_NE = "n/e";
|
private static final String ENABLE_TESTABLE_INJECT = "com.alibaba.testable.annotation.EnableTestableInject";
|
||||||
private final ClassNode cn = new ClassNode();
|
private static final String TEST_POSTFIX = "Test";
|
||||||
|
|
||||||
public TestableClassTransformer(String className) throws IOException {
|
private static final Set<String> loadedClassNames = new HashSet<String>();
|
||||||
ClassReader cr = new ClassReader(className);
|
|
||||||
cr.accept(cn, 0);
|
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
|
||||||
|
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
|
||||||
|
if (isSystemClass(loader, className) || loadedClassNames.contains(className)) {
|
||||||
|
// Ignore system class and duplicate class
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getBytes() {
|
List<String> annotations = ClassUtil.getAnnotations(className);
|
||||||
transform();
|
List<String> testAnnotations = ClassUtil.getAnnotations(className + TEST_POSTFIX);
|
||||||
ClassWriter cw = new ClassWriter( 0);
|
if (!isNeedTransform(annotations, testAnnotations)) {
|
||||||
cn.accept(cw);
|
// Neither EnableTestable on test class, nor EnableTestableInject on source class
|
||||||
return cw.toByteArray();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void transform() {
|
try {
|
||||||
List<String> methodNames = new ArrayList<String>();
|
loadedClassNames.add(className);
|
||||||
for (MethodNode m : cn.methods) {
|
return new TestableClassTransformer(className).getBytes();
|
||||||
if (!CONSTRUCTOR.equals(m.name)) {
|
} catch (IOException e) {
|
||||||
methodNames.add(m.name);
|
return null;
|
||||||
}
|
|
||||||
}
|
|
||||||
for (MethodNode m : cn.methods) {
|
|
||||||
transformMethod(m, methodNames);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void transformMethod(MethodNode mn, List<String> methodNames) {
|
private boolean isSystemClass(ClassLoader loader, String className) {
|
||||||
AbstractInsnNode[] instructions = mn.instructions.toArray();
|
return !(loader instanceof URLClassLoader) || null == className || className.startsWith("jdk/");
|
||||||
TravelStatus status = TravelStatus.INIT;
|
|
||||||
String target = "";
|
|
||||||
int rangeStart = 0;
|
|
||||||
int i = 0;
|
|
||||||
do {
|
|
||||||
if (instructions[i].getOpcode() == Opcodes.NEW) {
|
|
||||||
TypeInsnNode node = (TypeInsnNode)instructions[i];
|
|
||||||
if (!SYS_CLASSES.contains(node.desc)) {
|
|
||||||
target = node.desc;
|
|
||||||
status = TravelStatus.NEW_REP;
|
|
||||||
rangeStart = i;
|
|
||||||
}
|
|
||||||
} else if (instructions[i].getOpcode() == Opcodes.INVOKESPECIAL) {
|
|
||||||
MethodInsnNode node = (MethodInsnNode)instructions[i];
|
|
||||||
if (methodNames.contains(node.name) && cn.name.equals(node.owner)) {
|
|
||||||
status = TravelStatus.MEM_REP;
|
|
||||||
} else if (TravelStatus.NEW_REP == status && CONSTRUCTOR.equals(node.name) && target.equals(node.owner)) {
|
|
||||||
instructions = replaceNewOps(mn, instructions, rangeStart, i);
|
|
||||||
i = rangeStart;
|
|
||||||
status = TravelStatus.INIT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
} while (i < instructions.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AbstractInsnNode[] replaceNewOps(MethodNode mn, AbstractInsnNode[] instructions, int start, int end) {
|
private boolean isNeedTransform(List<String> annotations, List<String> testAnnotations) {
|
||||||
String classType = ((TypeInsnNode)instructions[start]).desc;
|
return annotations != null &&
|
||||||
String paramTypes = ((MethodInsnNode)instructions[end]).desc;
|
(annotations.contains(ENABLE_TESTABLE_INJECT) ||
|
||||||
mn.instructions.insertBefore(instructions[start], new LdcInsnNode(Type.getType("L" + classType + ";")));
|
(testAnnotations != null && testAnnotations.contains(ENABLE_TESTABLE)));
|
||||||
InsnList il = new InsnList();
|
|
||||||
il.add(new MethodInsnNode(INVOKESTATIC, TESTABLE_NE, "w", ClassUtil.generateTargetDesc(paramTypes), false));
|
|
||||||
il.add(new TypeInsnNode(CHECKCAST, classType));
|
|
||||||
mn.instructions.insertBefore(instructions[end], il);
|
|
||||||
mn.instructions.remove(instructions[start]);
|
|
||||||
mn.instructions.remove(instructions[start + 1]);
|
|
||||||
mn.instructions.remove(instructions[end]);
|
|
||||||
mn.maxStack += 1;
|
|
||||||
return mn.instructions.toArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
package com.alibaba.testable.transformer;
|
|
||||||
|
|
||||||
import com.alibaba.testable.util.ClassUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.instrument.ClassFileTransformer;
|
|
||||||
import java.net.URLClassLoader;
|
|
||||||
import java.security.ProtectionDomain;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author flin
|
|
||||||
*/
|
|
||||||
public class TestableFileTransformer implements ClassFileTransformer {
|
|
||||||
|
|
||||||
private static final String ENABLE_TESTABLE = "com.alibaba.testable.annotation.EnableTestable";
|
|
||||||
private static final String ENABLE_TESTABLE_INJECT = "com.alibaba.testable.annotation.EnableTestableInject";
|
|
||||||
private static final String TEST_POSTFIX = "Test";
|
|
||||||
|
|
||||||
private static final Set<String> loadedClassNames = new HashSet<String>();
|
|
||||||
|
|
||||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
|
|
||||||
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
|
|
||||||
if (isSystemClass(loader, className) || loadedClassNames.contains(className)) {
|
|
||||||
// Ignore system class and duplicate class
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> annotations = ClassUtil.getAnnotations(className);
|
|
||||||
List<String> testAnnotations = ClassUtil.getAnnotations(className + TEST_POSTFIX);
|
|
||||||
if (!isNeedTransform(annotations, testAnnotations)) {
|
|
||||||
// Neither EnableTestable on test class, nor EnableTestableInject on source class
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
loadedClassNames.add(className);
|
|
||||||
return new TestableClassTransformer(className).getBytes();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSystemClass(ClassLoader loader, String className) {
|
|
||||||
return !(loader instanceof URLClassLoader) || null == className || className.startsWith("jdk/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isNeedTransform(List<String> annotations, List<String> testAnnotations) {
|
|
||||||
return annotations != null &&
|
|
||||||
(annotations.contains(ENABLE_TESTABLE_INJECT) ||
|
|
||||||
(testAnnotations != null && testAnnotations.contains(ENABLE_TESTABLE)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user