mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-24 19:31:17 +08:00
remove mock context after test
This commit is contained in:
parent
8814276d63
commit
5ef06c4bde
@ -1,10 +1,15 @@
|
||||
package com.alibaba.testable.agent.handler;
|
||||
|
||||
import com.alibaba.testable.agent.handler.test.*;
|
||||
import com.alibaba.testable.agent.model.TestCaseMethodType;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
import com.alibaba.testable.core.util.LogUtil;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.tree.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
@ -14,14 +19,16 @@ 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 = "(Ljava/lang/String;Ljava/lang/String;)V";
|
||||
private final List<String> testAnnotations = Arrays.asList(
|
||||
// JUnit 4
|
||||
"org.junit.Test",
|
||||
// JUnit 5
|
||||
"org.junit.jupiter.api.Test",
|
||||
// TestNG
|
||||
"org.testng.annotations.Test"
|
||||
);
|
||||
private static final String METHOD_CLEAN = "clean";
|
||||
private static final String DESC_METHOD_CLEAN = "()V";
|
||||
private static final String THIS = "this";
|
||||
|
||||
private final Framework[] frameworkClasses = new Framework[] {
|
||||
new JUnit4Framework(),
|
||||
new JUnit5Framework(),
|
||||
new TestNgFramework(),
|
||||
new TestNgOnClassFramework()
|
||||
};
|
||||
|
||||
public TestClassHandler(String mockClassName) {
|
||||
this.mockClassName = mockClassName;
|
||||
@ -33,21 +40,67 @@ public class TestClassHandler extends BaseClassWithContextHandler {
|
||||
*/
|
||||
@Override
|
||||
protected void transform(ClassNode cn) {
|
||||
Framework framework = checkFramework(cn);
|
||||
if (framework == null) {
|
||||
LogUtil.warn("Failed to detect test framework for " + cn.name);
|
||||
return;
|
||||
}
|
||||
if (!framework.hasTestAfterMethod) {
|
||||
addTestAfterMethod(cn, framework.getTestAfterAnnotation());
|
||||
}
|
||||
for (MethodNode mn : cn.methods) {
|
||||
handleTestableUtil(mn);
|
||||
handleTestCaseMethod(cn, mn);
|
||||
handleTestCaseMethod(cn, mn, framework);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTestCaseMethod(ClassNode cn, MethodNode mn) {
|
||||
if (mn.visibleAnnotations == null) {
|
||||
return;
|
||||
}
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (testAnnotations.contains(ClassUtil.toDotSeparateFullClassName(an.desc))) {
|
||||
injectMockContextInit(cn.name, mn);
|
||||
private Framework checkFramework(ClassNode cn) {
|
||||
Set<String> classAnnotationSet = new HashSet<String>();
|
||||
Set<String> methodAnnotationSet = new HashSet<String>();
|
||||
if (cn.visibleAnnotations != null) {
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
classAnnotationSet.add(an.desc);
|
||||
}
|
||||
}
|
||||
for (MethodNode mn : cn.methods) {
|
||||
if (mn.visibleAnnotations != null) {
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
methodAnnotationSet.add(an.desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Framework i : frameworkClasses) {
|
||||
if (i.fit(classAnnotationSet, methodAnnotationSet)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addTestAfterMethod(ClassNode cn, String testAfterAnnotation) {
|
||||
MethodNode afterTestMethod = new MethodNode(ACC_PUBLIC, "testableAfterTestCase", "()V", null, null);
|
||||
afterTestMethod.visibleAnnotations = Collections.singletonList(new AnnotationNode(testAfterAnnotation));
|
||||
InsnList il = new InsnList();
|
||||
LabelNode startLabel = new LabelNode(new Label());
|
||||
LabelNode endLabel = new LabelNode(new Label());
|
||||
il.add(startLabel);
|
||||
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.maxLocals = 1;
|
||||
afterTestMethod.maxStack = 0;
|
||||
cn.methods.add(afterTestMethod);
|
||||
}
|
||||
|
||||
private void handleTestCaseMethod(ClassNode cn, MethodNode mn, Framework framework) {
|
||||
TestCaseMethodType type = framework.checkMethodType(mn);
|
||||
if (type.equals(TestCaseMethodType.TEST)) {
|
||||
injectMockContextInit(cn.name, mn);
|
||||
} else if (type.equals(TestCaseMethodType.AFTER_TEST)) {
|
||||
injectMockContextClean(mn);
|
||||
}
|
||||
}
|
||||
|
||||
private void injectMockContextInit(String testClassName, MethodNode mn) {
|
||||
@ -58,4 +111,9 @@ public class TestClassHandler extends BaseClassWithContextHandler {
|
||||
mn.instructions.insertBefore(mn.instructions.getFirst(), il);
|
||||
}
|
||||
|
||||
private void injectMockContextClean(MethodNode mn) {
|
||||
mn.instructions.insertBefore(mn.instructions.getFirst(), new MethodInsnNode(INVOKESTATIC,
|
||||
CLASS_MOCK_CONTEXT_UTIL, METHOD_CLEAN, DESC_METHOD_CLEAN, false));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
package com.alibaba.testable.agent.handler.test;
|
||||
|
||||
|
||||
import com.alibaba.testable.agent.model.TestCaseMethodType;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
abstract public class Framework {
|
||||
|
||||
public boolean hasTestAfterMethod;
|
||||
|
||||
/**
|
||||
* Check whether the test class using current test framework
|
||||
* @param classAnnotations annotations of the class
|
||||
* @param methodAnnotations annotations of all methods
|
||||
* @return fit or not
|
||||
*/
|
||||
public boolean fit(Set<String> classAnnotations, Set<String> methodAnnotations) {
|
||||
if (methodAnnotations.contains(getTestAfterAnnotation())) {
|
||||
hasTestAfterMethod = true;
|
||||
return true;
|
||||
} else {
|
||||
return methodAnnotations.contains(getTestAnnotation());
|
||||
}
|
||||
}
|
||||
|
||||
public TestCaseMethodType checkMethodType(MethodNode mn) {
|
||||
if (mn.visibleAnnotations == null) {
|
||||
return TestCaseMethodType.OTHERS;
|
||||
}
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (an.desc.equals(getTestAnnotation())) {
|
||||
return TestCaseMethodType.TEST;
|
||||
} else if (an.desc.equals(getTestAfterAnnotation())) {
|
||||
return TestCaseMethodType.AFTER_TEST;
|
||||
}
|
||||
}
|
||||
return TestCaseMethodType.OTHERS;
|
||||
}
|
||||
|
||||
public abstract String getTestAnnotation();
|
||||
|
||||
public abstract String getTestAfterAnnotation();
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.alibaba.testable.agent.handler.test;
|
||||
|
||||
public class JUnit4Framework extends Framework {
|
||||
|
||||
private static final String ANNOTATION_TEST = "Lorg/junit/Test;";
|
||||
private static final String ANNOTATION_AFTER_TEST = "Lorg/junit/After;";
|
||||
|
||||
@Override
|
||||
public String getTestAnnotation() {
|
||||
return ANNOTATION_TEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTestAfterAnnotation() {
|
||||
return ANNOTATION_AFTER_TEST;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.alibaba.testable.agent.handler.test;
|
||||
|
||||
public class JUnit5Framework extends Framework {
|
||||
|
||||
private static final String ANNOTATION_TEST = "Lorg/junit/jupiter/api/Test;";
|
||||
private static final String ANNOTATION_AFTER_TEST = "Lorg/junit/jupiter/api/AfterEach;";
|
||||
|
||||
@Override
|
||||
public String getTestAnnotation() {
|
||||
return ANNOTATION_TEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTestAfterAnnotation() {
|
||||
return ANNOTATION_AFTER_TEST;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.alibaba.testable.agent.handler.test;
|
||||
|
||||
public class TestNgFramework extends Framework {
|
||||
|
||||
private static final String ANNOTATION_TEST = "Lorg/testng/annotations/Test;";
|
||||
private static final String ANNOTATION_AFTER_TEST = "Lorg/testng/annotations/AfterMethod;";
|
||||
|
||||
@Override
|
||||
public String getTestAnnotation() {
|
||||
return ANNOTATION_TEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTestAfterAnnotation() {
|
||||
return ANNOTATION_AFTER_TEST;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.alibaba.testable.agent.handler.test;
|
||||
|
||||
import com.alibaba.testable.agent.model.TestCaseMethodType;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
||||
|
||||
public class TestNgOnClassFramework extends TestNgFramework {
|
||||
|
||||
@Override
|
||||
public boolean fit(Set<String> classAnnotations, Set<String> methodAnnotations) {
|
||||
if (classAnnotations.contains(getTestAnnotation())) {
|
||||
if (methodAnnotations.contains(getTestAfterAnnotation())) {
|
||||
hasTestAfterMethod = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestCaseMethodType checkMethodType(MethodNode mn) {
|
||||
if (mn.visibleAnnotations == null) {
|
||||
return (mn.access & ACC_PUBLIC) != 0 ? TestCaseMethodType.TEST : TestCaseMethodType.OTHERS;
|
||||
}
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (an.desc.equals(getTestAfterAnnotation())) {
|
||||
return TestCaseMethodType.AFTER_TEST;
|
||||
}
|
||||
}
|
||||
return (mn.access & ACC_PUBLIC) != 0 ? TestCaseMethodType.TEST : TestCaseMethodType.OTHERS;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.alibaba.testable.agent.model;
|
||||
|
||||
public enum TestCaseMethodType {
|
||||
|
||||
/**
|
||||
* Test case method
|
||||
*/
|
||||
TEST,
|
||||
|
||||
/**
|
||||
* After each test case method
|
||||
*/
|
||||
AFTER_TEST,
|
||||
|
||||
/**
|
||||
* None of above
|
||||
*/
|
||||
OTHERS
|
||||
|
||||
}
|
@ -26,7 +26,7 @@ public class InvokeVerifier {
|
||||
* @return the verifier object
|
||||
*/
|
||||
public static InvokeVerifier verify(String mockMethodName) {
|
||||
return new InvokeVerifier(MockContextUtil.invokeRecord().get(mockMethodName));
|
||||
return new InvokeVerifier(MockContextUtil.context.get().invokeRecord.get(mockMethodName));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.alibaba.testable.core.util;
|
||||
|
||||
import com.alibaba.testable.core.model.MockContext;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
@ -19,13 +21,14 @@ public class InvokeRecordUtil {
|
||||
public static void recordMockInvoke(Object[] args, boolean isConstructor, boolean isTargetClassInParameter) {
|
||||
StackTraceElement mockMethodTraceElement = Thread.currentThread().getStackTrace()[INDEX_OF_TEST_CLASS];
|
||||
String mockMethodName = mockMethodTraceElement.getMethodName();
|
||||
String testClass = MockContextUtil.context.get().testClassName;
|
||||
String testCaseName = MockContextUtil.context.get().testCaseName;
|
||||
MockContext mockContext = MockContextUtil.context.get();
|
||||
String testClass = mockContext.testClassName;
|
||||
String testCaseName = mockContext.testCaseName;
|
||||
if (isConstructor) {
|
||||
MockContextUtil.invokeRecord().get(mockMethodName).add(args);
|
||||
mockContext.invokeRecord.get(mockMethodName).add(args);
|
||||
LogUtil.verbose(" Mock constructor \"%s\" invoked in %s::%s", mockMethodName, testClass, testCaseName);
|
||||
} else {
|
||||
MockContextUtil.invokeRecord().get(mockMethodName).add(isTargetClassInParameter ? slice(args, 1) : args);
|
||||
mockContext.invokeRecord.get(mockMethodName).add(isTargetClassInParameter ? slice(args, 1) : args);
|
||||
LogUtil.verbose(" Mock method \"%s\" invoked in %s::%s\"", mockMethodName, testClass, testCaseName);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package com.alibaba.testable.core.util;
|
||||
import com.alibaba.testable.core.model.MockContext;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MockContextUtil {
|
||||
@ -28,7 +27,4 @@ public class MockContextUtil {
|
||||
return MockContextUtil.context.get().parameters;
|
||||
}
|
||||
|
||||
public static Map<String, List<Object[]>> invokeRecord() {
|
||||
return MockContextUtil.context.get().invokeRecord;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user