use mock context to fetch current test case

This commit is contained in:
金戟 2021-02-13 14:29:48 +08:00
parent 94c5aa8621
commit 9a22f361f3
9 changed files with 14 additions and 107 deletions

View File

@ -32,7 +32,6 @@
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>

View File

@ -108,14 +108,13 @@ class DemoMockTest {
} }
@Test @Test
void should_able_to_get_test_case_name() throws Exception { void should_able_to_set_mock_context() throws Exception {
MOCK_CONTEXT.put("case", "special_case"); MOCK_CONTEXT.put("case", "special_case");
// synchronous // synchronous
assertEquals("mock_special", demoMock.callerOne()); assertEquals("mock_special", demoMock.callerOne());
// asynchronous // asynchronous
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne()).get()); assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne()).get());
verify("callFromDifferentMethod").withTimes(2); verify("callFromDifferentMethod").withTimes(2);
MOCK_CONTEXT.clear();
} }
} }

View File

@ -19,10 +19,11 @@ public class PreMain {
private static final String EQUAL = "="; private static final String EQUAL = "=";
public static void premain(String agentArgs, Instrumentation inst) { public static void premain(String agentArgs, Instrumentation inst) {
// add transmittable thread local transformer
TtlAgent.premain(agentArgs, inst);
// add testable mock transformer
parseArgs(agentArgs); parseArgs(agentArgs);
inst.addTransformer(new TestableClassTransformer()); inst.addTransformer(new TestableClassTransformer());
// add TTL Transformer
TtlAgent.premain(agentArgs, inst);
} }
private static void parseArgs(String args) { private static void parseArgs(String args) {

View File

@ -1,8 +1,6 @@
package com.alibaba.testable.agent.handler; package com.alibaba.testable.agent.handler;
import com.alibaba.testable.agent.util.ClassUtil; import com.alibaba.testable.core.util.MockContextUtil;
import com.alibaba.testable.core.util.InvokeRecordUtil;
import com.alibaba.testable.core.util.TestableUtil;
import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.*;
/** /**
@ -13,14 +11,11 @@ abstract public class BaseClassWithContextHandler extends BaseClassHandler {
private static final String CLASS_TESTABLE_TOOL = "com/alibaba/testable/core/tool/TestableTool"; 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_TESTABLE_UTIL = "com/alibaba/testable/core/util/TestableUtil";
private static final String CLASS_MOCK_CONTEXT_HOLDER = "com/alibaba/testable/agent/model/MockContextHolder"; private static final String CLASS_MOCK_CONTEXT_HOLDER = "com/alibaba/testable/agent/model/MockContextHolder";
private static final String FIELD_TEST_CASE = "TEST_CASE";
private static final String FIELD_SOURCE_METHOD = "SOURCE_METHOD"; private static final String FIELD_SOURCE_METHOD = "SOURCE_METHOD";
private static final String FIELD_MOCK_CONTEXT = "MOCK_CONTEXT"; private static final String FIELD_MOCK_CONTEXT = "MOCK_CONTEXT";
private static final String FIELD_PARAMETERS = "parameters"; private static final String FIELD_PARAMETERS = "parameters";
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_CURRENT_SOURCE_METHOD_NAME = "currentSourceMethodName";
private static final String METHOD_GET_TEST_CASE_MARK = "getTestCaseMark"; private static final String METHOD_GET_TEST_CASE_MARK = "getTestCaseMark";
private static final String SIGNATURE_CURRENT_TEST_CASE_NAME = "(Ljava/lang/String;)Ljava/lang/String;";
private static final String SIGNATURE_CURRENT_SOURCE_METHOD_NAME = "()Ljava/lang/String;"; private static final String SIGNATURE_CURRENT_SOURCE_METHOD_NAME = "()Ljava/lang/String;";
private static final String SIGNATURE_GET_TEST_CASE_MARK = "()Ljava/lang/String;"; private static final String SIGNATURE_GET_TEST_CASE_MARK = "()Ljava/lang/String;";
private static final String SIGNATURE_PARAMETERS = "Ljava/util/Map;"; private static final String SIGNATURE_PARAMETERS = "Ljava/util/Map;";
@ -42,18 +37,13 @@ abstract public class BaseClassWithContextHandler extends BaseClassHandler {
private boolean isTestableUtilField(FieldInsnNode fieldInsnNode) { private boolean isTestableUtilField(FieldInsnNode fieldInsnNode) {
return fieldInsnNode.owner.equals(CLASS_TESTABLE_TOOL) && return fieldInsnNode.owner.equals(CLASS_TESTABLE_TOOL) &&
(fieldInsnNode.name.equals(FIELD_TEST_CASE) || fieldInsnNode.name.equals(FIELD_SOURCE_METHOD) || (fieldInsnNode.name.equals(FIELD_SOURCE_METHOD) || fieldInsnNode.name.equals(FIELD_MOCK_CONTEXT));
fieldInsnNode.name.equals(FIELD_MOCK_CONTEXT));
} }
private AbstractInsnNode[] replaceTestableUtilField(ClassNode cn, MethodNode mn, AbstractInsnNode[] instructions, private AbstractInsnNode[] replaceTestableUtilField(ClassNode cn, MethodNode mn, AbstractInsnNode[] instructions,
String fieldName, int pos) { String fieldName, int pos) {
InsnList il = new InsnList(); InsnList il = new InsnList();
if (FIELD_TEST_CASE.equals(fieldName)) { if (FIELD_SOURCE_METHOD.equals(fieldName)) {
il.add(new LdcInsnNode(ClassUtil.toDotSeparatedName(cn.name)));
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_TESTABLE_UTIL, METHOD_CURRENT_TEST_CASE_NAME,
SIGNATURE_CURRENT_TEST_CASE_NAME, false));
} else if (FIELD_SOURCE_METHOD.equals(fieldName)) {
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_TESTABLE_UTIL, METHOD_CURRENT_SOURCE_METHOD_NAME, il.add(new MethodInsnNode(INVOKESTATIC, CLASS_TESTABLE_UTIL, METHOD_CURRENT_SOURCE_METHOD_NAME,
SIGNATURE_CURRENT_SOURCE_METHOD_NAME, false)); SIGNATURE_CURRENT_SOURCE_METHOD_NAME, false));
} else if (FIELD_MOCK_CONTEXT.equals(fieldName)) { } else if (FIELD_MOCK_CONTEXT.equals(fieldName)) {
@ -71,10 +61,8 @@ abstract public class BaseClassWithContextHandler extends BaseClassHandler {
} }
public static String getTestCaseMark() { public static String getTestCaseMark() {
String clazz = Thread.currentThread().getStackTrace()[InvokeRecordUtil.INDEX_OF_TEST_CLASS].getClassName(); String testClass = MockContextUtil.context.get().testClassName;
// TODO: temporary used String testCaseName = MockContextUtil.context.get().testCaseName;
String testClass = clazz.endsWith("Mock") ? clazz.substring(0, clazz.length() - 5) : clazz;
String testCaseName = TestableUtil.currentTestCaseName(testClass);
return testClass + "::" + testCaseName; return testClass + "::" + testCaseName;
} }

View File

@ -3,6 +3,7 @@ package com.alibaba.testable.core.matcher;
import com.alibaba.testable.core.error.VerifyFailedError; import com.alibaba.testable.core.error.VerifyFailedError;
import com.alibaba.testable.core.model.Verification; import com.alibaba.testable.core.model.Verification;
import com.alibaba.testable.core.util.InvokeRecordUtil; import com.alibaba.testable.core.util.InvokeRecordUtil;
import com.alibaba.testable.core.util.MockContextUtil;
import com.alibaba.testable.core.util.TestableUtil; import com.alibaba.testable.core.util.TestableUtil;
import java.security.InvalidParameterException; import java.security.InvalidParameterException;
@ -26,8 +27,8 @@ public class InvokeVerifier {
* @return the verifier object * @return the verifier object
*/ */
public static InvokeVerifier verify(String mockMethodName) { public static InvokeVerifier verify(String mockMethodName) {
String testClass = Thread.currentThread().getStackTrace()[InvokeRecordUtil.INDEX_OF_TEST_CLASS].getClassName(); String testClass = MockContextUtil.context.get().testClassName;
String testCaseName = TestableUtil.currentTestCaseName(testClass); String testCaseName = MockContextUtil.context.get().testCaseName;
String recordIdentify = InvokeRecordUtil.getInvokeIdentify(mockMethodName, testClass, testCaseName); String recordIdentify = InvokeRecordUtil.getInvokeIdentify(mockMethodName, testClass, testCaseName);
return new InvokeVerifier(InvokeRecordUtil.getInvokeRecord(recordIdentify)); return new InvokeVerifier(InvokeRecordUtil.getInvokeRecord(recordIdentify));
} }

View File

@ -7,13 +7,6 @@ import java.util.Map;
*/ */
public class TestableTool { public class TestableTool {
/**
* Name of current test case method
* @deprecated prefer using `MOCK_CONTEXT` to distinguish test cases
*/
@Deprecated
public static String TEST_CASE;
/** /**
* Name of the last visited method in source class * Name of the last visited method in source class
*/ */

View File

@ -30,10 +30,8 @@ public class InvokeRecordUtil {
public static void recordMockInvoke(Object[] args, boolean isConstructor, boolean isTargetClassInParameter) { public static void recordMockInvoke(Object[] args, boolean isConstructor, boolean isTargetClassInParameter) {
StackTraceElement mockMethodTraceElement = Thread.currentThread().getStackTrace()[INDEX_OF_TEST_CLASS]; StackTraceElement mockMethodTraceElement = Thread.currentThread().getStackTrace()[INDEX_OF_TEST_CLASS];
String mockMethodName = mockMethodTraceElement.getMethodName(); String mockMethodName = mockMethodTraceElement.getMethodName();
String mockClass = mockMethodTraceElement.getClassName(); String testClass = MockContextUtil.context.get().testClassName;
// TODO: temporary used String testCaseName = MockContextUtil.context.get().testCaseName;
String testClass = mockClass.substring(0, mockClass.length() - 5);
String testCaseName = TestableUtil.currentTestCaseName(testClass);
String identify = getInvokeIdentify(mockMethodName, testClass, testCaseName); String identify = getInvokeIdentify(mockMethodName, testClass, testCaseName);
List<Object[]> records = getInvokeRecord(identify); List<Object[]> records = getInvokeRecord(identify);
if (isConstructor) { if (isConstructor) {

View File

@ -1,6 +1,5 @@
package com.alibaba.testable.core.util; package com.alibaba.testable.core.util;
import java.util.Set;
/** /**
* @author flin * @author flin
@ -24,27 +23,6 @@ public class TestableUtil {
return Thread.currentThread().getStackTrace()[INDEX_OF_SOURCE_METHOD].getMethodName(); return Thread.currentThread().getStackTrace()[INDEX_OF_SOURCE_METHOD].getMethodName();
} }
/**
* Get current test case method
* @param testClassName name of current test class
* @return method name
*/
public static String currentTestCaseName(String testClassName) {
// try current thread
String testCaseName = findFirstMethodFromTestClass(testClassName, Thread.currentThread().getStackTrace());
if (testCaseName.isEmpty()) {
Set<Thread> threads = Thread.getAllStackTraces().keySet();
// travel all possible threads
for (Thread t : threads) {
testCaseName = findFirstMethodFromTestClass(testClassName, t.getStackTrace());
if (!testCaseName.isEmpty()) {
return testCaseName;
}
}
}
return testCaseName;
}
/** /**
* Get file name and line number of where current method was called * Get file name and line number of where current method was called
* @return in "filename:linenumber" format * @return in "filename:linenumber" format
@ -54,31 +32,4 @@ public class TestableUtil {
return stack.getFileName() + ":" + stack.getLineNumber(); return stack.getFileName() + ":" + stack.getLineNumber();
} }
private static String findFirstMethodFromTestClass(String testClassName, StackTraceElement[] stack) {
for (int i = stack.length - 1; i >= 0; i--) {
if (getOuterClassName(stack[i].getClassName()).equals(testClassName)) {
return stack[i].getClassName().indexOf('$') > 0 ?
// test case using async call
getMethodNameFromLambda(stack[i].getClassName()) :
// in case of lambda method
getMethodNameFromLambda(stack[i].getMethodName());
}
}
return "";
}
private static String getMethodNameFromLambda(String originName) {
int beginOfMethodName = originName.indexOf('$');
if (beginOfMethodName < 0) {
return originName;
}
int endOfMethodName = originName.indexOf('$', beginOfMethodName + 1);
return originName.substring(beginOfMethodName + 1, endOfMethodName);
}
private static String getOuterClassName(String className) {
int posOfInnerClass = className.indexOf('$');
return posOfInnerClass > 0 ? className.substring(0, posOfInnerClass) : className;
}
} }

View File

@ -1,23 +0,0 @@
package com.alibaba.testable.core.util;
import com.alibaba.testable.core.accessor.PrivateAccessor;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TestableUtilTest {
@Test
void should_get_outer_class_name() {
assertEquals("com.alibaba.testable.demo.DemoMockTest",
PrivateAccessor.<String>invokeStatic(TestableUtil.class, "getOuterClassName", "com.alibaba.testable.demo.DemoMockTest$should_able_to_get_source_method_name$1"));
}
@Test
void should_get_method_name_from_lambda_class_or_method() {
assertEquals("should_able_to_get_source_method_name",
PrivateAccessor.<String>invokeStatic(TestableUtil.class, "getMethodNameFromLambda", "com.alibaba.testable.demo.DemoMockTest$should_able_to_get_source_method_name$1"));
assertEquals("should_able_to_get_source_method_name",
PrivateAccessor.<String>invokeStatic(TestableUtil.class, "getMethodNameFromLambda", "lambda$should_able_to_get_source_method_name$0"));
}
}