mirror of
https://github.com/alibaba/testable-mock.git
synced 2026-08-19 09:43:29 +08:00
feat: add RICH_INTERFACE option to avoid introduce omni class in system interface implementation
This commit is contained in:
@@ -51,7 +51,8 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
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;"))
|
||||
entryOf("java/io/BufferedOutputStream", arrayOf("Ljava/io/Reader;")),
|
||||
entryOf("java/nio/CharBuffer", arrayOf("I", "I", "I", "I"))
|
||||
);
|
||||
|
||||
private static final String[] JUNIT_TEST_ANNOTATIONS = new String[] {
|
||||
|
||||
@@ -12,6 +12,12 @@ public enum ConstructionOption {
|
||||
* 不初始化接口和抽象类型的成员
|
||||
* allow members of interface or abstract class type be initialized as null
|
||||
*/
|
||||
EXCEPT_INTERFACE
|
||||
EXCEPT_INTERFACE,
|
||||
|
||||
/**
|
||||
* 构造的接口成员方法返回非空对象(某些JDK内置接口不兼容此选项)
|
||||
* methods in constructed interface return real object instead of null
|
||||
*/
|
||||
RICH_INTERFACE
|
||||
|
||||
}
|
||||
|
||||
@@ -121,7 +121,8 @@ public class OmniConstructor {
|
||||
}
|
||||
|
||||
private static <T> T newAbstractClass(Class<T> clazz, ConstructionOption[] options) throws InstantiationException {
|
||||
return CollectionUtil.contains(options, EXCEPT_INTERFACE) ? null : ConstructionUtil.generateSubClassOf(clazz);
|
||||
return CollectionUtil.contains(options, EXCEPT_INTERFACE) ? null
|
||||
: ConstructionUtil.generateSubClassOf(clazz, options);
|
||||
}
|
||||
|
||||
private static <T> T newInterface(Class<T> clazz, ConstructionOption[] options) throws InstantiationException {
|
||||
@@ -262,6 +263,9 @@ public class OmniConstructor {
|
||||
} 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.util.Date")) {
|
||||
// better to use current time
|
||||
return (T) new Date();
|
||||
} else if (clazz.getName().equals("java.nio.ByteBuffer")) {
|
||||
// has package-private abstract methods
|
||||
try {
|
||||
|
||||
@@ -1,18 +1,42 @@
|
||||
package com.alibaba.testable.core.util;
|
||||
|
||||
import com.alibaba.testable.core.compile.InMemoryJavaCompiler;
|
||||
import com.alibaba.testable.core.model.ConstructionOption;
|
||||
import com.alibaba.testable.core.tool.OmniConstructor;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
|
||||
import static com.alibaba.testable.core.constant.ConstPool.DOT;
|
||||
import static com.alibaba.testable.core.model.ConstructionOption.RICH_INTERFACE;
|
||||
import static com.alibaba.testable.core.util.CollectionUtil.entryOf;
|
||||
import static com.alibaba.testable.core.util.CollectionUtil.mapOf;
|
||||
|
||||
public class ConstructionUtil {
|
||||
|
||||
private static final String TESTABLE_IMPL = "$TestableImpl";
|
||||
|
||||
public static <T> T generateSubClassOf(Class<T> clazz) throws InstantiationException {
|
||||
private static final Map<String, String> RETURN_VALUES = mapOf(
|
||||
entryOf("java.lang.String", "\"mock\""),
|
||||
entryOf("byte", "'\0'"),
|
||||
entryOf("java.lang.Byte", "'\0'"),
|
||||
entryOf("char", "'\0'"),
|
||||
entryOf("java.lang.Character", "'\0'"),
|
||||
entryOf("double", "0.0D"),
|
||||
entryOf("java.lang.Double", "0.0D"),
|
||||
entryOf("float", "0.0"),
|
||||
entryOf("java.lang.Float", "0.0"),
|
||||
entryOf("int", "0"),
|
||||
entryOf("java.lang.Integer", "0"),
|
||||
entryOf("short", "0"),
|
||||
entryOf("java.lang.Short", "0"),
|
||||
entryOf("long", "0L"),
|
||||
entryOf("java.lang.Long", "0L"),
|
||||
entryOf("boolean", "true"),
|
||||
entryOf("java.lang.Boolean", "true")
|
||||
);
|
||||
|
||||
public static <T> T generateSubClassOf(Class<T> clazz, ConstructionOption[] options) throws InstantiationException {
|
||||
StringBuilder sourceCode = new StringBuilder();
|
||||
String packageName = adaptName(clazz.getPackage().getName());
|
||||
Map<String, String> noMapping = new HashMap<String, String>();
|
||||
@@ -25,7 +49,7 @@ public class ConstructionUtil {
|
||||
.append(getClassName(clazz, noMapping))
|
||||
.append(getTypeParameters(clazz.getTypeParameters(), false, noMapping))
|
||||
.append(" {\n");
|
||||
for (String method : generateMethodsOf(clazz, new HashSet<String>(), noMapping)) {
|
||||
for (String method : generateMethodsOf(clazz, new HashSet<String>(), noMapping, options)) {
|
||||
sourceCode.append(method);
|
||||
}
|
||||
sourceCode.append("}");
|
||||
@@ -42,7 +66,8 @@ public class ConstructionUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> methodPool, Map<String, String> genericTypes) {
|
||||
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> methodPool,
|
||||
Map<String, String> genericTypes, ConstructionOption[] options) {
|
||||
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.
|
||||
@@ -73,13 +98,22 @@ public class ConstructionUtil {
|
||||
}
|
||||
}
|
||||
sourceCode.append(") {\n");
|
||||
if (!m.getReturnType().equals(void.class)) {
|
||||
sourceCode.append("\t\treturn (").append(getClassName(m.getGenericReturnType(), genericTypes))
|
||||
.append(") ")
|
||||
.append(getClassName(OmniConstructor.class, genericTypes))
|
||||
.append(".")
|
||||
.append("newInstance(").append(getClassName(m.getReturnType(), genericTypes))
|
||||
.append(".class);\n");
|
||||
String returnType = getClassName(m.getGenericReturnType(), genericTypes);
|
||||
if (!"void".equals(returnType)) {
|
||||
if (RETURN_VALUES.containsKey(returnType)) {
|
||||
sourceCode.append("\t\treturn ").append(RETURN_VALUES.get(returnType)).append(";\n");
|
||||
} else if (CollectionUtil.contains(options, RICH_INTERFACE)) {
|
||||
sourceCode.append("\t\treturn (")
|
||||
.append(returnType)
|
||||
.append(") ")
|
||||
.append(getClassName(OmniConstructor.class, genericTypes))
|
||||
.append(".")
|
||||
.append("newInstance(")
|
||||
.append(getClassName(m.getReturnType(), genericTypes))
|
||||
.append(".class);\n");
|
||||
} else {
|
||||
sourceCode.append("\t\treturn null;\n");
|
||||
}
|
||||
}
|
||||
sourceCode.append("\t}\n");
|
||||
methods.add(sourceCode.toString());
|
||||
@@ -93,9 +127,9 @@ public class ConstructionUtil {
|
||||
for (Type t : superTypes) {
|
||||
if (t instanceof ParameterizedType) {
|
||||
ParameterizedType pt = (ParameterizedType) t;
|
||||
methods.addAll(generateMethodsOf((Class<?>) pt.getRawType(), methodPool, parseGenericTypes(pt)));
|
||||
methods.addAll(generateMethodsOf((Class<?>) pt.getRawType(), methodPool, parseGenericTypes(pt), options));
|
||||
} else if (t instanceof Class) {
|
||||
methods.addAll(generateMethodsOf((Class<?>) t, methodPool, Collections.<String, String>emptyMap()));
|
||||
methods.addAll(generateMethodsOf((Class<?>) t, methodPool, Collections.<String, String>emptyMap(), options));
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.alibaba.testable.core.util;
|
||||
|
||||
import com.alibaba.testable.core.model.ConstructionOption;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sun.reflect.generics.factory.CoreReflectionFactory;
|
||||
import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl;
|
||||
@@ -54,39 +55,41 @@ class ConstructionUtilTest {
|
||||
|
||||
public interface Inner$Interface {}
|
||||
|
||||
private final ConstructionOption[] options = arrayOf();
|
||||
|
||||
@Test
|
||||
void should_generate_empty_interface() throws Exception {
|
||||
EmptyInterface ins = ConstructionUtil.generateSubClassOf(EmptyInterface.class);
|
||||
EmptyInterface ins = ConstructionUtil.generateSubClassOf(EmptyInterface.class, options);
|
||||
assertNotNull(ins);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_generate_real_interface() throws Exception {
|
||||
RealInterface ins = ConstructionUtil.generateSubClassOf(RealInterface.class);
|
||||
RealInterface ins = ConstructionUtil.generateSubClassOf(RealInterface.class, options);
|
||||
assertNotNull(ins);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_generate_abstract_class() throws Exception {
|
||||
RealInterface ins = ConstructionUtil.generateSubClassOf(AbstractClazz.class);
|
||||
RealInterface ins = ConstructionUtil.generateSubClassOf(AbstractClazz.class, options);
|
||||
assertNotNull(ins);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_generate_parameterized_class() throws Exception {
|
||||
RealInterface ins = ConstructionUtil.generateSubClassOf(ParameterizedClazz.class);
|
||||
RealInterface ins = ConstructionUtil.generateSubClassOf(ParameterizedClazz.class, options);
|
||||
assertNotNull(ins);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_generate_implicit_generic_interface() throws Exception {
|
||||
StringMap ins = ConstructionUtil.generateSubClassOf(StringMap.class);
|
||||
StringMap ins = ConstructionUtil.generateSubClassOf(StringMap.class, options);
|
||||
assertNotNull(ins);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_generate_name_with_dollar() throws Exception {
|
||||
Inner$Interface ins = ConstructionUtil.generateSubClassOf(Inner$Interface.class);
|
||||
Inner$Interface ins = ConstructionUtil.generateSubClassOf(Inner$Interface.class, options);
|
||||
assertNotNull(ins);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user