mirror of
https://github.com/alibaba/testable-mock.git
synced 2026-08-21 02:33:31 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e46ec3d98a | ||
|
|
1849cf85c5 | ||
|
|
f8450d7047 | ||
|
|
196dffc73d | ||
|
|
1ff6ec5961 | ||
|
|
e50633b884 | ||
|
|
2976ca0211 | ||
|
|
24c6a9cc5c | ||
|
|
9e7ceb2dc1 | ||
|
|
8dfd0b4516 | ||
|
|
190cf4cc3c | ||
|
|
d49f526376 | ||
|
|
f01ff10fba | ||
|
|
f64b07e9da |
@@ -11,9 +11,8 @@ Usage Document: https://alibaba.github.io/testable-mock/#/en-us/
|
||||
|
||||
`TestableMock` is still under heavy development, the following version plans may be adjusted during the iteration
|
||||
|
||||
- `v0.4` it's the current version, refer to the [issue](https://github.com/alibaba/testable-mock/issues) list for the work in progress
|
||||
- `v0.5` implementation mock class inherit mechanism, so that the same mock method can be reused between test classes conveniently
|
||||
- `v0.6` narrows the default effective scope of mock methods to the class under test, to avoid interaction of mocking cross classes
|
||||
- `v0.5` it's the current version, refer to the [issue](https://github.com/alibaba/testable-mock/issues) list for the work in progress
|
||||
- `v0.6` implement the functionality of "parameter constructor"
|
||||
- `v1.0` all functions are stable, a brand-new start
|
||||
|
||||
## Directory Structure
|
||||
|
||||
@@ -13,8 +13,8 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.5.0')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.5.0')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.5.1')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.5.1')
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
|
||||
@@ -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.5.0</testable.version>
|
||||
<testable.version>0.5.1</testable.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.alibaba.testable.demo.basic;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockConstructor;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import com.alibaba.testable.demo.basic.DemoMock;
|
||||
import com.alibaba.testable.demo.basic.model.BlackBox;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ 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.5.0")
|
||||
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.5.0")
|
||||
testImplementation("com.alibaba.testable:testable-all:0.5.1")
|
||||
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.5.1")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
|
||||
@@ -14,7 +14,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.5.0</testable.version>
|
||||
<testable.version>0.5.1</testable.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
4
docs/en-us/doc/annotations.md
Normal file
4
docs/en-us/doc/annotations.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Available Annotations
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
4
docs/en-us/doc/design-and-mechanism.md
Normal file
4
docs/en-us/doc/design-and-mechanism.md
Normal file
@@ -0,0 +1,4 @@
|
||||
How Mock Works
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
4
docs/en-us/doc/javaagent-args.md
Normal file
4
docs/en-us/doc/javaagent-args.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Available Global Arguments
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
4
docs/en-us/doc/mock-method-reusing.md
Normal file
4
docs/en-us/doc/mock-method-reusing.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Reusing Mock Methods
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
@@ -1,5 +1,11 @@
|
||||
# Release Note
|
||||
|
||||
## 0.5.1
|
||||
- Use kotlin-style method descriptor in `VERBOSE` diagnose logs
|
||||
- Support parameterized test of JUnit 5
|
||||
- Fix an exception caused by method parameter with ternary operator
|
||||
- Fix a bug cause log message lost when `@MockWith` annotation used
|
||||
|
||||
## 0.4.12
|
||||
- support verbose diagnose log for better self-troubleshooting
|
||||
- support disable private access target existence check
|
||||
|
||||
4
docs/en-us/doc/scope-of-mock.md
Normal file
4
docs/en-us/doc/scope-of-mock.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Effective Scope Of Mocking
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
@@ -16,7 +16,7 @@ It is recommended to add a `property` field that identifies the TestableMock ver
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
<testable.version>0.5.0</testable.version>
|
||||
<testable.version>0.5.1</testable.version>
|
||||
</properties>
|
||||
```
|
||||
|
||||
@@ -63,8 +63,8 @@ Add dependence of `TestableMock` in `build.gradle` file:
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
testImplementation('com.alibaba.testable:testable-all:0.5.0')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.5.0')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.5.1')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.5.1')
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,9 +15,11 @@ class DemoTest {
|
||||
The output log example is as follows:
|
||||
|
||||
```text
|
||||
[DIAGNOSE] Handling test class com/alibaba/testable/demo/DemoMockTest
|
||||
[DIAGNOSE] Handling test class com/alibaba/testable/demo/basic/DemoMockTest
|
||||
[DIAGNOSE] Found 6 test cases
|
||||
[DIAGNOSE] Handling mock class com/alibaba/testable/demo/basic/DemoMockTest$Mock
|
||||
[DIAGNOSE] Found 8 mock methods
|
||||
[DIAGNOSE] Handling source class com/alibaba/testable/demo/DemoMock
|
||||
[DIAGNOSE] Handling source class com/alibaba/testable/demo/basic/DemoMock
|
||||
[DIAGNOSE] Handling method <init>
|
||||
[DIAGNOSE] Handling method newFunc
|
||||
[DIAGNOSE] Line 19, mock method "createBlackBox" used
|
||||
@@ -48,39 +50,33 @@ class DemoTest {
|
||||
}
|
||||
```
|
||||
|
||||
Executing the unit test again will print out the runtime-signatures of all mock methods, and the runtime-signatures of all invocations scanned in the class under test:
|
||||
Executing the unit test again will print out the signatures of all mock methods, and the signatures of all invocations scanned in the class under test:
|
||||
|
||||
```text
|
||||
[DIAGNOSE] Handling test class com/alibaba/testable/demo/DemoMockTest
|
||||
[VERBOSE] Mock constructor "createBlackBox" as "(Ljava/lang/String;)V" for "com/alibaba/testable/demo/model/BlackBox"
|
||||
[VERBOSE] Mock method "innerFunc" as "(Ljava/lang/String;)Ljava/lang/String;"
|
||||
[VERBOSE] Mock method "staticFunc" as "()Ljava/lang/String;"
|
||||
[VERBOSE] Mock method "trim" as "()Ljava/lang/String;"
|
||||
[VERBOSE] Mock method "sub" as "(II)Ljava/lang/String;"
|
||||
[VERBOSE] Mock method "startsWith" as "(Ljava/lang/String;)Z"
|
||||
[VERBOSE] Mock method "secretBox" as "()Lcom/alibaba/testable/demo/model/BlackBox;"
|
||||
[DIAGNOSE] Handling test class com/alibaba/testable/demo/basic/DemoMockTest
|
||||
[VERBOSE] Test case "should_able_to_mock_new_object"
|
||||
... ...
|
||||
[VERBOSE] Test case "should_able_to_set_mock_context"
|
||||
[DIAGNOSE] Found 6 test cases
|
||||
[DIAGNOSE] Handling mock class com/alibaba/testable/demo/basic/DemoMockTest$Mock
|
||||
[VERBOSE] Mock constructor "createBlackBox" as "com.alibaba.testable.demo.basic.model.BlackBox(java.lang.String)"
|
||||
[VERBOSE] Mock method "innerFunc" as "com.alibaba.testable.demo.basic.DemoMock::innerFunc(java.lang.String) : java.lang.String"
|
||||
... ...
|
||||
[VERBOSE] Mock method "callFromDifferentMethod" as "()Ljava/lang/String;"
|
||||
[DIAGNOSE] Found 8 mock methods
|
||||
[DIAGNOSE] Handling source class com/alibaba/testable/demo/DemoMock
|
||||
[DIAGNOSE] Handling source class com/alibaba/testable/demo/basic/DemoMock
|
||||
[DIAGNOSE] Handling method <init>
|
||||
[VERBOSE] Line 13, constructing "java/lang/Object" as "()V"
|
||||
[VERBOSE] Line 13, constructing "java.lang.Object()"
|
||||
[DIAGNOSE] Handling method newFunc
|
||||
[VERBOSE] Line 19, constructing "com/alibaba/testable/demo/model/BlackBox" as "(Ljava/lang/String;)V"
|
||||
[VERBOSE] Line 19, constructing "com.alibaba.testable.demo.basic.model.BlackBox(java.lang.String)"
|
||||
[DIAGNOSE] Line 19, mock method "createBlackBox" used
|
||||
[VERBOSE] Line 19, invoking "createBlackBox" as "(Ljava/lang/String;)Lcom/alibaba/testable/demo/model/BlackBox;"
|
||||
[VERBOSE] Line 20, invoking "get" as "()Ljava/lang/String;"
|
||||
[VERBOSE] Line 19, invoking "com.alibaba.testable.demo.basic.DemoMockTest$Mock::createBlackBox(java.lang.String) : com.alibaba.testable.demo.basic.model.BlackBox"
|
||||
[VERBOSE] Line 20, invoking "com.alibaba.testable.demo.basic.model.BlackBox::get() : java.lang.String"
|
||||
[DIAGNOSE] Handling method outerFunc
|
||||
[VERBOSE] Line 27, constructing "java/lang/StringBuilder" as "()V"
|
||||
[VERBOSE] Line 27, invoking "append" as "(Ljava/lang/String;)Ljava/lang/StringBuilder;"
|
||||
[VERBOSE] Line 27, invoking "innerFunc" as "(Ljava/lang/String;)Ljava/lang/String;"
|
||||
[VERBOSE] Line 27, constructing "java.lang.StringBuilder()"
|
||||
[VERBOSE] Line 27, invoking "java.lang.StringBuilder::append(java.lang.String) : java.lang.StringBuilder"
|
||||
[VERBOSE] Line 27, invoking "com.alibaba.testable.demo.basic.DemoMock::innerFunc(java.lang.String) : java.lang.String"
|
||||
[DIAGNOSE] Line 27, mock method "innerFunc" used
|
||||
[VERBOSE] Line 27, invoking "innerFunc" as "(Ljava/lang/String;)Ljava/lang/String;"
|
||||
[VERBOSE] Line 27, invoking "append" as "(Ljava/lang/String;)Ljava/lang/StringBuilder;"
|
||||
[VERBOSE] Line 27, invoking "staticFunc" as "()Ljava/lang/String;"
|
||||
[DIAGNOSE] Line 27, mock method "staticFunc" used
|
||||
[VERBOSE] Line 27, invoking "append" as "(Ljava/lang/String;)Ljava/lang/StringBuilder;"
|
||||
[VERBOSE] Line 27, invoking "append" as "(Ljava/lang/String;)Ljava/lang/StringBuilder;"
|
||||
[VERBOSE] Line 27, invoking "toString" as "()Ljava/lang/String;"
|
||||
... ...
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Upgrade to version 0.5
|
||||
Upgrade To Version 0.5
|
||||
---
|
||||
|
||||
After nearly a month of design and development, the `0.5` version of TestableMock has finally come out. Compared with the `0.4` version, the new version solves the three historical problems left over before:
|
||||
|
||||
4
docs/en-us/doc/use-mock-with.md
Normal file
4
docs/en-us/doc/use-mock-with.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Use MockWith Annotation
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
@@ -7,13 +7,19 @@
|
||||
|
||||
- Usage Guide
|
||||
- [Verify Mock Invocation](en-us/doc/invoke-matcher.md)
|
||||
- [Effective Scope Of Mocking](zh-cn/doc/scope-of-mock.md)
|
||||
- [Reusing Mock Methods](zh-cn/doc/mock-method-reusing.md)
|
||||
- [Use MockWith Annotation](zh-cn/doc/use-mock-with.md)
|
||||
- [Frequently Asked Questions](en-us/doc/frequently-asked-questions.md)
|
||||
- [Use TestableMock In IDE](en-us/doc/use-in-ide.md)
|
||||
- [Self-Help Troubleshooting](en-us/doc/troubleshooting.md)
|
||||
- [Testable Maven Plugin](en-us/doc/use-maven-plugin.md)
|
||||
|
||||
- Technical Reference
|
||||
- [Upgrade To 0.5 Version](en-us/doc/upgrade-to-v05.md)
|
||||
- [Upgrade To Version 0.5](en-us/doc/upgrade-to-v05.md)
|
||||
- [Available Global Arguments](zh-cn/doc/javaagent-args.md)
|
||||
- [Available Annotations](zh-cn/doc/annotations.md)
|
||||
- [How Mock Works](zh-cn/doc/design-and-mechanism.md)
|
||||
- [Mock Tools Comparison](en-us/doc/comparation.md)
|
||||
- [Release Note](en-us/doc/release-note.md)
|
||||
- [About Us](en-us/doc/about-us.md)
|
||||
|
||||
29
docs/zh-cn/doc/design-and-mechanism.md
Normal file
29
docs/zh-cn/doc/design-and-mechanism.md
Normal file
@@ -0,0 +1,29 @@
|
||||
TestableMock的设计和原理
|
||||
---
|
||||
|
||||
这篇文档主要介绍`TestableMock`中Mock功能的设计思想和实现原理。
|
||||
|
||||
与常见的Mock工具在每个测试用例里写Mock定义不同,`TestableMock`让每个业务类直接提供自己的Mock方法集合,描述自身在测试时需要被Mock的调用以及相应替代逻辑(即每个业务类有自己的独立Test类和独立Mock类)。采用约定优于配置,降低Mock学习理解成本、减少冗余信息。
|
||||
|
||||
这种设计基于两项基本假设:
|
||||
|
||||
1. 同一个测试类里,一个测试用例里需要Mock掉的方法,在其他测试用例里通常也都需要Mock。因为这些被Mock的方法往往访问了不便于测试的外部依赖。
|
||||
2. 需要Mock的调用都来自被测类的代码。(此假设是符合单元测试初衷的,即单元测试只应该关注当前单元的内部行为,单元外的逻辑应该被替换为Mock)
|
||||
|
||||
据此通过约定来简化符合该假设的单元测试场景,通过配置来支持其余复杂的使用场景。
|
||||
|
||||
`TestableMock`的原理可以用一句话概括:<u>利用JavaAgent动态修改字节码,把被测的业务类中与所有与Mock方法定义匹配的调用在单元测试运行时替换成对Mock方法的调用</u>。
|
||||
|
||||
最终达到的效果则是,不论代码用什么服务框架、什么对象容器,不论要Mock的目标对象是注入的、new出来的、全局的还是局部的,不论要Mock的目标方法是私有的、外部的、静态的、继承来的或者重载过的,全部无差别通吃,让单元测试回归简单。
|
||||
|
||||
> 划重点:Mock的目标是**被测类**中的**方法调用**。测试用例里的代码不会被Mock,方法的定义本身没有变化,只是发起调用的代码被替换了。
|
||||
|
||||
具体来说,在单元测试启动时,`TestableMock`会对加载到内存中的类进行预处理,同时分别建立“被测类”、“测试类”、“Mock容器类”之间的关联关系(可以是一对一,也可以是多对一)。这个关联一方面是为了在测试用例执行时能够正确匹配Mock调用并进行替换,另一方面则是为了能控制Mock方法的生效范围。
|
||||
|
||||
对于被测类,将匹配到的调用换成对Mock容器方法的调用。
|
||||
|
||||
对于测试类,在每个测试用例开头插入Mock上下文初始化代码。
|
||||
|
||||
对于Mock容器类,增加`testableIns()`方法变成单例类,在每个Mock方法开头插入记录调用的代码。
|
||||
|
||||
以上是整个Mock的核心逻辑,更多实现细节,请参考源码。若有任何问题、建议、改进提议,都欢迎通过Github Issue和Pull Request参与讨论、贡献😃
|
||||
@@ -1,9 +1,13 @@
|
||||
常见使用问题
|
||||
---
|
||||
|
||||
#### 1. 如何Mock被测类中通过`@Autowired`初始化的字段?
|
||||
#### 1. 如何初始化被测类中通过`@Autowired`或`@Resource`注入的私有字段?
|
||||
|
||||
直接创建被测类对象,然后利用`TestableMock`访问私有成员的能力直接给这些字段赋值即可。
|
||||
若该对象的方法在测试时需要被Mock,则无需初始化。
|
||||
|
||||
若测试运行时需用到该对象的真实调用,则可以在测试类的构造方法内直接创建对象,然后利用`TestableMock`访问私有成员的能力给这些字段赋值。
|
||||
|
||||
对于JUnit框架,还可以使用`@RunWith(SpringRunner.class)`注解将Spring上下文启动起来,然后在测试类里用`@Resource`把需要依赖对象注入进来,再利用`PrivateAccessor`赋值给被测类的私有字段。
|
||||
|
||||
#### 2. `TestableMock`是否能够与其他Mock工具一起使用?
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Release Note
|
||||
|
||||
## 0.5.1
|
||||
- 在`VERBOSE`级别诊断日志中使用更易于阅读的方法签名格式
|
||||
- 增加对JUnit5参数化测试的支持 (issue-98)
|
||||
- 修复在调用参数中包含三元表达式时的Mock异常(issue-92)
|
||||
- 修复使用`@MockWith`时,日志输出丢失的BUG (issue-99)
|
||||
|
||||
## 0.5.0
|
||||
- 分离测试类与Mock类,实现Mock类和Mock方法的复用机制
|
||||
- 支持测试类与被测类在不同包路径的情况下实施Mock
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
<testable.version>0.5.0</testable.version>
|
||||
<testable.version>0.5.1</testable.version>
|
||||
</properties>
|
||||
```
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
testImplementation('com.alibaba.testable:testable-all:0.5.0')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.5.0')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.5.1')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.5.1')
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ class DemoTest {
|
||||
输出日志示例如下:
|
||||
|
||||
```text
|
||||
[DIAGNOSE] Handling test class com/alibaba/testable/demo/DemoMockTest
|
||||
[DIAGNOSE] Handling test class com/alibaba/testable/demo/basic/DemoMockTest
|
||||
[DIAGNOSE] Found 6 test cases
|
||||
[DIAGNOSE] Handling mock class com/alibaba/testable/demo/DemoMockTest$Mock
|
||||
[DIAGNOSE] Handling mock class com/alibaba/testable/demo/basic/DemoMockTest$Mock
|
||||
[DIAGNOSE] Found 8 mock methods
|
||||
[DIAGNOSE] Handling source class com/alibaba/testable/demo/DemoMock
|
||||
[DIAGNOSE] Handling source class com/alibaba/testable/demo/basic/DemoMock
|
||||
[DIAGNOSE] Handling method <init>
|
||||
[DIAGNOSE] Handling method newFunc
|
||||
[DIAGNOSE] Line 19, mock method "createBlackBox" used
|
||||
@@ -55,43 +55,41 @@ class DemoTest {
|
||||
}
|
||||
```
|
||||
|
||||
再次执行单元测试,此时将会打印出所有Mock方法的运行期签名,以及被测类中扫描到所有调用的运行期签名:
|
||||
再次执行单元测试,此时将会打印出所有Mock方法的签名定义,以及被测类中扫描到所有调用的实际方法签名:
|
||||
|
||||
```text
|
||||
[DIAGNOSE] Handling test class com/alibaba/testable/demo/DemoMockTest
|
||||
[DIAGNOSE] Handling test class com/alibaba/testable/demo/basic/DemoMockTest
|
||||
[VERBOSE] Test case "should_able_to_mock_new_object"
|
||||
... ...
|
||||
[VERBOSE] Test case "should_able_to_set_mock_context"
|
||||
[DIAGNOSE] Found 6 test cases
|
||||
[DIAGNOSE] Handling mock class com/alibaba/testable/demo/DemoMockTest$Mock
|
||||
[VERBOSE] Mock constructor "createBlackBox" as "(Ljava/lang/String;)V" for "com/alibaba/testable/demo/model/BlackBox"
|
||||
[VERBOSE] Mock method "innerFunc" as "(Ljava/lang/String;)Ljava/lang/String;"
|
||||
[DIAGNOSE] Handling mock class com/alibaba/testable/demo/basic/DemoMockTest$Mock
|
||||
[VERBOSE] Mock constructor "createBlackBox" as "com.alibaba.testable.demo.basic.model.BlackBox(java.lang.String)"
|
||||
[VERBOSE] Mock method "innerFunc" as "com.alibaba.testable.demo.basic.DemoMock::innerFunc(java.lang.String) : java.lang.String"
|
||||
... ...
|
||||
[VERBOSE] Mock method "callFromDifferentMethod" as "()Ljava/lang/String;"
|
||||
[DIAGNOSE] Found 8 mock methods
|
||||
[DIAGNOSE] Handling source class com/alibaba/testable/demo/DemoMock
|
||||
[DIAGNOSE] Handling source class com/alibaba/testable/demo/basic/DemoMock
|
||||
[DIAGNOSE] Handling method <init>
|
||||
[VERBOSE] Line 13, constructing "java/lang/Object" as "()V"
|
||||
[VERBOSE] Line 13, constructing "java.lang.Object()"
|
||||
[DIAGNOSE] Handling method newFunc
|
||||
[VERBOSE] Line 19, constructing "com/alibaba/testable/demo/model/BlackBox" as "(Ljava/lang/String;)V"
|
||||
[VERBOSE] Line 19, constructing "com.alibaba.testable.demo.basic.model.BlackBox(java.lang.String)"
|
||||
[DIAGNOSE] Line 19, mock method "createBlackBox" used
|
||||
[VERBOSE] Line 19, invoking "createBlackBox" as "(Ljava/lang/String;)Lcom/alibaba/testable/demo/model/BlackBox;"
|
||||
[VERBOSE] Line 20, invoking "get" as "()Ljava/lang/String;"
|
||||
[VERBOSE] Line 19, invoking "com.alibaba.testable.demo.basic.DemoMockTest$Mock::createBlackBox(java.lang.String) : com.alibaba.testable.demo.basic.model.BlackBox"
|
||||
[VERBOSE] Line 20, invoking "com.alibaba.testable.demo.basic.model.BlackBox::get() : java.lang.String"
|
||||
[DIAGNOSE] Handling method outerFunc
|
||||
[VERBOSE] Line 27, constructing "java/lang/StringBuilder" as "()V"
|
||||
[VERBOSE] Line 27, invoking "append" as "(Ljava/lang/String;)Ljava/lang/StringBuilder;"
|
||||
[VERBOSE] Line 27, invoking "innerFunc" as "(Ljava/lang/String;)Ljava/lang/String;"
|
||||
[VERBOSE] Line 27, constructing "java.lang.StringBuilder()"
|
||||
[VERBOSE] Line 27, invoking "java.lang.StringBuilder::append(java.lang.String) : java.lang.StringBuilder"
|
||||
[VERBOSE] Line 27, invoking "com.alibaba.testable.demo.basic.DemoMock::innerFunc(java.lang.String) : java.lang.String"
|
||||
[DIAGNOSE] Line 27, mock method "innerFunc" used
|
||||
... ...
|
||||
```
|
||||
|
||||
输出日志结构参考如下:
|
||||
|
||||
- `Mock constructor "<Mock方法名>" as "<方法签名>" for "<类型>"` 在测试类中扫描到的**Mock构造方法**及其运行期签名
|
||||
- `Mock method "<Mock方法名>" as "<方法签名>"` 在测试类中扫描到的**普通Mock方法**及其运行期签名
|
||||
- `Line XX, constructing "<类型>" as "<方法签名>"` 在被测类中扫描掉的**构造方法调用**及其运行期签名
|
||||
- `Line XX, invoking "<方法名>" as "<方法签名>"` 在被测类中扫描到的**成员方法调用**及其运行期签名
|
||||
|
||||
"运行期签名"是目标方法参数和返回值类型的在字节码中的表示形式,其结构相比方法的原始Java签名更紧凑精炼,通过对比相应代码行调用时的方法签名与Mock方法的实际签名,通常能够快速定位出Mock未匹配的原因。
|
||||
- `Mock constructor "<Mock方法名>" as "<方法签名>" for "<类型>"` 在测试类中扫描到的**Mock构造方法**及其签名
|
||||
- `Mock method "<Mock方法名>" as "<方法签名>"` 在测试类中扫描到的**普通Mock方法**及其签名
|
||||
- `Line XX, constructing "<类型>" as "<方法签名>"` 在被测类中扫描掉的**构造方法调用**及其签名
|
||||
- `Line XX, invoking "<方法名>" as "<方法签名>"` 在被测类中扫描到的**成员方法调用**及其签名
|
||||
|
||||
> 在`0.4.x`版本使用测试类添加`@MockWith`注解的`diagnose`参数来启用诊断信息的方法在`0.5`版本中依然可用,但将在未来版本中移除,请优先使用`@MockDiagnose`注解替代。
|
||||
|
||||
@@ -88,7 +88,7 @@ public class ServiceTest {
|
||||
|
||||
完整代码示例见`java-demo`和`kotlin-demo`示例项目中`OneToMultiSvcTest`测试类的用例。
|
||||
|
||||
> 由于当前版本里,测试类无法通过`@MockWith`与多个Mock容器关联,对生效范围为`MockScope.ASSOCIATED`的Mock方法会遇到Mock无效的情况,为已知BUG,将在未来版本中修复。
|
||||
> 由于当前版本里,测试类无法通过`@MockWith`与多个Mock容器关联,目前这种用法仅支持生效范围为`MockScope.GLOBAL`的Mock方法,这是已知BUG,将在未来版本中修复。
|
||||
|
||||
### 4. 使用不包含Mock方法的Mock容器类
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
- [升级到0.5版本](zh-cn/doc/upgrade-to-v05.md)
|
||||
- [全局运行参数](zh-cn/doc/javaagent-args.md)
|
||||
- [注解参数清单](zh-cn/doc/annotations.md)
|
||||
- [Mock的设计和原理](zh-cn/doc/design-and-mechanism.md)
|
||||
- [主流Mock工具对比](zh-cn/doc/comparation.md)
|
||||
- [版本更新](zh-cn/doc/release-note.md)
|
||||
- [关于我们](zh-cn/doc/about-us.md)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<version>0.5.1</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-agent</artifactId>
|
||||
|
||||
@@ -14,6 +14,7 @@ public class ByteCodeConst {
|
||||
public static final byte TYPE_VOID = 'V';
|
||||
public static final byte PARAM_END = ')';
|
||||
public static final byte CLASS_END = ';';
|
||||
public static final byte PKG_SEGMENT = '/';
|
||||
public static final byte TYPE_ARRAY = '[';
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import org.objectweb.asm.tree.*;
|
||||
|
||||
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.util.ClassUtil.toDotSeparateFullClassName;
|
||||
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
||||
|
||||
@@ -44,10 +46,13 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
||||
mn.access &= ~ACC_PRIVATE;
|
||||
mn.access &= ~ACC_PROTECTED;
|
||||
mn.access |= ACC_PUBLIC;
|
||||
// below transform order is important
|
||||
// firstly, unfold target class from annotation to parameter
|
||||
unfoldTargetClass(mn);
|
||||
// secondly, add invoke recorder at the beginning of mock method
|
||||
injectInvokeRecorder(mn);
|
||||
// thirdly, add association checker before invoke recorder
|
||||
injectAssociationChecker(mn);
|
||||
// finally, handle testable util variables
|
||||
handleTestableUtil(mn);
|
||||
}
|
||||
}
|
||||
@@ -170,7 +175,7 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
||||
if (VOID_RES.equals(returnType)) {
|
||||
il.add(new InsnNode(POP));
|
||||
il.add(new InsnNode(RETURN));
|
||||
} else if (returnType.startsWith("[") || returnType.startsWith("L")) {
|
||||
} else if (returnType.charAt(0) == TYPE_ARRAY ||returnType.charAt(0) == TYPE_CLASS) {
|
||||
il.add(new TypeInsnNode(CHECKCAST, returnType));
|
||||
il.add(new InsnNode(ARETURN));
|
||||
} else {
|
||||
@@ -187,13 +192,12 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
||||
Type className;
|
||||
String methodName = mn.name;
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc)) {
|
||||
String name = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_METHOD,
|
||||
null, String.class);
|
||||
if (isMockMethodAnnotation(an)) {
|
||||
String name = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
|
||||
if (name != null) {
|
||||
methodName = name;
|
||||
}
|
||||
} else if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc)) {
|
||||
} else if (isMockConstructorAnnotation(an)) {
|
||||
methodName = CONSTRUCTOR;
|
||||
}
|
||||
}
|
||||
@@ -207,8 +211,7 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
||||
|
||||
private boolean isGlobalScope(MethodNode mn) {
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc) ||
|
||||
ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc)) {
|
||||
if (isMockMethodAnnotation(an) || isMockConstructorAnnotation(an)) {
|
||||
MockScope scope = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_SCOPE,
|
||||
GlobalConfig.getDefaultMockScope(), MockScope.class);
|
||||
if (scope.equals(MockScope.GLOBAL)) {
|
||||
@@ -224,14 +227,23 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
||||
return false;
|
||||
}
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc) ||
|
||||
ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc)) {
|
||||
if (isMockMethodAnnotation(an) && AnnotationUtil.isValidMockMethod(mn, an)) {
|
||||
return true;
|
||||
} else if (isMockConstructorAnnotation(an)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isMockConstructorAnnotation(AnnotationNode an) {
|
||||
return ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc);
|
||||
}
|
||||
|
||||
private boolean isMockMethodAnnotation(AnnotationNode an) {
|
||||
return ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc);
|
||||
}
|
||||
|
||||
private void injectInvokeRecorder(MethodNode mn) {
|
||||
InsnList il = new InsnList();
|
||||
il.add(duplicateParameters(mn));
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.alibaba.testable.agent.handler;
|
||||
|
||||
import com.alibaba.testable.agent.model.MethodInfo;
|
||||
import com.alibaba.testable.agent.model.TravelStatus;
|
||||
import com.alibaba.testable.agent.util.BytecodeUtil;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
import com.alibaba.testable.agent.util.MethodUtil;
|
||||
import com.alibaba.testable.core.util.LogUtil;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.*;
|
||||
|
||||
@@ -65,25 +67,35 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
if (invokeOps.contains(instructions[i].getOpcode())) {
|
||||
MethodInsnNode node = (MethodInsnNode)instructions[i];
|
||||
if (CONSTRUCTOR.equals(node.name)) {
|
||||
LogUtil.verbose(" Line %d, constructing \"%s\" as \"%s\"", getLineNum(instructions, i),
|
||||
node.owner, node.desc);
|
||||
if (LogUtil.isVerboseEnabled()) {
|
||||
LogUtil.verbose(" Line %d, constructing \"%s\"", getLineNum(instructions, i),
|
||||
MethodUtil.toJavaMethodDesc(node.owner, node.desc));
|
||||
}
|
||||
MethodInfo newOperatorInjectMethod = getNewOperatorInjectMethod(newOperatorInjectMethods, node);
|
||||
if (newOperatorInjectMethod != null) {
|
||||
// it's a new operation and an inject method for it exist
|
||||
int rangeStart = getConstructorStart(instructions, node.owner, i);
|
||||
if (rangeStart >= 0) {
|
||||
if (rangeStart < i) {
|
||||
handleFrameStackChange(mn, newOperatorInjectMethod, rangeStart, i);
|
||||
}
|
||||
instructions = replaceNewOps(mn, newOperatorInjectMethod, instructions, rangeStart, i);
|
||||
i = rangeStart;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LogUtil.verbose(" Line %d, invoking \"%s\" as \"%s\"", getLineNum(instructions, i),
|
||||
node.name, node.desc);
|
||||
if (LogUtil.isVerboseEnabled()) {
|
||||
LogUtil.verbose(" Line %d, invoking \"%s\"", getLineNum(instructions, i),
|
||||
MethodUtil.toJavaMethodDesc(node.owner, 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) {
|
||||
if (rangeStart < i) {
|
||||
handleFrameStackChange(mn, mockMethod, rangeStart, i);
|
||||
}
|
||||
instructions = replaceMemberCallOps(mn, mockMethod,
|
||||
instructions, node.owner, node.getOpcode(), rangeStart, i);
|
||||
i = rangeStart;
|
||||
@@ -144,10 +156,36 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
if (stackLevel < 0) {
|
||||
return rangeEnd;
|
||||
}
|
||||
Label labelToJump = null;
|
||||
TravelStatus status = TravelStatus.Normal;
|
||||
for (int i = rangeEnd - 1; i >= 0; i--) {
|
||||
stackLevel += getStackLevelChange(instructions[i]);
|
||||
if (stackLevel < 0) {
|
||||
return i;
|
||||
switch (status) {
|
||||
case Normal:
|
||||
if (instructions[i] instanceof FrameNode) {
|
||||
status = TravelStatus.LookingForLabel;
|
||||
} else {
|
||||
stackLevel += getStackLevelChange(instructions[i]);
|
||||
if (stackLevel < 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LookingForLabel:
|
||||
if (instructions[i] instanceof LabelNode) {
|
||||
labelToJump = ((LabelNode)instructions[i]).getLabel();
|
||||
status = TravelStatus.LookingForJump;
|
||||
}
|
||||
break;
|
||||
case LookingForJump:
|
||||
if (instructions[i] instanceof JumpInsnNode &&
|
||||
((JumpInsnNode)instructions[i]).label.getLabel().equals(labelToJump)) {
|
||||
stackLevel += getStackLevelChange(instructions[i]);
|
||||
labelToJump = null;
|
||||
status = TravelStatus.Normal;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
@@ -249,6 +287,26 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
return mn.instructions.toArray();
|
||||
}
|
||||
|
||||
private void handleFrameStackChange(MethodNode mn, MethodInfo mockMethod, int start, int end) {
|
||||
AbstractInsnNode curInsn = mn.instructions.get(start);
|
||||
AbstractInsnNode endInsn = mn.instructions.get(end);
|
||||
do {
|
||||
if (curInsn instanceof FrameNode) {
|
||||
FrameNode fn = (FrameNode)curInsn;
|
||||
if (fn.type == F_FULL) {
|
||||
fn.stack.add(0, mockMethod.getMockClass());
|
||||
// remove label reference in stack of frame node
|
||||
for (int i = fn.stack.size() - 1; i >= 0; i--) {
|
||||
if (fn.stack.get(i) instanceof LabelNode) {
|
||||
fn.stack.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
curInsn = curInsn.getNext();
|
||||
} while (!curInsn.equals(endInsn));
|
||||
}
|
||||
|
||||
private boolean isCompanionMethod(String ownerClass, int opcode) {
|
||||
return Opcodes.INVOKEVIRTUAL == opcode && ClassUtil.isCompanionClassName(ownerClass);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package com.alibaba.testable.agent.handler.test;
|
||||
|
||||
|
||||
import com.alibaba.testable.agent.model.TestCaseMethodType;
|
||||
import com.alibaba.testable.agent.util.CollectionUtil;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
abstract public class Framework {
|
||||
@@ -22,7 +23,7 @@ abstract public class Framework {
|
||||
hasTestAfterMethod = true;
|
||||
return true;
|
||||
} else {
|
||||
return methodAnnotations.contains(getTestAnnotation());
|
||||
return CollectionUtil.containsAny(methodAnnotations, getTestAnnotations());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +32,7 @@ abstract public class Framework {
|
||||
return TestCaseMethodType.OTHERS;
|
||||
}
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (an.desc.equals(getTestAnnotation())) {
|
||||
if (getTestAnnotations().contains(an.desc)) {
|
||||
return TestCaseMethodType.TEST;
|
||||
} else if (an.desc.equals(getTestAfterAnnotation())) {
|
||||
return TestCaseMethodType.AFTER_TEST;
|
||||
@@ -40,7 +41,7 @@ abstract public class Framework {
|
||||
return TestCaseMethodType.OTHERS;
|
||||
}
|
||||
|
||||
public abstract String getTestAnnotation();
|
||||
public abstract List<String> getTestAnnotations();
|
||||
|
||||
public abstract String getTestAfterAnnotation();
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package com.alibaba.testable.agent.handler.test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class JUnit4Framework extends Framework {
|
||||
|
||||
private static final String ANNOTATION_TEST = "Lorg/junit/Test;";
|
||||
private static final String ANNOTATION_AFTER_TEST = "Lorg/junit/After;";
|
||||
|
||||
@Override
|
||||
public String getTestAnnotation() {
|
||||
return ANNOTATION_TEST;
|
||||
public List<String> getTestAnnotations() {
|
||||
return Collections.singletonList(ANNOTATION_TEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.alibaba.testable.agent.handler.test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class JUnit5Framework extends Framework {
|
||||
|
||||
private static final String ANNOTATION_TEST = "Lorg/junit/jupiter/api/Test;";
|
||||
private static final String ANNOTATION_PARAMETERIZED_TEST = "Lorg/junit/jupiter/params/ParameterizedTest;";
|
||||
private static final String ANNOTATION_AFTER_TEST = "Lorg/junit/jupiter/api/AfterEach;";
|
||||
|
||||
@Override
|
||||
public String getTestAnnotation() {
|
||||
return ANNOTATION_TEST;
|
||||
public List<String> getTestAnnotations() {
|
||||
return Arrays.asList(ANNOTATION_TEST, ANNOTATION_PARAMETERIZED_TEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package com.alibaba.testable.agent.handler.test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class TestNgFramework extends Framework {
|
||||
|
||||
private static final String ANNOTATION_TEST = "Lorg/testng/annotations/Test;";
|
||||
private static final String ANNOTATION_AFTER_TEST = "Lorg/testng/annotations/AfterMethod;";
|
||||
|
||||
@Override
|
||||
public String getTestAnnotation() {
|
||||
return ANNOTATION_TEST;
|
||||
public List<String> getTestAnnotations() {
|
||||
return Collections.singletonList(ANNOTATION_TEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.alibaba.testable.agent.handler.test;
|
||||
|
||||
import com.alibaba.testable.agent.model.TestCaseMethodType;
|
||||
import com.alibaba.testable.agent.util.CollectionUtil;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
@@ -12,7 +13,7 @@ public class TestNgOnClassFramework extends TestNgFramework {
|
||||
|
||||
@Override
|
||||
public boolean fit(Set<String> classAnnotations, Set<String> methodAnnotations) {
|
||||
if (classAnnotations.contains(getTestAnnotation())) {
|
||||
if (CollectionUtil.containsAny(classAnnotations, getTestAnnotations())) {
|
||||
if (methodAnnotations.contains(getTestAfterAnnotation())) {
|
||||
hasTestAfterMethod = true;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ public class MethodInfo {
|
||||
* parameter and return value of the source method
|
||||
*/
|
||||
private final String desc;
|
||||
/**
|
||||
* name of the class where this mock method defined (in slash-separate format)
|
||||
*/
|
||||
private final String mockClass;
|
||||
/**
|
||||
* name of the mock method
|
||||
*/
|
||||
@@ -30,10 +34,11 @@ public class MethodInfo {
|
||||
*/
|
||||
private final boolean isStatic;
|
||||
|
||||
public MethodInfo(String clazz, String name, String desc, String mockName, String mockDesc, boolean isStatic) {
|
||||
public MethodInfo(String clazz, String name, String desc, String mockClass, String mockName, String mockDesc, boolean isStatic) {
|
||||
this.clazz = clazz;
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
this.mockClass = mockClass;
|
||||
this.mockName = mockName;
|
||||
this.mockDesc = mockDesc;
|
||||
this.isStatic = isStatic;
|
||||
@@ -51,6 +56,10 @@ public class MethodInfo {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public String getMockClass() {
|
||||
return mockClass;
|
||||
}
|
||||
|
||||
public String getMockName() {
|
||||
return mockName;
|
||||
}
|
||||
@@ -74,6 +83,7 @@ public class MethodInfo {
|
||||
if (!clazz.equals(that.clazz)) { return false; }
|
||||
if (!name.equals(that.name)) { return false; }
|
||||
if (!desc.equals(that.desc)) { return false; }
|
||||
if (!mockClass.equals(that.mockClass)) { return false; }
|
||||
if (!mockName.equals(that.mockName)) { return false; }
|
||||
return mockDesc.equals(that.mockDesc);
|
||||
}
|
||||
@@ -83,6 +93,7 @@ public class MethodInfo {
|
||||
int result = clazz.hashCode();
|
||||
result = 31 * result + name.hashCode();
|
||||
result = 31 * result + desc.hashCode();
|
||||
result = 31 * result + mockClass.hashCode();
|
||||
result = 31 * result + mockName.hashCode();
|
||||
result = 31 * result + mockDesc.hashCode();
|
||||
result = 31 * result + (isStatic ? 1 : 0);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.alibaba.testable.agent.model;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public enum TravelStatus {
|
||||
|
||||
/**
|
||||
* traveling common bytecode
|
||||
*/
|
||||
Normal,
|
||||
|
||||
/**
|
||||
* looking for label opcode
|
||||
*/
|
||||
LookingForLabel,
|
||||
|
||||
/**
|
||||
* looking for jump opcode
|
||||
*/
|
||||
LookingForJump
|
||||
|
||||
}
|
||||
@@ -86,17 +86,22 @@ public class MockClassParser {
|
||||
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,
|
||||
MethodUtil.extractParameters(mn.desc), MethodUtil.getReturnType(mn.desc));
|
||||
if (LogUtil.isVerboseEnabled()) {
|
||||
LogUtil.verbose(" Mock constructor \"%s\" as \"%s\"", mn.name, MethodUtil.toJavaMethodDesc(
|
||||
ClassUtil.toDotSeparateFullClassName(MethodUtil.getReturnType(mn.desc)), mn.desc));
|
||||
}
|
||||
addMockConstructor(methodInfos, cn, mn);
|
||||
} else if (fullClassName.equals(ConstPool.MOCK_METHOD)) {
|
||||
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, getTargetMethodDesc(mn, an));
|
||||
} else if (fullClassName.equals(ConstPool.MOCK_METHOD) && AnnotationUtil.isValidMockMethod(mn, an)) {
|
||||
if (LogUtil.isVerboseEnabled()) {
|
||||
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, MethodUtil.toJavaMethodDesc(
|
||||
getTargetMethodOwner(mn, an), getTargetMethodName(mn, an), getTargetMethodDesc(mn, an)));
|
||||
}
|
||||
String targetMethod = AnnotationUtil.getAnnotationParameter(
|
||||
an, ConstPool.FIELD_TARGET_METHOD, mn.name, String.class);
|
||||
if (CONSTRUCTOR.equals(targetMethod)) {
|
||||
addMockConstructor(methodInfos, cn, mn);
|
||||
} else {
|
||||
MethodInfo mi = getMethodInfo(mn, an, targetMethod);
|
||||
MethodInfo mi = getMethodInfo(cn, mn, an, targetMethod);
|
||||
if (mi != null) {
|
||||
methodInfos.add(mi);
|
||||
}
|
||||
@@ -106,13 +111,25 @@ public class MockClassParser {
|
||||
}
|
||||
}
|
||||
|
||||
private String getTargetMethodOwner(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
||||
Type type = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_CLASS,
|
||||
null, Type.class);
|
||||
return type == null ? MethodUtil.getFirstParameter(mn.desc) : type.getClassName();
|
||||
}
|
||||
|
||||
private String getTargetMethodName(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
||||
String name = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_METHOD,
|
||||
null, String.class);
|
||||
return name == null ? mn.name : name;
|
||||
}
|
||||
|
||||
private String getTargetMethodDesc(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
||||
Type type = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_CLASS,
|
||||
null, Type.class);
|
||||
return type == null ? MethodUtil.removeFirstParameter(mn.desc) : mn.desc;
|
||||
}
|
||||
|
||||
private MethodInfo getMethodInfo(MethodNode mn, AnnotationNode an, String targetMethod) {
|
||||
private MethodInfo getMethodInfo(ClassNode cn, MethodNode mn, AnnotationNode an, String targetMethod) {
|
||||
Type targetType = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
||||
boolean isStatic = isStatic(mn);
|
||||
if (targetType == null) {
|
||||
@@ -121,18 +138,19 @@ public class MockClassParser {
|
||||
if (methodDescPair == null) {
|
||||
return null;
|
||||
}
|
||||
return new MethodInfo(methodDescPair.left, targetMethod, methodDescPair.right, mn.name, mn.desc, isStatic);
|
||||
return new MethodInfo(methodDescPair.left, targetMethod, methodDescPair.right, cn.name, mn.name, mn.desc,
|
||||
isStatic);
|
||||
} else {
|
||||
// "targetClass" found, use it as target class type
|
||||
String slashSeparatedName = ClassUtil.toSlashSeparatedName(targetType.getClassName());
|
||||
return new MethodInfo(slashSeparatedName, targetMethod, mn.desc, mn.name,
|
||||
return new MethodInfo(slashSeparatedName, targetMethod, mn.desc, cn.name, mn.name,
|
||||
MethodUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(slashSeparatedName)), isStatic);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMockConstructor(List<MethodInfo> methodInfos, ClassNode cn, MethodNode mn) {
|
||||
String sourceClassName = ClassUtil.getSourceClassName(cn.name);
|
||||
methodInfos.add(new MethodInfo(sourceClassName, CONSTRUCTOR, mn.desc, mn.name, mn.desc, isStatic(mn)));
|
||||
methodInfos.add(new MethodInfo(sourceClassName, CONSTRUCTOR, mn.desc, cn.name, mn.name, mn.desc, isStatic(mn)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -232,6 +232,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
if (isExpectedType(cn.name, type, expectedType)) {
|
||||
Type clazz = AnnotationUtil.getAnnotationParameter(an, FIELD_VALUE,
|
||||
Type.getType(NullType.class), Type.class);
|
||||
DiagnoseUtil.setupByClass(ClassUtil.getClassNode(clazz.getClassName()));
|
||||
return clazz.getClassName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.alibaba.testable.agent.util;
|
||||
|
||||
import com.alibaba.testable.agent.constant.ConstPool;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_CLASS;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
@@ -59,4 +64,16 @@ public class AnnotationUtil {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is MockMethod annotation is used on a valid mock method
|
||||
* @param mn mock method
|
||||
* @param an MockMethod annotation
|
||||
* @return valid or not
|
||||
*/
|
||||
public static boolean isValidMockMethod(MethodNode mn, AnnotationNode an) {
|
||||
Type targetClass = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
||||
String firstParameter = MethodUtil.getFirstParameter(mn.desc);
|
||||
return targetClass != null || firstParameter.charAt(0) == TYPE_CLASS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,21 @@ public class DiagnoseUtil {
|
||||
private static final String FIELD_DIAGNOSE = "diagnose";
|
||||
|
||||
public static void setupByClass(ClassNode cn) {
|
||||
if (cn.visibleAnnotations == null) {
|
||||
if (cn == null || cn.visibleAnnotations == null) {
|
||||
return;
|
||||
}
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
setupByAnnotation(an);
|
||||
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.MOCK_DIAGNOSE)) {
|
||||
setupDiagnose(an, FIELD_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setupByAnnotation(AnnotationNode an) {
|
||||
// to be remove in v0.6
|
||||
if (toDotSeparateFullClassName(an.desc).equals(MOCK_WITH)) {
|
||||
setupDiagnose(an, FIELD_DIAGNOSE);
|
||||
}
|
||||
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.MOCK_DIAGNOSE)) {
|
||||
setupDiagnose(an, FIELD_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupDiagnose(AnnotationNode an, String fieldDiagnose) {
|
||||
|
||||
@@ -73,13 +73,13 @@ public class MethodUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* parse method desc, fetch first parameter type
|
||||
* parse method desc, fetch first parameter type (assume first parameter is an object type)
|
||||
* @param desc method description
|
||||
* @return types of first parameter
|
||||
*/
|
||||
public static String getFirstParameter(String desc) {
|
||||
int typeEdge = desc.indexOf(CLASS_END);
|
||||
return desc.substring(1, typeEdge + 1);
|
||||
return typeEdge > 0 ? desc.substring(1, typeEdge + 1) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,4 +105,105 @@ public class MethodUtil {
|
||||
return b == TYPE_BYTE || b == TYPE_CHAR || b == TYPE_DOUBLE || b == TYPE_FLOAT
|
||||
|| b == TYPE_INT || b == TYPE_LONG || b == TYPE_SHORT || b == TYPE_BOOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* format to java style constructor descriptor
|
||||
* @param owner class of method belongs to
|
||||
* @param desc method constructor in bytecode format
|
||||
* @return java style constructor descriptor
|
||||
*/
|
||||
public static String toJavaMethodDesc(String owner, String desc) {
|
||||
String ownerInDotFormat = ClassUtil.toDotSeparatedName(owner);
|
||||
String parameters = toJavaParameterDesc(extractParameters(desc));
|
||||
return String.format("%s(%s)", ownerInDotFormat, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* format to java style method descriptor
|
||||
* @param owner class of method belongs to
|
||||
* @param name method name
|
||||
* @param desc method descriptor in bytecode format
|
||||
* @return java style method descriptor
|
||||
*/
|
||||
public static String toJavaMethodDesc(String owner, String name, String desc) {
|
||||
String ownerInDotFormat = ClassUtil.toDotSeparatedName(owner);
|
||||
String returnType = toJavaParameterDesc(getReturnType(desc));
|
||||
String parameters = toJavaParameterDesc(extractParameters(desc));
|
||||
return String.format("%s::%s(%s) : %s", ownerInDotFormat, name, parameters, returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert bytecode style parameter descriptor to java style descriptor
|
||||
* @param desc bytecode style descriptor
|
||||
* @return java style descriptor
|
||||
*/
|
||||
private static String toJavaParameterDesc(String desc) {
|
||||
if (desc.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean isArray = false;
|
||||
boolean isTravellingClass = false;
|
||||
for (byte b : desc.getBytes()) {
|
||||
if (isTravellingClass) {
|
||||
switch (b) {
|
||||
case CLASS_END:
|
||||
sb.append(isArray ? "[]" : "");
|
||||
isArray = false;
|
||||
isTravellingClass = false;
|
||||
break;
|
||||
case PKG_SEGMENT:
|
||||
sb.append('.');
|
||||
break;
|
||||
default:
|
||||
sb.append((char)b);
|
||||
}
|
||||
} else {
|
||||
switch (b) {
|
||||
case TYPE_ARRAY:
|
||||
isArray = true;
|
||||
break;
|
||||
case TYPE_BYTE:
|
||||
sb.append(",byte").append(isArray ? "[]" : "");
|
||||
isArray = false;
|
||||
break;
|
||||
case TYPE_CHAR:
|
||||
sb.append(",char").append(isArray ? "[]" : "");
|
||||
isArray = false;
|
||||
break;
|
||||
case TYPE_DOUBLE:
|
||||
sb.append(",double").append(isArray ? "[]" : "");
|
||||
isArray = false;
|
||||
break;
|
||||
case TYPE_FLOAT:
|
||||
sb.append(",float").append(isArray ? "[]" : "");
|
||||
isArray = false;
|
||||
break;
|
||||
case TYPE_INT:
|
||||
sb.append(",int").append(isArray ? "[]" : "");
|
||||
isArray = false;
|
||||
break;
|
||||
case TYPE_LONG:
|
||||
sb.append(",long").append(isArray ? "[]" : "");
|
||||
isArray = false;
|
||||
break;
|
||||
case TYPE_SHORT:
|
||||
sb.append(",short").append(isArray ? "[]" : "");
|
||||
isArray = false;
|
||||
break;
|
||||
case TYPE_BOOL:
|
||||
sb.append(",boolean").append(isArray ? "[]" : "");
|
||||
isArray = false;
|
||||
break;
|
||||
case TYPE_CLASS:
|
||||
sb.append(",");
|
||||
isTravellingClass = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.alibaba.testable.agent.util;
|
||||
|
||||
import com.alibaba.testable.core.accessor.PrivateAccessor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -33,6 +34,18 @@ class MethodUtilTest {
|
||||
@Test
|
||||
void should_able_to_get_first_parameter() {
|
||||
assertEquals("Ljava/lang/String;", MethodUtil.getFirstParameter("(Ljava/lang/String;Ljava/lang/Object;I)V"));
|
||||
assertEquals("Ljava/lang/String;", MethodUtil.getFirstParameter("(Ljava/lang/String;)V"));
|
||||
assertEquals("", MethodUtil.getFirstParameter("()V"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_convert_bytecode_parameters() {
|
||||
assertEquals("", PrivateAccessor.invokeStatic(MethodUtil.class, "toJavaParameterDesc", ""));
|
||||
assertEquals("char", PrivateAccessor.invokeStatic(MethodUtil.class, "toJavaParameterDesc", "C"));
|
||||
assertEquals("int,long", PrivateAccessor.invokeStatic(MethodUtil.class, "toJavaParameterDesc", "IJ"));
|
||||
assertEquals("int[],long[]", PrivateAccessor.invokeStatic(MethodUtil.class, "toJavaParameterDesc", "[I[J"));
|
||||
assertEquals("int,java.lang.String", PrivateAccessor.invokeStatic(MethodUtil.class, "toJavaParameterDesc", "ILjava/lang/String;"));
|
||||
assertEquals("java.lang.String,int,long[]", PrivateAccessor.invokeStatic(MethodUtil.class, "toJavaParameterDesc", "Ljava/lang/String;I[J"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<version>0.5.1</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-all</artifactId>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<version>0.5.1</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-core</artifactId>
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.alibaba.testable.core.util.TypeUtil;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
@@ -24,8 +25,10 @@ public class PrivateAccessor {
|
||||
Field declaredField = ref.getClass().getDeclaredField(field);
|
||||
declaredField.setAccessible(true);
|
||||
return (T)declaredField.get(ref);
|
||||
} catch (Exception e) {
|
||||
throw new MemberAccessException("Failed to get private field \"" + field + "\"", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new MemberAccessException("Failed to access private field \"" + field + "\"", e);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new MemberAccessException("Private field \"" + field + "\" not exist", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +43,10 @@ public class PrivateAccessor {
|
||||
Field declaredField = ref.getClass().getDeclaredField(field);
|
||||
declaredField.setAccessible(true);
|
||||
declaredField.set(ref, value);
|
||||
} catch (Exception e) {
|
||||
throw new MemberAccessException("Failed to set private field \"" + field + "\"", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new MemberAccessException("Failed to access private field \"" + field + "\"", e);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new MemberAccessException("Private field \"" + field + "\" not exist", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,10 +65,15 @@ public class PrivateAccessor {
|
||||
declaredMethod.setAccessible(true);
|
||||
return (T)declaredMethod.invoke(ref, args);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new MemberAccessException("Failed to invoke private method \"" + method + "\"", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new MemberAccessException("Failed to access private method \"" + method + "\"", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getTargetException() instanceof RuntimeException) {
|
||||
throw (RuntimeException)e.getTargetException();
|
||||
}
|
||||
throw new MemberAccessException("Invoke private method \"" + method + "\" failed with exception", e);
|
||||
}
|
||||
throw new MemberAccessException("Private method \"" + method + "\" not found");
|
||||
throw new MemberAccessException("Private method \"" + method + "\" not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,8 +86,10 @@ public class PrivateAccessor {
|
||||
Field declaredField = clazz.getDeclaredField(field);
|
||||
declaredField.setAccessible(true);
|
||||
return (T)declaredField.get(null);
|
||||
} catch (Exception e) {
|
||||
throw new MemberAccessException("Failed to get private static field \"" + field + "\"", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new MemberAccessException("Failed to access private static field \"" + field + "\"", e);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new MemberAccessException("Private static field \"" + field + "\" not exist", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,8 +104,10 @@ public class PrivateAccessor {
|
||||
Field declaredField = clazz.getDeclaredField(field);
|
||||
declaredField.setAccessible(true);
|
||||
declaredField.set(null, value);
|
||||
} catch (Exception e) {
|
||||
throw new MemberAccessException("Failed to set private static field \"" + field + "\"", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new MemberAccessException("Failed to access private static field \"" + field + "\"", e);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new MemberAccessException("Private static field \"" + field + "\" not exist", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,10 +134,17 @@ public class PrivateAccessor {
|
||||
declaredMethod.setAccessible(true);
|
||||
return (T)declaredMethod.invoke(companionInstance, args);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new MemberAccessException("Failed to invoke private static method \"" + method + "\"", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new MemberAccessException("Failed to access private static method \"" + method + "\"", e);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new MemberAccessException("Private static method \"" + method + "\" not exist");
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getTargetException() instanceof RuntimeException) {
|
||||
throw (RuntimeException)e.getTargetException();
|
||||
}
|
||||
throw new MemberAccessException("Invoke private static method \"" + method + "\" failed with exception", e);
|
||||
}
|
||||
throw new MemberAccessException("Private static method \"" + method + "\" not found");
|
||||
throw new MemberAccessException("Neither Private static method nor companion method \"" + method + "\" exist");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,9 +160,16 @@ public class PrivateAccessor {
|
||||
constructor.setAccessible(true);
|
||||
return (T)constructor.newInstance(args);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new MemberAccessException("Failed to invoke private constructor of \"" + clazz.getSimpleName() + "\"", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new MemberAccessException("Failed to access private constructor of \"" + clazz.getSimpleName() + "\"", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getTargetException() instanceof RuntimeException) {
|
||||
throw (RuntimeException)e.getTargetException();
|
||||
}
|
||||
throw new MemberAccessException("Invoke private constructor of \"" + clazz.getSimpleName() + "\" failed with exception", e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new MemberAccessException("Failed to instantiate object of \"" + clazz.getSimpleName() + "\"", e);
|
||||
}
|
||||
throw new MemberAccessException("Private static constructor of \"" + clazz.getSimpleName() + "\" not found");
|
||||
throw new MemberAccessException("Private constructor of \"" + clazz.getSimpleName() + "\" not exist");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class LogUtil {
|
||||
private static LogLevel currentLogLevel = LogLevel.LEVEL_WARN;
|
||||
|
||||
public static void verbose(String msg, Object... args) {
|
||||
if (currentLogLevel.level >= LogLevel.LEVEL_VERBOSE.level) {
|
||||
if (isVerboseEnabled()) {
|
||||
System.out.println(String.format("[VERBOSE] " + msg, args));
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,13 @@ public class LogUtil {
|
||||
System.err.println(String.format("[ERROR] " + msg, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* a pre-check method for reduce verbose parameter calculation
|
||||
*/
|
||||
public static boolean isVerboseEnabled() {
|
||||
return currentLogLevel.level >= LogLevel.LEVEL_VERBOSE.level;
|
||||
}
|
||||
|
||||
public static void setLevel(LogLevel level) {
|
||||
currentLogLevel = level;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<version>0.5.1</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-maven-plugin</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<version>0.5.1</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.5.0</testable.version>
|
||||
<testable.version>0.5.1</testable.version>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<version>0.5.1</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-processor</artifactId>
|
||||
|
||||
@@ -66,12 +66,13 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
|
||||
try {
|
||||
Class<?> cls = getSourceClass(clazz, sourceClassFullName);
|
||||
if (cls == null) {
|
||||
cx.logger.error("Failed to load source class: " + sourceClassFullName);
|
||||
cx.logger.fatal("Failed to load source class \"" + sourceClassFullName + "\"");
|
||||
} else {
|
||||
findAllPrivateMembers(cls);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// for any reason, interrupt the compile process
|
||||
cx.logger.fatal("Failed to load source class \"" + sourceClassFullName + "\": " + e);
|
||||
}
|
||||
this.privateAccessChecker = (p.verifyTargetExistence == null || p.verifyTargetExistence) ?
|
||||
new PrivateAccessChecker(cx, sourceClassShortName, memberRecord) : null;
|
||||
|
||||
@@ -16,17 +16,27 @@ public class TestableLogger {
|
||||
this.messager = messager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print hint message
|
||||
*/
|
||||
public void info(String msg) {
|
||||
// Message level lower than warning is not shown by default, use stdout instead
|
||||
System.out.println("[INFO] " + msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print warning message
|
||||
*/
|
||||
public void warn(String msg) {
|
||||
// Message level WARNING won't show, use MANDATORY_WARNING instead
|
||||
messager.printMessage(Diagnostic.Kind.MANDATORY_WARNING, msg);
|
||||
}
|
||||
|
||||
public void error(String msg) {
|
||||
/**
|
||||
* Print fatal message
|
||||
* Note: this will stop current compile process
|
||||
*/
|
||||
public void fatal(String msg) {
|
||||
messager.printMessage(Diagnostic.Kind.ERROR, msg);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user