add mock verifier support spock

This commit is contained in:
金戟 2021-04-03 13:41:51 +08:00
parent 46706ba56c
commit 58da25de53
9 changed files with 175 additions and 80 deletions

View File

@ -11,7 +11,7 @@
<font size="5">**0.6版本已发布**</font>,从`0.5.x`升级到`0.6.x`版本请参考[版本升级指南](https://alibaba.github.io/testable-mock/#/zh-cn/doc/upgrade-guide)
如果有遇到其他任何使用问题和建议,请直接在[Issue](https://github.com/alibaba/testable-mock/issues)中提出,也可通过[Pull Request](https://github.com/alibaba/testable-mock/pulls)提交您的代码我们将在24小时内回复并处理
如果有遇到其他任何使用问题和建议,请直接在[Issues](https://github.com/alibaba/testable-mock/issues)中提出,也可通过[Pull Request](https://github.com/alibaba/testable-mock/pulls)提交您的代码我们将在24小时内回复并处理
-----

View File

@ -2,17 +2,12 @@ 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.Collections;
import java.util.HashSet;
import java.util.Set;
import static com.alibaba.testable.core.constant.ConstPool.THIS_REF;
/**
* @author flin
*/
@ -24,12 +19,14 @@ public class TestClassHandler extends BaseClassWithContextHandler {
private static final String DESC_METHOD_CLEAN = "()V";
private int testCaseCount = 0;
private boolean shouldGenerateCleanupMethod = true;
private final Framework[] frameworkClasses = new Framework[] {
new JUnit4Framework(),
new JUnit5Framework(),
new TestNgFramework(),
new TestNgOnClassFramework()
new TestNgOnClassFramework(),
new SpockFramework()
};
/**
@ -43,13 +40,15 @@ public class TestClassHandler extends BaseClassWithContextHandler {
LogUtil.warn("Failed to detect test framework for %s", cn.name);
return;
}
if (!framework.hasTestAfterMethod) {
addTestAfterMethod(cn, framework.getTestAfterAnnotation());
}
for (MethodNode mn : cn.methods) {
handleTestableUtil(mn);
handleTestCaseMethod(mn, framework);
}
if (shouldGenerateCleanupMethod) {
MethodNode cleanupMethod = framework.getCleanupMethod(cn.name);
injectMockContextClean(cleanupMethod);
cn.methods.add(cleanupMethod);
}
LogUtil.diagnose(" Found %d test cases", testCaseCount);
}
@ -76,23 +75,6 @@ public class TestClassHandler extends BaseClassWithContextHandler {
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_REF,
ClassUtil.toByteCodeClassName(cn.name), null, startLabel, endLabel, 0));
afterTestMethod.maxLocals = 1;
afterTestMethod.maxStack = 0;
cn.methods.add(afterTestMethod);
}
private void handleTestCaseMethod(MethodNode mn, Framework framework) {
TestCaseMethodType type = framework.checkMethodType(mn);
if (type.equals(TestCaseMethodType.TEST)) {
@ -101,6 +83,7 @@ public class TestClassHandler extends BaseClassWithContextHandler {
testCaseCount++;
} else if (type.equals(TestCaseMethodType.AFTER_TEST)) {
injectMockContextClean(mn);
shouldGenerateCleanupMethod = false;
}
}

View File

@ -0,0 +1,74 @@
package com.alibaba.testable.agent.handler.test;
import com.alibaba.testable.agent.model.TestCaseMethodType;
import com.alibaba.testable.agent.util.ClassUtil;
import com.alibaba.testable.agent.util.CollectionUtil;
import org.objectweb.asm.Label;
import org.objectweb.asm.tree.*;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.alibaba.testable.core.constant.ConstPool.THIS_REF;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.RETURN;
/**
* @author flin
*/
abstract public class CommonFramework implements Framework {
private static final String DEFAULT_CLEANUP_METHOD = "testableCleanup";
@Override
public boolean fit(Set<String> classAnnotations, Set<String> methodAnnotations) {
return CollectionUtil.containsAny(methodAnnotations, getTestMethodAnnotations());
}
@Override
public TestCaseMethodType checkMethodType(MethodNode mn) {
if (mn.visibleAnnotations == null) {
return TestCaseMethodType.OTHERS;
}
for (AnnotationNode an : mn.visibleAnnotations) {
if (getTestMethodAnnotations().contains(an.desc)) {
return TestCaseMethodType.TEST;
} else if (an.desc.equals(getCleanupMethodAnnotation())) {
return TestCaseMethodType.AFTER_TEST;
}
}
return TestCaseMethodType.OTHERS;
}
@Override
public MethodNode getCleanupMethod(String className) {
MethodNode cleanupMethod = new MethodNode(ACC_PUBLIC, DEFAULT_CLEANUP_METHOD, "()V", null, null);
cleanupMethod.visibleAnnotations = Collections.singletonList(new AnnotationNode(getCleanupMethodAnnotation()));
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);
cleanupMethod.instructions = il;
cleanupMethod.localVariables = Collections.singletonList(new LocalVariableNode(THIS_REF,
ClassUtil.toByteCodeClassName(className), null, startLabel, endLabel, 0));
cleanupMethod.maxLocals = 1;
cleanupMethod.maxStack = 0;
return cleanupMethod;
}
/**
* Get all annotations that identify test case method
* @return list of annotation full name
*/
public abstract List<String> getTestMethodAnnotations();
/**
* Get annotation that identify test cleanup method
* @return full name of cleanup method annotation
*/
public abstract String getCleanupMethodAnnotation();
}

View File

@ -1,16 +1,14 @@
package com.alibaba.testable.agent.handler.test;
import com.alibaba.testable.agent.model.TestCaseMethodType;
import com.alibaba.testable.agent.util.CollectionUtil;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.List;
import java.util.Set;
abstract public class Framework {
public boolean hasTestAfterMethod;
/**
* @author flin
*/
public interface Framework {
/**
* Check whether the test class using current test framework
@ -18,31 +16,20 @@ abstract public class Framework {
* @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 CollectionUtil.containsAny(methodAnnotations, getTestAnnotations());
}
}
boolean fit(Set<String> classAnnotations, Set<String> methodAnnotations);
public TestCaseMethodType checkMethodType(MethodNode mn) {
if (mn.visibleAnnotations == null) {
return TestCaseMethodType.OTHERS;
}
for (AnnotationNode an : mn.visibleAnnotations) {
if (getTestAnnotations().contains(an.desc)) {
return TestCaseMethodType.TEST;
} else if (an.desc.equals(getTestAfterAnnotation())) {
return TestCaseMethodType.AFTER_TEST;
}
}
return TestCaseMethodType.OTHERS;
}
/**
* Check whether a method is test or cleanup method
* @param mn method node
* @return test method / cleanup method / other method
*/
TestCaseMethodType checkMethodType(MethodNode mn);
public abstract List<String> getTestAnnotations();
public abstract String getTestAfterAnnotation();
/**
* Generate cleanup method with correct name and annotations
* @param className full name of test class
* @return cleanup method for current framework
*/
MethodNode getCleanupMethod(String className);
}

View File

@ -3,18 +3,18 @@ package com.alibaba.testable.agent.handler.test;
import java.util.Collections;
import java.util.List;
public class JUnit4Framework extends Framework {
public class JUnit4Framework extends CommonFramework {
public static final String ANNOTATION_TEST = "Lorg/junit/Test;";
private static final String ANNOTATION_AFTER_TEST = "Lorg/junit/After;";
private static final String ANNOTATION_CLEANUP = "Lorg/junit/After;";
@Override
public List<String> getTestAnnotations() {
public List<String> getTestMethodAnnotations() {
return Collections.singletonList(ANNOTATION_TEST);
}
@Override
public String getTestAfterAnnotation() {
return ANNOTATION_AFTER_TEST;
public String getCleanupMethodAnnotation() {
return ANNOTATION_CLEANUP;
}
}

View File

@ -3,19 +3,19 @@ package com.alibaba.testable.agent.handler.test;
import java.util.Arrays;
import java.util.List;
public class JUnit5Framework extends Framework {
public class JUnit5Framework extends CommonFramework {
public static final String ANNOTATION_TEST = "Lorg/junit/jupiter/api/Test;";
public static final String ANNOTATION_PARAMETERIZED_TEST = "Lorg/junit/jupiter/params/ParameterizedTest;";
private static final String ANNOTATION_AFTER_TEST = "Lorg/junit/jupiter/api/AfterEach;";
private static final String ANNOTATION_CLEANUP = "Lorg/junit/jupiter/api/AfterEach;";
@Override
public List<String> getTestAnnotations() {
public List<String> getTestMethodAnnotations() {
return Arrays.asList(ANNOTATION_TEST, ANNOTATION_PARAMETERIZED_TEST);
}
@Override
public String getTestAfterAnnotation() {
return ANNOTATION_AFTER_TEST;
public String getCleanupMethodAnnotation() {
return ANNOTATION_CLEANUP;
}
}

View File

@ -0,0 +1,57 @@
package com.alibaba.testable.agent.handler.test;
import com.alibaba.testable.agent.model.TestCaseMethodType;
import com.alibaba.testable.agent.util.ClassUtil;
import org.objectweb.asm.Label;
import org.objectweb.asm.tree.*;
import java.util.Collections;
import java.util.Set;
import static com.alibaba.testable.core.constant.ConstPool.THIS_REF;
import static org.objectweb.asm.Opcodes.*;
public class SpockFramework implements Framework {
public static final String ANNOTATION_TEST = "Lorg/spockframework/runtime/model/FeatureMetadata;";
private static final String NAME_CLEANUP = "cleanup";
private static final String DESC_CLEANUP = "()Ljava/lang/Object;";
@Override
public boolean fit(Set<String> classAnnotations, Set<String> methodAnnotations) {
return methodAnnotations.contains(ANNOTATION_TEST);
}
@Override
public TestCaseMethodType checkMethodType(MethodNode mn) {
if (NAME_CLEANUP.equals(mn.name)) {
return TestCaseMethodType.AFTER_TEST;
} else if (mn.visibleAnnotations == null) {
return TestCaseMethodType.OTHERS;
}
for (AnnotationNode an : mn.visibleAnnotations) {
if (ANNOTATION_TEST.equals(an.desc)) {
return TestCaseMethodType.TEST;
}
}
return TestCaseMethodType.OTHERS;
}
@Override
public MethodNode getCleanupMethod(String className) {
MethodNode cleanupMethod = new MethodNode(ACC_PRIVATE, NAME_CLEANUP, DESC_CLEANUP, null, null);
InsnList il = new InsnList();
LabelNode startLabel = new LabelNode(new Label());
LabelNode endLabel = new LabelNode(new Label());
il.add(startLabel);
il.add(new InsnNode(ACONST_NULL));
il.add(new InsnNode(ARETURN));
il.add(endLabel);
cleanupMethod.instructions = il;
cleanupMethod.localVariables = Collections.singletonList(new LocalVariableNode(THIS_REF,
ClassUtil.toByteCodeClassName(className), null, startLabel, endLabel, 0));
cleanupMethod.maxLocals = 1;
cleanupMethod.maxStack = 1;
return cleanupMethod;
}
}

View File

@ -3,18 +3,18 @@ package com.alibaba.testable.agent.handler.test;
import java.util.Collections;
import java.util.List;
public class TestNgFramework extends Framework {
public class TestNgFramework extends CommonFramework {
private static final String ANNOTATION_TEST = "Lorg/testng/annotations/Test;";
private static final String ANNOTATION_AFTER_TEST = "Lorg/testng/annotations/AfterMethod;";
private static final String ANNOTATION_CLEANUP = "Lorg/testng/annotations/AfterMethod;";
@Override
public List<String> getTestAnnotations() {
public List<String> getTestMethodAnnotations() {
return Collections.singletonList(ANNOTATION_TEST);
}
@Override
public String getTestAfterAnnotation() {
return ANNOTATION_AFTER_TEST;
public String getCleanupMethodAnnotation() {
return ANNOTATION_CLEANUP;
}
}

View File

@ -13,13 +13,7 @@ public class TestNgOnClassFramework extends TestNgFramework {
@Override
public boolean fit(Set<String> classAnnotations, Set<String> methodAnnotations) {
if (CollectionUtil.containsAny(classAnnotations, getTestAnnotations())) {
if (methodAnnotations.contains(getTestAfterAnnotation())) {
hasTestAfterMethod = true;
}
return true;
}
return false;
return CollectionUtil.containsAny(classAnnotations, getTestMethodAnnotations());
}
@Override
@ -28,7 +22,7 @@ public class TestNgOnClassFramework extends TestNgFramework {
return (mn.access & ACC_PUBLIC) != 0 ? TestCaseMethodType.TEST : TestCaseMethodType.OTHERS;
}
for (AnnotationNode an : mn.visibleAnnotations) {
if (an.desc.equals(getTestAfterAnnotation())) {
if (an.desc.equals(getCleanupMethodAnnotation())) {
return TestCaseMethodType.AFTER_TEST;
}
}