From 281be6bd512e6f9fad50391d2174cbf7a340e1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Sat, 26 Dec 2020 14:27:08 +0800 Subject: [PATCH] let mock method be static --- .../testable/agent/constant/ConstPool.java | 1 - .../agent/handler/SourceClassHandler.java | 10 +- .../agent/handler/TestClassHandler.java | 93 ++++++++----------- .../testable/core/util/TestableUtil.java | 12 --- 4 files changed, 40 insertions(+), 76 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 51859cb..1ac673e 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 @@ -9,7 +9,6 @@ public class ConstPool { public static final String SLASH = "/"; public static final String TEST_POSTFIX = "Test"; - public static final String TESTABLE_INJECT_REF = "_testableInternalRef"; public static final String FIELD_TARGET_METHOD = "targetMethod"; diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java index 8702da4..cbe231a 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java @@ -178,9 +178,7 @@ public class SourceClassHandler extends BaseClassHandler { String classType = ((TypeInsnNode)instructions[start]).desc; String constructorDesc = ((MethodInsnNode)instructions[end]).desc; String testClassName = ClassUtil.getTestClassName(cn.name); - mn.instructions.insertBefore(instructions[start], new FieldInsnNode(GETSTATIC, testClassName, - ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(testClassName))); - mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName, + mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKESTATIC, testClassName, newOperatorInjectMethodName, getConstructorInjectDesc(constructorDesc, classType), false)); mn.instructions.remove(instructions[start]); mn.instructions.remove(instructions[start + 1]); @@ -209,8 +207,6 @@ public class SourceClassHandler extends BaseClassHandler { mn.maxStack++; MethodInsnNode method = (MethodInsnNode)instructions[end]; String testClassName = ClassUtil.getTestClassName(cn.name); - mn.instructions.insertBefore(instructions[start], new FieldInsnNode(GETSTATIC, testClassName, - ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(testClassName))); if (Opcodes.INVOKESTATIC == opcode || isCompanionMethod(ownerClass, opcode)) { // append a null value if it was a static invoke or in kotlin companion class mn.instructions.insertBefore(instructions[start], new InsnNode(ACONST_NULL)); @@ -219,8 +215,8 @@ public class SourceClassHandler extends BaseClassHandler { mn.instructions.remove(instructions[end - 1]); } } - // method with @MockMethod will be modified as public access, so INVOKEVIRTUAL is used - mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName, + // method with @MockMethod will be modified as public static access, so INVOKESTATIC is used + mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKESTATIC, testClassName, substitutionMethod, addFirstParameter(method.desc, ClassUtil.fitCompanionClassName(ownerClass)), false)); mn.instructions.remove(instructions[end]); return mn.instructions.toArray(); 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 fce19cd..5a5fc02 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 @@ -7,7 +7,6 @@ import com.alibaba.testable.agent.util.ClassUtil; import com.alibaba.testable.core.util.LogUtil; import org.objectweb.asm.tree.*; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -21,15 +20,14 @@ public class TestClassHandler 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_INVOKE_RECORD_UTIL = "com/alibaba/testable/core/util/InvokeRecordUtil"; + private static final String TESTABLE_MARK_FIELD = "__testable"; private static final String FIELD_TEST_CASE = "TEST_CASE"; private static final String FIELD_SOURCE_METHOD = "SOURCE_METHOD"; private static final String METHOD_CURRENT_TEST_CASE_NAME = "currentTestCaseName"; private static final String METHOD_CURRENT_SOURCE_METHOD_NAME = "currentSourceMethodName"; - private static final String METHOD_MARK_TEST_CASE_BEGIN = "markTestCaseBegin"; private static final String METHOD_RECORD_MOCK_INVOKE = "recordMockInvoke"; private static final String SIGNATURE_CURRENT_TEST_CASE_NAME = "(Ljava/lang/Object;)Ljava/lang/String;"; private static final String SIGNATURE_CURRENT_SOURCE_METHOD_NAME = "()Ljava/lang/String;"; - private static final String SIGNATURE_VOID_METHOD_WITHOUT_PARAMETER = "()V"; private static final String SIGNATURE_INVOKE_RECORDER_METHOD = "([Ljava/lang/Object;Z)V"; /** @@ -38,47 +36,52 @@ public class TestClassHandler extends BaseClassHandler { */ @Override protected void transform(ClassNode cn) { - Iterator iterator = cn.fields.iterator(); - if (iterator.hasNext()) { - if (ConstPool.TESTABLE_INJECT_REF.equals(iterator.next().name)) { - // avoid duplicate injection - LogUtil.verbose("Duplicate injection found, ignore " + cn.name); - return; - } - } - cn.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, ConstPool.TESTABLE_INJECT_REF, - ClassUtil.toByteCodeClassName(cn.name), null, null)); - for (MethodNode m : cn.methods) { - transformMethod(cn, m); - } - } - - private void transformMethod(ClassNode cn, MethodNode mn) { - handleAnnotation(cn, mn); - handleInstruction(mn); - } - - private void handleAnnotation(ClassNode cn, MethodNode mn) { - List visibleAnnotationNames = new ArrayList(); - if (mn.visibleAnnotations == null) { - // let's assume test case should has a annotation, e.g. @Test or whatever + if (wasTransformed(cn)) { return; } - for (AnnotationNode n : mn.visibleAnnotations) { - visibleAnnotationNames.add(n.desc); + for (MethodNode mn : cn.methods) { + handleMockMethod(mn); + handleInstruction(mn); } - if (visibleAnnotationNames.contains(ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD)) || - visibleAnnotationNames.contains(ClassUtil.toByteCodeClassName(ConstPool.TESTABLE_MOCK)) || - visibleAnnotationNames.contains(ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR))) { + } + + private boolean wasTransformed(ClassNode cn) { + Iterator iterator = cn.fields.iterator(); + if (iterator.hasNext()) { + if (TESTABLE_MARK_FIELD.equals(iterator.next().name)) { + // avoid duplicate injection + LogUtil.verbose("Duplicate injection found, ignore " + cn.name); + return true; + } + } + cn.fields.add(new FieldNode(ACC_PRIVATE, TESTABLE_MARK_FIELD, "I", null, null)); + return false; + } + + private void handleMockMethod(MethodNode mn) { + if (isMockMethod(mn)) { mn.access &= ~ACC_PRIVATE; mn.access &= ~ACC_PROTECTED; mn.access |= ACC_PUBLIC; + mn.access |= ACC_STATIC; injectInvokeRecorder(mn); - } else if (couldBeTestMethod(mn)) { - injectTestableRef(cn, mn); } } + private boolean isMockMethod(MethodNode mn) { + if (mn.visibleAnnotations == null) { + return false; + } + for (AnnotationNode an : mn.visibleAnnotations) { + if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc) || + ClassUtil.toByteCodeClassName(ConstPool.TESTABLE_MOCK).equals(an.desc) || + ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc)) { + return true; + } + } + return false; + } + private void handleInstruction(MethodNode mn) { AbstractInsnNode[] instructions = mn.instructions.toArray(); int i = 0; @@ -199,26 +202,4 @@ public class TestClassHandler extends BaseClassHandler { } } - private void injectTestableRef(ClassNode cn, MethodNode mn) { - InsnList il = new InsnList(); - // Initialize "_testableInternalRef" - il.add(new VarInsnNode(ALOAD, 0)); - il.add(new FieldInsnNode(PUTSTATIC, cn.name, ConstPool.TESTABLE_INJECT_REF, - ClassUtil.toByteCodeClassName(cn.name))); - // Invoke "markTestCaseBegin" - il.add(new MethodInsnNode(INVOKESTATIC, CLASS_TESTABLE_UTIL, METHOD_MARK_TEST_CASE_BEGIN, - SIGNATURE_VOID_METHOD_WITHOUT_PARAMETER, false)); - mn.instructions.insertBefore(mn.instructions.getFirst(), il); - } - - /** - * Different unit test framework may have different @Test annotation - * but they should always NOT private, protected or static - * and has neither parameter nor return value - */ - private boolean couldBeTestMethod(MethodNode mn) { - return (mn.access & (ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC)) == 0 && - mn.desc.equals(SIGNATURE_VOID_METHOD_WITHOUT_PARAMETER); - } - } diff --git a/testable-core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java b/testable-core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java index cf60528..213d318 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java @@ -15,10 +15,6 @@ public class TestableUtil { * [0]Thread.getStackTrace() → [1]previousStackLocation() → [2]Invoker -> [3]Caller of invoker */ private static final int INDEX_OF_CALLER_METHOD = 3; - /** - * [0]Thread.getStackTrace() → [1]markTestCaseBegin() → [2]TestCaseMethod - */ - private static final int INDEX_OF_TEST_CASE_METHOD = 2; /** * Just a special number to identify test worker thread */ @@ -82,14 +78,6 @@ public class TestableUtil { return stack.getFileName() + ":" + stack.getLineNumber(); } - /** - * Do prepare when probable test case start - */ - public static void markTestCaseBegin() { - String testCaseName = Thread.currentThread().getStackTrace()[INDEX_OF_TEST_CASE_METHOD].getMethodName(); - LogUtil.verbose("Start testcase: " + testCaseName); - } - private static Thread findTestWorkerThread(Set threads) { for (Thread t : threads) { if (t.getPriority() == TEST_WORKER_THREAD_PRIORITY) {