From 4c6fc5b22855117742fb5c0791c5f10112bcf394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Wed, 17 Feb 2021 18:48:43 +0800 Subject: [PATCH] add association check at beginning of mock method --- .../testable/agent/constant/ConstPool.java | 1 + .../handler/BaseClassWithContextHandler.java | 7 +- .../agent/handler/MockClassHandler.java | 87 ++++++++++++++----- .../agent/handler/TestClassHandler.java | 1 - 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java b/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java index 6e7b782..e496409 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java @@ -12,6 +12,7 @@ public class ConstPool { public static final String FIELD_TARGET_METHOD = "targetMethod"; public static final String FIELD_TARGET_CLASS = "targetClass"; + public static final String FIELD_SCOPE = "scope"; public static final String MOCK_WITH = "com.alibaba.testable.core.annotation.MockWith"; public static final String MOCK_DIAGNOSE = "com.alibaba.testable.core.annotation.MockDiagnose"; diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/BaseClassWithContextHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/BaseClassWithContextHandler.java index 5dfcf57..f17ee54 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/BaseClassWithContextHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/BaseClassWithContextHandler.java @@ -7,9 +7,10 @@ import org.objectweb.asm.tree.*; */ abstract public class BaseClassWithContextHandler extends BaseClassHandler { - private static final String CLASS_TESTABLE_TOOL = "com/alibaba/testable/core/tool/TestableTool"; - private static final String CLASS_TESTABLE_UTIL = "com/alibaba/testable/core/util/TestableUtil"; - private static final String CLASS_MOCK_CONTEXT_UTIL = "com/alibaba/testable/core/util/MockContextUtil"; + protected static final String CLASS_TESTABLE_TOOL = "com/alibaba/testable/core/tool/TestableTool"; + protected static final String CLASS_TESTABLE_UTIL = "com/alibaba/testable/core/util/TestableUtil"; + protected static final String CLASS_MOCK_CONTEXT_UTIL = "com/alibaba/testable/core/util/MockContextUtil"; + protected static final String CLASS_INVOKE_RECORD_UTIL = "com/alibaba/testable/core/util/InvokeRecordUtil"; private static final String FIELD_SOURCE_METHOD = "SOURCE_METHOD"; private static final String FIELD_MOCK_CONTEXT = "MOCK_CONTEXT"; private static final String METHOD_PARAMETERS = "parameters"; diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java index 20319a7..b568f8c 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java @@ -4,10 +4,11 @@ import com.alibaba.testable.agent.constant.ConstPool; import com.alibaba.testable.agent.tool.ImmutablePair; import com.alibaba.testable.agent.util.AnnotationUtil; import com.alibaba.testable.agent.util.ClassUtil; +import com.alibaba.testable.core.model.MockScope; +import org.objectweb.asm.Label; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import javax.lang.model.type.NullType; import java.util.List; import static com.alibaba.testable.agent.constant.ConstPool.CONSTRUCTOR; @@ -18,9 +19,10 @@ import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassNa */ public class MockClassHandler extends BaseClassWithContextHandler { - private static final String CLASS_INVOKE_RECORD_UTIL = "com/alibaba/testable/core/util/InvokeRecordUtil"; private static final String METHOD_RECORD_MOCK_INVOKE = "recordMockInvoke"; private static final String SIGNATURE_INVOKE_RECORDER_METHOD = "([Ljava/lang/Object;ZZ)V"; + private static final String METHOD_IS_ASSOCIATED = "isAssociated"; + private static final String SIGNATURE_IS_ASSOCIATED = "()Z"; public MockClassHandler(String className) { this.mockClassName = className; @@ -28,7 +30,7 @@ public class MockClassHandler extends BaseClassWithContextHandler { @Override protected void transform(ClassNode cn) { - addGetInstanceMethod(cn); + injectGetInstanceMethod(cn); for (MethodNode mn : cn.methods) { if (isMockMethod(mn)) { mn.access &= ~ACC_PRIVATE; @@ -36,11 +38,36 @@ public class MockClassHandler extends BaseClassWithContextHandler { mn.access |= ACC_PUBLIC; unfoldTargetClass(mn); injectInvokeRecorder(mn); + injectAssociationChecker(mn); handleTestableUtil(mn); } } } + /** + * add method to fetch singleton instance of this mock class + */ + private void injectGetInstanceMethod(ClassNode cn) { + MethodNode getInstanceMethod = new MethodNode(ACC_PUBLIC | ACC_STATIC, GET_TESTABLE_REF, + VOID_ARGS + ClassUtil.toByteCodeClassName(mockClassName), null, null); + InsnList il = new InsnList(); + il.add(new FieldInsnNode(GETSTATIC, mockClassName, TESTABLE_REF, ClassUtil.toByteCodeClassName(mockClassName))); + LabelNode label = new LabelNode(); + il.add(new JumpInsnNode(IFNONNULL, label)); + il.add(new TypeInsnNode(NEW, mockClassName)); + il.add(new InsnNode(DUP)); + il.add(new MethodInsnNode(INVOKESPECIAL, mockClassName, CONSTRUCTOR, VOID_ARGS + VOID_RES, false)); + il.add(new FieldInsnNode(PUTSTATIC, mockClassName, TESTABLE_REF, ClassUtil.toByteCodeClassName(mockClassName))); + il.add(label); + il.add(new FrameNode(F_SAME, 0, null, 0, null)); + il.add(new FieldInsnNode(GETSTATIC, mockClassName, TESTABLE_REF, ClassUtil.toByteCodeClassName(mockClassName))); + il.add(new InsnNode(ARETURN)); + getInstanceMethod.instructions = il; + getInstanceMethod.maxStack = 2; + getInstanceMethod.maxLocals = 0; + cn.methods.add(getInstanceMethod); + } + /** * put targetClass parameter in @MockMethod to first parameter of the mock method */ @@ -74,25 +101,41 @@ public class MockClassHandler extends BaseClassWithContextHandler { } } - private void addGetInstanceMethod(ClassNode cn) { - MethodNode getInstanceMethod = new MethodNode(ACC_PUBLIC | ACC_STATIC, GET_TESTABLE_REF, - VOID_ARGS + ClassUtil.toByteCodeClassName(mockClassName), null, null); + private void injectAssociationChecker(MethodNode mn) { + if (isGlobalScope(mn)) { + return; + } + LabelNode firstLine = new LabelNode(new Label()); InsnList il = new InsnList(); - il.add(new FieldInsnNode(GETSTATIC, mockClassName, TESTABLE_REF, ClassUtil.toByteCodeClassName(mockClassName))); - LabelNode label = new LabelNode(); - il.add(new JumpInsnNode(IFNONNULL, label)); - il.add(new TypeInsnNode(NEW, mockClassName)); - il.add(new InsnNode(DUP)); - il.add(new MethodInsnNode(INVOKESPECIAL, mockClassName, CONSTRUCTOR, VOID_ARGS + VOID_RES, false)); - il.add(new FieldInsnNode(PUTSTATIC, mockClassName, TESTABLE_REF, ClassUtil.toByteCodeClassName(mockClassName))); - il.add(label); - il.add(new FrameNode(F_SAME, 0, null, 0, null)); - il.add(new FieldInsnNode(GETSTATIC, mockClassName, TESTABLE_REF, ClassUtil.toByteCodeClassName(mockClassName))); - il.add(new InsnNode(ARETURN)); - getInstanceMethod.instructions = il; - getInstanceMethod.maxStack = 2; - getInstanceMethod.maxLocals = 0; - cn.methods.add(getInstanceMethod); + il.add(new MethodInsnNode(INVOKESTATIC, CLASS_MOCK_CONTEXT_UTIL, METHOD_IS_ASSOCIATED, + SIGNATURE_IS_ASSOCIATED, false)); + il.add(new JumpInsnNode(IFNE, firstLine)); + il.add(firstLine); + il.add( new FrameNode(F_SAME, 0, null, 0, null)); + mn.instructions.insertBefore(mn.instructions.getFirst(), il); + } + + private boolean isGlobalScope(MethodNode mn) { + for (AnnotationNode an : mn.visibleAnnotations) { + if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc) || + ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc)) { + MockScope scope = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_SCOPE, + MockScope.ASSOCIATED, MockScope.class); + if (scope.equals(MockScope.GLOBAL)) { + return true; + } + } + } + return false; + } + + private LabelNode getFirstLabel(MethodNode mn) { + for (AbstractInsnNode n : mn.instructions) { + if (n instanceof LabelNode) { + return (LabelNode)n; + } + } + return null; } private boolean isMockMethod(MethodNode mn) { @@ -137,7 +180,7 @@ public class MockClassHandler extends BaseClassWithContextHandler { il.add(new InsnNode(ICONST_1)); il.add(new MethodInsnNode(INVOKESTATIC, CLASS_INVOKE_RECORD_UTIL, METHOD_RECORD_MOCK_INVOKE, SIGNATURE_INVOKE_RECORDER_METHOD, false)); - mn.instructions.insertBefore(mn.instructions.get(0), il); + mn.instructions.insertBefore(mn.instructions.getFirst(), il); } private boolean isMockForConstructor(MethodNode mn) { 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 90868d7..83be580 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 @@ -16,7 +16,6 @@ import java.util.Set; */ public class TestClassHandler extends BaseClassWithContextHandler { - private static final String CLASS_MOCK_CONTEXT_UTIL = "com/alibaba/testable/core/util/MockContextUtil"; private static final String METHOD_INIT = "init"; private static final String DESC_METHOD_INIT = "()V"; private static final String METHOD_CLEAN = "clean";