diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java b/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java index 6830936..ffa79fd 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ConstPool.java @@ -22,4 +22,5 @@ public class ConstPool { public static final String KOTLIN_POSTFIX_COMPANION = "$Companion"; public static final String KOTLIN_PREFIX_ACCESS = "access$"; + public static final String CLASS_OBJECT = "java/lang/Object"; } diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java index 5f17392..76987b6 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java @@ -5,6 +5,7 @@ import com.alibaba.testable.agent.constant.ConstPool; import com.alibaba.testable.agent.tool.ImmutablePair; import com.alibaba.testable.agent.util.*; import com.alibaba.testable.core.model.MockScope; +import com.alibaba.testable.core.util.MockAssociationUtil; import org.objectweb.asm.Label; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; @@ -13,6 +14,7 @@ import java.util.List; import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_ARRAY; import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_CLASS; +import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT; import static com.alibaba.testable.agent.util.ClassUtil.toJavaStyleClassName; import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR; @@ -41,6 +43,10 @@ public class MockClassHandler extends BaseClassWithContextHandler { @Override protected void transform(ClassNode cn) { + if (!CLASS_OBJECT.equals(cn.superName)) { + MockAssociationUtil.recordSubMockContainer(ClassUtil.toDotSeparatedName(cn.superName), + ClassUtil.toDotSeparatedName(cn.name)); + } injectRefFieldAndGetInstanceMethod(cn); for (MethodNode mn : cn.methods) { if (isMockMethod(mn)) { @@ -269,7 +275,7 @@ public class MockClassHandler extends BaseClassWithContextHandler { List types = MethodUtil.getParameterTypes(mn.desc); int size = types.size(); il.add(getIntInsn(size)); - il.add(new TypeInsnNode(ANEWARRAY, ClassUtil.CLASS_OBJECT)); + il.add(new TypeInsnNode(ANEWARRAY, CLASS_OBJECT)); int parameterOffset = MethodUtil.isStatic(mn) ? 0 : 1; for (int i = 0; i < size; i++) { il.add(new InsnNode(DUP)); 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 787321d..3371c86 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 @@ -9,7 +9,7 @@ import org.objectweb.asm.tree.*; import java.util.List; -import static com.alibaba.testable.agent.util.ClassUtil.CLASS_OBJECT; +import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT; import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR; import static com.alibaba.testable.core.constant.ConstPool.THIS_REF; import static com.alibaba.testable.core.util.CollectionUtil.contains; diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/MockClassParser.java b/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/MockClassParser.java index cff44d0..cc0be3c 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/MockClassParser.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/MockClassParser.java @@ -16,14 +16,13 @@ import org.objectweb.asm.tree.MethodNode; import java.util.ArrayList; import java.util.List; +import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT; import static com.alibaba.testable.agent.util.ClassUtil.toJavaStyleClassName; import static com.alibaba.testable.agent.util.MethodUtil.isStatic; import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR; public class MockClassParser { - private static final String CLASS_OBJECT = "java/lang/Object"; - /** * Get information of all mock methods * @param className mock class name diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java b/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java index 96b140a..3210e9a 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java @@ -18,7 +18,6 @@ import static org.objectweb.asm.Opcodes.*; */ public class ClassUtil { - public static final String CLASS_OBJECT = "java/lang/Object"; private static final String CLASS_BYTE = "java/lang/Byte"; private static final String CLASS_CHARACTER = "java/lang/Character"; private static final String CLASS_DOUBLE = "java/lang/Double"; diff --git a/testable-core/src/main/java/com/alibaba/testable/core/util/MockAssociationUtil.java b/testable-core/src/main/java/com/alibaba/testable/core/util/MockAssociationUtil.java index 9b1feae..e0a156f 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/util/MockAssociationUtil.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/util/MockAssociationUtil.java @@ -16,6 +16,12 @@ public class MockAssociationUtil { */ public static final int INDEX_OF_MOCK_CLASS = 2; + /** + * Sub-class of specified mock class + * SuperMockClassName (dot-separated) → Set of [SubMockClassName (dot-separated)] + */ + public static Map> subMockContainers = UnnullableMap.of(new HashSet()); + /** * Mock class referred by @MockWith annotation to list of its test classes * MockClassName (dot-separated) → Set of associated [TestClassNames (dot-separated)] @@ -35,9 +41,24 @@ public class MockAssociationUtil { } String testClassName = mockContext.testClassName; String mockClassName = Thread.currentThread().getStackTrace()[INDEX_OF_MOCK_CLASS].getClassName(); + return recursiveAssociationCheck(testClassName, mockClassName); + } + + private static boolean recursiveAssociationCheck(String testClassName, String mockClassName) { return isAssociatedByInnerMockClass(testClassName, mockClassName) || isAssociatedByOuterMockClass(testClassName, mockClassName) || - isAssociatedByMockWithAnnotation(testClassName, mockClassName); + isAssociatedByMockWithAnnotation(testClassName, mockClassName) || + (subMockContainers.containsKey(mockClassName) && + recursiveAssociationCheck(testClassName, subMockContainers.get(mockClassName))); + } + + private static boolean recursiveAssociationCheck(String testClassName, Set mockClassNames) { + for (String name : mockClassNames) { + if (recursiveAssociationCheck(testClassName, name)) { + return true; + } + } + return false; } /** @@ -54,6 +75,10 @@ public class MockAssociationUtil { } } + public static void recordSubMockContainer(String superClassName, String subClassName) { + subMockContainers.get(superClassName).add(subClassName); + } + private static boolean isAssociatedByInnerMockClass(String testClassName, String mockClassName) { return mockClassName.equals(String.format("%s$%s", testClassName, MOCK_POSTFIX)); }