diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/config/PropertiesParser.java b/testable-agent/src/main/java/com/alibaba/testable/agent/config/PropertiesParser.java index 9170f47..594e3c7 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/config/PropertiesParser.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/config/PropertiesParser.java @@ -22,6 +22,7 @@ public class PropertiesParser { private static final String LOG_FILE = "log.file"; private static final String LOG_LEVEL = "log.level"; private static final String INNER_MOCK_CLASS_NAME = "mock.innerClass.name"; + private static final String MOCK_PKG_MAPPING_PREFIX = "mock.package.mapping."; private static final String DEFAULT_MOCK_SCOPE = "mock.scope.default"; private static final String ENABLE_OMNI_INJECT = "omni.constructor.enhance.enable"; private static final String OMNI_INJECT_EXCLUDES = "omni.constructor.enhance.pkgPrefix.excludes"; @@ -62,6 +63,8 @@ public class PropertiesParser { GlobalConfig.setLogLevel(v); } else if (k.equals(INNER_MOCK_CLASS_NAME)) { GlobalConfig.setInnerMockClassName(v); + } else if (k.startsWith(MOCK_PKG_MAPPING_PREFIX)) { + GlobalConfig.addMockPackageMapping(k.substring(MOCK_PKG_MAPPING_PREFIX.length()), v); } else if (k.equals(DEFAULT_MOCK_SCOPE)) { GlobalConfig.setDefaultMockScope(MockScope.of(v)); } else if (k.equals(ENABLE_OMNI_INJECT)) { 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 48af3dd..c1a4cf7 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 @@ -21,7 +21,6 @@ import java.security.ProtectionDomain; import java.util.List; import static com.alibaba.testable.agent.constant.ConstPool.*; -import static com.alibaba.testable.agent.util.ClassUtil.toJavaStyleClassName; import static com.alibaba.testable.core.constant.ConstPool.DOLLAR; import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX; import static com.alibaba.testable.core.util.PathUtil.createFolder; @@ -67,29 +66,26 @@ public class TestableClassTransformer implements ClassFileTransformer { } private byte[] transformMock(byte[] bytes, ClassNode cn) { - String className = cn.name; + String className = (GlobalConfig.getMockPackageMapping() == null) ? cn.name : mapPackage(cn.name); try { if (mockClassParser.isMockClass(cn)) { // it's a mock class LogUtil.diagnose("Found mock class %s", className); bytes = new MockClassHandler(className).getBytes(bytes); BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes); + } else if (foundMockForTestClass(className) != null) { + // it's a test class with testable enabled + LogUtil.diagnose("Found test class %s", className); + bytes = new TestClassHandler().getBytes(bytes); + BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes); } else { - String mockClass = foundMockForTestClass(className); + String mockClass = foundMockForSourceClass(className); if (mockClass != null) { - // it's a test class with testable enabled - LogUtil.diagnose("Found test class %s", className); - bytes = new TestClassHandler().getBytes(bytes); + // it's a source class with testable enabled + List injectMethods = mockClassParser.getTestableMockMethods(mockClass); + LogUtil.diagnose("Found source class %s", className); + bytes = new SourceClassHandler(injectMethods, mockClass).getBytes(bytes); BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes); - } else { - mockClass = foundMockForSourceClass(className); - if (mockClass != null) { - // it's a source class with testable enabled - List injectMethods = mockClassParser.getTestableMockMethods(mockClass); - LogUtil.diagnose("Found source class %s", className); - bytes = new SourceClassHandler(injectMethods, mockClass).getBytes(bytes); - BytecodeUtil.dumpByte(className, GlobalConfig.getDumpPath(), bytes); - } } } } catch (Throwable t) { @@ -103,6 +99,17 @@ public class TestableClassTransformer implements ClassFileTransformer { return bytes; } + private String mapPackage(String name) { + String dotSeparatedName = ClassUtil.toDotSeparatedName(name); + for (String prefix : GlobalConfig.getMockPackageMapping().keySet()) { + if (dotSeparatedName.startsWith(prefix)) { + return ClassUtil.toSlashSeparatedName(GlobalConfig.getMockPackageMapping().get(prefix)) + + name.substring(prefix.length()); + } + } + return name; + } + private String foundMockForSourceClass(String className) { String mockClass = lookForMockWithAnnotationAsSourceClass(className); if (mockClass != null) { @@ -236,7 +243,7 @@ public class TestableClassTransformer implements ClassFileTransformer { private String parseMockWithAnnotation(ClassNode cn, ClassType expectedType) { if (cn.visibleAnnotations != null) { for (AnnotationNode an : cn.visibleAnnotations) { - if (toJavaStyleClassName(an.desc).equals(ConstPool.MOCK_WITH)) { + if (ClassUtil.toJavaStyleClassName(an.desc).equals(ConstPool.MOCK_WITH)) { ClassType type = AnnotationUtil.getAnnotationParameter(an, FIELD_TREAT_AS, ClassType.GuessByName, ClassType.class); if (isExpectedType(cn.name, type, expectedType)) { @@ -254,7 +261,7 @@ public class TestableClassTransformer implements ClassFileTransformer { private String getDumpPathByAnnotation(ClassNode cn) { if (cn.visibleAnnotations != null) { for (AnnotationNode an : cn.visibleAnnotations) { - if (toJavaStyleClassName(an.desc).equals(ConstPool.DUMP_TO)) { + if (ClassUtil.toJavaStyleClassName(an.desc).equals(ConstPool.DUMP_TO)) { String path = AnnotationUtil.getAnnotationParameter(an, FIELD_VALUE, null, String.class); String fullPath = PathUtil.join(System.getProperty(PROPERTY_USER_DIR), path); if (createFolder(fullPath)) { diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/util/GlobalConfig.java b/testable-agent/src/main/java/com/alibaba/testable/agent/util/GlobalConfig.java index c397b8a..b6531c1 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/util/GlobalConfig.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/util/GlobalConfig.java @@ -7,12 +7,13 @@ import com.alibaba.testable.core.util.LogUtil; import java.io.File; import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static com.alibaba.testable.agent.constant.ConstPool.PROPERTY_TEMP_DIR; import static com.alibaba.testable.agent.constant.ConstPool.PROPERTY_USER_DIR; -import static com.alibaba.testable.core.constant.ConstPool.COMMA; -import static com.alibaba.testable.core.constant.ConstPool.DOT; +import static com.alibaba.testable.core.constant.ConstPool.*; import static com.alibaba.testable.core.util.PathUtil.createFolder; /** @@ -36,6 +37,7 @@ public class GlobalConfig { private static boolean enhanceThreadLocal = false; private static boolean enhanceOmniConstructor = false; private static String innerMockClassName = "Mock"; + private static Map mockPkgMapping = null; public static void setLogLevel(String level) { if (level.equals(MUTE)) { @@ -103,7 +105,7 @@ public class GlobalConfig { private static String getBuildOutputFolder() { String contextFolder = System.getProperty(PROPERTY_USER_DIR); - URL rootResourceFolder = Object.class.getResource("/"); + URL rootResourceFolder = Object.class.getResource(SLASH); if (rootResourceFolder != null) { return PathUtil.getFirstLevelFolder(contextFolder, rootResourceFolder.getPath()); } else if (PathUtil.folderExists(PathUtil.join(contextFolder, DEFAULT_MAVEN_OUTPUT_FOLDER))) { @@ -139,6 +141,17 @@ public class GlobalConfig { return innerMockClassName; } + public static void addMockPackageMapping(String originPkg, String mockClassPkg) { + if (mockPkgMapping == null) { + mockPkgMapping = new HashMap(5); + } + mockPkgMapping.put(originPkg + DOT, mockClassPkg + DOT); + } + + public static Map getMockPackageMapping() { + return mockPkgMapping; + } + private static List parsePkgPrefixList(String prefixes) { List whiteList = new ArrayList(); for (String p : prefixes.split(COMMA)) { 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 58a211e..2cfe37e 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 @@ -177,7 +177,6 @@ public class OmniConstructor { // don't travel null object return; } - LogUtil.verbose(classPool.size(), "Verifying %s", type.getName()); classPool.put(type, instance); for (Field f : TypeUtil.getAllFields(type)) { if (f.getName().startsWith("$") || isStaticFinalField(f)) { @@ -190,14 +189,14 @@ public class OmniConstructor { if (fieldType.isArray()) { Class componentType = fieldType.getComponentType(); if (fieldIns != null && !TypeUtil.isBasicType(componentType)) { - LogUtil.verbose(classPool.size(), "Field(Array[%d]) %s", Array.getLength(fieldIns), f.getName()); + LogUtil.verbose(classPool.size(), "Verifying Field(Array[%d]) %s", Array.getLength(fieldIns), f.getName()); handleCircleReferenceOfArrayField(fieldIns, componentType, classPool); } } else if (!TypeUtil.isBasicType(fieldType)) { if (fieldIns == null && classPool.containsKey(fieldType)) { f.set(instance, classPool.get(fieldType)); } else if (!classPool.containsKey(fieldType)) { - LogUtil.verbose(classPool.size(), "Field %s", f.getName()); + LogUtil.verbose(classPool.size(), "Verifying Field %s", f.getName()); handleCircleReference(fieldIns, fieldType, classPool); } }