diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java index a5f136d..54af248 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/OmniClassHandler.java @@ -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[] { diff --git a/testable-core/src/main/java/com/alibaba/testable/core/model/ConstructionOption.java b/testable-core/src/main/java/com/alibaba/testable/core/model/ConstructionOption.java index 9344f25..b99127c 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/model/ConstructionOption.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/model/ConstructionOption.java @@ -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 } diff --git a/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java b/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java index 75d1330..d5cfcec 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/tool/OmniConstructor.java @@ -121,7 +121,8 @@ public class OmniConstructor { } private static T newAbstractClass(Class 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 newInterface(Class 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 { diff --git a/testable-core/src/main/java/com/alibaba/testable/core/util/ConstructionUtil.java b/testable-core/src/main/java/com/alibaba/testable/core/util/ConstructionUtil.java index b7cc072..e232226 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/util/ConstructionUtil.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/util/ConstructionUtil.java @@ -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 generateSubClassOf(Class clazz) throws InstantiationException { + private static final Map 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 generateSubClassOf(Class clazz, ConstructionOption[] options) throws InstantiationException { StringBuilder sourceCode = new StringBuilder(); String packageName = adaptName(clazz.getPackage().getName()); Map noMapping = new HashMap(); @@ -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(), noMapping)) { + for (String method : generateMethodsOf(clazz, new HashSet(), noMapping, options)) { sourceCode.append(method); } sourceCode.append("}"); @@ -42,7 +66,8 @@ public class ConstructionUtil { } } - private static Set generateMethodsOf(Class clazz, Set methodPool, Map genericTypes) { + private static Set generateMethodsOf(Class clazz, Set methodPool, + Map genericTypes, ConstructionOption[] options) { Set methods = new HashSet(); // 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.emptyMap())); + methods.addAll(generateMethodsOf((Class) t, methodPool, Collections.emptyMap(), options)); } } return methods; diff --git a/testable-core/src/test/java/com/alibaba/testable/core/util/ConstructionUtilTest.java b/testable-core/src/test/java/com/alibaba/testable/core/util/ConstructionUtilTest.java index cb0c0d1..7a6021d 100644 --- a/testable-core/src/test/java/com/alibaba/testable/core/util/ConstructionUtilTest.java +++ b/testable-core/src/test/java/com/alibaba/testable/core/util/ConstructionUtilTest.java @@ -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); }