mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-24 19:31:17 +08:00
create omni constructor correctly
This commit is contained in:
parent
9b56e3d64a
commit
9871a96db0
@ -2,6 +2,13 @@ package com.alibaba.demo.basic.model.omni;
|
||||
|
||||
public class Child {
|
||||
|
||||
/**
|
||||
* 我是一个私有的构造方法
|
||||
* This class have only private constructor
|
||||
*/
|
||||
private Child() {
|
||||
}
|
||||
|
||||
// ---------- 内部成员字段 ----------
|
||||
|
||||
private GrandChild grandChild;
|
||||
|
@ -2,6 +2,14 @@ package com.alibaba.demo.basic.model.omni;
|
||||
|
||||
public class Parent {
|
||||
|
||||
/**
|
||||
* 我是一个虽然存在但无法正常使用的构造方法
|
||||
* This class have constructor with exception throw
|
||||
*/
|
||||
public Parent() {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
// ---------- 内部成员字段 ----------
|
||||
|
||||
private Child child;
|
||||
|
@ -1,24 +1,112 @@
|
||||
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.ClassUtil;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import com.alibaba.testable.agent.util.CollectionUtil;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.tree.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.alibaba.testable.agent.util.ClassUtil.CLASS_OBJECT;
|
||||
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
||||
import static com.alibaba.testable.core.constant.ConstPool.THIS_REF;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class OmniClassHandler extends BaseClassHandler {
|
||||
|
||||
private static final String TYPE_NULL = "com.alibaba.testable.core.model.Null";
|
||||
private static final String TESTABLE_NULL_TYPE = "com.alibaba.testable.core.model.TestableNull";
|
||||
private static final String NULL_TYPE = "javax.lang.model.type.NullType";
|
||||
private static final String IGNORE = "ignore";
|
||||
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[] JUNIT_TEST_ANNOTATIONS = new String[] {
|
||||
JUnit4Framework.ANNOTATION_TEST, JUnit5Framework.ANNOTATION_TEST, JUnit5Framework.ANNOTATION_PARAMETERIZED_TEST
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void transform(ClassNode cn) {
|
||||
if ((cn.access & (ACC_ABSTRACT | ACC_SUPER)) == 0) {
|
||||
cn.methods.add(new MethodNode(ACC_PRIVATE, CONSTRUCTOR,
|
||||
"(" + ClassUtil.toByteCodeClassName(TYPE_NULL) + ")V", null, null));
|
||||
if ((cn.access & ACC_INTERFACE) != 0 || cn.superName == null) {
|
||||
return;
|
||||
}
|
||||
if (cn.superName.equals(CLASS_OBJECT)) {
|
||||
// JUnit require test class contains only one constructor
|
||||
if (isJUnitTestClass(cn)) {
|
||||
return;
|
||||
}
|
||||
MethodNode constructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR,
|
||||
METHOD_START + ClassUtil.toByteCodeClassName(NULL_TYPE) + VOID_METHOD_END, null, null);
|
||||
LabelNode start = new LabelNode(new Label());
|
||||
LabelNode end = new LabelNode(new Label());
|
||||
constructor.instructions = invokeSuperWithoutParameter(start, end);
|
||||
constructor.localVariables = createLocalVariables(cn, start, end);
|
||||
constructor.maxStack = 1;
|
||||
constructor.maxLocals = 2;
|
||||
cn.methods.add(constructor);
|
||||
} else {
|
||||
MethodNode constructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR,
|
||||
METHOD_START + ClassUtil.toByteCodeClassName(NULL_TYPE) + VOID_METHOD_END, null, null);
|
||||
LabelNode start = new LabelNode(new Label());
|
||||
LabelNode end = new LabelNode(new Label());
|
||||
constructor.instructions = invokeSuperWithTestableNullParameter(cn, start, end);
|
||||
constructor.localVariables = createLocalVariables(cn, start, end);
|
||||
constructor.maxStack = 3;
|
||||
constructor.maxLocals = 2;
|
||||
cn.methods.add(constructor);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isJUnitTestClass(ClassNode cn) {
|
||||
for (MethodNode mn : cn.methods) {
|
||||
if (mn.visibleAnnotations == null) {
|
||||
continue;
|
||||
}
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
for (String annotation : JUNIT_TEST_ANNOTATIONS) {
|
||||
if (an.desc.equals(annotation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private InsnList invokeSuperWithoutParameter(LabelNode start, LabelNode end) {
|
||||
InsnList il = new InsnList();
|
||||
il.add(start);
|
||||
il.add(new VarInsnNode(ALOAD, 0));
|
||||
il.add(new MethodInsnNode(INVOKESPECIAL, CLASS_OBJECT, CONSTRUCTOR, VOID_METHOD, false));
|
||||
il.add(new InsnNode(RETURN));
|
||||
il.add(end);
|
||||
return il;
|
||||
}
|
||||
|
||||
private InsnList invokeSuperWithTestableNullParameter(ClassNode cn, LabelNode start, LabelNode end) {
|
||||
InsnList il = new InsnList();
|
||||
il.add(start);
|
||||
il.add(new VarInsnNode(ALOAD, 0));
|
||||
il.add(new TypeInsnNode(NEW, ClassUtil.toSlashSeparatedName(TESTABLE_NULL_TYPE)));
|
||||
il.add(new InsnNode(DUP));
|
||||
il.add(new MethodInsnNode(INVOKESPECIAL, ClassUtil.toSlashSeparatedName(TESTABLE_NULL_TYPE), CONSTRUCTOR,
|
||||
VOID_METHOD, false));
|
||||
il.add(new MethodInsnNode(INVOKESPECIAL, cn.superName, CONSTRUCTOR,
|
||||
METHOD_START + ClassUtil.toByteCodeClassName(NULL_TYPE) + VOID_METHOD_END, false));
|
||||
il.add(new InsnNode(RETURN));
|
||||
il.add(end);
|
||||
return il;
|
||||
}
|
||||
|
||||
private List<LocalVariableNode> createLocalVariables(ClassNode cn, LabelNode start, LabelNode end) {
|
||||
return CollectionUtil.listOf(
|
||||
new LocalVariableNode(THIS_REF, ClassUtil.toByteCodeClassName(cn.name), null, start, end, 0),
|
||||
new LocalVariableNode(IGNORE, ClassUtil.toByteCodeClassName(NULL_TYPE), null, start, end, 1)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.alibaba.testable.core.constant.ConstPool.THIS_REF;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
@ -20,7 +22,6 @@ public class TestClassHandler extends BaseClassWithContextHandler {
|
||||
private static final String DESC_METHOD_INIT = "()V";
|
||||
private static final String METHOD_CLEAN = "clean";
|
||||
private static final String DESC_METHOD_CLEAN = "()V";
|
||||
private static final String THIS = "this";
|
||||
|
||||
private final String mockClassName;
|
||||
private int testCaseCount = 0;
|
||||
@ -90,8 +91,8 @@ public class TestClassHandler extends BaseClassWithContextHandler {
|
||||
il.add(new InsnNode(RETURN));
|
||||
il.add(endLabel);
|
||||
afterTestMethod.instructions = il;
|
||||
afterTestMethod.localVariables = Collections.singletonList(
|
||||
new LocalVariableNode(THIS, ClassUtil.toByteCodeClassName(cn.name), null, startLabel, endLabel, 0));
|
||||
afterTestMethod.localVariables = Collections.singletonList(new LocalVariableNode(THIS_REF,
|
||||
ClassUtil.toByteCodeClassName(cn.name), null, startLabel, endLabel, 0));
|
||||
afterTestMethod.maxLocals = 1;
|
||||
afterTestMethod.maxStack = 0;
|
||||
cn.methods.add(afterTestMethod);
|
||||
|
@ -5,7 +5,7 @@ import java.util.List;
|
||||
|
||||
public class JUnit4Framework extends Framework {
|
||||
|
||||
private static final String ANNOTATION_TEST = "Lorg/junit/Test;";
|
||||
public static final String ANNOTATION_TEST = "Lorg/junit/Test;";
|
||||
private static final String ANNOTATION_AFTER_TEST = "Lorg/junit/After;";
|
||||
|
||||
@Override
|
||||
|
@ -5,8 +5,8 @@ import java.util.List;
|
||||
|
||||
public class JUnit5Framework extends Framework {
|
||||
|
||||
private static final String ANNOTATION_TEST = "Lorg/junit/jupiter/api/Test;";
|
||||
private static final String ANNOTATION_PARAMETERIZED_TEST = "Lorg/junit/jupiter/params/ParameterizedTest;";
|
||||
public static final String ANNOTATION_TEST = "Lorg/junit/jupiter/api/Test;";
|
||||
public static final String ANNOTATION_PARAMETERIZED_TEST = "Lorg/junit/jupiter/params/ParameterizedTest;";
|
||||
private static final String ANNOTATION_AFTER_TEST = "Lorg/junit/jupiter/api/AfterEach;";
|
||||
|
||||
@Override
|
||||
|
@ -6,6 +6,7 @@ public class ConstPool {
|
||||
* Name of the constructor method
|
||||
*/
|
||||
public static final String CONSTRUCTOR = "<init>";
|
||||
public static final String THIS_REF = "this";
|
||||
|
||||
/**
|
||||
* Postfix or test and mock file
|
||||
|
@ -1,7 +0,0 @@
|
||||
package com.alibaba.testable.core.model;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class Null {
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.alibaba.testable.core.model;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.type.NullType;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeVisitor;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class TestableNull implements NullType {
|
||||
|
||||
@Override
|
||||
public TypeKind getKind() {
|
||||
return TypeKind.NULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, P> R accept(TypeVisitor<R, P> v, P p) {
|
||||
return v.visitNull(this, p);
|
||||
}
|
||||
|
||||
public List<? extends AnnotationMirror> getAnnotationMirrors() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationType) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
package com.alibaba.testable.core.tool;
|
||||
|
||||
import com.alibaba.testable.core.exception.ClassConstructionException;
|
||||
import com.alibaba.testable.core.model.Null;
|
||||
import com.alibaba.testable.core.model.TestableNull;
|
||||
import com.alibaba.testable.core.util.TypeUtil;
|
||||
|
||||
import javax.lang.model.type.NullType;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
|
||||
@ -173,8 +174,8 @@ public class OmniConstructor {
|
||||
throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
constructor.setAccessible(true);
|
||||
Class<?>[] types = constructor.getParameterTypes();
|
||||
if (types.length == 1 && types[0].equals(Null.class)) {
|
||||
return constructor.newInstance(new Null());
|
||||
if (types.length == 1 && types[0].equals(NullType.class)) {
|
||||
return constructor.newInstance(new TestableNull());
|
||||
} else {
|
||||
Object[] args = new Object[types.length];
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
@ -189,7 +190,7 @@ public class OmniConstructor {
|
||||
int minimalParametersSize = 999;
|
||||
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
|
||||
Class<?>[] types = constructor.getParameterTypes();
|
||||
if (types.length == 1 && types[0].equals(Null.class)) {
|
||||
if (types.length == 1 && types[0].equals(NullType.class)) {
|
||||
return constructor;
|
||||
} else if (types.length < minimalParametersSize) {
|
||||
minimalParametersSize = types.length;
|
||||
|
Loading…
Reference in New Issue
Block a user