Compare commits

..

8 Commits

Author SHA1 Message Date
金戟
d57dedb6fc release v0.4.12 2021-02-07 10:41:07 +08:00
金戟
d860684e91 add switch to disable private access target existence check 2021-02-06 21:56:36 +08:00
金戟
7bbdf82a41 support specify package prefix of class to be mocked 2021-02-06 11:31:51 +08:00
金戟
126c7257d8 fix out of index exception when mocking native method 2021-02-06 10:44:51 +08:00
金戟
adfba4bac0 support verbose log for self diagnosing 2021-02-05 22:30:04 +08:00
金戟
2c18ea12ed use space instead of tab 2021-02-04 17:59:26 +08:00
金戟
d4bce13918 remove note about 0.4.5 version 2021-02-03 18:03:07 +08:00
金戟
b23b8b98b3 add self troubleshooting information 2021-02-02 14:24:12 +08:00
32 changed files with 314 additions and 206 deletions

View File

@@ -13,8 +13,8 @@ repositories {
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
testImplementation('com.alibaba.testable:testable-all:0.4.11')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.11')
testImplementation('com.alibaba.testable:testable-all:0.4.12')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.12')
}
tasks.withType(JavaCompile) {

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.6.2</junit.version>
<testable.version>0.4.11</testable.version>
<testable.version>0.4.12</testable.version>
</properties>
<dependencies>
@@ -33,14 +33,14 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
</configuration>
</plugin>
<configuration>
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
</configuration>
</plugin>
<!-- <plugin>-->
<!-- <groupId>com.alibaba.testable</groupId>-->
<!-- <artifactId>testable-maven-plugin</artifactId>-->

View File

@@ -8,8 +8,8 @@ import org.junit.jupiter.api.Test;
import java.util.concurrent.Executors;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
import static com.alibaba.testable.core.tool.TestableTool.MOCK_CONTEXT;
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**

View File

@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.4.10"
kotlin("jvm") version "1.4.10"
}
group = "com.alibaba.testable"
@@ -9,25 +9,25 @@ version = "1.0.0-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8
repositories {
mavenCentral()
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
testImplementation("com.alibaba.testable:testable-all:0.4.11")
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.4.11")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
testImplementation("com.alibaba.testable:testable-all:0.4.12")
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.4.12")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
tasks.withType<Test> {
jvmArgs("-javaagent:${classpath.find { it.name.contains("testable-agent") }!!.absolutePath}")
useJUnitPlatform()
jvmArgs("-javaagent:${classpath.find { it.name.contains("testable-agent") }!!.absolutePath}")
useJUnitPlatform()
}

View File

@@ -1,119 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.testable</groupId>
<artifactId>kotlin-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>kotlin-demo</name>
<description>Demo project for TestableMock</description>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.testable</groupId>
<artifactId>kotlin-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>kotlin-demo</name>
<description>Demo project for TestableMock</description>
<properties>
<java.version>1.8</java.version>
<kotlin.version>1.3.72</kotlin.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.6.2</junit.version>
<testable.version>0.4.11</testable.version>
</properties>
<properties>
<java.version>1.8</java.version>
<kotlin.version>1.3.72</kotlin.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.6.2</junit.version>
<testable.version>0.4.12</testable.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-all</artifactId>
<version>${testable.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-all</artifactId>
<version>${testable.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<!-- <configuration>-->
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
<!-- </configuration>-->
</plugin>
<plugin>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-maven-plugin</artifactId>
<version>${testable.version}</version>
<executions>
<execution>
<id>prepare</id>
<goals>
<goal>prepare</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>target/jacoco.exec</dataFile>
<outputDirectory>target/jacoco-ut</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<!-- <configuration>-->
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
<!-- </configuration>-->
</plugin>
<plugin>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-maven-plugin</artifactId>
<version>${testable.version}</version>
<executions>
<execution>
<id>prepare</id>
<goals>
<goal>prepare</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>target/jacoco.exec</dataFile>
<outputDirectory>target/jacoco-ut</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,5 +1,11 @@
# Release Note
## 0.4.12
- support verbose diagnose log for better self-troubleshooting
- support disable private access target existence check
- support specify mock scanning packages
- fix an ArrayIndexOutOfBoundsException issue when transforming native method
## 0.4.11
- support accessing private members of class under test in different package path
- validate the number of private method parameters accessed by `PrivateAccessor`

View File

@@ -16,7 +16,7 @@ It is recommended to add a `property` field that identifies the TestableMock ver
```xml
<properties>
<testable.version>0.4.11</testable.version>
<testable.version>0.4.12</testable.version>
</properties>
```
@@ -63,8 +63,8 @@ Add dependence of `TestableMock` in `build.gradle` file:
```groovy
dependencies {
testImplementation('com.alibaba.testable:testable-all:0.4.11')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.11')
testImplementation('com.alibaba.testable:testable-all:0.4.12')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.12')
}
```

View File

@@ -38,3 +38,9 @@ The output log example is as follows:
```
The log shows all the mocked invocation and corresponding code line numbers in the class under test.
- Self troubleshooting:
- If there is no output, please check whether the `pom.xml` or `build.gradle` configuration correctly introduces `TestableMock` dependencies
- If only the first line of `Handling test class` is output, please check whether the test class is in the same package of the class under test, and the name is "<ClassUnderTest>+Test" (required for `0.4.x` version)
- If `Handling source class` and `Handling method xxx` are output, but there is no mock replacement happen at the expected code line, please check whether the mock method definition matches the target method

View File

@@ -5,10 +5,6 @@ Use TestableMock In IDE
IntelliJ IDE supports the `JSR-269` annotation processor and the `maven-surefire-plugin` arguments very well (both are techniques back the `TestableMock`). Usually you don't need any special configuration to make everything work, it's all out of the box.
> In IntelliJ 2020.3 and later versions, its built-in annotation processor had some parameter types changed, which no longer consistent with the standard `Maven` compilation process. In the version of `TestableMock` lower than `0.4.5`, you will encounter the problem that the `@EnablePrivateAccess` annotation does not take effect. You can turn on "Delegate IDE build/run actions to maven" option in "Build Tools > Maven > Runner" of the system configuration to solve the problem.
>
> ![delegate-ide-build-to-maven](https://testable-code.oss-cn-beijing.aliyuncs.com/delegate-ide-build-to-maven.png)
## Use Eclipse IDE
Since the built-in compilation feature of `Eclipse` is based on a self-made compiler, it is not compatible with the standard `javac` compilation process, which will cause the `@EnablePrivateAccess` annotation to be invalid when running test cases in the IDE. However, the function of accessing the private members of the class under test through the `PrivateAccessor` tool class will not be affected by differences in the compiler.

View File

@@ -1,5 +1,11 @@
# Release Note
## 0.4.12
- 支持`VERBOSE`级别的Mocking过程日志增强错误自助排查能力
- 支持使用`verifyTargetOnCompile`参数禁用编译期私有目标校验功能
- 支持通过agent参数指定Mock目标的扫描包范围
- 修复一处`ArrayIndexOutOfBoundsException`异常 (issue-52
## 0.4.11
- 支持测试类访问与自身包路径不同的被测类的私有成员
- 增加`PrivateAccessor`访问的私有方法参数数目检查,提高抗代码重构能力

View File

@@ -16,7 +16,7 @@
```xml
<properties>
<testable.version>0.4.11</testable.version>
<testable.version>0.4.12</testable.version>
</properties>
```
@@ -63,8 +63,8 @@
```groovy
dependencies {
testImplementation('com.alibaba.testable:testable-all:0.4.11')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.11')
testImplementation('com.alibaba.testable:testable-all:0.4.12')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.12')
}
```

View File

@@ -38,3 +38,9 @@ class DemoTest {
```
该日志展示了被测类中所有发生了Mock替换的调用和相应代码行号。
简单排查方法:
- 若没有任何输出,请检查`pom.xml``build.gradle`配置是否正确引入了TestableMock依赖
- 若只输出了第一行`Handling test class`,请检查被测类与测试类是否包路径相同,且名称为"被测类+Test"`0.4.x`版本要求)
- 若输出了`Handling source class`以及`Handling method xxx`但预期的代码行位置没有发生Mock替换请检查Mock方法定义是否未与目标方法匹配

View File

@@ -5,10 +5,6 @@
IntelliJ IDE对`TestableMock`所用到的`JSR-269`注释处理器以及`maven-surefire-plugin`插件的附加参数均支持良好。通常无需特殊配置,可开箱即用。
> 在IntelliJ 2020.3及后续版本里,其内置的编译期注释处理器运行时参数类型与标准`Maven`编译过程不一致。在`TestableMock`低于`0.4.5`的版本中会遇到`@EnablePrivateAccess`注解未生效的问题,可通过在系统配置的"Build Tools > Maven > Runner"中开启"Delegate IDE build/run actions to maven"选项解决。
>
> ![delegate-ide-build-to-maven](https://testable-code.oss-cn-beijing.aliyuncs.com/delegate-ide-build-to-maven.png)
## 使用Eclipse IDE
由于`Eclipse`内置的自动编译功能基于三方编译器实现,与标准`javac`编译过程不兼容会导致在IDE中运行测试用例时`@EnablePrivateAccess`注解无效。不过,通过`PrivateAccessor`工具类访问被测类私有成员的功能不会受编译器差异影响。

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-agent</artifactId>

View File

@@ -14,6 +14,7 @@ public class PreMain {
private static final String AND = "&";
private static final String LOG_LEVEL = "logLevel";
private static final String DUMP_PATH = "dumpPath";
private static final String PKG_PREFIX = "pkgPrefix";
private static final String EQUAL = "=";
public static void premain(String agentArgs, Instrumentation inst) {
@@ -34,6 +35,8 @@ public class PreMain {
GlobalConfig.setLogLevel(v);
} else if (k.equals(DUMP_PATH)) {
GlobalConfig.setDumpPath(v);
} else if (k.equals(PKG_PREFIX)) {
GlobalConfig.setPkgPrefix(v);
}
} else {
GlobalConfig.setLogLevel(a);

View File

@@ -60,29 +60,21 @@ public class SourceClassHandler extends BaseClassHandler {
Set<MethodInfo> newOperatorInjectMethods) {
LogUtil.diagnose(" Handling method %s", mn.name);
AbstractInsnNode[] instructions = mn.instructions.toArray();
if (instructions.length == 0) {
// native method (issue-52)
return;
}
int i = 0;
int maxStackDiff = 0;
do {
if (invokeOps.contains(instructions[i].getOpcode())) {
MethodInsnNode node = (MethodInsnNode)instructions[i];
MethodInfo mockMethod = getMemberInjectMethodName(memberInjectMethods, node);
if (mockMethod != null) {
// it's a member or static method and an inject method for it exist
int rangeStart = getMemberMethodStart(instructions, i);
if (rangeStart >= 0) {
ModifiedInsnNodes modifiedInsnNodes = replaceMemberCallOps(cn, mn, mockMethod,
instructions, node.owner, node.getOpcode(), rangeStart, i);
instructions = modifiedInsnNodes.nodes;
maxStackDiff = Math.max(maxStackDiff, modifiedInsnNodes.stackDiff);
i = rangeStart;
} else {
LogUtil.warn("Potential missed mocking at %s:%s", mn.name, getLineNum(instructions, i));
}
} else if (ConstPool.CONSTRUCTOR.equals(node.name)) {
// it's a new operation
if (ConstPool.CONSTRUCTOR.equals(node.name)) {
LogUtil.verbose(" Line %d, constructing \"%s\" as \"%s\"", getLineNum(instructions, i),
node.owner, node.desc);
String newOperatorInjectMethodName = getNewOperatorInjectMethodName(newOperatorInjectMethods, node);
if (newOperatorInjectMethodName != null) {
// and an inject method for it exist
// it's a new operation and an inject method for it exist
int rangeStart = getConstructorStart(instructions, node.owner, i);
if (rangeStart >= 0) {
ModifiedInsnNodes modifiedInsnNodes = replaceNewOps(cn, mn, newOperatorInjectMethodName,
@@ -92,6 +84,23 @@ public class SourceClassHandler extends BaseClassHandler {
i = rangeStart;
}
}
} else {
LogUtil.verbose(" Line %d, invoking \"%s\" as \"%s\"", getLineNum(instructions, i),
node.name, node.desc);
MethodInfo mockMethod = getMemberInjectMethodName(memberInjectMethods, node);
if (mockMethod != null) {
// it's a member or static method and an inject method for it exist
int rangeStart = getMemberMethodStart(instructions, i);
if (rangeStart >= 0) {
ModifiedInsnNodes modifiedInsnNodes = replaceMemberCallOps(cn, mn, mockMethod,
instructions, node.owner, node.getOpcode(), rangeStart, i);
instructions = modifiedInsnNodes.nodes;
maxStackDiff = Math.max(maxStackDiff, modifiedInsnNodes.stackDiff);
i = rangeStart;
} else {
LogUtil.warn("Potential missed mocking at %s:%s", mn.name, getLineNum(instructions, i));
}
}
}
}
i++;
@@ -194,7 +203,7 @@ public class SourceClassHandler extends BaseClassHandler {
private ModifiedInsnNodes replaceNewOps(ClassNode cn, MethodNode mn, String newOperatorInjectMethodName,
AbstractInsnNode[] instructions, int start, int end) {
LogUtil.diagnose(" Line %d, mock method %s used", getLineNum(instructions, start),
LogUtil.diagnose(" Line %d, mock method \"%s\" used", getLineNum(instructions, start),
newOperatorInjectMethodName);
String classType = ((TypeInsnNode)instructions[start]).desc;
String constructorDesc = ((MethodInsnNode)instructions[end]).desc;
@@ -224,7 +233,7 @@ public class SourceClassHandler extends BaseClassHandler {
private ModifiedInsnNodes replaceMemberCallOps(ClassNode cn, MethodNode mn, MethodInfo mockMethod,
AbstractInsnNode[] instructions, String ownerClass,
int opcode, int start, int end) {
LogUtil.diagnose(" Line %d, mock method %s used", getLineNum(instructions, start),
LogUtil.diagnose(" Line %d, mock method \"%s\" used", getLineNum(instructions, start),
mockMethod.getMockName());
boolean shouldAppendTypeParameter = !mockMethod.getDesc().equals(mockMethod.getMockDesc());
String testClassName = ClassUtil.getTestClassName(cn.name);

View File

@@ -36,6 +36,7 @@ import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassNa
public class TestableClassTransformer implements ClassFileTransformer {
private static final String FIELD_DIAGNOSE = "diagnose";
private static final String COMMA = ",";
/**
* Just avoid spend time to scan those surely non-user classes
@@ -57,21 +58,21 @@ public class TestableClassTransformer implements ClassFileTransformer {
try {
if (shouldTransformAsSourceClass(className)) {
// it's a source class with testable enabled
LogUtil.diagnose("Handling source class %s", className);
List<MethodInfo> injectMethods = getTestableMockMethods(ClassUtil.getTestClassName(className));
LogUtil.diagnose("Handling source class %s", className);
bytes = new SourceClassHandler(injectMethods).getBytes(classFileBuffer);
dumpByte(className, bytes);
resetMockContext();
} else if (shouldTransformAsTestClass(className)) {
// it's a test class with testable enabled
LogUtil.diagnose("Handling test class %s", className);
bytes = new TestClassHandler().getBytes(classFileBuffer);
dumpByte(className, bytes);
resetMockContext();
}
} catch (Throwable t) {
LogUtil.warn("Failed to transform class " + className);
LogUtil.diagnose(t.toString());
} finally {
LogUtil.resetLogLevel();
}
return bytes;
}
@@ -105,14 +106,25 @@ public class TestableClassTransformer implements ClassFileTransformer {
if (null == className) {
return true;
}
for (String prefix : WHITELIST_PREFIXES) {
if (className.startsWith(prefix)) {
return false;
String whitePrefix = GlobalConfig.getPkgPrefix();
if (whitePrefix != null) {
for (String prefix : whitePrefix.split(COMMA)) {
if (className.startsWith(prefix)) {
// Only consider package in provided list as non-system class
return false;
}
}
}
for (String prefix : BLACKLIST_PREFIXES) {
if (className.startsWith(prefix)) {
return true;
return true;
} else {
for (String prefix : WHITELIST_PREFIXES) {
if (className.startsWith(prefix)) {
return false;
}
}
for (String prefix : BLACKLIST_PREFIXES) {
if (className.startsWith(prefix)) {
return true;
}
}
}
return false;
@@ -140,9 +152,12 @@ public class TestableClassTransformer implements ClassFileTransformer {
for (AnnotationNode an : mn.visibleAnnotations) {
String fullClassName = toDotSeparateFullClassName(an.desc);
if (fullClassName.equals(ConstPool.MOCK_CONSTRUCTOR)) {
LogUtil.verbose(" Mock constructor \"%s\" as \"(%s)V\" for \"%s\"", mn.name,
ClassUtil.extractParameters(mn.desc), ClassUtil.getReturnType(mn.desc));
addMockConstructor(methodInfos, cn, mn);
} else if (fullClassName.equals(ConstPool.MOCK_METHOD) ||
fullClassName.equals(ConstPool.TESTABLE_MOCK)) {
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, mn.desc);
String targetMethod = AnnotationUtil.getAnnotationParameter(
an, ConstPool.FIELD_TARGET_METHOD, mn.name, String.class);
if (ConstPool.CONSTRUCTOR.equals(targetMethod)) {
@@ -218,14 +233,11 @@ public class TestableClassTransformer implements ClassFileTransformer {
private void setupMockContext(AnnotationNode an) {
MockDiagnose diagnose = AnnotationUtil.getAnnotationParameter(an, FIELD_DIAGNOSE, null, MockDiagnose.class);
if (diagnose != null) {
LogUtil.enableDiagnose(diagnose == MockDiagnose.ENABLE);
LogUtil.setLevel(diagnose == MockDiagnose.ENABLE ? LogUtil.LogLevel.LEVEL_DIAGNOSE :
(diagnose == MockDiagnose.VERBOSE ? LogUtil.LogLevel.LEVEL_VERBOSE : LogUtil.LogLevel.LEVEL_MUTE));
}
}
private void resetMockContext() {
LogUtil.resetLogLevel();
}
/**
* Split desc to "first parameter" and "desc of rest parameters"
* @param desc method desc

View File

@@ -140,6 +140,16 @@ public class ClassUtil {
return parameterTypes;
}
/**
* extract parameter part of method desc
* @param desc method description
* @return parameter value
*/
public static String extractParameters(String desc) {
int returnTypeEdge = desc.lastIndexOf(PARAM_END);
return desc.substring(1, returnTypeEdge);
}
/**
* parse method desc, fetch return value types
* @param desc method description

View File

@@ -12,6 +12,7 @@ public class GlobalConfig {
private static final String VERBOSE = "verbose";
private static String dumpPath = null;
private static String pkgPrefix = null;
public static boolean setLogLevel(String level) {
if (level.equals(MUTE)) {
@@ -35,4 +36,11 @@ public class GlobalConfig {
return dumpPath;
}
public static String getPkgPrefix() {
return pkgPrefix;
}
public static void setPkgPrefix(String pkgPrefix) {
GlobalConfig.pkgPrefix = pkgPrefix;
}
}

View File

@@ -15,6 +15,12 @@ class ClassUtilTest {
assertEquals(3, ClassUtil.getParameterTypes("(Ljava/lang/String;[I[Ljava/lang/String;)V").size());
}
@Test
void should_able_to_extract_parameter() {
assertEquals("", ClassUtil.extractParameters("()I"));
assertEquals("Ljava/lang/String;", ClassUtil.extractParameters("(Ljava/lang/String;)I"));
}
@Test
void should_able_to_get_return_type() {
assertEquals("", ClassUtil.getReturnType("(Ljava/lang/String;)V"));

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-all</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-core</artifactId>

View File

@@ -14,6 +14,11 @@ public enum MockDiagnose {
/**
* Print diagnose logs
*/
ENABLE
ENABLE,
/**
* Print verbose log
*/
VERBOSE
}

View File

@@ -54,8 +54,8 @@ public class LogUtil {
System.err.println(String.format("[ERROR] " + msg, args));
}
public static void enableDiagnose(boolean enable) {
currentLogLevel = enable ? LogLevel.LEVEL_DIAGNOSE : LogLevel.LEVEL_MUTE;
public static void setLevel(LogLevel level) {
currentLogLevel = level;
}
public static void setDefaultLevel(LogLevel level) {

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-maven-plugin</artifactId>

View File

@@ -1,5 +1,6 @@
package com.alibaba.testable;
import com.google.common.base.Strings;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
@@ -45,6 +46,12 @@ public class TestableMojo extends AbstractMojo
@Parameter
private String dumpPath;
/**
* Package prefixes of class need to be mocked (comma split)
*/
@Parameter
private String pkgPrefix;
/**
* Name of the Testable Agent artifact.
*/
@@ -71,13 +78,17 @@ public class TestableMojo extends AbstractMojo
getLog().error("failed to fetch project properties");
return;
}
String extraArgs = "";
if (logLevel != null && !logLevel.isEmpty()) {
if (!Strings.isNullOrEmpty(logLevel)) {
extraArgs += "&logLevel=" + logLevel;
}
if (dumpPath != null && !dumpPath.isEmpty()) {
if (!Strings.isNullOrEmpty(dumpPath)) {
extraArgs += "&dumpPath=" + dumpPath;
}
if (!Strings.isNullOrEmpty(pkgPrefix)) {
extraArgs += "&pkgPrefix=" + pkgPrefix;
}
final String oldArgs = projectProperties.getProperty(testArgsPropertyKey);
String newArgs = (oldArgs == null) ? getAgentJarArgs().trim() : (oldArgs + getAgentJarArgs());
if (!extraArgs.isEmpty()) {
@@ -91,6 +102,7 @@ public class TestableMojo extends AbstractMojo
final Artifact testableAgentArtifact = pluginArtifactMap.get(AGENT_ARTIFACT_NAME);
if (testableAgentArtifact == null) {
getLog().error("failed to find testable agent jar");
return "";
}
return " -javaagent:" + testableAgentArtifact.getFile().getAbsolutePath();
}

View File

@@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<packaging>pom</packaging>
<name>testable-parent</name>
<description>Unit test enhancement toolkit</description>
@@ -42,7 +42,7 @@
<plugin.gpg.version>1.6</plugin.gpg.version>
<plugin.staging.version>1.6.8</plugin.staging.version>
<plugin.maven.version>3.6.0</plugin.maven.version>
<testable.version>0.4.11</testable.version>
<testable.version>0.4.12</testable.version>
</properties>
<profiles>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-processor</artifactId>

View File

@@ -2,6 +2,7 @@ package com.alibaba.testable.processor;
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
import com.alibaba.testable.processor.constant.ConstPool;
import com.alibaba.testable.processor.model.Parameters;
import com.alibaba.testable.processor.model.TestableContext;
import com.alibaba.testable.processor.translator.EnablePrivateAccessTranslator;
import com.alibaba.testable.processor.util.JavacUtil;
@@ -31,6 +32,7 @@ import java.util.Set;
public class EnablePrivateAccessProcessor extends AbstractProcessor {
private static final String SRC_CLASS = "srcClass";
private static final String VERIFY_ON_COMPILE = "verifyTargetOnCompile";
private TestableContext cx;
@@ -58,8 +60,8 @@ public class EnablePrivateAccessProcessor extends AbstractProcessor {
for (Element element : elements) {
if (element.getKind().isClass()) {
Symbol.ClassSymbol testClass = (Symbol.ClassSymbol)element;
String sourceClassName = getSourceClassName(testClass);
processClassElement(testClass, sourceClassName);
Parameters parameters = getAnnotationParameters(testClass);
processClassElement(testClass, parameters);
}
}
return true;
@@ -71,17 +73,20 @@ public class EnablePrivateAccessProcessor extends AbstractProcessor {
return SourceVersion.values()[SourceVersion.values().length - 1];
}
private String getSourceClassName(Symbol.ClassSymbol testClass) {
private Parameters getAnnotationParameters(Symbol.ClassSymbol testClass) {
Parameters parameters = new Parameters();
for (Attribute.Compound annotation : testClass.getMetadata().getDeclarationAttributes()) {
if (ConstPool.ENABLE_PRIVATE_ACCESS.equals(annotation.type.tsym.toString())) {
for (Pair<Symbol.MethodSymbol, Attribute> p : annotation.values) {
if (SRC_CLASS.equals(p.fst.name.toString())) {
return p.snd.getValue().toString();
parameters.sourceClassName = p.snd.getValue().toString();
} else if (VERIFY_ON_COMPILE.equals(p.fst.name.toString())) {
parameters.verifyTargetExistence = (Boolean)p.snd.getValue();
}
}
}
}
return null;
return parameters;
}
private JavacProcessingEnvironment getJavacProcessingEnvironment(ProcessingEnvironment processingEnv) {
@@ -92,10 +97,10 @@ public class EnablePrivateAccessProcessor extends AbstractProcessor {
}
}
private void processClassElement(Symbol.ClassSymbol testClass, String sourceClassName) {
private void processClassElement(Symbol.ClassSymbol testClass, Parameters parameters) {
if (cx.trees != null) {
JCTree tree = cx.trees.getTree(testClass);
tree.accept(new EnablePrivateAccessTranslator(cx, testClass, sourceClassName));
tree.accept(new EnablePrivateAccessTranslator(cx, testClass, parameters));
}
}

View File

@@ -19,4 +19,10 @@ public @interface EnablePrivateAccess {
*/
Class<?> srcClass() default NullType.class;
/**
* whether enable compile-time existence verification for the private members accessed
* @return
*/
boolean verifyTargetOnCompile() default true;
}

View File

@@ -0,0 +1,12 @@
package com.alibaba.testable.processor.model;
/**
* @author flin
*/
public class Parameters {
public String sourceClassName;
public Boolean verifyTargetExistence;
}

View File

@@ -3,6 +3,7 @@ package com.alibaba.testable.processor.translator;
import com.alibaba.testable.processor.generator.PrivateAccessStatementGenerator;
import com.alibaba.testable.processor.model.MemberRecord;
import com.alibaba.testable.processor.model.MemberType;
import com.alibaba.testable.processor.model.Parameters;
import com.alibaba.testable.processor.model.TestableContext;
import com.alibaba.testable.processor.util.PathUtil;
import com.sun.tools.javac.code.Symbol;
@@ -51,13 +52,13 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
private final PrivateAccessStatementGenerator privateAccessStatementGenerator;
private final PrivateAccessChecker privateAccessChecker;
public EnablePrivateAccessTranslator(TestableContext cx, Symbol.ClassSymbol clazz, String srcClassName) {
public EnablePrivateAccessTranslator(TestableContext cx, Symbol.ClassSymbol clazz, Parameters p) {
String sourceClassFullName;
if (srcClassName == null) {
if (p.sourceClassName == null) {
String testClassFullName = clazz.fullname.toString();
sourceClassFullName = testClassFullName.substring(0, testClassFullName.length() - TEST_POSTFIX.length());
} else {
sourceClassFullName = srcClassName;
sourceClassFullName = p.sourceClassName;
}
String sourceClassShortName = sourceClassFullName.substring(sourceClassFullName.lastIndexOf('.') + 1);
this.privateAccessStatementGenerator = new PrivateAccessStatementGenerator(cx);
@@ -72,7 +73,8 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
} catch (Exception e) {
e.printStackTrace();
}
this.privateAccessChecker = new PrivateAccessChecker(cx, sourceClassShortName, memberRecord);
this.privateAccessChecker = (p.verifyTargetExistence == null || p.verifyTargetExistence) ?
new PrivateAccessChecker(cx, sourceClassShortName, memberRecord) : null;
}
/**
@@ -157,7 +159,9 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
} else if (memberType.equals(MemberType.STATIC_PRIVATE)) {
expr = privateAccessStatementGenerator.fetchStaticInvokeStatement(invocation);
}
privateAccessChecker.validate((JCMethodInvocation)expr);
if (privateAccessChecker != null) {
privateAccessChecker.validate((JCMethodInvocation)expr);
}
}
// check the casted expression
if (expr instanceof JCTypeCast) {