mirror of
https://github.com/alibaba/testable-mock.git
synced 2026-08-19 09:43:29 +08:00
feat: handle many special cases of system class creation
This commit is contained in:
@@ -37,31 +37,21 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
private static final String CLASS_ABSTRACT_COLLECTION = "java/util/AbstractCollection";
|
||||
private static final String CLASS_NUMBER = "java/lang/Number";
|
||||
private static final String CLASS_HASH_SET = "java/util/HashSet";
|
||||
private static final String CLASS_READER = "java/io/Reader";
|
||||
private static final String CLASS_WRITER = "java/io/Writer";
|
||||
private static final String CLASS_BUFFERED_READER = "java/io/BufferedReader";
|
||||
private static final String CLASS_BUFFERED_WRITER = "java/io/BufferedWriter";
|
||||
private static final String CLASS_INPUT_STREAM = "java/io/InputStream";
|
||||
private static final String CLASS_OUTPUT_STREAM = "java/io/OutputStream";
|
||||
private static final String CLASS_BUFFERED_INPUT_STREAM = "java/io/BufferedInputStream";
|
||||
private static final String CLASS_BUFFERED_OUTPUT_STREAM = "java/io/BufferedOutputStream";
|
||||
private static final String CLASS_THREAD = "java/lang/Thread";
|
||||
private static final String CLASS_FILE = "java/io/File";
|
||||
|
||||
// below classes are loaded before OmniClassHandler, cannot be instrumented
|
||||
// map of class name to constructor parameters
|
||||
private static final Map<String, String[]> PRELOADED_CLASSES = mapOf(
|
||||
entryOf(CLASS_OBJECT, CollectionUtil.<String>arrayOf()),
|
||||
entryOf(CLASS_READER, CollectionUtil.<String>arrayOf()),
|
||||
entryOf(CLASS_WRITER, CollectionUtil.<String>arrayOf()),
|
||||
entryOf(CLASS_INPUT_STREAM, CollectionUtil.<String>arrayOf()),
|
||||
entryOf(CLASS_OUTPUT_STREAM, CollectionUtil.<String>arrayOf()),
|
||||
entryOf(CLASS_THREAD, CollectionUtil.<String>arrayOf()),
|
||||
entryOf(CLASS_FILE, arrayOf("Ljava/lang/String;")),
|
||||
entryOf(CLASS_BUFFERED_READER, arrayOf("Ljava/io/Reader;")),
|
||||
entryOf(CLASS_BUFFERED_WRITER, arrayOf("Ljava/io/Reader;")),
|
||||
entryOf(CLASS_BUFFERED_INPUT_STREAM, arrayOf("Ljava/io/Reader;")),
|
||||
entryOf(CLASS_BUFFERED_OUTPUT_STREAM, arrayOf("Ljava/io/Reader;"))
|
||||
entryOf("java/io/Reader", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/io/Writer", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/io/InputStream", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/io/OutputStream", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/lang/Thread", CollectionUtil.<String>arrayOf()),
|
||||
entryOf("java/io/File", arrayOf("Ljava/lang/String;")),
|
||||
entryOf("java/io/BufferedReader", arrayOf("Ljava/io/Reader;")),
|
||||
entryOf("java/io/BufferedWriter", arrayOf("Ljava/io/Reader;")),
|
||||
entryOf("java/io/BufferedInputStream", arrayOf("Ljava/io/Reader;")),
|
||||
entryOf("java/io/BufferedOutputStream", arrayOf("Ljava/io/Reader;"))
|
||||
);
|
||||
|
||||
private static final String[] JUNIT_TEST_ANNOTATIONS = new String[] {
|
||||
@@ -86,7 +76,7 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
LabelNode start = new LabelNode(new Label());
|
||||
LabelNode end = new LabelNode(new Label());
|
||||
if (PRELOADED_CLASSES.containsKey(cn.superName)) {
|
||||
constructor.instructions = invokeSuperWithoutParameter(cn.superName, start, end);
|
||||
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, start, end);
|
||||
constructor.localVariables = createLocalVariables(cn, start, end);
|
||||
constructor.maxStack = 1 + PRELOADED_CLASSES.get(cn.superName).length;
|
||||
} else {
|
||||
@@ -136,7 +126,7 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
private InsnList invokeSuperWithoutParameter(String superName, LabelNode start, LabelNode end) {
|
||||
private InsnList invokeSuperWithoutTestableParameter(String superName, LabelNode start, LabelNode end) {
|
||||
String[] parameters = PRELOADED_CLASSES.get(superName);
|
||||
InsnList il = new InsnList();
|
||||
il.add(start);
|
||||
|
||||
@@ -67,9 +67,9 @@ public class AnnotationUtil {
|
||||
* @return yes or no
|
||||
*/
|
||||
public static boolean hasAllAnnotationParameters(AnnotationNode an, String... keys) {
|
||||
boolean found = false;
|
||||
if (an != null && an.values != null) {
|
||||
for (String key : keys) {
|
||||
boolean found = false;
|
||||
for (int i = 0; i < an.values.size(); i += 2) {
|
||||
if (an.values.get(i).equals(key)) {
|
||||
found = true;
|
||||
@@ -80,8 +80,9 @@ public class AnnotationUtil {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.alibaba.testable.core.util.LogUtil;
|
||||
import com.alibaba.testable.core.util.TypeUtil;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
|
||||
import static com.alibaba.testable.core.constant.ConstPool.DOLLAR;
|
||||
@@ -61,6 +62,10 @@ public class OmniConstructor {
|
||||
return null;
|
||||
}
|
||||
classPool.add(clazz);
|
||||
T ins = createSpecialClass(clazz);
|
||||
if (ins != null) {
|
||||
return ins;
|
||||
}
|
||||
try {
|
||||
if (clazz.isPrimitive()) {
|
||||
return newPrimitive(clazz);
|
||||
@@ -229,10 +234,6 @@ public class OmniConstructor {
|
||||
|
||||
private static Object createInstance(Class<?> clazz, Set<Class<?>> classPool, ConstructionOption[] options)
|
||||
throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
Object ins = createSpecialClass(clazz);
|
||||
if (ins != null) {
|
||||
return ins;
|
||||
}
|
||||
Constructor<?> constructor = getBestConstructor(clazz);
|
||||
if (constructor == null) {
|
||||
throw new ClassConstructionException("Fail to invoke constructor of " + clazz.getName());
|
||||
@@ -250,14 +251,31 @@ public class OmniConstructor {
|
||||
}
|
||||
}
|
||||
|
||||
private static Object createSpecialClass(Class<?> clazz) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
try {
|
||||
// below classes are loaded before OmniClassHandler, cannot be instrumented
|
||||
if (clazz.getName().equals("java.net.URL")) {
|
||||
private static <T> T createSpecialClass(Class<T> clazz) {
|
||||
if (clazz.getName().equals("java.net.URL")) {
|
||||
// loaded before OmniClassHandler, cannot be instrumented
|
||||
try {
|
||||
return clazz.getDeclaredConstructor(String.class).newInstance("https://");
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
} else if (clazz.getName().equals("java.nio.charset.Charset")) {
|
||||
// better to use its default instance
|
||||
return (T) Charset.defaultCharset();
|
||||
} else if (clazz.getName().equals("java.nio.ByteBuffer")) {
|
||||
// has package-private abstract methods
|
||||
try {
|
||||
return (T) newInstance(Class.forName("java.nio.DirectByteBuffer"));
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
} else if (clazz.getName().equals("java.nio.CharBuffer")) {
|
||||
// has package-private abstract methods
|
||||
try {
|
||||
return (T) newInstance(Class.forName("java.nio.StringCharBuffer"));
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -42,20 +42,23 @@ public class ConstructionUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> finalMethods, Map<String, String> genericTypes) {
|
||||
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> methodPool, Map<String, String> genericTypes) {
|
||||
Set<String> methods = new HashSet<String>();
|
||||
// in a very special situation, getDeclaredMethods() could fetch method declaration in the parent interface
|
||||
// as none-abstract, that will cause the corresponding abstract method in current class be skipped.
|
||||
// it happens to e.g. CharSequence.subSequence(int,int) and CharBuffer.subSequence(int,int)
|
||||
// so we would pass the methods to pool after all method in current level have been handled.
|
||||
Set<String> thisLevelMethodPool = new HashSet<String>();
|
||||
for (Method m : clazz.getDeclaredMethods()) {
|
||||
StringBuilder methodSignatureBuilder = new StringBuilder(m.getName());
|
||||
for (Type p : m.getGenericParameterTypes()) {
|
||||
methodSignatureBuilder.append("#").append(getParameterName(p, genericTypes));
|
||||
}
|
||||
String methodSignature = methodSignatureBuilder.toString();
|
||||
if (finalMethods.contains(methodSignature)) {
|
||||
if (methodPool.contains(methodSignature)) {
|
||||
continue;
|
||||
}
|
||||
if (Modifier.isFinal(m.getModifiers())) {
|
||||
finalMethods.add(methodSignature);
|
||||
}
|
||||
thisLevelMethodPool.add(methodSignature);
|
||||
if (Modifier.isAbstract(m.getModifiers())) {
|
||||
StringBuilder sourceCode = new StringBuilder();
|
||||
sourceCode.append("\tpublic ")
|
||||
@@ -82,6 +85,7 @@ public class ConstructionUtil {
|
||||
methods.add(sourceCode.toString());
|
||||
}
|
||||
}
|
||||
methodPool.addAll(thisLevelMethodPool);
|
||||
List<Type> superTypes = new ArrayList<Type>(Arrays.asList(clazz.getGenericInterfaces()));
|
||||
if (clazz.getGenericSuperclass() != null) {
|
||||
superTypes.add(clazz.getGenericSuperclass());
|
||||
@@ -89,9 +93,9 @@ public class ConstructionUtil {
|
||||
for (Type t : superTypes) {
|
||||
if (t instanceof ParameterizedType) {
|
||||
ParameterizedType pt = (ParameterizedType) t;
|
||||
methods.addAll(generateMethodsOf((Class<?>) pt.getRawType(), finalMethods, parseGenericTypes(pt)));
|
||||
methods.addAll(generateMethodsOf((Class<?>) pt.getRawType(), methodPool, parseGenericTypes(pt)));
|
||||
} else if (t instanceof Class) {
|
||||
methods.addAll(generateMethodsOf((Class<?>) t, finalMethods, Collections.<String, String>emptyMap()));
|
||||
methods.addAll(generateMethodsOf((Class<?>) t, methodPool, Collections.<String, String>emptyMap()));
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
|
||||
Reference in New Issue
Block a user