remove mock context after test

This commit is contained in:
金戟 2021-02-14 13:22:28 +08:00
parent 8814276d63
commit 5ef06c4bde
10 changed files with 238 additions and 27 deletions

View File

@ -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));
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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));
}
/**

View File

@ -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);
}
}

View File

@ -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;
}
}