From 9871a96db021d24c21397872aad3d0a9bd9d0782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Wed, 24 Mar 2021 00:34:17 +0800 Subject: [PATCH] create omni constructor correctly --- .../alibaba/demo/basic/model/omni/Child.java | 7 ++ .../alibaba/demo/basic/model/omni/Parent.java | 8 ++ .../agent/handler/OmniClassHandler.java | 100 ++++++++++++++++-- .../agent/handler/TestClassHandler.java | 7 +- .../agent/handler/test/JUnit4Framework.java | 2 +- .../agent/handler/test/JUnit5Framework.java | 4 +- .../testable/core/constant/ConstPool.java | 1 + .../com/alibaba/testable/core/model/Null.java | 7 -- .../testable/core/model/TestableNull.java | 36 +++++++ .../testable/core/tool/OmniConstructor.java | 9 +- 10 files changed, 158 insertions(+), 23 deletions(-) delete mode 100644 testable-core/src/main/java/com/alibaba/testable/core/model/Null.java create mode 100644 testable-core/src/main/java/com/alibaba/testable/core/model/TestableNull.java diff --git a/demo/java-demo/src/main/java/com/alibaba/demo/basic/model/omni/Child.java b/demo/java-demo/src/main/java/com/alibaba/demo/basic/model/omni/Child.java index e8917fc..c1331e3 100644 --- a/demo/java-demo/src/main/java/com/alibaba/demo/basic/model/omni/Child.java +++ b/demo/java-demo/src/main/java/com/alibaba/demo/basic/model/omni/Child.java @@ -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; diff --git a/demo/java-demo/src/main/java/com/alibaba/demo/basic/model/omni/Parent.java b/demo/java-demo/src/main/java/com/alibaba/demo/basic/model/omni/Parent.java index fbf1da9..19cf555 100644 --- a/demo/java-demo/src/main/java/com/alibaba/demo/basic/model/omni/Parent.java +++ b/demo/java-demo/src/main/java/com/alibaba/demo/basic/model/omni/Parent.java @@ -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; diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java index fa12d68..97ed76e 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java @@ -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 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) + ); + } + } diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java index 5c4d242..ae7eab7 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/TestClassHandler.java @@ -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); diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/test/JUnit4Framework.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/test/JUnit4Framework.java index 762a47c..9bbfda2 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/test/JUnit4Framework.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/test/JUnit4Framework.java @@ -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 diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/test/JUnit5Framework.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/test/JUnit5Framework.java index 43909af..d6bff7e 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/test/JUnit5Framework.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/test/JUnit5Framework.java @@ -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 diff --git a/testable-core/src/main/java/com/alibaba/testable/core/constant/ConstPool.java b/testable-core/src/main/java/com/alibaba/testable/core/constant/ConstPool.java index 418f81f..b553c71 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/constant/ConstPool.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/constant/ConstPool.java @@ -6,6 +6,7 @@ public class ConstPool { * Name of the constructor method */ public static final String CONSTRUCTOR = ""; + public static final String THIS_REF = "this"; /** * Postfix or test and mock file diff --git a/testable-core/src/main/java/com/alibaba/testable/core/model/Null.java b/testable-core/src/main/java/com/alibaba/testable/core/model/Null.java deleted file mode 100644 index 136655c..0000000 --- a/testable-core/src/main/java/com/alibaba/testable/core/model/Null.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.alibaba.testable.core.model; - -/** - * @author flin - */ -public class Null { -} diff --git a/testable-core/src/main/java/com/alibaba/testable/core/model/TestableNull.java b/testable-core/src/main/java/com/alibaba/testable/core/model/TestableNull.java new file mode 100644 index 0000000..21390ac --- /dev/null +++ b/testable-core/src/main/java/com/alibaba/testable/core/model/TestableNull.java @@ -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 accept(TypeVisitor v, P p) { + return v.visitNull(this, p); + } + + public List getAnnotationMirrors() { + return null; + } + + public A getAnnotation(Class annotationType) { + return null; + } + + public A[] getAnnotationsByType(Class annotationType) { + return null; + } +} diff --git a/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java b/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java index f55e546..5e827cb 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java @@ -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;