mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-02-10 11:51:09 +08:00
let mock method be static
This commit is contained in:
parent
a8b58ffe17
commit
281be6bd51
@ -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";
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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<FieldNode> 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<String> visibleAnnotationNames = new ArrayList<String>();
|
||||
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<FieldNode> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Thread> threads) {
|
||||
for (Thread t : threads) {
|
||||
if (t.getPriority() == TEST_WORKER_THREAD_PRIORITY) {
|
||||
|
Loading…
Reference in New Issue
Block a user