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 2096cff..6e7b782 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 @@ -10,9 +10,6 @@ public class ConstPool { public static final String DOLLAR = "$"; public static final String UNDERLINE = "_"; - public static final String TEST_POSTFIX = "Test"; - public static final String MOCK_POSTFIX = "Mock"; - public static final String FIELD_TARGET_METHOD = "targetMethod"; public static final String FIELD_TARGET_CLASS = "targetClass"; diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/TestableClassTransformer.java b/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/TestableClassTransformer.java index 3695dd6..0914513 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/TestableClassTransformer.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/TestableClassTransformer.java @@ -7,14 +7,12 @@ import com.alibaba.testable.agent.handler.TestClassHandler; import com.alibaba.testable.agent.model.MethodInfo; import com.alibaba.testable.agent.util.*; import com.alibaba.testable.core.model.ClassType; -import com.alibaba.testable.core.model.LogLevel; import com.alibaba.testable.core.util.LogUtil; import com.alibaba.testable.core.util.MockContextUtil; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InnerClassNode; -import org.objectweb.asm.tree.MethodNode; import java.io.File; import java.io.FileOutputStream; @@ -25,6 +23,7 @@ import java.util.List; import static com.alibaba.testable.agent.constant.ConstPool.*; import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassName; +import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_STATIC; @@ -183,7 +182,7 @@ public class TestableClassTransformer implements ClassFileTransformer { // look for MockWith annotation String mockClassName = parseMockWithAnnotation(cn, ClassType.TestClass); if (mockClassName != null) { - MockContextUtil.mockToTests.get(mockClassName).add(className); + MockContextUtil.mockToTests.get(mockClassName).add(ClassUtil.toDotSeparateFullClassName(className)); return ClassUtil.toSlashSeparatedName(mockClassName); } // look for Mock inner class 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 ad850f2..098abd1 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 @@ -11,6 +11,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static com.alibaba.testable.core.constant.ConstPool.MOCK_POSTFIX; +import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX; import static org.objectweb.asm.Opcodes.INVOKESTATIC; /** @@ -93,7 +95,7 @@ public class ClassUtil { * @return mock class name */ public static String getMockClassName(String sourceClassName) { - return sourceClassName + ConstPool.MOCK_POSTFIX; + return sourceClassName + MOCK_POSTFIX; } /** @@ -102,7 +104,7 @@ public class ClassUtil { * @return test class name */ public static String getTestClassName(String sourceClassName) { - return sourceClassName + ConstPool.TEST_POSTFIX; + return sourceClassName + TEST_POSTFIX; } /** @@ -111,7 +113,7 @@ public class ClassUtil { * @return source class name */ public static String getSourceClassName(String testClassName) { - return testClassName.substring(0, testClassName.length() - ConstPool.TEST_POSTFIX.length()); + return testClassName.substring(0, testClassName.length() - TEST_POSTFIX.length()); } /** diff --git a/testable-core/src/main/java/com/alibaba/testable/core/constant/ConstPool.java b/testable-core/src/main/java/com/alibaba/testable/core/constant/ConstPool.java new file mode 100644 index 0000000..6d1d51e --- /dev/null +++ b/testable-core/src/main/java/com/alibaba/testable/core/constant/ConstPool.java @@ -0,0 +1,8 @@ +package com.alibaba.testable.core.constant; + +public class ConstPool { + + public static final String TEST_POSTFIX = "Test"; + public static final String MOCK_POSTFIX = "Mock"; + +} diff --git a/testable-core/src/main/java/com/alibaba/testable/core/model/MockContext.java b/testable-core/src/main/java/com/alibaba/testable/core/model/MockContext.java index 9c0ce30..7bf7045 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/model/MockContext.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/model/MockContext.java @@ -9,6 +9,9 @@ import java.util.Map; public class MockContext { + /** + * dot separated class name + */ public final String testClassName; public final String testCaseName; diff --git a/testable-core/src/main/java/com/alibaba/testable/core/util/MockContextUtil.java b/testable-core/src/main/java/com/alibaba/testable/core/util/MockContextUtil.java index c9855dc..27c511b 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/util/MockContextUtil.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/util/MockContextUtil.java @@ -5,31 +5,40 @@ import com.alibaba.ttl.TransmittableThreadLocal; import java.util.*; +import static com.alibaba.testable.core.constant.ConstPool.MOCK_POSTFIX; +import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX; + public class MockContextUtil { + /** + * Container to store context information of each test case + */ public static InheritableThreadLocal context = new TransmittableThreadLocal(); /** - * mock class referred by @MockWith annotation to list of its test classes + * Mock class referred by @MockWith annotation to list of its test classes + * MockClassName (dot-separated) → Set of associated [TestClassNames (dot-separated)] */ public static Map> mockToTests = UnnullableMap.of(new HashSet()); /** - * [0]Thread → [1]MockContextUtil → [2]TestClass + * [0]Thread → [1]MockContextUtil → [2]TestClass/MockClass */ - public static final int INDEX_OF_TEST_CLASS = 2; + public static final int INDEX_OF_INVOKER_CLASS = 2; /** - * Should be invoked at the beginning of each test case method + * Initialize mock context + * should be invoked at the beginning of each test case method */ public static void init() { - String testClassName = Thread.currentThread().getStackTrace()[INDEX_OF_TEST_CLASS].getClassName(); - String testCaseName = Thread.currentThread().getStackTrace()[INDEX_OF_TEST_CLASS].getMethodName(); + String testClassName = Thread.currentThread().getStackTrace()[INDEX_OF_INVOKER_CLASS].getClassName(); + String testCaseName = Thread.currentThread().getStackTrace()[INDEX_OF_INVOKER_CLASS].getMethodName(); context.set(new MockContext(testClassName, testCaseName)); } /** - * Should be invoked at the end of each test case execution + * Clean up mock context + * should be invoked at the end of each test case execution */ public static void clean() { context.remove(); @@ -40,4 +49,30 @@ public class MockContextUtil { return mockContext == null ? new HashMap() : mockContext.parameters; } + /** + * Check whether current mock method is invoked from its associated test class + * should be invoked in mock method + */ + public static boolean isAssociated() { + MockContext mockContext = context.get(); + String testClassName = (mockContext == null) ? "" : mockContext.testClassName; + String mockClassName = Thread.currentThread().getStackTrace()[INDEX_OF_INVOKER_CLASS].getClassName(); + return isAssociatedByInnerMockClass(testClassName, mockClassName) || + isAssociatedByOuterMockClass(testClassName, mockClassName) || + isAssociatedByMockWithAnnotation(testClassName, mockClassName); + } + + private static boolean isAssociatedByInnerMockClass(String testClassName, String mockClassName) { + return mockClassName.equals(String.format("%s$%s", testClassName, MOCK_POSTFIX)); + } + + private static boolean isAssociatedByOuterMockClass(String testClassName, String mockClassName) { + return testClassName.endsWith(TEST_POSTFIX) && + mockClassName.equals(testClassName.substring(0, testClassName.length() - 4) + MOCK_POSTFIX); + } + + private static boolean isAssociatedByMockWithAnnotation(String testClassName, String mockClassName) { + return mockToTests.get(mockClassName).contains(testClassName); + } + } diff --git a/testable-core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java b/testable-core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java index 137c13b..5f8506c 100644 --- a/testable-core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java +++ b/testable-core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java @@ -7,11 +7,11 @@ package com.alibaba.testable.core.util; public class TestableUtil { /** - * [0]Thread.getStackTrace() → [1]currentSourceMethodName() → [2]MockMethod -> [3]SourceMethod + * [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 + * [0]Thread.getStackTrace() → [1]previousStackLocation() → [2]Invoker → [3]Caller of invoker */ private static final int INDEX_OF_CALLER_METHOD = 3; diff --git a/testable-core/src/test/java/com/alibaba/testable/core/util/MockContextUtilTest.java b/testable-core/src/test/java/com/alibaba/testable/core/util/MockContextUtilTest.java new file mode 100644 index 0000000..c44e510 --- /dev/null +++ b/testable-core/src/test/java/com/alibaba/testable/core/util/MockContextUtilTest.java @@ -0,0 +1,26 @@ +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 MockContextUtilTest { + + @Test + void should_able_to_associate_by_inner_mock_class() { + assertTrue((Boolean)PrivateAccessor.invokeStatic(MockContextUtil.class, "isAssociatedByInnerMockClass", + "com.alibaba.testable.DemoTest", "com.alibaba.testable.DemoTest$Mock")); + assertFalse((Boolean)PrivateAccessor.invokeStatic(MockContextUtil.class, "isAssociatedByInnerMockClass", + "com.alibaba.testable.DemoTest", "com.alibaba.testable.DemoTestMock")); + } + + @Test + void should_able_to_associate_by_outer_mock_class() { + assertTrue((Boolean)PrivateAccessor.invokeStatic(MockContextUtil.class, "isAssociatedByOuterMockClass", + "com.alibaba.testable.DemoTest", "com.alibaba.testable.DemoMock")); + assertFalse((Boolean)PrivateAccessor.invokeStatic(MockContextUtil.class, "isAssociatedByOuterMockClass", + "com.alibaba.testable.DemoTester", "com.alibaba.testable.DemoMock")); + } + +}