fit test case name fetcher for gradle build

This commit is contained in:
金戟 2020-11-29 10:39:55 +08:00
parent 9ab737ce57
commit 1bf6ab3b21
4 changed files with 84 additions and 47 deletions

View File

@ -1,10 +0,0 @@
package com.alibaba.testable.core.constant;
/**
* @author flin
*/
public final class ConstPool {
public static final String TEST_POSTFIX = "Test";
}

View File

@ -108,7 +108,7 @@ public class InvokeVerifier {
public InvokeVerifier times(int count) {
if (lastVerification == null) {
// when used independently, equals to `withTimes()`
System.out.println("Warning: [" + TestableUtil.getPreviousStackLocation() + "] using \"times()\" method "
System.out.println("Warning: [" + TestableUtil.previousStackLocation() + "] using \"times()\" method "
+ "without \"with()\" or \"withInOrder()\" is not recommended, please use \"withTimes()\" instead.");
return withTimes(count);
}

View File

@ -1,27 +1,32 @@
package com.alibaba.testable.core.util;
import com.alibaba.testable.core.constant.ConstPool;
import java.util.Set;
/**
* @author flin
*/
public class TestableUtil {
/**
* [0]Thread.getStackTrace() [1]currentSourceMethodName() [2]MockMethod -> [3]SourceMethod
*/
private static final int INDEX_OF_SOURCE_METHOD = 3;
/**
* [0]Thread.getStackTrace() [1]previousStackLocation() [2]Invoker -> [3]Caller of invoker
*/
private static final int INDEX_OF_CALLER_METHOD = 3;
/**
* Just a special number to identify test worker thread
*/
private static final int TEST_WORKER_THREAD_PRIORITY = 55555;
/**
* Get the last visit method in source file
* @param testClassRef usually `this` variable of the test class
* @return method name
*/
public static String currentSourceMethodName(Object testClassRef) {
Class<?> testClass = testClassRef.getClass();
String testClassName = getRealClassName(testClass);
String sourceClassName = testClassName.substring(0, testClassName.length() - ConstPool.TEST_POSTFIX.length());
String sourceMethod = findLastMethodFromSourceClass(sourceClassName, getMainThread().getStackTrace());
if (sourceMethod.isEmpty()) {
return findLastMethodFromSourceClass(sourceClassName, Thread.currentThread().getStackTrace());
}
return sourceMethod;
return Thread.currentThread().getStackTrace()[INDEX_OF_SOURCE_METHOD].getMethodName();
}
/**
@ -31,7 +36,7 @@ public class TestableUtil {
*/
public static String currentTestCaseName(Object testClassRef) {
Class<?> testClass = testClassRef.getClass();
String testClassName = getRealClassName(testClass);
String testClassName = getOuterClassName(testClass.getName());
return currentTestCaseName(testClassName);
}
@ -41,10 +46,28 @@ public class TestableUtil {
* @return method name
*/
public static String currentTestCaseName(String testClassName) {
String testCaseName = findFirstMethodFromTestClass(testClassName, getMainThread().getStackTrace());
// try current thread
String testCaseName = findFirstMethodFromTestClass(testClassName, Thread.currentThread().getStackTrace());
if (testCaseName.isEmpty()) {
return findFirstMethodFromTestClass(testClassName, Thread.currentThread().getStackTrace());
Set<Thread> threads = Thread.getAllStackTraces().keySet();
// try find previously marked thread
Thread testWorkerThread = findTestWorkerThread(threads);
if (testWorkerThread != null) {
testCaseName = findFirstMethodFromTestClass(testClassName, testWorkerThread.getStackTrace());
if (!testCaseName.isEmpty()) {
return testCaseName;
}
}
// travel all possible threads
for (Thread t : threads) {
testCaseName = findFirstMethodFromTestClass(testClassName, t.getStackTrace());
if (!testCaseName.isEmpty()) {
t.setPriority(TEST_WORKER_THREAD_PRIORITY);
return testCaseName;
}
}
}
System.err.println("testCaseName: " + testCaseName);
return testCaseName;
}
@ -52,44 +75,45 @@ public class TestableUtil {
* Get file name and line number of where current method was called
* @return in "filename:linenumber" format
*/
public static String getPreviousStackLocation() {
// 0 - Thread.getStackTrace(), 1 - this method, 2 - code call this method, 3 - code call the caller method
StackTraceElement stack = getMainThread().getStackTrace()[3];
public static String previousStackLocation() {
StackTraceElement stack = Thread.currentThread().getStackTrace()[INDEX_OF_CALLER_METHOD];
return stack.getFileName() + ":" + stack.getLineNumber();
}
private static String findLastMethodFromSourceClass(String sourceClassName, StackTraceElement[] stack) {
for (StackTraceElement element : stack) {
if (element.getClassName().equals(sourceClassName)) {
return element.getMethodName();
private static Thread findTestWorkerThread(Set<Thread> threads) {
for (Thread t : threads) {
if (t.getPriority() == TEST_WORKER_THREAD_PRIORITY) {
return t;
}
}
return "";
return null;
}
private static String findFirstMethodFromTestClass(String testClassName, StackTraceElement[] stack) {
for (int i = stack.length - 1; i >= 0; i--) {
if (stack[i].getClassName().equals(testClassName)) {
return stack[i].getMethodName();
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 getRealClassName(Class<?> testClass) {
String className = testClass.getName();
int posOfInnerClass = className.lastIndexOf('$');
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;
}
private static Thread getMainThread() {
for (Thread t : Thread.getAllStackTraces().keySet()) {
if (t.getId() == 1L) {
return t;
}
}
// usually impossible to go here
return Thread.currentThread();
}
}

View File

@ -0,0 +1,23 @@
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_async_method_name_from_inner_class_name() {
assertEquals("should_able_to_get_source_method_name",
PrivateAccessor.<String>invokeStatic(TestableUtil.class, "getAsyncMethodName", "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, "getAsyncMethodName", "lambda$should_able_to_get_source_method_name$0"));
}
}