create omni constructor correctly

This commit is contained in:
金戟 2021-03-24 00:34:17 +08:00
parent 9b56e3d64a
commit 9871a96db0
10 changed files with 158 additions and 23 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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)
);
}
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
package com.alibaba.testable.core.model;
/**
* @author flin
*/
public class Null {
}

View File

@ -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;
}
}

View File

@ -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;