mirror of
https://github.com/alibaba/testable-mock.git
synced 2026-08-19 09:43:29 +08:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30570ae69d | ||
|
|
320ee632b7 | ||
|
|
ba284f6518 | ||
|
|
c6e861e5cf | ||
|
|
0287d27d85 | ||
|
|
1e9079e8d3 | ||
|
|
e7a2b06698 | ||
|
|
ab87ef1fd0 | ||
|
|
d5c1f780de | ||
|
|
a463bcbfd9 | ||
|
|
c53f1e01f0 | ||
|
|
12989aa612 | ||
|
|
61a3090431 | ||
|
|
e9ce61d83c | ||
|
|
d9f65189f7 | ||
|
|
1446382f53 | ||
|
|
ada70f88c0 | ||
|
|
65b58d0861 | ||
|
|
a67dd7b73f | ||
|
|
1f6fc2e4bc | ||
|
|
eb8ccb0a40 | ||
|
|
dcbb305e10 | ||
|
|
8c0a74264a | ||
|
|
dcc5108149 | ||
|
|
0a40e07739 | ||
|
|
9e5f722076 | ||
|
|
1dcbe6b0f2 | ||
|
|
e4ff8ce57e | ||
|
|
47871d0eb6 | ||
|
|
e52538acf3 | ||
|
|
db91a0ea34 | ||
|
|
d912e53ebc | ||
|
|
1e2e1e6d84 | ||
|
|
1bb6007eb5 | ||
|
|
6632fae529 | ||
|
|
9018301bfc | ||
|
|
dbaeb3bcf1 | ||
|
|
fc7e4af2c6 | ||
|
|
76cff45123 | ||
|
|
5c8ef00fce | ||
|
|
f8501b0722 | ||
|
|
46e4499035 | ||
|
|
f508f7aba9 | ||
|
|
6e029f61d8 | ||
|
|
b654a3cfaa | ||
|
|
038ae8dba0 | ||
|
|
11a5565cee | ||
|
|
ba77a0138c | ||
|
|
9788dd9ecc | ||
|
|
55a6e141fc | ||
|
|
81a2817b6c | ||
|
|
ef6099a63d | ||
|
|
3c11676408 | ||
|
|
b2a87bffbe | ||
|
|
1a2083416e | ||
|
|
c1e6d00953 | ||
|
|
39b742b290 | ||
|
|
0a5666c0be | ||
|
|
006e742c28 | ||
|
|
237b9ebb15 |
@@ -49,7 +49,7 @@ dependencies {
|
|||||||
testImplementation 'androidx.test:runner:1.4.0-alpha05'
|
testImplementation 'androidx.test:runner:1.4.0-alpha05'
|
||||||
testImplementation 'junit:junit:4.+'
|
testImplementation 'junit:junit:4.+'
|
||||||
testImplementation 'org.robolectric:robolectric:4.5.1'
|
testImplementation 'org.robolectric:robolectric:4.5.1'
|
||||||
testImplementation 'com.alibaba.testable:testable-all:0.7.6'
|
testImplementation 'com.alibaba.testable:testable-all:0.7.9'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
}
|
}
|
||||||
@@ -13,8 +13,8 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
|
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
|
||||||
testImplementation('com.alibaba.testable:testable-all:0.7.6')
|
testImplementation('com.alibaba.testable:testable-all:0.7.9')
|
||||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.6')
|
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.9')
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(JavaCompile) {
|
tasks.withType(JavaCompile) {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
<junit.version>5.6.2</junit.version>
|
<junit.version>5.9.1</junit.version>
|
||||||
<testable.version>0.7.6</testable.version>
|
<testable.version>0.7.9</testable.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.0.0-M5</version>
|
<version>3.0.0-M7</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
|
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
<artifactId>jacoco-maven-plugin</artifactId>
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
<version>0.8.6</version>
|
<version>0.8.8</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>prepare-agent</id>
|
<id>prepare-agent</id>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.alibaba.demo.basic.model.mock.Color;
|
|||||||
* 演示父类变量引用子类对象时的Mock场景
|
* 演示父类变量引用子类对象时的Mock场景
|
||||||
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
||||||
*/
|
*/
|
||||||
public class DemoInherit {
|
public class DemoReference {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* call method overridden by subclass via parent class variable
|
* call method overridden by subclass via parent class variable
|
||||||
@@ -15,6 +15,14 @@ public class Child {
|
|||||||
|
|
||||||
private InnerChild subChild;
|
private InnerChild subChild;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这是一个私有内部类
|
||||||
|
* An private inner class
|
||||||
|
*/
|
||||||
|
private class InnerChild {
|
||||||
|
private String secret;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------------------------------
|
/* ---------------------------------------
|
||||||
以下Getters/Setters方法仅为便于功能演示而添加
|
以下Getters/Setters方法仅为便于功能演示而添加
|
||||||
并非OmniConstructor或OmniAccessor功能所需
|
并非OmniConstructor或OmniAccessor功能所需
|
||||||
@@ -36,12 +44,4 @@ public class Child {
|
|||||||
this.subChild = subChild;
|
this.subChild = subChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 这是一个私有内部类
|
|
||||||
* An private inner class
|
|
||||||
*/
|
|
||||||
private class InnerChild {
|
|
||||||
private String secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.alibaba.demo.inherit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示使用@MockContainer注解实现Mock容器类的多重继承
|
||||||
|
* Demonstrate multiple inherit of mock container class with @MockContainer annotation
|
||||||
|
*/
|
||||||
|
public class DemoMultipleInherit {
|
||||||
|
|
||||||
|
private String prefix() {
|
||||||
|
return "ori_";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String middle() {
|
||||||
|
return "gin";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String suffix(int some, String more, Object[] parameters) {
|
||||||
|
return "_al";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String entry() {
|
||||||
|
return prefix() + middle() + suffix(0, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.alibaba.demo.inherit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示使用extends关键字实现Mock容器类的继承
|
||||||
|
* Demonstrate inherit of mock container class
|
||||||
|
*/
|
||||||
|
public class DemoSingleInherit {
|
||||||
|
|
||||||
|
private String prefix() {
|
||||||
|
return "re_";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String suffix() {
|
||||||
|
return "_al";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String entry(String word) {
|
||||||
|
return prefix() + word + suffix();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
package com.alibaba.demo.lambda;
|
package com.alibaba.demo.lambda;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author jim
|
* @author jim
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,11 +13,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
* 演示父类变量引用子类对象时的Mock场景
|
* 演示父类变量引用子类对象时的Mock场景
|
||||||
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
||||||
*/
|
*/
|
||||||
class DemoInheritTest {
|
class DemoReferenceTest {
|
||||||
|
|
||||||
private DemoInherit demoInherit = new DemoInherit();
|
private DemoReference demoReference = new DemoReference();
|
||||||
|
|
||||||
public static class Mock {
|
public static class Mock {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当需要引用原对象时,不加`targetClass`参数,将原类型作为Mock方法的首个参数,建议将参数命名为`self`
|
||||||
|
* When method body need to refer the invoked object, skip the use `targetClass` parameter,
|
||||||
|
* and add a parameter `self` with target class type before other paramter of the target method
|
||||||
|
*/
|
||||||
@MockInvoke(targetMethod = "put")
|
@MockInvoke(targetMethod = "put")
|
||||||
private void put_into_box(Box self, String something) {
|
private void put_into_box(Box self, String something) {
|
||||||
self.put("put_" + something + "_into_box");
|
self.put("put_" + something + "_into_box");
|
||||||
@@ -28,77 +34,81 @@ class DemoInheritTest {
|
|||||||
self.put("put_" + something + "_into_blackbox");
|
self.put("put_" + something + "_into_blackbox");
|
||||||
}
|
}
|
||||||
|
|
||||||
@MockInvoke(targetMethod = "get")
|
/**
|
||||||
private String get_from_box(Box self) {
|
* 当不需要引用原对象时,将目标类型作为`@MockInvoke`注解的`targetClass`参数值即可
|
||||||
|
* When the invoked object is not used in the mock method, set `targetClass` value to the type who owns the mocked method
|
||||||
|
*/
|
||||||
|
@MockInvoke(targetClass = Box.class, targetMethod = "get")
|
||||||
|
private String get_from_box() {
|
||||||
return "get_from_box";
|
return "get_from_box";
|
||||||
}
|
}
|
||||||
|
|
||||||
@MockInvoke(targetMethod = "get")
|
@MockInvoke(targetClass = BlackBox.class, targetMethod = "get")
|
||||||
private String get_from_blackbox(BlackBox self) {
|
private String get_from_blackbox() {
|
||||||
return "get_from_blackbox";
|
return "get_from_blackbox";
|
||||||
}
|
}
|
||||||
|
|
||||||
@MockInvoke(targetMethod = "getColor")
|
@MockInvoke(targetClass = Color.class, targetMethod = "getColor")
|
||||||
private String get_color_from_color(Color self) {
|
private String get_color_from_color() {
|
||||||
return "color_from_color";
|
return "color_from_color";
|
||||||
}
|
}
|
||||||
|
|
||||||
@MockInvoke(targetMethod = "getColor")
|
@MockInvoke(targetClass = BlackBox.class, targetMethod = "getColor")
|
||||||
private String get_color_from_blackbox(BlackBox self) {
|
private String get_color_from_blackbox() {
|
||||||
return "color_from_blackbox";
|
return "color_from_blackbox";
|
||||||
}
|
}
|
||||||
|
|
||||||
@MockInvoke(targetMethod = "getColorIndex")
|
@MockInvoke(targetClass = Color.class, targetMethod = "getColorIndex")
|
||||||
private String get_colorIdx_from_color(Color self) {
|
private String get_colorIdx_from_color() {
|
||||||
return "colorIdx_from_color";
|
return "colorIdx_from_color";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_mock_call_sub_object_method_by_parent_object() {
|
void should_mock_call_sub_object_method_by_parent_object() {
|
||||||
BlackBox box = (BlackBox)demoInherit.putIntoBox();
|
BlackBox box = (BlackBox) demoReference.putIntoBox();
|
||||||
verifyInvoked("put_into_box").withTimes(1);
|
verifyInvoked("put_into_box").withTimes(1);
|
||||||
assertEquals("put_data_into_box", box.get());
|
assertEquals("put_data_into_box", box.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_mock_call_sub_object_method_by_sub_object() {
|
void should_mock_call_sub_object_method_by_sub_object() {
|
||||||
BlackBox box = demoInherit.putIntoBlackBox();
|
BlackBox box = demoReference.putIntoBlackBox();
|
||||||
verifyInvoked("put_into_blackbox").withTimes(1);
|
verifyInvoked("put_into_blackbox").withTimes(1);
|
||||||
assertEquals("put_data_into_blackbox", box.get());
|
assertEquals("put_data_into_blackbox", box.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_mock_call_parent_object_method_by_parent_object() {
|
void should_mock_call_parent_object_method_by_parent_object() {
|
||||||
String content = demoInherit.getFromBox();
|
String content = demoReference.getFromBox();
|
||||||
verifyInvoked("get_from_box").withTimes(1);
|
verifyInvoked("get_from_box").withTimes(1);
|
||||||
assertEquals("get_from_box", content);
|
assertEquals("get_from_box", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_mock_call_parent_object_method_by_sub_object() {
|
void should_mock_call_parent_object_method_by_sub_object() {
|
||||||
String content = demoInherit.getFromBlackBox();
|
String content = demoReference.getFromBlackBox();
|
||||||
verifyInvoked("get_from_blackbox").withTimes(1);
|
verifyInvoked("get_from_blackbox").withTimes(1);
|
||||||
assertEquals("get_from_blackbox", content);
|
assertEquals("get_from_blackbox", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_mock_call_interface_method_by_interface_object() {
|
void should_mock_call_interface_method_by_interface_object() {
|
||||||
String color = demoInherit.getColorViaColor();
|
String color = demoReference.getColorViaColor();
|
||||||
verifyInvoked("get_color_from_color").withTimes(1);
|
verifyInvoked("get_color_from_color").withTimes(1);
|
||||||
assertEquals("color_from_color", color);
|
assertEquals("color_from_color", color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_mock_call_interface_method_by_sub_class_object() {
|
void should_mock_call_interface_method_by_sub_class_object() {
|
||||||
String color = demoInherit.getColorViaBox();
|
String color = demoReference.getColorViaBox();
|
||||||
verifyInvoked("get_color_from_blackbox").withTimes(1);
|
verifyInvoked("get_color_from_blackbox").withTimes(1);
|
||||||
assertEquals("color_from_blackbox", color);
|
assertEquals("color_from_blackbox", color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_mock_call_interface_method_by_sub_interface_object() {
|
void should_mock_call_interface_method_by_sub_interface_object() {
|
||||||
String colorIdx = demoInherit.getColorIdxViaColor();
|
String colorIdx = demoReference.getColorIdxViaColor();
|
||||||
verifyInvoked("get_colorIdx_from_color").withTimes(1);
|
verifyInvoked("get_colorIdx_from_color").withTimes(1);
|
||||||
assertEquals("colorIdx_from_color", colorIdx);
|
assertEquals("colorIdx_from_color", colorIdx);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.alibaba.demo.inherit;
|
||||||
|
|
||||||
|
import com.alibaba.testable.core.annotation.MockContainer;
|
||||||
|
import com.alibaba.testable.core.annotation.MockInvoke;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示使用@MockContainer注解实现Mock容器类的多重继承
|
||||||
|
* Demonstrate multiple inherit of mock container class with @MockContainer annotation
|
||||||
|
*/
|
||||||
|
class DemoMultipleInheritTest {
|
||||||
|
|
||||||
|
private DemoMultipleInherit demoMultipleInherit = new DemoMultipleInherit();
|
||||||
|
|
||||||
|
public static class PrefixMock {
|
||||||
|
@MockInvoke(targetClass = DemoMultipleInherit.class)
|
||||||
|
private String prefix() {
|
||||||
|
return "in_";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SuffixMock {
|
||||||
|
@MockInvoke(targetClass = DemoMultipleInherit.class)
|
||||||
|
private String suffix(int some, String more, Object[] parameters) {
|
||||||
|
return "_it";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MockContainer(inherits = { PrefixMock.class, SuffixMock.class })
|
||||||
|
public static class Mock {
|
||||||
|
@MockInvoke(targetClass = DemoMultipleInherit.class)
|
||||||
|
private String middle() {
|
||||||
|
return "her";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void should_use_mock_method_in_parent_class() {
|
||||||
|
assertEquals("in_her_it", demoMultipleInherit.entry());
|
||||||
|
verifyInvoked("prefix").withTimes(1);
|
||||||
|
verifyInvoked("middle").withTimes(1);
|
||||||
|
verifyInvoked("suffix").withTimes(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.alibaba.demo.inherit;
|
||||||
|
|
||||||
|
import com.alibaba.testable.core.annotation.MockInvoke;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 演示Mock容器类的继承
|
||||||
|
* Demonstrate inherit of mock container class with extends keyword
|
||||||
|
*/
|
||||||
|
class DemoSingleInheritTest {
|
||||||
|
|
||||||
|
private DemoSingleInherit demoSingleInherit = new DemoSingleInherit();
|
||||||
|
|
||||||
|
public static class BasicMock {
|
||||||
|
@MockInvoke(targetClass = DemoSingleInherit.class)
|
||||||
|
private String suffix() {
|
||||||
|
return "_ck";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Mock extends BasicMock {
|
||||||
|
@MockInvoke(targetClass = DemoSingleInherit.class)
|
||||||
|
private String prefix() {
|
||||||
|
return "mo_";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void should_use_mock_method_in_parent_class() {
|
||||||
|
assertEquals("mo_test_ck", demoSingleInherit.entry("test"));
|
||||||
|
verifyInvoked("prefix").withTimes(1);
|
||||||
|
verifyInvoked("suffix").withTimes(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.alibaba.demo.lambda;
|
package com.alibaba.demo.lambda;
|
||||||
|
|
||||||
import com.alibaba.testable.core.annotation.MockDiagnose;
|
|
||||||
import com.alibaba.testable.core.annotation.MockInvoke;
|
import com.alibaba.testable.core.annotation.MockInvoke;
|
||||||
import com.alibaba.testable.core.model.LogLevel;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
|
||||||
@@ -13,7 +11,6 @@ public class CollectionListCodeDemoTest {
|
|||||||
|
|
||||||
private final CollectionListCodeDemo instance = new CollectionListCodeDemo();
|
private final CollectionListCodeDemo instance = new CollectionListCodeDemo();
|
||||||
|
|
||||||
//@MockDiagnose(LogLevel.VERBOSE)
|
|
||||||
public static class Mock {
|
public static class Mock {
|
||||||
@MockInvoke(targetClass = String.class, targetMethod = "contains")
|
@MockInvoke(targetClass = String.class, targetMethod = "contains")
|
||||||
public boolean mockContains(CharSequence s) {
|
public boolean mockContains(CharSequence s) {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.alibaba.demo.lambda;
|
package com.alibaba.demo.lambda;
|
||||||
|
|
||||||
import com.alibaba.testable.core.annotation.MockDiagnose;
|
|
||||||
import com.alibaba.testable.core.annotation.MockInvoke;
|
import com.alibaba.testable.core.annotation.MockInvoke;
|
||||||
import com.alibaba.testable.core.model.LogLevel;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -20,7 +18,6 @@ public class InvokeInterfaceDemoTest {
|
|||||||
|
|
||||||
private final InvokeInterfaceDemo instance = new InvokeInterfaceDemo();
|
private final InvokeInterfaceDemo instance = new InvokeInterfaceDemo();
|
||||||
|
|
||||||
//@MockDiagnose(LogLevel.VERBOSE)
|
|
||||||
public static class Mock {
|
public static class Mock {
|
||||||
|
|
||||||
@MockInvoke(targetClass = InvokeInterfaceDemo.ILambda.class, targetMethod = "run")
|
@MockInvoke(targetClass = InvokeInterfaceDemo.ILambda.class, targetMethod = "run")
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.alibaba.demo.lambda;
|
package com.alibaba.demo.lambda;
|
||||||
|
|
||||||
import com.alibaba.testable.core.annotation.MockDiagnose;
|
|
||||||
import com.alibaba.testable.core.annotation.MockInvoke;
|
import com.alibaba.testable.core.annotation.MockInvoke;
|
||||||
import com.alibaba.testable.core.model.LogLevel;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
|
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
|
||||||
@@ -15,16 +13,21 @@ public class StaticInstanceReferenceTest {
|
|||||||
|
|
||||||
private final StaticInstanceReference instance = new StaticInstanceReference();
|
private final StaticInstanceReference instance = new StaticInstanceReference();
|
||||||
|
|
||||||
//@MockDiagnose(LogLevel.VERBOSE)
|
|
||||||
public static class Mock {
|
public static class Mock {
|
||||||
@MockInvoke(targetClass = StaticInstanceReference.StaticClassA.class, targetMethod = "doIt")
|
@MockInvoke(targetClass = StaticInstanceReference.StaticClassA.class, targetMethod = "doIt")
|
||||||
private void mockDoIt() {
|
private void mockDoIt() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MockInvoke(targetClass = StaticInstanceReference.StaticClassA.class)
|
||||||
|
private Integer function2(String s) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldMockDoIt() {
|
public void shouldMockDoIt() {
|
||||||
instance.staticMethodReference();
|
instance.staticMethodReference();
|
||||||
verifyInvoked("mockDoIt").withTimes(1);
|
verifyInvoked("mockDoIt").withTimes(1);
|
||||||
|
verifyInvoked("function2").withTimes(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ dependencies {
|
|||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
|
||||||
testImplementation("com.alibaba.testable:testable-all:0.7.6")
|
testImplementation("com.alibaba.testable:testable-all:0.7.9")
|
||||||
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.7.6")
|
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.7.9")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<KotlinCompile> {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
<kotlin.version>1.3.72</kotlin.version>
|
<kotlin.version>1.3.72</kotlin.version>
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
<junit.version>5.6.2</junit.version>
|
<junit.version>5.9.1</junit.version>
|
||||||
<testable.version>0.7.6</testable.version>
|
<testable.version>0.7.9</testable.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.0.0-M5</version>
|
<version>3.0.0-M7</version>
|
||||||
<!-- <configuration>-->
|
<!-- <configuration>-->
|
||||||
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
|
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
|
||||||
<!-- </configuration>-->
|
<!-- </configuration>-->
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
<artifactId>jacoco-maven-plugin</artifactId>
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
<version>0.8.6</version>
|
<version>0.8.8</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>prepare-agent</id>
|
<id>prepare-agent</id>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.alibaba.demo.basic.model.mock.Color
|
|||||||
* 演示父类变量引用子类对象时的Mock场景
|
* 演示父类变量引用子类对象时的Mock场景
|
||||||
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
||||||
*/
|
*/
|
||||||
class DemoInherit {
|
class DemoReference {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* call method overridden by subclass via parent class variable
|
* call method overridden by subclass via parent class variable
|
||||||
@@ -109,7 +109,7 @@ internal class DemoMockTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_get_test_case_name() {
|
fun should_set_mock_context() {
|
||||||
MOCK_CONTEXT["case"] = "special_case"
|
MOCK_CONTEXT["case"] = "special_case"
|
||||||
// synchronous
|
// synchronous
|
||||||
assertEquals("mock_special", demoMock.callerOne())
|
assertEquals("mock_special", demoMock.callerOne())
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import org.junit.jupiter.api.Test
|
|||||||
* 演示父类变量引用子类对象时的Mock场景
|
* 演示父类变量引用子类对象时的Mock场景
|
||||||
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
||||||
*/
|
*/
|
||||||
internal class DemoInheritTest {
|
internal class DemoReferenceTest {
|
||||||
|
|
||||||
private val demoInherit = DemoInherit()
|
private val demoReference = DemoReference()
|
||||||
|
|
||||||
class Mock {
|
class Mock {
|
||||||
@MockInvoke(targetMethod = "put")
|
@MockInvoke(targetMethod = "put")
|
||||||
@@ -50,42 +50,42 @@ internal class DemoInheritTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_mock_call_sub_object_method_by_parent_object() {
|
fun should_mock_call_sub_object_method_by_parent_object() {
|
||||||
val box = demoInherit.putIntoBox() as BlackBox
|
val box = demoReference.putIntoBox() as BlackBox
|
||||||
InvocationVerifier.verifyInvoked("put_into_box").withTimes(1)
|
InvocationVerifier.verifyInvoked("put_into_box").withTimes(1)
|
||||||
assertEquals("put_data_into_box", box.get())
|
assertEquals("put_data_into_box", box.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_mock_call_sub_object_method_by_sub_object() {
|
fun should_mock_call_sub_object_method_by_sub_object() {
|
||||||
val box = demoInherit.putIntoBlackBox()
|
val box = demoReference.putIntoBlackBox()
|
||||||
InvocationVerifier.verifyInvoked("put_into_blackbox").withTimes(1)
|
InvocationVerifier.verifyInvoked("put_into_blackbox").withTimes(1)
|
||||||
assertEquals("put_data_into_blackbox", box.get())
|
assertEquals("put_data_into_blackbox", box.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_mock_call_parent_object_method_by_parent_object() {
|
fun should_mock_call_parent_object_method_by_parent_object() {
|
||||||
val content = demoInherit.fromBox
|
val content = demoReference.fromBox
|
||||||
InvocationVerifier.verifyInvoked("get_from_box").withTimes(1)
|
InvocationVerifier.verifyInvoked("get_from_box").withTimes(1)
|
||||||
assertEquals("get_from_box", content)
|
assertEquals("get_from_box", content)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_mock_call_parent_object_method_by_sub_object() {
|
fun should_mock_call_parent_object_method_by_sub_object() {
|
||||||
val content = demoInherit.fromBlackBox
|
val content = demoReference.fromBlackBox
|
||||||
InvocationVerifier.verifyInvoked("get_from_blackbox").withTimes(1)
|
InvocationVerifier.verifyInvoked("get_from_blackbox").withTimes(1)
|
||||||
assertEquals("get_from_blackbox", content)
|
assertEquals("get_from_blackbox", content)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_mock_call_interface_method_by_interface_object() {
|
fun should_mock_call_interface_method_by_interface_object() {
|
||||||
val color = demoInherit.colorViaColor
|
val color = demoReference.colorViaColor
|
||||||
InvocationVerifier.verifyInvoked("get_color_from_color").withTimes(1)
|
InvocationVerifier.verifyInvoked("get_color_from_color").withTimes(1)
|
||||||
assertEquals("color_from_color", color)
|
assertEquals("color_from_color", color)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_mock_call_interface_method_by_sub_class_object() {
|
fun should_mock_call_interface_method_by_sub_class_object() {
|
||||||
val color = demoInherit.colorViaBox
|
val color = demoReference.colorViaBox
|
||||||
InvocationVerifier.verifyInvoked("get_color_from_blackbox").withTimes(1)
|
InvocationVerifier.verifyInvoked("get_color_from_blackbox").withTimes(1)
|
||||||
assertEquals("color_from_blackbox", color)
|
assertEquals("color_from_blackbox", color)
|
||||||
}
|
}
|
||||||
@@ -14,8 +14,8 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'org.codehaus.groovy:groovy-all:3.0.7'
|
testImplementation 'org.codehaus.groovy:groovy-all:3.0.7'
|
||||||
testImplementation 'org.spockframework:spock-core:2.0-M5-groovy-3.0'
|
testImplementation 'org.spockframework:spock-core:2.0-M5-groovy-3.0'
|
||||||
testImplementation('com.alibaba.testable:testable-all:0.7.6')
|
testImplementation('com.alibaba.testable:testable-all:0.7.9')
|
||||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.6')
|
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.9')
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(JavaCompile) {
|
tasks.withType(JavaCompile) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
<testable.version>0.7.6</testable.version>
|
<testable.version>0.7.9</testable.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.0.0-M5</version>
|
<version>3.0.0-M7</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<argLine>-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
|
<argLine>-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
|
||||||
<useFile>false</useFile>
|
<useFile>false</useFile>
|
||||||
|
|||||||
131
docs/en-us/doc/collection-tools.md
Normal file
131
docs/en-us/doc/collection-tools.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
Collection Tools
|
||||||
|
---
|
||||||
|
|
||||||
|
During test data preparation, various collection types are often used. However, collection data initialization in Java needs series of redundant call to the `add()` or `put()` method, making the code very tedious.
|
||||||
|
|
||||||
|
Inspired by the collection operations of Kotlin language, `TestableMock` draws on a set of concise collection construction method, which makes the creation of Java collection objects elegant.
|
||||||
|
|
||||||
|
The utility class `CollectionTool` provides following global static methods:
|
||||||
|
|
||||||
|
## arrayOf
|
||||||
|
|
||||||
|
Quickly construct an array of any type, equivalent to Kotlin's `arrayOf()` method.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.arrayOf;
|
||||||
|
|
||||||
|
// Construct a string array
|
||||||
|
String[] manyWords = arrayOf("this", "is", "an", "example");
|
||||||
|
|
||||||
|
// Construct a double array
|
||||||
|
Double[] manyValues = arrayOf(1.0D, 2.0D, 3.0D);
|
||||||
|
```
|
||||||
|
|
||||||
|
## listOf
|
||||||
|
|
||||||
|
Quickly construct an list of any type, equivalent to Kotlin's `mutableListOf()` method.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.listOf;
|
||||||
|
|
||||||
|
// Construct a string list
|
||||||
|
List<String> manyWords = listOf("this", "is", "an", "example");
|
||||||
|
|
||||||
|
// Construct a double list
|
||||||
|
List<Double> manyValues = listOf(1.0D, 2.0D, 3.0D);
|
||||||
|
```
|
||||||
|
|
||||||
|
## setOf
|
||||||
|
|
||||||
|
Quickly construct an set of any type, equivalent to Kotlin's `mutableSetOf()` method.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.setOf;
|
||||||
|
|
||||||
|
// Construct a string set
|
||||||
|
Set<String> manyWords = setOf("this", "is", "an", "example");
|
||||||
|
|
||||||
|
// Construct a double set
|
||||||
|
Set<Double> manyValues = setOf(1.0D, 2.0D, 3.0D);
|
||||||
|
```
|
||||||
|
|
||||||
|
## mapOf
|
||||||
|
|
||||||
|
Quickly construct an map of any type, equivalent to Kotlin's `mutableMapOf()` method.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.mapOf;
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.entryOf;
|
||||||
|
|
||||||
|
// Construct a string to string map
|
||||||
|
Map<String, String> manyWords = mapOf(
|
||||||
|
entryOf("language", "java"),
|
||||||
|
entryOf("type", "unittest"),
|
||||||
|
entryOf("library", "testable")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Construct a integer to double map
|
||||||
|
Map<Integer, Double> manyValues = mapOf(
|
||||||
|
entryOf(1, 10.12D),
|
||||||
|
entryOf(2, 20.24D),
|
||||||
|
entryOf(3, 30.36D)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## orderedMapOf
|
||||||
|
|
||||||
|
Quickly construct ordered map (LinkedListMap) of any type. Compared with the HashMap map created by `mapOf()`, when traversing its keys or values in a loop, the order will always be the same as entries passed to `orderedMapOf()` method during construction.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.orderedMapOf;
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.entryOf;
|
||||||
|
|
||||||
|
// Construct a string to string ordered map
|
||||||
|
Map<String, String> manyWords = orderedMapOf(
|
||||||
|
entryOf("language", "java"),
|
||||||
|
entryOf("type", "unittest"),
|
||||||
|
entryOf("library", "testable")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Construct a integer to double ordered map
|
||||||
|
Map<Integer, Double> manyValues = orderedMapOf(
|
||||||
|
entryOf(1, 10.12D),
|
||||||
|
entryOf(2, 20.24D),
|
||||||
|
entryOf(3, 30.36D)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## entryOf
|
||||||
|
|
||||||
|
Used with `mapOf()` and `orderedMapOf()` methods to construct a map.
|
||||||
|
|
||||||
|
## slice
|
||||||
|
|
||||||
|
Used to extract a part of data from any type of array to a new array.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.slice;
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.arrayOf;
|
||||||
|
|
||||||
|
// Extract the elements of the specified string array from subscript 1 to 3
|
||||||
|
// the returned new array content are ["is", "a", "simple"]
|
||||||
|
String[] fullWords = arrayOf("this", "is", "a", "simple", "example");
|
||||||
|
String[] partOfWords = slice(fullWords, 1, 3);
|
||||||
|
|
||||||
|
// If only given the index of starting position, all elements after this position are extracted
|
||||||
|
// the new array returned in the following example is [5, 8, 13, 21]
|
||||||
|
Integer[] fibonacci = arrayOf(1, 1, 2, 3, 5, 8, 13, 21);
|
||||||
|
Integer[] partOfFibonacci = slice(fibonacci, 4);
|
||||||
|
```
|
||||||
@@ -25,9 +25,9 @@ In the code, there are often cases of using <u>interface variables or parent cla
|
|||||||
|
|
||||||
At this time, follow a principle that the type of the first parameter of the mock method is always the same as the type of the variable that initiated the call.
|
At this time, follow a principle that the type of the first parameter of the mock method is always the same as the type of the variable that initiated the call.
|
||||||
|
|
||||||
Therefore, regardless of whether the actually called method comes from the parent class or the subclass, and whether the subclass overrides the method. If the calling variable is of the parent type (or interface type), the first parameter type of the mock method should use the corresponding parent type (or interface) type.
|
Therefore, regardless of whether the actually called method comes from the parent class or the subclass, and whether the subclass overrides the method. If the calling variable is of the parent type (or interface type), the `targetClass` value of `@MockInvoke` annotation (or the first parameter type of the mock method) should use the corresponding parent type / interface type.
|
||||||
|
|
||||||
See the use case of the `DemoInheritTest` test class in the Java and Kotlin examples.
|
See the use case of the `DemoReferenceTest` test class in the Java and Kotlin examples.
|
||||||
|
|
||||||
#### 5. How to mock generic methods (template methods)?
|
#### 5. How to mock generic methods (template methods)?
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ Parent parent = OmniConstructor.newInstance(Parent.class);
|
|||||||
parent.getChild().getGrandChild().getContent();
|
parent.getChild().getGrandChild().getContent();
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note 1**: In the current version, the member fields of type interface or abstract class will still be initialized to `null`, this problem will be fixed in the future.
|
> Interface and abstract class can also be instantiated by `OmniConstructor`, you can try it e.g. `OmniConstructor.newInstance(CharSequence.class)` ^_^
|
||||||
>
|
|
||||||
> **Note 2**: Based on the light-weight principle, in the default mode, `OmniConstructor` will only uses the original constructor of the class to create objects. For POJO and most model layer objects, this mode has been able to meet the needs.
|
> **Notice**: Based on the light-weight principle, in the default mode, `OmniConstructor` will only uses the original constructor of the class to create objects. For POJO and most model layer objects, this mode has been able to meet the needs.
|
||||||
> But for more complex situations, such as when certain class have constructors throwing exceptions or contain other statements that hinder the normal execution of the construction, the object construction may fail.
|
> But for more complex situations, such as when certain class have constructors throwing exceptions or contain other statements that hinder the normal execution of the construction, the object construction may fail.
|
||||||
> In those situations, you can use the [Testable global configuration](en-us/doc/javaagent-args.md) `omni.constructor.enhance.enable = true` to enable bytecode enhancement mode of `OmniConstructor`, in this mode, any Java class can be constructed.
|
> In those situations, you can use the [Testable global configuration](en-us/doc/javaagent-args.md) `omni.constructor.enhance.enable = true` to enable bytecode enhancement mode of `OmniConstructor`, in this mode, any Java class can be constructed.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# Release Note
|
# Release Note
|
||||||
|
|
||||||
|
## 0.7.9
|
||||||
|
- fix many issues relate to interface instance creation of `OmniConstructor`
|
||||||
|
- add `@MockContainer` annotation to support multiply parents inheritance between mock classes
|
||||||
|
- add `targetClassName` parameter to `@MockInvoke` annotation, support mock method of private internal class
|
||||||
|
- move collection creation methods from `CollectionUtil` class to `CollectionTool` class
|
||||||
|
|
||||||
|
## 0.7.8
|
||||||
|
- support use `OmniConstructor` class to create instance of interface type or abstract class
|
||||||
|
- provide a `CollectionUtil` class which contains many utility collection creation methods
|
||||||
|
- fix a compatibility issue with jdk 1.6
|
||||||
|
|
||||||
|
## 0.7.7
|
||||||
|
- skip bytecode process for classes which not associated to any mock class, speed up test initialization
|
||||||
|
- only generate substitution method for mocked method references
|
||||||
|
- fix compatibility issues with jdk 17
|
||||||
|
|
||||||
## 0.7.6
|
## 0.7.6
|
||||||
- define package mapping rule according to order in config, to avoid a short path overwrite longer ones
|
- define package mapping rule according to order in config, to avoid a short path overwrite longer ones
|
||||||
- fix compatibility issue with jdk 9+
|
- fix compatibility issue with jdk 9+
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Use TestableMock
|
|||||||
- [Access private members of the class under test](en-us/doc/private-accessor.md): enable unit tests directly invoke or access private members of the class under test, solve the problems of private member initialization and private method testing
|
- [Access private members of the class under test](en-us/doc/private-accessor.md): enable unit tests directly invoke or access private members of the class under test, solve the problems of private member initialization and private method testing
|
||||||
- [Quickly construct complicated parameter object](en-us/doc/omni-constructor.md):generate arbitrarily nested object instances, simplify their internal member assignment methods, solve the problem of long initialization codes for method parameters
|
- [Quickly construct complicated parameter object](en-us/doc/omni-constructor.md):generate arbitrarily nested object instances, simplify their internal member assignment methods, solve the problem of long initialization codes for method parameters
|
||||||
- [Assist test void method](en-us/doc/test-void-method.md): use the mock validator to check the internal logic of method, solve the problem that unit testing is difficult to implement to the method with no return value
|
- [Assist test void method](en-us/doc/test-void-method.md): use the mock validator to check the internal logic of method, solve the problem that unit testing is difficult to implement to the method with no return value
|
||||||
|
- [Quickly create collection instances](en-us/doc/collection-tools.md): provide utility methods for collection creation, solve the problem of redundant codes during java test data preparation
|
||||||
|
|
||||||
## Use in Maven project
|
## Use in Maven project
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ It is recommended to add a `property` field that identifies the TestableMock ver
|
|||||||
|
|
||||||
```xml
|
```xml
|
||||||
<properties>
|
<properties>
|
||||||
<testable.version>0.7.6</testable.version>
|
<testable.version>0.7.9</testable.version>
|
||||||
</properties>
|
</properties>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -63,8 +64,8 @@ Add dependence of `TestableMock` in `build.gradle` file:
|
|||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation('com.alibaba.testable:testable-all:0.7.6')
|
testImplementation('com.alibaba.testable:testable-all:0.7.9')
|
||||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.6')
|
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.9')
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -159,13 +159,13 @@ private Data mockDemo() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For complete code examples, see the `should_get_source_method_name()` and `should_get_test_case_name()` test cases in the `java-demo` and `kotlin-demo` sample projects.
|
For complete code examples, see the `should_get_source_method_name()` and `should_set_mock_context()` test cases in the `java-demo` and `kotlin-demo` sample projects.
|
||||||
|
|
||||||
### 3. Verify the sequence and parameters of the mock method being invoked
|
### 3. Verify the sequence and parameters of the mock method being invoked
|
||||||
|
|
||||||
In test cases, you can use the `InvocationVerifier.verifyInvoked()` method, and cooperate with `with()`, `withInOrder()`, `without()`, `withTimes()` and other methods to verify the mock call situation.
|
In test cases, you can use the `InvocationVerifier.verifyInvoked()` method, and cooperate with `with()`, `withInOrder()`, `without()`, `withTimes()` and other methods to verify the mock call situation.
|
||||||
|
|
||||||
For details, please refer to the [Check Mock Call](en-us/doc/matcher.md) document.
|
For details, please refer to the [Check Mock Call](en-us/doc/invoke-matcher.md) document.
|
||||||
|
|
||||||
### 4. Special instructions
|
### 4. Special instructions
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
- [Private Accessor](en-us/doc/private-accessor.md)
|
- [Private Accessor](en-us/doc/private-accessor.md)
|
||||||
- [Omni Constructor](en-us/doc/omni-constructor.md)
|
- [Omni Constructor](en-us/doc/omni-constructor.md)
|
||||||
- [Test Void Method](en-us/doc/test-void-method.md)
|
- [Test Void Method](en-us/doc/test-void-method.md)
|
||||||
|
- [Collection Tools](en-us/doc/collection-tools.md)
|
||||||
|
|
||||||
- Usage Guide
|
- Usage Guide
|
||||||
- [Verify Mock Invocation](en-us/doc/invoke-matcher.md)
|
- [Verify Mock Invocation](en-us/doc/invoke-matcher.md)
|
||||||
|
|||||||
@@ -20,11 +20,14 @@
|
|||||||
|
|
||||||
- 作用于:Mock容器类中的方法
|
- 作用于:Mock容器类中的方法
|
||||||
|
|
||||||
| 参数 | 类型 | 是否必须 | 默认值 | 作用 |
|
| 参数 | 类型 | 是否必须 | 默认值 | 作用 |
|
||||||
| --- | --- | --- | ---- | --- |
|
| --- | --- | --- | ---- | --- |
|
||||||
| targetClass | Class | 否 | N/A | 指定Mock目标的调用者类型 |
|
| targetClass | Class | 否 | N/A | 指定Mock目标的调用者类型 |
|
||||||
| targetMethod | String | 否 | N/A | 指定Mock目标的方法名 |
|
| targetClassName | String | 否 | N/A | 与`targetClass`相同,主要用于无法直接引用的私有内部类 |
|
||||||
| scope | MockScope | 否 | MockScope.GLOBAL | 指定Mock的生效范围 |
|
| targetMethod | String | 否 | N/A | 指定Mock目标的方法名 |
|
||||||
|
| scope | MockScope | 否 | MockScope.GLOBAL | 指定Mock的生效范围 |
|
||||||
|
|
||||||
|
> 说明:`targetClass`和`targetClassName`参数不能同时使用
|
||||||
|
|
||||||
#### @MockNew
|
#### @MockNew
|
||||||
|
|
||||||
@@ -36,6 +39,15 @@
|
|||||||
| --- | --- | --- | ---- | --- |
|
| --- | --- | --- | ---- | --- |
|
||||||
| scope | MockScope | 否 | MockScope.GLOBAL | 指定Mock的生效范围 |
|
| scope | MockScope | 否 | MockScope.GLOBAL | 指定Mock的生效范围 |
|
||||||
|
|
||||||
|
#### @MockContainer
|
||||||
|
|
||||||
|
标识指定类为Mock容器类,并从任意个其他Mock类里继承所有Mock方法。
|
||||||
|
|
||||||
|
- 作用于:Mock容器类
|
||||||
|
|
||||||
|
| 参数 | 类型 | 是否必须 | 默认值 | 作用 |
|
||||||
|
| ---- | --- | --- | --- | --- |
|
||||||
|
| inherits | Class[] | 否 | N/A | 指定要继承的Mock类 |
|
||||||
|
|
||||||
#### @MockWith
|
#### @MockWith
|
||||||
|
|
||||||
|
|||||||
129
docs/zh-cn/doc/collection-tools.md
Normal file
129
docs/zh-cn/doc/collection-tools.md
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
常用集合构建工具
|
||||||
|
---
|
||||||
|
|
||||||
|
在编写测试数据的时候,经常会用到各种集合类型的对象,然而Java原生的集合类型需要反复调用`add()`或`put()`方法来添加数据,使代码显得十分冗长。
|
||||||
|
|
||||||
|
`TestableMock`借鉴了Kotlin语言简洁的集合构造方法,提供了一个实用的集合构造工具类`CollectionTool`,让Java集合对象的创建从此变得优雅。
|
||||||
|
|
||||||
|
这个工具类提供了以下全局静态方法:
|
||||||
|
|
||||||
|
## arrayOf
|
||||||
|
|
||||||
|
快速构造任意类型的数组,等效于Kotlin的`arrayOf()`方法。
|
||||||
|
|
||||||
|
用法示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.arrayOf;
|
||||||
|
|
||||||
|
// 构造一个字符串数组
|
||||||
|
String[] manyWords = arrayOf("this", "is", "an", "example");
|
||||||
|
|
||||||
|
// 构造一个浮点值数组
|
||||||
|
Double[] manyValues = arrayOf(1.0D, 2.0D, 3.0D);
|
||||||
|
```
|
||||||
|
|
||||||
|
## listOf
|
||||||
|
|
||||||
|
快速构造任意类型的列表,等效于Kotlin的`mutableListOf()`方法。
|
||||||
|
|
||||||
|
用法示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.listOf;
|
||||||
|
|
||||||
|
// 构造一个字符串列表
|
||||||
|
List<String> manyWords = listOf("this", "is", "an", "example");
|
||||||
|
|
||||||
|
// 构造一个浮点值列表
|
||||||
|
List<Double> manyValues = listOf(1.0D, 2.0D, 3.0D);
|
||||||
|
```
|
||||||
|
|
||||||
|
## setOf
|
||||||
|
|
||||||
|
快速构造任意类型的集合,等效于Kotlin的`mutableSetOf()`方法。
|
||||||
|
|
||||||
|
用法示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.setOf;
|
||||||
|
|
||||||
|
// 构造一个字符串集合
|
||||||
|
Set<String> manyWords = setOf("this", "is", "an", "example");
|
||||||
|
|
||||||
|
// 构造一个浮点值集合
|
||||||
|
Set<Double> manyValues = setOf(1.0D, 2.0D, 3.0D);
|
||||||
|
```
|
||||||
|
|
||||||
|
## mapOf
|
||||||
|
|
||||||
|
快速构造任意类型的无序映射表,等效于Kotlin的`mutableMapOf()`方法。
|
||||||
|
|
||||||
|
用法示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.mapOf;
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.entryOf;
|
||||||
|
|
||||||
|
// 构造一个字符串到字符串的映射
|
||||||
|
Map<String, String> manyWords = mapOf(
|
||||||
|
entryOf("language", "java"),
|
||||||
|
entryOf("type", "unittest"),
|
||||||
|
entryOf("library", "testable")
|
||||||
|
);
|
||||||
|
|
||||||
|
// 构造一个整型数值到浮点数值的映射
|
||||||
|
Map<Integer, Double> manyValues = mapOf(
|
||||||
|
entryOf(1, 10.12D),
|
||||||
|
entryOf(2, 20.24D),
|
||||||
|
entryOf(3, 30.36D)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## orderedMapOf
|
||||||
|
|
||||||
|
快速构造任意类型的有序映射表(LinkedListMap),相较于`mapOf()`创建的HashMap对象,当在循环中遍历它的键或值列表时,其访问顺序始终会与构造时传给`orderedMapOf()`方法的数据顺序一致。
|
||||||
|
|
||||||
|
用法示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.orderedMapOf;
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.entryOf;
|
||||||
|
|
||||||
|
// 构造一个字符串到字符串的有序映射
|
||||||
|
Map<String, String> manyWords = orderedMapOf(
|
||||||
|
entryOf("language", "java"),
|
||||||
|
entryOf("type", "unittest"),
|
||||||
|
entryOf("library", "testable")
|
||||||
|
);
|
||||||
|
|
||||||
|
// 构造一个整型数值到浮点数值的有序映射
|
||||||
|
Map<Integer, Double> manyValues = orderedMapOf(
|
||||||
|
entryOf(1, 10.12D),
|
||||||
|
entryOf(2, 20.24D),
|
||||||
|
entryOf(3, 30.36D)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## entryOf
|
||||||
|
|
||||||
|
配合`mapOf()`和`orderedMapOf()`方法使用,用于构造映射表。
|
||||||
|
|
||||||
|
## slice
|
||||||
|
|
||||||
|
用于提取任意类型数组中间的一部分数据,组成新的数组。
|
||||||
|
|
||||||
|
用法示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.slice;
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.arrayOf;
|
||||||
|
|
||||||
|
// 提取指定字符串数组从下标1到3之间的元素,得到的新数组内容为["is", "a", "simple"]
|
||||||
|
String[] fullWords = arrayOf("this", "is", "a", "simple", "example");
|
||||||
|
String[] partOfWords = slice(fullWords, 1, 3);
|
||||||
|
|
||||||
|
// 只传入起始位置下标,提取该位置之后的所有元素,下例返回的新数组内容为[5, 8, 13, 21]
|
||||||
|
Integer[] fibonacci = arrayOf(1, 1, 2, 3, 5, 8, 13, 21);
|
||||||
|
Integer[] partOfFibonacci = slice(fibonacci, 4);
|
||||||
|
```
|
||||||
@@ -29,9 +29,9 @@
|
|||||||
|
|
||||||
这时候遵循一个原则,Mock方法的首个参数类型**始终与发起调用的变量类型一致**。
|
这时候遵循一个原则,Mock方法的首个参数类型**始终与发起调用的变量类型一致**。
|
||||||
|
|
||||||
因此,不论实际被调用方法来自父类还是子类,也不论子类是否覆写该方法。若发起调用的变量为父类型(或接口类型),则Mock方法的首个参数类型都应该使用相应的父类(或接口)类型。
|
因此,不论实际被调用方法来自父类还是子类,也不论子类是否覆写该方法。若发起调用的变量为父类型(或接口类型),则`@MockInvoke`注解的`targetClass`参数值(或Mock方法的首个参数类型)都应该使用相应的父类/接口类的类型。
|
||||||
|
|
||||||
参见Java和Kotlin示例中`DemoInheritTest`测试类的用例。
|
参见Java和Kotlin示例中`DemoReferenceTest`测试类的用例。
|
||||||
|
|
||||||
#### 5. 如何Mock对于泛型方法(模板方法)?
|
#### 5. 如何Mock对于泛型方法(模板方法)?
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ Parent parent = OmniConstructor.newInstance(Parent.class);
|
|||||||
parent.getChild().getGrandChild().getContent();
|
parent.getChild().getGrandChild().getContent();
|
||||||
```
|
```
|
||||||
|
|
||||||
> **注意 1** :在当前版本中,类型为接口或抽象类的成员字段依然会被初始化为`null`,此问题将在后续版本修复
|
> 接口类型和抽象类型也可以直接通过`OmniConstructor`实例化哦,不信试试看`OmniConstructor.newInstance(CharSequence.class)` ^_^
|
||||||
>
|
|
||||||
> **注意 2** :基于轻量优先原则,默认模式下,`OmniConstructor`仅利用类型原有的构造方法来创建对象,对于绝大多数POJO和`Model`层对象而言这种模式已经能够满足需要。
|
> **注意**:基于轻量优先原则,默认模式下,`OmniConstructor`仅利用类型原有的构造方法来创建对象,对于绝大多数POJO和`Model`层对象而言这种模式已经能够满足需要。
|
||||||
> 但对于更复杂的情形,譬如遇到某些类型的构造方法会抛出异常或包含其他妨碍构造正常执行的语句时,对象构造可能会失败。
|
> 但对于更复杂的情形,譬如遇到某些类型的构造方法会抛出异常或包含其他妨碍构造正常执行的语句时,对象构造可能会失败。
|
||||||
> 此时可通过[Testable全局配置](zh-cn/doc/javaagent-args.md)`omni.constructor.enhance.enable = true`启用`OmniConstructor`的字节码增强模式,在该模式下,任何Java类型皆可构造。
|
> 此时可通过[Testable全局配置](zh-cn/doc/javaagent-args.md)`omni.constructor.enhance.enable = true`启用`OmniConstructor`的字节码增强模式,在该模式下,任何Java类型皆可构造。
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# Release Note
|
# Release Note
|
||||||
|
|
||||||
|
## 0.7.9
|
||||||
|
- 修复多处`OmniConstructor`创建接口类型对象相关的问题
|
||||||
|
- 新增`@MockContainer`注解,支持Mock容器类之间的多重继承
|
||||||
|
- `@MockInvoke`注解新增`targetClassName`参数,支持Mock私有内部类的方法
|
||||||
|
- 将`CollectionUtil`中的集合构造方法独立为`CollectionTool`工具类并发布
|
||||||
|
|
||||||
|
## 0.7.8
|
||||||
|
- 支持使用`OmniConstructor`工具类自动创建接口和抽象类的实例
|
||||||
|
- 提供`CollectionUtil`工具类,包含在测试中常用的集合创建方法
|
||||||
|
- 修复一处JDK 1.6的兼容问题
|
||||||
|
|
||||||
|
## 0.7.7
|
||||||
|
- 未关联Mock类的类型自动跳过字节码处理,提升大型项目测试启动速度
|
||||||
|
- 仅对被Mock的方法引用生成替代方法 (issue-280)
|
||||||
|
- 修复JDK 17的兼容性问题 (issue-283)
|
||||||
|
|
||||||
## 0.7.6
|
## 0.7.6
|
||||||
- 依据定义顺序匹配包映射规则,防止短路径覆盖长路径(issue-275)
|
- 依据定义顺序匹配包映射规则,防止短路径覆盖长路径(issue-275)
|
||||||
- 修复JDK 9+的兼容性问题(issue-270 / pr-274)
|
- 修复JDK 9+的兼容性问题(issue-270 / pr-274)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
- [访问被测类私有成员](zh-cn/doc/private-accessor.md):使单元测试能直接调用和访问被测类的私有成员,解决私有成员初始化和私有方法测试的问题
|
- [访问被测类私有成员](zh-cn/doc/private-accessor.md):使单元测试能直接调用和访问被测类的私有成员,解决私有成员初始化和私有方法测试的问题
|
||||||
- [快速构造参数对象](zh-cn/doc/omni-constructor.md):生成任意复杂嵌套的对象实例,并简化其内部成员赋值方式,解决被测方法参数初始化代码冗长的问题
|
- [快速构造参数对象](zh-cn/doc/omni-constructor.md):生成任意复杂嵌套的对象实例,并简化其内部成员赋值方式,解决被测方法参数初始化代码冗长的问题
|
||||||
- [辅助测试void方法](zh-cn/doc/test-void-method.md):利用Mock校验器对方法的内部逻辑进行检查,解决无返回值方法难以实施单元测试的问题
|
- [辅助测试void方法](zh-cn/doc/test-void-method.md):利用Mock校验器对方法的内部逻辑进行检查,解决无返回值方法难以实施单元测试的问题
|
||||||
|
- [快速构造集合对象](zh-cn/doc/collection-tools.md):提供简洁易用的集合构造和常用操作方法,解决准备测试数据时Java集合初始化代码冗长的问题
|
||||||
|
|
||||||
## 在Maven项目中使用
|
## 在Maven项目中使用
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
|
|
||||||
```xml
|
```xml
|
||||||
<properties>
|
<properties>
|
||||||
<testable.version>0.7.6</testable.version>
|
<testable.version>0.7.9</testable.version>
|
||||||
</properties>
|
</properties>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -63,8 +64,8 @@
|
|||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation('com.alibaba.testable:testable-all:0.7.6')
|
testImplementation('com.alibaba.testable:testable-all:0.7.9')
|
||||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.6')
|
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.9')
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -159,13 +159,13 @@ private Data mockDemo() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_get_source_method_name()`和`should_get_test_case_name()`测试用例。
|
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_get_source_method_name()`和`should_set_mock_context()`测试用例。
|
||||||
|
|
||||||
### 3. 验证Mock方法被调用的顺序和参数
|
### 3. 验证Mock方法被调用的顺序和参数
|
||||||
|
|
||||||
在测试用例中可用通过`InvocationVerifier.verifyInvoked()`方法,配合`with()`、`withInOrder()`、`without()`、`withTimes()`等方法实现对Mock调用情况的验证。
|
在测试用例中可用通过`InvocationVerifier.verifyInvoked()`方法,配合`with()`、`withInOrder()`、`without()`、`withTimes()`等方法实现对Mock调用情况的验证。
|
||||||
|
|
||||||
详见[校验Mock调用](zh-cn/doc/matcher.md)文档。
|
详见[校验Mock调用](zh-cn/doc/invoke-matcher.md)文档。
|
||||||
|
|
||||||
### 4. 特别说明
|
### 4. 特别说明
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Mock线程池内的调用
|
|||||||
|
|
||||||
```xml
|
```xml
|
||||||
<properties>
|
<properties>
|
||||||
<transmittable.thread.local.version>2.12.1</transmittable.thread.local.version>
|
<transmittable.thread.local.version>2.14.2</transmittable.thread.local.version>
|
||||||
</properties>
|
</properties>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
- [直接访问私有成员](zh-cn/doc/private-accessor.md)
|
- [直接访问私有成员](zh-cn/doc/private-accessor.md)
|
||||||
- [快速构造复杂入参](zh-cn/doc/omni-constructor.md)
|
- [快速构造复杂入参](zh-cn/doc/omni-constructor.md)
|
||||||
- [测试无返回值的方法](zh-cn/doc/test-void-method.md)
|
- [测试无返回值的方法](zh-cn/doc/test-void-method.md)
|
||||||
|
- [常用集合构建工具](zh-cn/doc/collection-tools.md)
|
||||||
|
|
||||||
- 使用指南
|
- 使用指南
|
||||||
- [校验Mock调用](zh-cn/doc/invoke-matcher.md)
|
- [校验Mock调用](zh-cn/doc/invoke-matcher.md)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.testable</groupId>
|
<groupId>com.alibaba.testable</groupId>
|
||||||
<artifactId>testable-parent</artifactId>
|
<artifactId>testable-parent</artifactId>
|
||||||
<version>0.7.6</version>
|
<version>0.7.9</version>
|
||||||
<relativePath>../testable-parent</relativePath>
|
<relativePath>../testable-parent</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>testable-agent</artifactId>
|
<artifactId>testable-agent</artifactId>
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
<name>testable-agent</name>
|
<name>testable-agent</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<asm.lib.version>8.0.1</asm.lib.version>
|
<asm.lib.version>9.3</asm.lib.version>
|
||||||
<junit.version>5.6.2</junit.version>
|
<junit.version>5.9.1</junit.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -123,6 +123,9 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>jar</goal>
|
<goal>jar</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -137,6 +140,13 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>sign</goal>
|
<goal>sign</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<!-- Prevent `gpg` from using pinentry programs -->
|
||||||
|
<gpgArguments>
|
||||||
|
<arg>--pinentry-mode</arg>
|
||||||
|
<arg>loopback</arg>
|
||||||
|
</gpgArguments>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ public class ConstPool {
|
|||||||
|
|
||||||
public static final String FIELD_TARGET_METHOD = "targetMethod";
|
public static final String FIELD_TARGET_METHOD = "targetMethod";
|
||||||
public static final String FIELD_TARGET_CLASS = "targetClass";
|
public static final String FIELD_TARGET_CLASS = "targetClass";
|
||||||
|
public static final String FIELD_TARGET_CLASS_NAME = "targetClassName";
|
||||||
public static final String FIELD_SCOPE = "scope";
|
public static final String FIELD_SCOPE = "scope";
|
||||||
public static final String FIELD_VALUE = "value";
|
public static final String FIELD_VALUE = "value";
|
||||||
|
public static final String FIELD_INHERITS = "inherits";
|
||||||
|
|
||||||
public static final String PROPERTY_USER_DIR = "user.dir";
|
public static final String PROPERTY_USER_DIR = "user.dir";
|
||||||
public static final String PROPERTY_TEMP_DIR = "java.io.tmpdir";
|
public static final String PROPERTY_TEMP_DIR = "java.io.tmpdir";
|
||||||
|
|
||||||
|
public static final String MOCK_CONTAINER = "com.alibaba.testable.core.annotation.MockContainer";
|
||||||
public static final String MOCK_WITH = "com.alibaba.testable.core.annotation.MockWith";
|
public static final String MOCK_WITH = "com.alibaba.testable.core.annotation.MockWith";
|
||||||
public static final String DUMP_TO = "com.alibaba.testable.core.annotation.DumpTo";
|
public static final String DUMP_TO = "com.alibaba.testable.core.annotation.DumpTo";
|
||||||
public static final String MOCK_DIAGNOSE = "com.alibaba.testable.core.annotation.MockDiagnose";
|
public static final String MOCK_DIAGNOSE = "com.alibaba.testable.core.annotation.MockDiagnose";
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.alibaba.testable.agent.handler;
|
package com.alibaba.testable.agent.handler;
|
||||||
|
|
||||||
import com.alibaba.testable.agent.constant.ConstPool;
|
|
||||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
import com.alibaba.testable.agent.tool.ImmutablePair;
|
||||||
import com.alibaba.testable.agent.util.*;
|
import com.alibaba.testable.agent.util.*;
|
||||||
import com.alibaba.testable.core.model.MockScope;
|
import com.alibaba.testable.core.model.MockScope;
|
||||||
@@ -10,12 +9,16 @@ import org.objectweb.asm.Label;
|
|||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.*;
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_ARRAY;
|
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_ARRAY;
|
||||||
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_CLASS;
|
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_CLASS;
|
||||||
import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT;
|
import static com.alibaba.testable.agent.constant.ConstPool.*;
|
||||||
|
import static com.alibaba.testable.agent.util.MockInvokeUtil.*;
|
||||||
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
||||||
|
import static com.alibaba.testable.core.util.CollectionUtil.fastListOf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author flin
|
* @author flin
|
||||||
@@ -63,9 +66,69 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
|||||||
handleTestableUtil(mn);
|
handleTestableUtil(mn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// should inject inherited at last to avoid above transfers
|
||||||
|
injectInheritedMockMethods(cn);
|
||||||
LogUtil.diagnose(" Found %d mock methods", mockMethodCount);
|
LogUtil.diagnose(" Found %d mock methods", mockMethodCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add member field of inherited mock class by @MockContainer annotation
|
||||||
|
*/
|
||||||
|
private void injectInheritedMockMethods(ClassNode cn) {
|
||||||
|
List<Type> inheritedTypes = new ArrayList<Type>();
|
||||||
|
AnnotationNode mockContainerAnnotation = AnnotationUtil.getClassAnnotation(cn, MOCK_CONTAINER);
|
||||||
|
if (mockContainerAnnotation != null) {
|
||||||
|
inheritedTypes.addAll(AnnotationUtil.getAnnotationParameter(mockContainerAnnotation, FIELD_INHERITS,
|
||||||
|
Collections.<Type>emptyList(), List.class));
|
||||||
|
}
|
||||||
|
for (Type inheritedType : inheritedTypes) {
|
||||||
|
String className = inheritedType.getClassName();
|
||||||
|
ClassNode inheritedClassNode = ClassUtil.getClassNode(className);
|
||||||
|
if (inheritedClassNode == null) {
|
||||||
|
throw new IllegalArgumentException("Failed to load class " + className);
|
||||||
|
}
|
||||||
|
for (MethodNode mn : inheritedClassNode.methods) {
|
||||||
|
if (mn.visibleAnnotations == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ClassUtil.hasMethod(cn, mn)) {
|
||||||
|
LogUtil.warn("Method '%s' with same signature already exists in class %s, skipped", mn.name, cn.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
|
if ((ClassUtil.toByteCodeClassName(MOCK_INVOKE)).equals(an.desc)) {
|
||||||
|
String targetClassName = getTargetClassName(an);
|
||||||
|
String targetMethod = AnnotationUtil.getAnnotationParameter(an, FIELD_TARGET_METHOD, null, String.class);
|
||||||
|
String desc = (targetClassName == null) ? mn.desc :
|
||||||
|
MethodUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(targetClassName));
|
||||||
|
String name = (targetMethod == null) ? mn.name : targetMethod;
|
||||||
|
MethodNode mockMethod = new MethodNode(ACC_PUBLIC, name, desc, null, null);
|
||||||
|
List<Byte> parameters = MethodUtil.getParameterTypes(mn.desc);
|
||||||
|
int maxStack = 2;
|
||||||
|
InsnList il = new InsnList();
|
||||||
|
il.add(new MethodInsnNode(INVOKESTATIC, ClassUtil.toSlashSeparatedName(className),
|
||||||
|
GET_TESTABLE_REF, VOID_ARGS + ClassUtil.toByteCodeClassName(className), false));
|
||||||
|
il.add(new VarInsnNode(ALOAD, 1));
|
||||||
|
for (int pi = 2; pi < parameters.size() + 2; pi++) {
|
||||||
|
ImmutablePair<Integer, Integer> codeAndStack = BytecodeUtil.getLoadParameterByteCode(parameters.get(pi - 2));
|
||||||
|
il.add(new VarInsnNode(codeAndStack.left, pi));
|
||||||
|
maxStack += codeAndStack.right;
|
||||||
|
}
|
||||||
|
il.add(new MethodInsnNode(INVOKEVIRTUAL, ClassUtil.toSlashSeparatedName(className), mn.name, desc, false));
|
||||||
|
il.add(new InsnNode(ARETURN));
|
||||||
|
mockMethod.instructions = il;
|
||||||
|
mockMethod.maxStack = maxStack;
|
||||||
|
mockMethod.maxLocals = 2 + parameters.size();
|
||||||
|
mockMethod.visibleAnnotations = fastListOf(new AnnotationNode(ClassUtil.toByteCodeClassName(MOCK_INVOKE)));
|
||||||
|
cn.methods.add(mockMethod);
|
||||||
|
} else if ((ClassUtil.toByteCodeClassName(MOCK_NEW)).equals(an.desc)) {
|
||||||
|
// TODO: should also support MockNew annotation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add method to fetch singleton instance of this mock class
|
* add method to fetch singleton instance of this mock class
|
||||||
*/
|
*/
|
||||||
@@ -98,12 +161,12 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
|||||||
private void unfoldTargetClass(MethodNode mn) {
|
private void unfoldTargetClass(MethodNode mn) {
|
||||||
String targetClassName = null;
|
String targetClassName = null;
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_INVOKE).equals(an.desc)) {
|
if (ClassUtil.toByteCodeClassName(MOCK_INVOKE).equals(an.desc)) {
|
||||||
Type type = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
String name = getTargetClassName(an);
|
||||||
if (type != null) {
|
if (name != null) {
|
||||||
targetClassName = ClassUtil.toByteCodeClassName(type.getClassName());
|
targetClassName = ClassUtil.toByteCodeClassName(name);
|
||||||
}
|
}
|
||||||
AnnotationUtil.removeAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS);
|
AnnotationUtil.removeAnnotationParameters(an, FIELD_TARGET_CLASS, FIELD_TARGET_CLASS_NAME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (targetClassName != null) {
|
if (targetClassName != null) {
|
||||||
@@ -211,7 +274,7 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
|||||||
String methodName = mn.name;
|
String methodName = mn.name;
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
if (isMockMethodAnnotation(an)) {
|
if (isMockMethodAnnotation(an)) {
|
||||||
String name = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
|
String name = AnnotationUtil.getAnnotationParameter(an, FIELD_TARGET_METHOD, null, String.class);
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
methodName = name;
|
methodName = name;
|
||||||
}
|
}
|
||||||
@@ -230,7 +293,7 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
|||||||
private boolean isGlobalScope(MethodNode mn) {
|
private boolean isGlobalScope(MethodNode mn) {
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
if (isMockMethodAnnotation(an) || isMockNewAnnotation(an)) {
|
if (isMockMethodAnnotation(an) || isMockNewAnnotation(an)) {
|
||||||
MockScope scope = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_SCOPE,
|
MockScope scope = AnnotationUtil.getAnnotationParameter(an, FIELD_SCOPE,
|
||||||
GlobalConfig.defaultMockScope, MockScope.class);
|
GlobalConfig.defaultMockScope, MockScope.class);
|
||||||
if (scope.equals(MockScope.GLOBAL)) {
|
if (scope.equals(MockScope.GLOBAL)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -246,6 +309,10 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
|||||||
}
|
}
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
if (isMockMethodAnnotation(an)) {
|
if (isMockMethodAnnotation(an)) {
|
||||||
|
if (hasDuplicatedTargetClass(an)) {
|
||||||
|
throw new IllegalArgumentException("Mock method " + mn.name + " could not use both '"
|
||||||
|
+ FIELD_TARGET_CLASS + "' and '" + FIELD_TARGET_CLASS_NAME + "' parameter");
|
||||||
|
}
|
||||||
if (LogUtil.isVerboseEnabled()) {
|
if (LogUtil.isVerboseEnabled()) {
|
||||||
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, MethodUtil.toJavaMethodDesc(
|
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, MethodUtil.toJavaMethodDesc(
|
||||||
getTargetMethodOwner(mn, an), getTargetMethodName(mn, an), getTargetMethodDesc(mn, an)));
|
getTargetMethodOwner(mn, an), getTargetMethodName(mn, an), getTargetMethodDesc(mn, an)));
|
||||||
@@ -263,29 +330,26 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getTargetMethodOwner(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
private String getTargetMethodOwner(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
||||||
Type type = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_CLASS,
|
String targetClassName = getTargetClassName(mockMethodAnnotation);
|
||||||
null, Type.class);
|
return targetClassName == null ? MethodUtil.getFirstParameter(mn.desc) : targetClassName;
|
||||||
return type == null ? MethodUtil.getFirstParameter(mn.desc) : type.getClassName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTargetMethodName(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
private String getTargetMethodName(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
||||||
String name = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_METHOD,
|
String name = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, FIELD_TARGET_METHOD,
|
||||||
null, String.class);
|
null, String.class);
|
||||||
return name == null ? mn.name : name;
|
return name == null ? mn.name : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTargetMethodDesc(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
private String getTargetMethodDesc(MethodNode mn, AnnotationNode mockMethodAnnotation) {
|
||||||
Type type = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_CLASS,
|
return hasTargetClassParameter(mockMethodAnnotation) ? mn.desc : MethodUtil.removeFirstParameter(mn.desc);
|
||||||
null, Type.class);
|
|
||||||
return type == null ? MethodUtil.removeFirstParameter(mn.desc) : mn.desc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMockNewAnnotation(AnnotationNode an) {
|
private boolean isMockNewAnnotation(AnnotationNode an) {
|
||||||
return ClassUtil.toByteCodeClassName(ConstPool.MOCK_NEW).equals(an.desc);
|
return ClassUtil.toByteCodeClassName(MOCK_NEW).equals(an.desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMockMethodAnnotation(AnnotationNode an) {
|
private boolean isMockMethodAnnotation(AnnotationNode an) {
|
||||||
return ClassUtil.toByteCodeClassName(ConstPool.MOCK_INVOKE).equals(an.desc);
|
return ClassUtil.toByteCodeClassName(MOCK_INVOKE).equals(an.desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void injectInvokeRecorder(MethodNode mn) {
|
private void injectInvokeRecorder(MethodNode mn) {
|
||||||
@@ -327,11 +391,11 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
|||||||
private boolean isMockForConstructor(MethodNode mn) {
|
private boolean isMockForConstructor(MethodNode mn) {
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
String annotationName = ClassUtil.toJavaStyleClassName(an.desc);
|
String annotationName = ClassUtil.toJavaStyleClassName(an.desc);
|
||||||
if (ConstPool.MOCK_NEW.equals(annotationName)) {
|
if (MOCK_NEW.equals(annotationName)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (ConstPool.MOCK_INVOKE.equals(annotationName)) {
|
} else if (MOCK_INVOKE.equals(annotationName)) {
|
||||||
String method = AnnotationUtil.getAnnotationParameter
|
String method = AnnotationUtil.getAnnotationParameter
|
||||||
(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
|
(an, FIELD_TARGET_METHOD, null, String.class);
|
||||||
if (CONSTRUCTOR.equals(method)) {
|
if (CONSTRUCTOR.equals(method)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,26 @@ package com.alibaba.testable.agent.handler;
|
|||||||
|
|
||||||
import com.alibaba.testable.agent.handler.test.JUnit4Framework;
|
import com.alibaba.testable.agent.handler.test.JUnit4Framework;
|
||||||
import com.alibaba.testable.agent.handler.test.JUnit5Framework;
|
import com.alibaba.testable.agent.handler.test.JUnit5Framework;
|
||||||
|
import com.alibaba.testable.agent.util.AnnotationUtil;
|
||||||
|
import com.alibaba.testable.agent.util.BytecodeUtil;
|
||||||
import com.alibaba.testable.agent.util.ClassUtil;
|
import com.alibaba.testable.agent.util.ClassUtil;
|
||||||
import com.alibaba.testable.agent.util.CollectionUtil;
|
import com.alibaba.testable.core.tool.CollectionTool;
|
||||||
|
import com.alibaba.testable.core.util.CollectionUtil;
|
||||||
|
import com.alibaba.testable.core.util.LogUtil;
|
||||||
|
import com.alibaba.testable.core.util.StringUtil;
|
||||||
|
import com.alibaba.testable.core.util.TypeUtil;
|
||||||
import org.objectweb.asm.Label;
|
import org.objectweb.asm.Label;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.*;
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT;
|
import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT;
|
||||||
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
||||||
import static com.alibaba.testable.core.constant.ConstPool.THIS_REF;
|
import static com.alibaba.testable.core.constant.ConstPool.THIS_REF;
|
||||||
import static com.alibaba.testable.core.util.CollectionUtil.contains;
|
import static com.alibaba.testable.core.tool.CollectionTool.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author flin
|
* @author flin
|
||||||
@@ -23,22 +32,28 @@ public class OmniClassHandler extends BaseClassHandler {
|
|||||||
private static final String IGNORE = "ignore";
|
private static final String IGNORE = "ignore";
|
||||||
private static final String METHOD_START = "(";
|
private static final String METHOD_START = "(";
|
||||||
private static final String VOID_METHOD_END = ")V";
|
private static final String VOID_METHOD_END = ")V";
|
||||||
private static final String VOID_METHOD = "()V";
|
private static final String ENABLE_CONFIGURATION = "org.springframework.context.annotation.Configuration";
|
||||||
private static final String ENABLE_CONFIGURATION = "Lorg/springframework/context/annotation/Configuration;";
|
private static final String CLASS_OMNI_CONSTRUCTOR = "com/alibaba/testable/core/tool/OmniConstructor";
|
||||||
private static final String CLASS_ABSTRACT_COLLECTION = "java/util/AbstractCollection";
|
public static final String CLASS_CONSTRUCTION_OPTION = "com/alibaba/testable/core/model/ConstructionOption";
|
||||||
private static final String CLASS_NUMBER = "java/lang/Number";
|
public static final String METHOD_NEW_INSTANCE = "newInstance";
|
||||||
private static final String CLASS_HASH_SET = "java/util/HashSet";
|
public static final String METHOD_DESC_NEW_INSTANCE = "(Ljava/lang/Class;[Lcom/alibaba/testable/core/model/ConstructionOption;)Ljava/lang/Object;";
|
||||||
|
|
||||||
|
// below classes are loaded before OmniClassHandler, cannot be instrumented
|
||||||
|
// map of class name to constructor parameters
|
||||||
|
private static final Map<String, String[]> PRELOADED_CLASSES = mapOf(
|
||||||
|
entryOf(CLASS_OBJECT, CollectionTool.<String>arrayOf())
|
||||||
|
);
|
||||||
|
|
||||||
private static final String[] JUNIT_TEST_ANNOTATIONS = new String[] {
|
private static final String[] JUNIT_TEST_ANNOTATIONS = new String[] {
|
||||||
JUnit4Framework.ANNOTATION_TEST, JUnit5Framework.ANNOTATION_TEST, JUnit5Framework.ANNOTATION_PARAMETERIZED_TEST
|
JUnit4Framework.ANNOTATION_TEST, JUnit5Framework.ANNOTATION_TEST, JUnit5Framework.ANNOTATION_PARAMETERIZED_TEST
|
||||||
};
|
};
|
||||||
private static final String[] UNREACHABLE_CLASSES = new String[] {
|
|
||||||
CLASS_ABSTRACT_COLLECTION, CLASS_NUMBER, CLASS_HASH_SET
|
private static final Map<String, Class<?>[]> constructorParameterCache = new HashMap<String, Class<?>[]>();
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void transform(ClassNode cn) {
|
protected void transform(ClassNode cn) {
|
||||||
if (isInterfaceOrAtom(cn) || isUniqueConstructorClass(cn) || isUninstantiableClass(cn) || hasSpecialAnnotation(cn)) {
|
if (isInterfaceOrAtom(cn) || isUniqueConstructorClass(cn) || isUninstantiableClass(cn) ||
|
||||||
|
AnnotationUtil.getClassAnnotation(cn, ENABLE_CONFIGURATION) != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addConstructorWithVoidTypeParameter(cn);
|
addConstructorWithVoidTypeParameter(cn);
|
||||||
@@ -49,29 +64,56 @@ public class OmniClassHandler extends BaseClassHandler {
|
|||||||
METHOD_START + ClassUtil.toByteCodeClassName(VOID_TYPE) + VOID_METHOD_END, null, null);
|
METHOD_START + ClassUtil.toByteCodeClassName(VOID_TYPE) + VOID_METHOD_END, null, null);
|
||||||
LabelNode start = new LabelNode(new Label());
|
LabelNode start = new LabelNode(new Label());
|
||||||
LabelNode end = new LabelNode(new Label());
|
LabelNode end = new LabelNode(new Label());
|
||||||
if (cn.superName.equals(CLASS_OBJECT)) {
|
int extraParameterCount = 2;
|
||||||
constructor.instructions = invokeSuperWithoutParameter(start, end);
|
|
||||||
constructor.localVariables = createLocalVariables(cn, start, end);
|
if (PRELOADED_CLASSES.containsKey(cn.superName)) {
|
||||||
constructor.maxStack = 1;
|
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, PRELOADED_CLASSES.get(cn.superName), start, end);
|
||||||
|
extraParameterCount = PRELOADED_CLASSES.get(cn.superName).length;
|
||||||
|
} else if (cn.superName.startsWith("java/")) {
|
||||||
|
try {
|
||||||
|
Class<?>[] constructorParameterTypes = constructorParameterCache.get(cn.superName);
|
||||||
|
if (constructorParameterTypes == null) {
|
||||||
|
Class<?> superClazz = Class.forName(ClassUtil.toDotSeparatedName(cn.superName));
|
||||||
|
constructorParameterTypes = TypeUtil.getBestConstructor(superClazz).getParameterTypes();
|
||||||
|
constructorParameterCache.put(superClazz.getName(), constructorParameterTypes);
|
||||||
|
}
|
||||||
|
if (constructorParameterTypes.length == 0) {
|
||||||
|
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, new String[0], start, end);
|
||||||
|
extraParameterCount = 0;
|
||||||
|
} else if (constructorParameterTypes.length == 1 &&
|
||||||
|
constructorParameterTypes[0].equals(Void.class)) {
|
||||||
|
constructor.instructions = invokeSuperWithTestableVoidParameter(cn.superName, start, end);
|
||||||
|
} else {
|
||||||
|
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName,
|
||||||
|
toByteCodeClassNames(constructorParameterTypes), start, end);
|
||||||
|
extraParameterCount = constructorParameterTypes.length;
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
LogUtil.warn("[OmniConstructor] Failed to load class " + cn.superName);
|
||||||
|
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, new String[0], start, end);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
constructor.instructions = invokeSuperWithTestableVoidParameter(cn, start, end);
|
constructor.instructions = invokeSuperWithTestableVoidParameter(cn.superName, start, end);
|
||||||
constructor.localVariables = createLocalVariables(cn, start, end);
|
|
||||||
constructor.maxStack = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor.localVariables = createLocalVariables(cn, start, end);
|
||||||
|
constructor.maxStack = 1 + extraParameterCount;
|
||||||
constructor.maxLocals = 2;
|
constructor.maxLocals = 2;
|
||||||
cn.methods.add(constructor);
|
cn.methods.add(constructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasSpecialAnnotation(ClassNode cn) {
|
private String[] toByteCodeClassNames(Class<?>[] classes) {
|
||||||
if (cn.visibleAnnotations == null) {
|
String[] names = new String[classes.length];
|
||||||
return false;
|
for (int i = 0; i < names.length; i++) {
|
||||||
}
|
if (classes[i].isPrimitive()) {
|
||||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
names[i] = BytecodeUtil.PRIMITIVE_TYPE_NAME_MAP.get(classes[i].getName());
|
||||||
if (an.desc.equals(ENABLE_CONFIGURATION)) {
|
} else if (classes[i].isArray()) {
|
||||||
return true;
|
names[i] = ClassUtil.toSlashSeparatedName(classes[i].getName());
|
||||||
|
} else {
|
||||||
|
names[i] = ClassUtil.toByteCodeClassName(classes[i].getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUninstantiableClass(ClassNode cn) {
|
private boolean isUninstantiableClass(ClassNode cn) {
|
||||||
@@ -104,7 +146,7 @@ public class OmniClassHandler extends BaseClassHandler {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
if (contains(JUNIT_TEST_ANNOTATIONS, an.desc)) {
|
if (CollectionUtil.contains(JUNIT_TEST_ANNOTATIONS, an.desc)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,34 +154,38 @@ public class OmniClassHandler extends BaseClassHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnList invokeSuperWithoutParameter(LabelNode start, LabelNode end) {
|
private InsnList invokeSuperWithoutTestableParameter(String superName, String[] parameters, LabelNode start, LabelNode end) {
|
||||||
InsnList il = new InsnList();
|
InsnList il = new InsnList();
|
||||||
il.add(start);
|
il.add(start);
|
||||||
il.add(new VarInsnNode(ALOAD, 0));
|
il.add(new VarInsnNode(ALOAD, 0));
|
||||||
il.add(new MethodInsnNode(INVOKESPECIAL, CLASS_OBJECT, CONSTRUCTOR, VOID_METHOD, false));
|
for (String p : parameters) {
|
||||||
|
il.add(new LdcInsnNode(Type.getType(p)));
|
||||||
|
il.add(new InsnNode(ICONST_0));
|
||||||
|
il.add(new TypeInsnNode(ANEWARRAY, CLASS_CONSTRUCTION_OPTION));
|
||||||
|
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_OMNI_CONSTRUCTOR, METHOD_NEW_INSTANCE, METHOD_DESC_NEW_INSTANCE, false));
|
||||||
|
il.add(new TypeInsnNode(CHECKCAST, (p.startsWith("[")) ? p : ClassUtil.toSlashSeparateJavaStyleName(p)));
|
||||||
|
}
|
||||||
|
il.add(new MethodInsnNode(INVOKESPECIAL, superName, CONSTRUCTOR,
|
||||||
|
METHOD_START + StringUtil.join("", parameters) + VOID_METHOD_END, false));
|
||||||
il.add(new InsnNode(RETURN));
|
il.add(new InsnNode(RETURN));
|
||||||
il.add(end);
|
il.add(end);
|
||||||
return il;
|
return il;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InsnList invokeSuperWithTestableVoidParameter(ClassNode cn, LabelNode start, LabelNode end) {
|
private InsnList invokeSuperWithTestableVoidParameter(String superName, LabelNode start, LabelNode end) {
|
||||||
InsnList il = new InsnList();
|
InsnList il = new InsnList();
|
||||||
il.add(start);
|
il.add(start);
|
||||||
il.add(new VarInsnNode(ALOAD, 0));
|
il.add(new VarInsnNode(ALOAD, 0));
|
||||||
if (contains(UNREACHABLE_CLASSES, cn.superName)) {
|
il.add(new VarInsnNode(ALOAD, 1));
|
||||||
il.add(new MethodInsnNode(INVOKESPECIAL, cn.superName, CONSTRUCTOR, VOID_METHOD, false));
|
il.add(new MethodInsnNode(INVOKESPECIAL, superName, CONSTRUCTOR,
|
||||||
} else {
|
METHOD_START + ClassUtil.toByteCodeClassName(VOID_TYPE) + VOID_METHOD_END, false));
|
||||||
il.add(new VarInsnNode(ALOAD, 1));
|
|
||||||
il.add(new MethodInsnNode(INVOKESPECIAL, cn.superName, CONSTRUCTOR,
|
|
||||||
METHOD_START + ClassUtil.toByteCodeClassName(VOID_TYPE) + VOID_METHOD_END, false));
|
|
||||||
}
|
|
||||||
il.add(new InsnNode(RETURN));
|
il.add(new InsnNode(RETURN));
|
||||||
il.add(end);
|
il.add(end);
|
||||||
return il;
|
return il;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LocalVariableNode> createLocalVariables(ClassNode cn, LabelNode start, LabelNode end) {
|
private List<LocalVariableNode> createLocalVariables(ClassNode cn, LabelNode start, LabelNode end) {
|
||||||
return CollectionUtil.listOf(
|
return listOf(
|
||||||
new LocalVariableNode(THIS_REF, ClassUtil.toByteCodeClassName(cn.name), null, start, end, 0),
|
new LocalVariableNode(THIS_REF, ClassUtil.toByteCodeClassName(cn.name), null, start, end, 0),
|
||||||
new LocalVariableNode(IGNORE, ClassUtil.toByteCodeClassName(VOID_TYPE), null, start, end, 1)
|
new LocalVariableNode(IGNORE, ClassUtil.toByteCodeClassName(VOID_TYPE), null, start, end, 1)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,13 +11,11 @@ import com.alibaba.testable.core.util.LogUtil;
|
|||||||
import org.objectweb.asm.*;
|
import org.objectweb.asm.*;
|
||||||
import org.objectweb.asm.tree.*;
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
||||||
|
import static com.alibaba.testable.core.constant.ConstPool.DOLLAR;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author flin
|
* @author flin
|
||||||
@@ -47,6 +45,10 @@ public class SourceClassHandler extends BaseClassHandler {
|
|||||||
@Override
|
@Override
|
||||||
protected void transform(ClassNode cn) {
|
protected void transform(ClassNode cn) {
|
||||||
LogUtil.diagnose("Found source class %s", cn.name);
|
LogUtil.diagnose("Found source class %s", cn.name);
|
||||||
|
if (injectMethods.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Set<MethodInfo> memberInjectMethods = new HashSet<MethodInfo>();
|
Set<MethodInfo> memberInjectMethods = new HashSet<MethodInfo>();
|
||||||
Set<MethodInfo> newOperatorInjectMethods = new HashSet<MethodInfo>();
|
Set<MethodInfo> newOperatorInjectMethods = new HashSet<MethodInfo>();
|
||||||
for (MethodInfo im : injectMethods) {
|
for (MethodInfo im : injectMethods) {
|
||||||
@@ -57,7 +59,9 @@ public class SourceClassHandler extends BaseClassHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveMethodReference(cn);
|
if (!memberInjectMethods.isEmpty()) {
|
||||||
|
resolveMethodReference(cn, memberInjectMethods);
|
||||||
|
}
|
||||||
|
|
||||||
for (MethodNode m : cn.methods) {
|
for (MethodNode m : cn.methods) {
|
||||||
transformMethod(m, memberInjectMethods, newOperatorInjectMethods);
|
transformMethod(m, memberInjectMethods, newOperatorInjectMethods);
|
||||||
@@ -67,7 +71,7 @@ public class SourceClassHandler extends BaseClassHandler {
|
|||||||
private void transformMethod(MethodNode mn, Set<MethodInfo> memberInjectMethods,
|
private void transformMethod(MethodNode mn, Set<MethodInfo> memberInjectMethods,
|
||||||
Set<MethodInfo> newOperatorInjectMethods) {
|
Set<MethodInfo> newOperatorInjectMethods) {
|
||||||
LogUtil.verbose(" Found method %s", mn.name);
|
LogUtil.verbose(" Found method %s", mn.name);
|
||||||
if (mn.name.startsWith("$")) {
|
if (mn.name.startsWith(DOLLAR)) {
|
||||||
// skip methods e.g. "$jacocoInit"
|
// skip methods e.g. "$jacocoInit"
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -355,11 +359,17 @@ public class SourceClassHandler extends BaseClassHandler {
|
|||||||
return handleList;
|
return handleList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resolveMethodReference(ClassNode cn) {
|
private void resolveMethodReference(ClassNode cn, Set<MethodInfo> mockedMethods) {
|
||||||
List<BsmArg> invokeDynamicList = new ArrayList<BsmArg>();
|
List<BsmArg> invokeDynamicList = new ArrayList<BsmArg>();
|
||||||
for (MethodNode method : cn.methods) {
|
for (MethodNode method : cn.methods) {
|
||||||
List<BsmArg> handleList = fetchInvokeDynamicHandle(method);
|
List<BsmArg> handleList = fetchInvokeDynamicHandle(method);
|
||||||
invokeDynamicList.addAll(handleList);
|
for (BsmArg arg : handleList) {
|
||||||
|
for (MethodInfo mi : mockedMethods) {
|
||||||
|
if (isMethodMocked(arg.getHandle(), mi)) {
|
||||||
|
invokeDynamicList.add(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process for method reference
|
// process for method reference
|
||||||
@@ -424,6 +434,19 @@ public class SourceClassHandler extends BaseClassHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isMethodMocked(Handle targetHandle, MethodInfo mockMethodInfo) {
|
||||||
|
if (targetHandle.getTag() == Opcodes.H_INVOKEINTERFACE || targetHandle.getTag() == Opcodes.H_INVOKEVIRTUAL) {
|
||||||
|
String targetMockDesc = MethodUtil.addParameterAtBegin(mockMethodInfo.getDesc(),
|
||||||
|
ClassUtil.toByteCodeClassName(mockMethodInfo.getClazz()));
|
||||||
|
return mockMethodInfo.getClazz().equals(targetHandle.getOwner()) &&
|
||||||
|
mockMethodInfo.getName().equals(targetHandle.getName()) &&
|
||||||
|
targetMockDesc.equals(targetHandle.getDesc());
|
||||||
|
}
|
||||||
|
return mockMethodInfo.getClazz().equals(targetHandle.getOwner()) &&
|
||||||
|
mockMethodInfo.getName().equals(targetHandle.getName()) &&
|
||||||
|
mockMethodInfo.getDesc().equals(targetHandle.getDesc());
|
||||||
|
}
|
||||||
|
|
||||||
private void visitLocalVariableByArguments(MethodVisitor mv, final int initVar, Type[] argumentTypes, Label l0, Label l1) {
|
private void visitLocalVariableByArguments(MethodVisitor mv, final int initVar, Type[] argumentTypes, Label l0, Label l1) {
|
||||||
int nextLocalVar = initVar;
|
int nextLocalVar = initVar;
|
||||||
for (int i = 0; i < argumentTypes.length; i++) {
|
for (int i = 0; i < argumentTypes.length; i++) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.alibaba.testable.agent.handler.test;
|
|||||||
|
|
||||||
import com.alibaba.testable.agent.model.TestCaseMethodType;
|
import com.alibaba.testable.agent.model.TestCaseMethodType;
|
||||||
import com.alibaba.testable.agent.util.ClassUtil;
|
import com.alibaba.testable.agent.util.ClassUtil;
|
||||||
import com.alibaba.testable.agent.util.CollectionUtil;
|
import com.alibaba.testable.core.util.CollectionUtil;
|
||||||
import org.objectweb.asm.Label;
|
import org.objectweb.asm.Label;
|
||||||
import org.objectweb.asm.tree.*;
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.alibaba.testable.agent.handler.test;
|
package com.alibaba.testable.agent.handler.test;
|
||||||
|
|
||||||
import com.alibaba.testable.agent.model.TestCaseMethodType;
|
import com.alibaba.testable.agent.model.TestCaseMethodType;
|
||||||
import com.alibaba.testable.agent.util.CollectionUtil;
|
import com.alibaba.testable.core.util.CollectionUtil;
|
||||||
import org.objectweb.asm.tree.AnnotationNode;
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.alibaba.testable.agent.model;
|
package com.alibaba.testable.agent.model;
|
||||||
|
|
||||||
import com.alibaba.testable.agent.util.StringUtil;
|
import com.alibaba.testable.core.util.StringUtil;
|
||||||
import org.objectweb.asm.Handle;
|
import org.objectweb.asm.Handle;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ import org.objectweb.asm.tree.InnerClassNode;
|
|||||||
import org.objectweb.asm.tree.MethodNode;
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_CLASS;
|
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_CLASS;
|
||||||
import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT;
|
import static com.alibaba.testable.agent.constant.ConstPool.*;
|
||||||
import static com.alibaba.testable.agent.constant.ConstPool.KOTLIN_POSTFIX_COMPANION;
|
|
||||||
import static com.alibaba.testable.agent.util.ClassUtil.toJavaStyleClassName;
|
import static com.alibaba.testable.agent.util.ClassUtil.toJavaStyleClassName;
|
||||||
import static com.alibaba.testable.agent.util.MethodUtil.isStatic;
|
import static com.alibaba.testable.agent.util.MethodUtil.isStatic;
|
||||||
|
import static com.alibaba.testable.agent.util.MockInvokeUtil.getTargetClassName;
|
||||||
|
import static com.alibaba.testable.agent.util.MockInvokeUtil.hasTargetClassParameter;
|
||||||
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
||||||
import static com.alibaba.testable.core.constant.ConstPool.MOCK_POSTFIX;
|
import static com.alibaba.testable.core.constant.ConstPool.MOCK_POSTFIX;
|
||||||
|
|
||||||
@@ -39,13 +41,14 @@ public class MockClassParser {
|
|||||||
for (MethodNode mn : getAllMethods(cn)) {
|
for (MethodNode mn : getAllMethods(cn)) {
|
||||||
addMethodWithAnnotationCheck(methodInfos, cn, mn);
|
addMethodWithAnnotationCheck(methodInfos, cn, mn);
|
||||||
}
|
}
|
||||||
|
handleMockContainerInherits(methodInfos, cn);
|
||||||
return methodInfos;
|
return methodInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether any method in specified class has mock-related annotation
|
* Check whether specified class is declared as mock container or has any method with mock-related annotation
|
||||||
*
|
*
|
||||||
* @param cn class that need to explore
|
* @param cn class to explore
|
||||||
* @return found annotation or not
|
* @return found annotation or not
|
||||||
*/
|
*/
|
||||||
public boolean isMockClass(ClassNode cn) {
|
public boolean isMockClass(ClassNode cn) {
|
||||||
@@ -53,6 +56,9 @@ public class MockClassParser {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
DiagnoseUtil.setupByClass(cn);
|
DiagnoseUtil.setupByClass(cn);
|
||||||
|
if (AnnotationUtil.getClassAnnotation(cn, MOCK_CONTAINER) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
for (MethodNode mn : cn.name.endsWith(MOCK_POSTFIX) ? getAllMethods(cn) : cn.methods) {
|
for (MethodNode mn : cn.name.endsWith(MOCK_POSTFIX) ? getAllMethods(cn) : cn.methods) {
|
||||||
if (mn.visibleAnnotations != null) {
|
if (mn.visibleAnnotations != null) {
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
@@ -92,6 +98,27 @@ public class MockClassParser {
|
|||||||
return mns;
|
return mns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take care of @MockContainer annotation
|
||||||
|
*/
|
||||||
|
private void handleMockContainerInherits(List<MethodInfo> methodInfos, ClassNode cn) {
|
||||||
|
AnnotationNode an = AnnotationUtil.getClassAnnotation(cn, MOCK_CONTAINER);
|
||||||
|
if (an != null) {
|
||||||
|
for (Object st : AnnotationUtil.getAnnotationParameter(an, FIELD_INHERITS,
|
||||||
|
Collections.<Type>emptyList(), List.class)) {
|
||||||
|
String superClassName = ((Type)st).getClassName();
|
||||||
|
ClassNode superCn = ClassUtil.getClassNode(superClassName);
|
||||||
|
if (superCn == null) {
|
||||||
|
LogUtil.warn("failed to load class '%s' inherited by '%s'", superClassName, cn.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (MethodNode mn : getAllMethods(superCn)) {
|
||||||
|
addMethodWithAnnotationCheck(methodInfos, cn, mn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addMethodWithAnnotationCheck(List<MethodInfo> methodInfos, ClassNode cn, MethodNode mn) {
|
private void addMethodWithAnnotationCheck(List<MethodInfo> methodInfos, ClassNode cn, MethodNode mn) {
|
||||||
if (mn.visibleAnnotations == null) {
|
if (mn.visibleAnnotations == null) {
|
||||||
return;
|
return;
|
||||||
@@ -122,9 +149,9 @@ public class MockClassParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private MethodInfo getMethodInfo(ClassNode cn, 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);
|
String targetTypeName = getTargetClassName(an);
|
||||||
boolean isStatic = isStatic(mn);
|
boolean isStatic = isStatic(mn);
|
||||||
if (targetType == null) {
|
if (targetTypeName == null) {
|
||||||
// "targetClass" unset, use first parameter as target class type
|
// "targetClass" unset, use first parameter as target class type
|
||||||
ImmutablePair<String, String> methodDescPair = MethodUtil.splitFirstAndRestParameters(mn.desc);
|
ImmutablePair<String, String> methodDescPair = MethodUtil.splitFirstAndRestParameters(mn.desc);
|
||||||
if (methodDescPair.left.isEmpty()) {
|
if (methodDescPair.left.isEmpty()) {
|
||||||
@@ -134,7 +161,7 @@ public class MockClassParser {
|
|||||||
isStatic);
|
isStatic);
|
||||||
} else {
|
} else {
|
||||||
// "targetClass" found, use it as target class type
|
// "targetClass" found, use it as target class type
|
||||||
String slashSeparatedName = ClassUtil.toSlashSeparatedName(targetType.getClassName());
|
String slashSeparatedName = ClassUtil.toSlashSeparatedName(targetTypeName);
|
||||||
return new MethodInfo(slashSeparatedName, targetMethod, mn.desc, cn.name, mn.name,
|
return new MethodInfo(slashSeparatedName, targetMethod, mn.desc, cn.name, mn.name,
|
||||||
MethodUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(slashSeparatedName)), isStatic);
|
MethodUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(slashSeparatedName)), isStatic);
|
||||||
}
|
}
|
||||||
@@ -145,11 +172,9 @@ public class MockClassParser {
|
|||||||
if (targetMethodName == null) {
|
if (targetMethodName == null) {
|
||||||
targetMethodName = mn.name;
|
targetMethodName = mn.name;
|
||||||
}
|
}
|
||||||
String targetClassName;
|
|
||||||
String targetMethodDesc;
|
String targetMethodDesc;
|
||||||
Type targetClass = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
String targetClassName = getTargetClassName(an);
|
||||||
if (targetClass != null) {
|
if (targetClassName != null) {
|
||||||
targetClassName = targetClass.getClassName();
|
|
||||||
targetMethodDesc = mn.desc;
|
targetMethodDesc = mn.desc;
|
||||||
checkMethodExists(cn.name, mn.name, targetClassName, targetMethodName, targetMethodDesc);
|
checkMethodExists(cn.name, mn.name, targetClassName, targetMethodName, targetMethodDesc);
|
||||||
} else if (mn.desc.charAt(1) == TYPE_CLASS) {
|
} else if (mn.desc.charAt(1) == TYPE_CLASS) {
|
||||||
@@ -207,8 +232,7 @@ public class MockClassParser {
|
|||||||
* @return valid or not
|
* @return valid or not
|
||||||
*/
|
*/
|
||||||
private boolean isValidMockMethod(MethodNode mn, AnnotationNode an) {
|
private boolean isValidMockMethod(MethodNode mn, AnnotationNode an) {
|
||||||
Type targetClass = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
|
if (hasTargetClassParameter(an)) {
|
||||||
if (targetClass != null) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
String firstParameter = MethodUtil.getFirstParameter(mn.desc);
|
String firstParameter = MethodUtil.getFirstParameter(mn.desc);
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ import java.lang.instrument.ClassFileTransformer;
|
|||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.alibaba.testable.agent.constant.ConstPool.CGLIB_CLASS_PATTERN;
|
import static com.alibaba.testable.agent.constant.ConstPool.*;
|
||||||
import static com.alibaba.testable.agent.constant.ConstPool.KOTLIN_POSTFIX_COMPANION;
|
|
||||||
import static com.alibaba.testable.core.constant.ConstPool.DOLLAR;
|
import static com.alibaba.testable.core.constant.ConstPool.DOLLAR;
|
||||||
import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX;
|
import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX;
|
||||||
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
||||||
@@ -31,7 +30,7 @@ import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
|||||||
public class TestableClassTransformer implements ClassFileTransformer {
|
public class TestableClassTransformer implements ClassFileTransformer {
|
||||||
|
|
||||||
private static final String FIELD_TREAT_AS = "treatAs";
|
private static final String FIELD_TREAT_AS = "treatAs";
|
||||||
private static final String CLASS_JUNIT_5_NESTED = "Lorg/junit/jupiter/api/Nested;";
|
private static final String CLASS_JUNIT_5_NESTED = "org.junit.jupiter.api.Nested";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Just avoid spend time to scan those surely non-user classes, should keep these lists as tiny as possible
|
* Just avoid spend time to scan those surely non-user classes, should keep these lists as tiny as possible
|
||||||
@@ -103,14 +102,17 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
|
|
||||||
private String foundMockForSourceClass(String name) {
|
private String foundMockForSourceClass(String name) {
|
||||||
String className = (GlobalConfig.getMockPackageMapping() == null) ? name : mapPackage(name);
|
String className = (GlobalConfig.getMockPackageMapping() == null) ? name : mapPackage(name);
|
||||||
|
// handle @MockWith annotation on source class
|
||||||
String mockClass = lookForMockWithAnnotationAsSourceClass(className);
|
String mockClass = lookForMockWithAnnotationAsSourceClass(className);
|
||||||
if (mockClass != null) {
|
if (mockClass != null) {
|
||||||
return mockClass;
|
return mockClass;
|
||||||
}
|
}
|
||||||
mockClass = foundMockForTestClass(ClassUtil.getTestClassName(className));
|
// look for [ThisClass]Test.Mock, [ThisClass]Mock and class with @MockContainer annotation
|
||||||
|
mockClass = foundMockForStandardClass(className);
|
||||||
if (mockClass != null) {
|
if (mockClass != null) {
|
||||||
return mockClass;
|
return mockClass;
|
||||||
}
|
}
|
||||||
|
// inner class should also look for mock class in the test class of its outer class
|
||||||
return foundMockForInnerSourceClass(className);
|
return foundMockForInnerSourceClass(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,38 +129,36 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
|
|
||||||
private String foundMockForInnerSourceClass(String className) {
|
private String foundMockForInnerSourceClass(String className) {
|
||||||
return (className.contains(DOLLAR) && !className.endsWith(KOTLIN_POSTFIX_COMPANION)) ?
|
return (className.contains(DOLLAR) && !className.endsWith(KOTLIN_POSTFIX_COMPANION)) ?
|
||||||
foundMockForTestClass(ClassUtil.getTestClassName(className.substring(0, className.indexOf(DOLLAR)))) : null;
|
foundMockForStandardClass(className.substring(0, className.indexOf(DOLLAR))) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String foundMockForTestClass(String className) {
|
private String foundMockForStandardClass(String className) {
|
||||||
ClassNode cn = adaptInnerClass(ClassUtil.getClassNode(className));
|
ClassNode cn = adaptInnerClass(ClassUtil.getClassNode(ClassUtil.getTestClassName(className)));
|
||||||
if (cn != null) {
|
if (cn != null) {
|
||||||
|
// handle @MockWith annotation on test class
|
||||||
String mockClass = lookForMockWithAnnotationAsTestClass(cn);
|
String mockClass = lookForMockWithAnnotationAsTestClass(cn);
|
||||||
if (mockClass != null) {
|
if (mockClass != null) {
|
||||||
return mockClass;
|
return mockClass;
|
||||||
}
|
}
|
||||||
|
// look for [ThisClass]Test.Mock or @MockContainer annotation
|
||||||
mockClass = lookForInnerMockClass(cn);
|
mockClass = lookForInnerMockClass(cn);
|
||||||
if (mockClass != null) {
|
if (mockClass != null) {
|
||||||
return mockClass;
|
return mockClass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// look for [ThisClass]Mock
|
||||||
return lookForOuterMockClass(className);
|
return lookForOuterMockClass(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClassNode adaptInnerClass(ClassNode cn) {
|
private ClassNode adaptInnerClass(ClassNode cn) {
|
||||||
if (cn == null || cn.visibleAnnotations == null) {
|
if (AnnotationUtil.getClassAnnotation(cn, CLASS_JUNIT_5_NESTED) != null) {
|
||||||
return cn;
|
return ClassUtil.getClassNode(ClassUtil.toOuterClassName(cn.name));
|
||||||
}
|
|
||||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
|
||||||
if (an.desc.equals(CLASS_JUNIT_5_NESTED)) {
|
|
||||||
return ClassUtil.getClassNode(ClassUtil.toOuterClassName(cn.name));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return cn;
|
return cn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String lookForOuterMockClass(String className) {
|
private String lookForOuterMockClass(String className) {
|
||||||
String mockClassName = ClassUtil.getMockClassName(ClassUtil.getSourceClassName(className));
|
String mockClassName = ClassUtil.getMockClassName(className);
|
||||||
if (mockClassParser.isMockClass(ClassUtil.getClassNode(mockClassName))) {
|
if (mockClassParser.isMockClass(ClassUtil.getClassNode(mockClassName))) {
|
||||||
return mockClassName;
|
return mockClassName;
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,9 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
private String lookForInnerMockClass(ClassNode cn) {
|
private String lookForInnerMockClass(ClassNode cn) {
|
||||||
for (InnerClassNode ic : cn.innerClasses) {
|
for (InnerClassNode ic : cn.innerClasses) {
|
||||||
ClassNode innerClassNode = ClassUtil.getClassNode(ic.name);
|
ClassNode innerClassNode = ClassUtil.getClassNode(ic.name);
|
||||||
if (ic.name.equals(getInnerMockClassName(cn.name)) && mockClassParser.isMockClass(innerClassNode)) {
|
boolean isNameMatched = ic.name.equals(getInnerMockClassName(cn.name)) ||
|
||||||
|
AnnotationUtil.getClassAnnotation(innerClassNode, MOCK_CONTAINER) != null;
|
||||||
|
if (isNameMatched && mockClassParser.isMockClass(innerClassNode)) {
|
||||||
if ((ic.access & ACC_STATIC) == 0) {
|
if ((ic.access & ACC_STATIC) == 0) {
|
||||||
LogUtil.warn("Mock class in \"%s\" is not declared as static", cn.name);
|
LogUtil.warn("Mock class in \"%s\" is not declared as static", cn.name);
|
||||||
} else {
|
} else {
|
||||||
@@ -248,18 +250,15 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
* @return mock class name
|
* @return mock class name
|
||||||
*/
|
*/
|
||||||
private String parseMockWithAnnotation(ClassNode cn, ClassType expectedType) {
|
private String parseMockWithAnnotation(ClassNode cn, ClassType expectedType) {
|
||||||
if (cn.visibleAnnotations != null) {
|
AnnotationNode an = AnnotationUtil.getClassAnnotation(cn, MOCK_WITH);
|
||||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
if (an != null) {
|
||||||
if ((ClassUtil.toByteCodeClassName(ConstPool.MOCK_WITH)).equals(an.desc)) {
|
ClassType type = AnnotationUtil.getAnnotationParameter(an, FIELD_TREAT_AS, ClassType.GuessByName,
|
||||||
ClassType type = AnnotationUtil.getAnnotationParameter(an, FIELD_TREAT_AS, ClassType.GuessByName,
|
ClassType.class);
|
||||||
ClassType.class);
|
if (isExpectedType(cn.name, type, expectedType)) {
|
||||||
if (isExpectedType(cn.name, type, expectedType)) {
|
Type clazz = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_VALUE,
|
||||||
Type clazz = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_VALUE,
|
Type.getType(NullType.class), Type.class);
|
||||||
Type.getType(NullType.class), Type.class);
|
DiagnoseUtil.setupByClass(ClassUtil.getClassNode(clazz.getClassName()));
|
||||||
DiagnoseUtil.setupByClass(ClassUtil.getClassNode(clazz.getClassName()));
|
return clazz.getClassName();
|
||||||
return clazz.getClassName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.alibaba.testable.agent.util;
|
package com.alibaba.testable.agent.util;
|
||||||
|
|
||||||
import org.objectweb.asm.tree.AnnotationNode;
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author flin
|
* @author flin
|
||||||
@@ -41,23 +42,85 @@ public class AnnotationUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove specified parameter from annotation
|
* Check whether annotation has any of the specified parameters
|
||||||
* @param an annotation node
|
* @param an annotation to check
|
||||||
* @param key name of parameter to remove
|
* @param keys name of parameters
|
||||||
* @return true - success, false - not found
|
* @return yes or no
|
||||||
*/
|
*/
|
||||||
public static boolean removeAnnotationParameter(AnnotationNode an, String key) {
|
public static boolean hasAnyAnnotationParameters(AnnotationNode an, String... keys) {
|
||||||
if (an.values == null) {
|
if (an != null && an.values != null) {
|
||||||
return false;
|
for (int i = 0; i < an.values.size(); i += 2) {
|
||||||
}
|
for (String key : keys) {
|
||||||
for (int i = 0; i < an.values.size(); i += 2) {
|
if (an.values.get(i).equals(key)) {
|
||||||
if (an.values.get(i).equals(key)) {
|
return true;
|
||||||
an.values.remove(i + 1);
|
}
|
||||||
an.values.remove(i);
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether annotation has all the specified parameters
|
||||||
|
* @param an annotation to check
|
||||||
|
* @param keys name of parameters
|
||||||
|
* @return yes or no
|
||||||
|
*/
|
||||||
|
public static boolean hasAllAnnotationParameters(AnnotationNode an, String... keys) {
|
||||||
|
if (an != null && an.values != null) {
|
||||||
|
for (String key : keys) {
|
||||||
|
boolean found = false;
|
||||||
|
for (int i = 0; i < an.values.size(); i += 2) {
|
||||||
|
if (an.values.get(i).equals(key)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove specified parameter from annotation
|
||||||
|
* @param an annotation node
|
||||||
|
* @param keys name of parameters to remove
|
||||||
|
* @return true - success, false - not found
|
||||||
|
*/
|
||||||
|
public static boolean removeAnnotationParameters(AnnotationNode an, String... keys) {
|
||||||
|
if (an.values == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (String key : keys) {
|
||||||
|
for (int i = 0; i < an.values.size(); i += 2) {
|
||||||
|
if (an.values.get(i).equals(key)) {
|
||||||
|
an.values.remove(i + 1);
|
||||||
|
an.values.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get specified annotation node from specified class, or null if the annotation not exist
|
||||||
|
* @param cn class to explore
|
||||||
|
* @param annotation name of annotation to look for
|
||||||
|
* @return the annotation instance or null
|
||||||
|
*/
|
||||||
|
public static AnnotationNode getClassAnnotation(ClassNode cn, String annotation) {
|
||||||
|
if (cn != null && cn.visibleAnnotations != null) {
|
||||||
|
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||||
|
if (ClassUtil.toByteCodeClassName(annotation).equals(an.desc)) {
|
||||||
|
return an;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.alibaba.testable.agent.util;
|
package com.alibaba.testable.agent.util;
|
||||||
|
|
||||||
import com.alibaba.testable.agent.constant.ByteCodeConst;
|
import com.alibaba.testable.agent.constant.ByteCodeConst;
|
||||||
import com.alibaba.testable.agent.constant.ConstPool;
|
|
||||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
import com.alibaba.testable.agent.tool.ImmutablePair;
|
||||||
import com.alibaba.testable.core.util.LogUtil;
|
import com.alibaba.testable.core.util.LogUtil;
|
||||||
import org.objectweb.asm.tree.*;
|
import org.objectweb.asm.tree.*;
|
||||||
@@ -11,8 +10,8 @@ import java.io.IOException;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static com.alibaba.testable.agent.constant.ConstPool.FIELD_VALUE;
|
import static com.alibaba.testable.agent.constant.ByteCodeConst.*;
|
||||||
import static com.alibaba.testable.agent.constant.ConstPool.PROPERTY_USER_DIR;
|
import static com.alibaba.testable.agent.constant.ConstPool.*;
|
||||||
import static com.alibaba.testable.core.constant.ConstPool.*;
|
import static com.alibaba.testable.core.constant.ConstPool.*;
|
||||||
import static com.alibaba.testable.core.util.PathUtil.createFolder;
|
import static com.alibaba.testable.core.util.PathUtil.createFolder;
|
||||||
import static org.objectweb.asm.Opcodes.*;
|
import static org.objectweb.asm.Opcodes.*;
|
||||||
@@ -189,7 +188,21 @@ public class BytecodeUtil {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get stack impact of a specified ops code
|
* java primitive type name to bytecode mapping
|
||||||
|
*/
|
||||||
|
public static Map<String, String> PRIMITIVE_TYPE_NAME_MAP = new HashMap<String, String>() {{
|
||||||
|
put("byte", String.valueOf((char)TYPE_BYTE));
|
||||||
|
put("char", String.valueOf((char)TYPE_CHAR));
|
||||||
|
put("double", String.valueOf((char)TYPE_DOUBLE));
|
||||||
|
put("float", String.valueOf((char)TYPE_FLOAT));
|
||||||
|
put("int", String.valueOf((char)TYPE_INT));
|
||||||
|
put("long", String.valueOf((char)TYPE_LONG));
|
||||||
|
put("short", String.valueOf((char)TYPE_SHORT));
|
||||||
|
put("boolean", String.valueOf((char)TYPE_BOOL));
|
||||||
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get stack impact of a specified ops code
|
||||||
* @param bytecode ops code to check
|
* @param bytecode ops code to check
|
||||||
* @return stack change
|
* @return stack change
|
||||||
*/
|
*/
|
||||||
@@ -198,7 +211,7 @@ public class BytecodeUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure method has public access
|
* make sure method has public access
|
||||||
* @param access original access mark
|
* @param access original access mark
|
||||||
* @return access mark with public flag
|
* @return access mark with public flag
|
||||||
*/
|
*/
|
||||||
@@ -210,7 +223,7 @@ public class BytecodeUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dump byte code to specified class file
|
* dump byte code to specified class file
|
||||||
* @param cn original class node
|
* @param cn original class node
|
||||||
* @param dumpPath folder to store class file
|
* @param dumpPath folder to store class file
|
||||||
* @param bytes original class bytes
|
* @param bytes original class bytes
|
||||||
@@ -283,15 +296,12 @@ public class BytecodeUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String getDumpPathByAnnotation(ClassNode cn) {
|
private static String getDumpPathByAnnotation(ClassNode cn) {
|
||||||
if (cn.visibleAnnotations != null) {
|
AnnotationNode an = AnnotationUtil.getClassAnnotation(cn, DUMP_TO);
|
||||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
if (an != null) {
|
||||||
if ((ClassUtil.toByteCodeClassName(ConstPool.DUMP_TO)).equals(an.desc)) {
|
String path = AnnotationUtil.getAnnotationParameter(an, FIELD_VALUE, null, String.class);
|
||||||
String path = AnnotationUtil.getAnnotationParameter(an, FIELD_VALUE, null, String.class);
|
String fullPath = PathUtil.join(System.getProperty(PROPERTY_USER_DIR), path);
|
||||||
String fullPath = PathUtil.join(System.getProperty(PROPERTY_USER_DIR), path);
|
if (createFolder(fullPath)) {
|
||||||
if (createFolder(fullPath)) {
|
return fullPath;
|
||||||
return fullPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.alibaba.testable.agent.tool.ImmutablePair;
|
|||||||
import org.objectweb.asm.ClassReader;
|
import org.objectweb.asm.ClassReader;
|
||||||
import org.objectweb.asm.tree.ClassNode;
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
import org.objectweb.asm.tree.MethodInsnNode;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -199,7 +200,7 @@ public class ClassUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert byte code class name to slash separated human readable name
|
* Convert byte code class name to slash separated human-readable name
|
||||||
* @param className original name
|
* @param className original name
|
||||||
* @return converted name
|
* @return converted name
|
||||||
*/
|
*/
|
||||||
@@ -208,7 +209,7 @@ public class ClassUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert byte code class name to dot separated human readable name
|
* Convert byte code class name to dot separated human-readable name
|
||||||
* @param className original name
|
* @param className original name
|
||||||
* @return converted name
|
* @return converted name
|
||||||
*/
|
*/
|
||||||
@@ -239,10 +240,25 @@ public class ClassUtil {
|
|||||||
* @return outer class name
|
* @return outer class name
|
||||||
*/
|
*/
|
||||||
public static String toOuterClassName(String name) {
|
public static String toOuterClassName(String name) {
|
||||||
int pos = name.lastIndexOf("$");
|
int pos = name.lastIndexOf(DOLLAR);
|
||||||
return (pos > 0) ? name.substring(0, pos) : name;
|
return (pos > 0) ? name.substring(0, pos) : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether class already has a method with same signature
|
||||||
|
* @param cn class to check
|
||||||
|
* @param mn method to find
|
||||||
|
* @return exist or not
|
||||||
|
*/
|
||||||
|
public static boolean hasMethod(ClassNode cn, MethodNode mn) {
|
||||||
|
for (MethodNode m : cn.methods) {
|
||||||
|
if (m.name.equals(mn.name) && m.desc.equals(mn.desc)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static String toDescriptor(Byte type, String objectType) {
|
private static String toDescriptor(Byte type, String objectType) {
|
||||||
return "(" + (char)type.byteValue() + ")L" + objectType + ";";
|
return "(" + (char)type.byteValue() + ")L" + objectType + ";";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
package com.alibaba.testable.agent.util;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author flin
|
|
||||||
*/
|
|
||||||
public class CollectionUtil {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check two collection has any equaled item
|
|
||||||
* @param collectionLeft the first collection
|
|
||||||
* @param collectionRight the second collection
|
|
||||||
* @return found or not
|
|
||||||
*/
|
|
||||||
public static boolean containsAny(Collection<?> collectionLeft, Collection<?> collectionRight) {
|
|
||||||
for (Object o : collectionLeft) {
|
|
||||||
for (Object i : collectionRight) {
|
|
||||||
if (o.equals(i)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a list of item
|
|
||||||
* @param items elements to add
|
|
||||||
* @param <T> type of element
|
|
||||||
* @return a ArrayList of provided elements
|
|
||||||
*/
|
|
||||||
public static <T> List<T> listOf(T... items) {
|
|
||||||
List<T> list = new ArrayList<T>(items.length);
|
|
||||||
Collections.addAll(list, items);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -6,16 +6,14 @@ import com.alibaba.testable.core.util.LogUtil;
|
|||||||
import org.objectweb.asm.tree.AnnotationNode;
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
import org.objectweb.asm.tree.ClassNode;
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.agent.constant.ConstPool.MOCK_DIAGNOSE;
|
||||||
|
|
||||||
public class DiagnoseUtil {
|
public class DiagnoseUtil {
|
||||||
|
|
||||||
public static void setupByClass(ClassNode cn) {
|
public static void setupByClass(ClassNode cn) {
|
||||||
if (cn == null || cn.visibleAnnotations == null) {
|
AnnotationNode an = AnnotationUtil.getClassAnnotation(cn, MOCK_DIAGNOSE);
|
||||||
return;
|
if (an != null) {
|
||||||
}
|
setupDiagnose(an, ConstPool.FIELD_VALUE);
|
||||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
|
||||||
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_DIAGNOSE).equals(an.desc)) {
|
|
||||||
setupDiagnose(an, ConstPool.FIELD_VALUE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.alibaba.testable.agent.util;
|
||||||
|
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.agent.constant.ConstPool.FIELD_TARGET_CLASS;
|
||||||
|
import static com.alibaba.testable.agent.constant.ConstPool.FIELD_TARGET_CLASS_NAME;
|
||||||
|
|
||||||
|
public class MockInvokeUtil {
|
||||||
|
|
||||||
|
public static String getTargetClassName(AnnotationNode an) {
|
||||||
|
Type targetClass = AnnotationUtil.getAnnotationParameter(an, FIELD_TARGET_CLASS, null, Type.class);
|
||||||
|
if (targetClass != null) {
|
||||||
|
return targetClass.getClassName();
|
||||||
|
}
|
||||||
|
return AnnotationUtil.getAnnotationParameter(an, FIELD_TARGET_CLASS_NAME, null, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasTargetClassParameter(AnnotationNode an) {
|
||||||
|
return AnnotationUtil.hasAnyAnnotationParameters(an, FIELD_TARGET_CLASS, FIELD_TARGET_CLASS_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasDuplicatedTargetClass(AnnotationNode an) {
|
||||||
|
return AnnotationUtil.hasAllAnnotationParameters(an, FIELD_TARGET_CLASS, FIELD_TARGET_CLASS_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.alibaba.testable.agent.util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author flin
|
|
||||||
*/
|
|
||||||
public class StringUtil {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* repeat a text many times
|
|
||||||
* @param text content to repeat
|
|
||||||
* @param times count of repeating
|
|
||||||
* @return joined string
|
|
||||||
*/
|
|
||||||
public static String repeat(String text, int times) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (int i = 0; i < times; i++) {
|
|
||||||
sb.append(text);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String join(String delimiter, String[] s) {
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
|
||||||
for (int i = 0; i < s.length; i++) {
|
|
||||||
stringBuilder.append(s[i]);
|
|
||||||
if (i != s.length - 1) {
|
|
||||||
stringBuilder.append(delimiter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stringBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ package com.alibaba.testable.agent.util;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.objectweb.asm.tree.AnnotationNode;
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
|
|
||||||
import static com.alibaba.testable.agent.util.CollectionUtil.listOf;
|
import static com.alibaba.testable.core.util.CollectionUtil.fastListOf;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
class AnnotationUtilTest {
|
class AnnotationUtilTest {
|
||||||
@@ -11,7 +11,7 @@ class AnnotationUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
void should_get_annotation_parameter() {
|
void should_get_annotation_parameter() {
|
||||||
AnnotationNode an = new AnnotationNode("");
|
AnnotationNode an = new AnnotationNode("");
|
||||||
an.values = listOf((Object)"testKey", "testValue", "demoKey", "demoValue");
|
an.values = fastListOf((Object)"testKey", "testValue", "demoKey", "demoValue");
|
||||||
assertEquals("testValue", AnnotationUtil.getAnnotationParameter(an, "testKey", "none", String.class));
|
assertEquals("testValue", AnnotationUtil.getAnnotationParameter(an, "testKey", "none", String.class));
|
||||||
assertEquals("demoValue", AnnotationUtil.getAnnotationParameter(an, "demoKey", "none", String.class));
|
assertEquals("demoValue", AnnotationUtil.getAnnotationParameter(an, "demoKey", "none", String.class));
|
||||||
assertEquals("none", AnnotationUtil.getAnnotationParameter(an, "testValue", "none", String.class));
|
assertEquals("none", AnnotationUtil.getAnnotationParameter(an, "testValue", "none", String.class));
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.alibaba.testable.agent.util;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class CollectionUtilTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void should_check_collection_contains_any_element() {
|
|
||||||
assertTrue(CollectionUtil.containsAny(
|
|
||||||
CollectionUtil.listOf("a", "b"), CollectionUtil.listOf("b", "c")
|
|
||||||
));
|
|
||||||
assertFalse(CollectionUtil.containsAny(
|
|
||||||
CollectionUtil.listOf("a", "b"), CollectionUtil.listOf("c", "d")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.testable</groupId>
|
<groupId>com.alibaba.testable</groupId>
|
||||||
<artifactId>testable-parent</artifactId>
|
<artifactId>testable-parent</artifactId>
|
||||||
<version>0.7.6</version>
|
<version>0.7.9</version>
|
||||||
<relativePath>../testable-parent</relativePath>
|
<relativePath>../testable-parent</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>testable-all</artifactId>
|
<artifactId>testable-all</artifactId>
|
||||||
@@ -59,6 +59,9 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>jar</goal>
|
<goal>jar</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -73,6 +76,13 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>sign</goal>
|
<goal>sign</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<!-- Prevent `gpg` from using pinentry programs -->
|
||||||
|
<gpgArguments>
|
||||||
|
<arg>--pinentry-mode</arg>
|
||||||
|
<arg>loopback</arg>
|
||||||
|
</gpgArguments>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.testable</groupId>
|
<groupId>com.alibaba.testable</groupId>
|
||||||
<artifactId>testable-parent</artifactId>
|
<artifactId>testable-parent</artifactId>
|
||||||
<version>0.7.6</version>
|
<version>0.7.9</version>
|
||||||
<relativePath>../testable-parent</relativePath>
|
<relativePath>../testable-parent</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>testable-core</artifactId>
|
<artifactId>testable-core</artifactId>
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
<name>testable-core</name>
|
<name>testable-core</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<junit.version>5.6.2</junit.version>
|
<junit.version>5.9.1</junit.version>
|
||||||
<transmittable.thread.local.version>2.12.4</transmittable.thread.local.version>
|
<transmittable.thread.local.version>2.14.2</transmittable.thread.local.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -80,6 +80,9 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>jar</goal>
|
<goal>jar</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -94,6 +97,13 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>sign</goal>
|
<goal>sign</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<!-- Prevent `gpg` from using pinentry programs -->
|
||||||
|
<gpgArguments>
|
||||||
|
<arg>--pinentry-mode</arg>
|
||||||
|
<arg>loopback</arg>
|
||||||
|
</gpgArguments>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.alibaba.testable.core.annotation;
|
||||||
|
|
||||||
|
import javax.lang.model.type.NullType;
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark specified class as mock container, and allow it to inherit mock methods from other classes
|
||||||
|
*
|
||||||
|
* @author flin
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Documented
|
||||||
|
public @interface MockContainer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* specify the classes to inherit methods from
|
||||||
|
* @return list of class
|
||||||
|
*/
|
||||||
|
Class<?>[] inherits() default NullType.class;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -27,6 +27,12 @@ public @interface MockInvoke {
|
|||||||
*/
|
*/
|
||||||
Class<?> targetClass() default NullType.class;
|
Class<?> targetClass() default NullType.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alternative to 'targetClass' field for private internal class
|
||||||
|
* @return target class full name
|
||||||
|
*/
|
||||||
|
String targetClassName() default "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* specify the effective scope of the mock method
|
* specify the effective scope of the mock method
|
||||||
* @return global or associated
|
* @return global or associated
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.alibaba.testable.core.compile;
|
||||||
|
|
||||||
|
public class CompilationException extends RuntimeException {
|
||||||
|
private static final long serialVersionUID = 5272588827551900536L;
|
||||||
|
|
||||||
|
public CompilationException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.alibaba.testable.core.compile;
|
||||||
|
|
||||||
|
import javax.tools.SimpleJavaFileObject;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by trung on 5/3/15.
|
||||||
|
*/
|
||||||
|
public class CompiledCode extends SimpleJavaFileObject {
|
||||||
|
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
private String className;
|
||||||
|
|
||||||
|
public CompiledCode(String className) throws Exception {
|
||||||
|
super(new URI(className), Kind.CLASS);
|
||||||
|
this.className = className;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClassName() {
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream openOutputStream() throws IOException {
|
||||||
|
return baos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getByteCode() {
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.alibaba.testable.core.compile;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DynamicClassLoader extends ClassLoader {
|
||||||
|
|
||||||
|
private Map<String, CompiledCode> customCompiledCode = new HashMap<String, CompiledCode>();
|
||||||
|
|
||||||
|
public DynamicClassLoader(ClassLoader parent) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCode(CompiledCode cc) {
|
||||||
|
customCompiledCode.put(cc.getName(), cc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||||
|
CompiledCode cc = customCompiledCode.get(name);
|
||||||
|
if (cc == null) {
|
||||||
|
return super.findClass(name);
|
||||||
|
}
|
||||||
|
byte[] byteCode = cc.getByteCode();
|
||||||
|
return defineClass(name, byteCode, 0, byteCode.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.alibaba.testable.core.compile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.tools.FileObject;
|
||||||
|
import javax.tools.ForwardingJavaFileManager;
|
||||||
|
import javax.tools.JavaFileManager;
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by trung on 5/3/15. Edited by turpid-monkey on 9/25/15, completed
|
||||||
|
* support for multiple compile units.
|
||||||
|
*/
|
||||||
|
public class ExtendedStandardJavaFileManager extends
|
||||||
|
ForwardingJavaFileManager<JavaFileManager> {
|
||||||
|
|
||||||
|
private List<CompiledCode> compiledCode = new ArrayList<CompiledCode>();
|
||||||
|
private DynamicClassLoader cl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of ForwardingJavaFileManager.
|
||||||
|
*
|
||||||
|
* @param fileManager
|
||||||
|
* delegate to this file manager
|
||||||
|
* @param cl
|
||||||
|
*/
|
||||||
|
protected ExtendedStandardJavaFileManager(JavaFileManager fileManager,
|
||||||
|
DynamicClassLoader cl) {
|
||||||
|
super(fileManager);
|
||||||
|
this.cl = cl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaFileObject getJavaFileForOutput(
|
||||||
|
JavaFileManager.Location location, String className,
|
||||||
|
JavaFileObject.Kind kind, FileObject sibling) throws IOException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
CompiledCode innerClass = new CompiledCode(className);
|
||||||
|
compiledCode.add(innerClass);
|
||||||
|
cl.addCode(innerClass);
|
||||||
|
return innerClass;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Error while creating in-memory output file for "
|
||||||
|
+ className, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClassLoader getClassLoader(JavaFileManager.Location location) {
|
||||||
|
return cl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package com.alibaba.testable.core.compile;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.tools.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile Java sources in-memory
|
||||||
|
*/
|
||||||
|
public class InMemoryJavaCompiler {
|
||||||
|
private JavaCompiler javac;
|
||||||
|
private DynamicClassLoader classLoader;
|
||||||
|
private Iterable<String> options;
|
||||||
|
boolean ignoreWarnings = false;
|
||||||
|
|
||||||
|
private Map<String, SourceCode> sourceCodes = new HashMap<String, SourceCode>();
|
||||||
|
|
||||||
|
public static InMemoryJavaCompiler newInstance() {
|
||||||
|
return new InMemoryJavaCompiler();
|
||||||
|
}
|
||||||
|
|
||||||
|
private InMemoryJavaCompiler() {
|
||||||
|
this.javac = ToolProvider.getSystemJavaCompiler();
|
||||||
|
this.classLoader = new DynamicClassLoader(ClassLoader.getSystemClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
public InMemoryJavaCompiler useParentClassLoader(ClassLoader parent) {
|
||||||
|
this.classLoader = new DynamicClassLoader(parent);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the class loader used internally by the compiler
|
||||||
|
*/
|
||||||
|
public ClassLoader getClassloader() {
|
||||||
|
return classLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options used by the compiler, e.g. '-Xlint:unchecked'.
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public InMemoryJavaCompiler useOptions(String... options) {
|
||||||
|
this.options = Arrays.asList(options);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore non-critical compiler output, like unchecked/unsafe operation
|
||||||
|
* warnings.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public InMemoryJavaCompiler ignoreWarnings() {
|
||||||
|
ignoreWarnings = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile all sources
|
||||||
|
*
|
||||||
|
* @return Map containing instances of all compiled classes
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public Map<String, Class<?>> compileAll() throws Exception {
|
||||||
|
if (sourceCodes.size() == 0) {
|
||||||
|
throw new CompilationException("No source code to compile");
|
||||||
|
}
|
||||||
|
Collection<SourceCode> compilationUnits = sourceCodes.values();
|
||||||
|
CompiledCode[] code = new CompiledCode[compilationUnits.size()];
|
||||||
|
Iterator<SourceCode> iter = compilationUnits.iterator();
|
||||||
|
for (int i = 0; i < code.length; i++) {
|
||||||
|
code[i] = new CompiledCode(iter.next().getClassName());
|
||||||
|
}
|
||||||
|
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<JavaFileObject>();
|
||||||
|
ExtendedStandardJavaFileManager fileManager = new ExtendedStandardJavaFileManager(javac.getStandardFileManager(null, null, null), classLoader);
|
||||||
|
JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, collector, options, null, compilationUnits);
|
||||||
|
boolean result = task.call();
|
||||||
|
if (!result || collector.getDiagnostics().size() > 0) {
|
||||||
|
StringBuilder exceptionMsg = new StringBuilder();
|
||||||
|
exceptionMsg.append("Unable to compile the source");
|
||||||
|
boolean hasWarnings = false;
|
||||||
|
boolean hasErrors = false;
|
||||||
|
for (Diagnostic<? extends JavaFileObject> d : collector.getDiagnostics()) {
|
||||||
|
switch (d.getKind()) {
|
||||||
|
case NOTE:
|
||||||
|
case MANDATORY_WARNING:
|
||||||
|
case WARNING:
|
||||||
|
hasWarnings = true;
|
||||||
|
break;
|
||||||
|
case OTHER:
|
||||||
|
case ERROR:
|
||||||
|
default:
|
||||||
|
hasErrors = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
exceptionMsg.append("\n").append("[kind=").append(d.getKind());
|
||||||
|
exceptionMsg.append(", ").append("line=").append(d.getLineNumber());
|
||||||
|
exceptionMsg.append(", ").append("message=").append(d.getMessage(Locale.US)).append("]");
|
||||||
|
}
|
||||||
|
if (hasWarnings && !ignoreWarnings || hasErrors) {
|
||||||
|
throw new CompilationException(exceptionMsg.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
|
||||||
|
for (String className : sourceCodes.keySet()) {
|
||||||
|
classes.put(className, classLoader.loadClass(className));
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile single source
|
||||||
|
*
|
||||||
|
* @param className
|
||||||
|
* @param sourceCode
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public Class<?> compile(String className, String sourceCode) throws Exception {
|
||||||
|
return addSource(className, sourceCode).compileAll().get(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add source code to the compiler
|
||||||
|
*
|
||||||
|
* @param className
|
||||||
|
* @param sourceCode
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
* @see {@link #compileAll()}
|
||||||
|
*/
|
||||||
|
public InMemoryJavaCompiler addSource(String className, String sourceCode) throws Exception {
|
||||||
|
sourceCodes.put(className, new SourceCode(className, sourceCode));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
This part of code is under [Apache License Version 2.0](LICENSE), modified from [InMemoryJavaCompiler](https://github.com/trung/InMemoryJavaCompiler) with modification (e.g. dropping unnecessary dependence and avoid throwing raw Exception).
|
||||||
|
|
||||||
|
Many thanks to the origin author and contributors of this lovely library:
|
||||||
|
- [trung](https://github.com/trung)
|
||||||
|
- [turpid-monkey](https://github.com/turpid-monkey)
|
||||||
|
- [PKeidel](https://github.com/PKeidel)
|
||||||
|
- [fossabot](https://github.com/fossabot)
|
||||||
|
- [KOLANICH](https://github.com/KOLANICH)
|
||||||
|
- [hammadirshad](https://github.com/hammadirshad)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.alibaba.testable.core.compile;
|
||||||
|
|
||||||
|
import javax.tools.SimpleJavaFileObject;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by trung on 5/3/15.
|
||||||
|
*/
|
||||||
|
public class SourceCode extends SimpleJavaFileObject {
|
||||||
|
private final String contents;
|
||||||
|
private final String className;
|
||||||
|
|
||||||
|
public SourceCode(String className, String contents) {
|
||||||
|
super(URI.create("string:///" + className.replace('.', '/')
|
||||||
|
+ Kind.SOURCE.extension), Kind.SOURCE);
|
||||||
|
this.contents = contents;
|
||||||
|
this.className = className;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClassName() {
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import com.alibaba.testable.core.util.MockContextUtil;
|
|||||||
import com.alibaba.testable.core.util.TestableUtil;
|
import com.alibaba.testable.core.util.TestableUtil;
|
||||||
|
|
||||||
import java.security.InvalidParameterException;
|
import java.security.InvalidParameterException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,9 +146,13 @@ public class InvocationVerifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean matches(Object expectValue, Object realValue) {
|
private boolean matches(Object expectValue, Object realValue) {
|
||||||
return expectValue instanceof InvocationMatcher ?
|
if (expectValue instanceof InvocationMatcher) {
|
||||||
((InvocationMatcher) expectValue).matchFunction.check(realValue) :
|
return ((InvocationMatcher) expectValue).matchFunction.check(realValue);
|
||||||
expectValue.equals(realValue);
|
} else if (expectValue.getClass().isArray() && realValue.getClass().isArray()) {
|
||||||
|
return Arrays.deepEquals((Object[])expectValue, (Object[])realValue);
|
||||||
|
} else {
|
||||||
|
return expectValue.equals(realValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String desc(Object[] args) {
|
private String desc(Object[] args) {
|
||||||
|
|||||||
@@ -12,6 +12,18 @@ public enum ConstructionOption {
|
|||||||
* 不初始化接口和抽象类型的成员
|
* 不初始化接口和抽象类型的成员
|
||||||
* allow members of interface or abstract class type be initialized as null
|
* allow members of interface or abstract class type be initialized as null
|
||||||
*/
|
*/
|
||||||
EXCEPT_INTERFACE
|
EXCEPT_INTERFACE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成的接口成员方法不返回初始化过的对象
|
||||||
|
* allow return value of methods in constructed interface be null
|
||||||
|
*/
|
||||||
|
EXCEPT_RETURN_VALUE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用虚拟抽象类调用父构造方法时,不使用初始化过的参数对象
|
||||||
|
* allow to invoke constructor of abstract class with null parameter
|
||||||
|
*/
|
||||||
|
EXCEPT_CONSTRUCTOR_PARAMETER
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,28 @@ package com.alibaba.testable.core.model;
|
|||||||
|
|
||||||
import com.alibaba.testable.core.util.UnnullableMap;
|
import com.alibaba.testable.core.util.UnnullableMap;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class MockContext {
|
public class MockContext {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dot separated class name
|
* current test class (dot separated) name
|
||||||
*/
|
*/
|
||||||
public final String testClassName;
|
public final String testClassName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* current test case name
|
||||||
|
*/
|
||||||
public final String testCaseName;
|
public final String testCaseName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* store of MOCK_CONTEXT.get() and MOCK_CONTEXT.set()
|
||||||
|
*/
|
||||||
public final Map<String, Object> parameters;
|
public final Map<String, Object> parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* invocation record of mock method and arguments
|
||||||
|
*/
|
||||||
public final Map<String, List<Object[]>> invokeRecord;
|
public final Map<String, List<Object[]>> invokeRecord;
|
||||||
|
|
||||||
public MockContext(String testClassName, String testCaseName) {
|
public MockContext(String testClassName, String testCaseName) {
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.alibaba.testable.core.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author flin
|
||||||
|
*/
|
||||||
|
public class Pair<L, R> implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -5197546316467446976L;
|
||||||
|
|
||||||
|
/** Left object */
|
||||||
|
private L left;
|
||||||
|
/** Right object */
|
||||||
|
private R right;
|
||||||
|
|
||||||
|
public Pair(L left, R right) {
|
||||||
|
this.left = left;
|
||||||
|
this.right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
public L getLeft() {
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public R getRight() {
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <L, R> Pair<L, R> of(L l, R r) {
|
||||||
|
return new Pair<L, R>(l, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.alibaba.testable.core.tool;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class CollectionTool {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get slice of args[startPos, args.length]
|
||||||
|
* @param args original item array
|
||||||
|
* @param startPos index of the first item to keep
|
||||||
|
* @return a new array with sliced items
|
||||||
|
*/
|
||||||
|
public static <T> T[] slice(T[] args, int startPos) {
|
||||||
|
return slice(args, startPos, args.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get slice of args[startPos, endPos]
|
||||||
|
* @param args original item array
|
||||||
|
* @param startPos index of the first item to keep
|
||||||
|
* @param endPos index of the last item to keep
|
||||||
|
* @return a new array with sliced items
|
||||||
|
*/
|
||||||
|
public static <T> T[] slice(T[] args, int startPos, int endPos) {
|
||||||
|
int size = endPos - startPos + 1;
|
||||||
|
if (size <= 0) {
|
||||||
|
return (T[]) Array.newInstance(args.getClass().getComponentType(), 0);
|
||||||
|
}
|
||||||
|
T[] slicedArgs = (T[])Array.newInstance(args.getClass().getComponentType(), size);
|
||||||
|
System.arraycopy(args, startPos, slicedArgs, 0, size);
|
||||||
|
return slicedArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an array
|
||||||
|
* @param items elements to add
|
||||||
|
* @return array of the provided items
|
||||||
|
*/
|
||||||
|
public static <T> T[] arrayOf(T... items) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mutable list
|
||||||
|
* @param items elements to add
|
||||||
|
* @return list of the provided items
|
||||||
|
*/
|
||||||
|
public static <T> List<T> listOf(T... items) {
|
||||||
|
return new ArrayList<T>(Arrays.asList(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mutable set
|
||||||
|
* @param items elements to add
|
||||||
|
* @return set of the provided items
|
||||||
|
*/
|
||||||
|
public static <T> Set<T> setOf(T... items) {
|
||||||
|
return new HashSet<T>(Arrays.asList(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mutable map
|
||||||
|
* @param entry elements to add
|
||||||
|
* @return map of the provided items
|
||||||
|
*/
|
||||||
|
public static <K, V> Map<K, V> mapOf(Map.Entry<K, V>... entry) {
|
||||||
|
return appendMap(new HashMap<K, V>(entry.length), entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an mutable ordered map
|
||||||
|
* @param entry elements to add
|
||||||
|
* @return ordered map of the provided items
|
||||||
|
*/
|
||||||
|
public static <K, V> Map<K, V> orderedMapOf(Map.Entry<K, V>... entry) {
|
||||||
|
return appendMap(new LinkedHashMap<K, V>(entry.length), entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a map entry
|
||||||
|
* @param key the key
|
||||||
|
* @param value the value
|
||||||
|
* @return entry of provided key and value
|
||||||
|
*/
|
||||||
|
public static <K, V> Map.Entry<K, V> entryOf(K key, V value) {
|
||||||
|
return new AbstractMap.SimpleEntry<K, V>(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <K, V> Map<K, V> appendMap(Map<K, V> kvs, Map.Entry<K, V>[] entry) {
|
||||||
|
for (Map.Entry<K, V> p : entry) {
|
||||||
|
kvs.put(p.getKey(), p.getValue());
|
||||||
|
}
|
||||||
|
return kvs;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -139,7 +139,7 @@ public class OmniAccessor {
|
|||||||
for (int i = 0; i < querySegments.length; i++) {
|
for (int i = 0; i < querySegments.length; i++) {
|
||||||
patternSegments[i] = toSinglePattern(querySegments[i]);
|
patternSegments[i] = toSinglePattern(querySegments[i]);
|
||||||
}
|
}
|
||||||
return PATTERN_PREFIX + CollectionUtil.join(Arrays.asList(patternSegments), SLASH);
|
return PATTERN_PREFIX + CollectionUtil.joinToString(Arrays.asList(patternSegments), SLASH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String toSinglePattern(String querySegment) {
|
private static String toSinglePattern(String querySegment) {
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ package com.alibaba.testable.core.tool;
|
|||||||
import com.alibaba.testable.core.exception.ClassConstructionException;
|
import com.alibaba.testable.core.exception.ClassConstructionException;
|
||||||
import com.alibaba.testable.core.model.ConstructionOption;
|
import com.alibaba.testable.core.model.ConstructionOption;
|
||||||
import com.alibaba.testable.core.util.CollectionUtil;
|
import com.alibaba.testable.core.util.CollectionUtil;
|
||||||
|
import com.alibaba.testable.core.util.ConstructionUtil;
|
||||||
import com.alibaba.testable.core.util.LogUtil;
|
import com.alibaba.testable.core.util.LogUtil;
|
||||||
import com.alibaba.testable.core.util.TypeUtil;
|
import com.alibaba.testable.core.util.TypeUtil;
|
||||||
|
|
||||||
import java.lang.reflect.*;
|
import java.lang.reflect.*;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static com.alibaba.testable.core.constant.ConstPool.DOLLAR;
|
import static com.alibaba.testable.core.constant.ConstPool.DOLLAR;
|
||||||
|
import static com.alibaba.testable.core.model.ConstructionOption.EXCEPT_INTERFACE;
|
||||||
import static com.alibaba.testable.core.model.ConstructionOption.EXCEPT_LOOP_NESTING;
|
import static com.alibaba.testable.core.model.ConstructionOption.EXCEPT_LOOP_NESTING;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,7 +33,7 @@ public class OmniConstructor {
|
|||||||
* @return 返回新创建的对象
|
* @return 返回新创建的对象
|
||||||
*/
|
*/
|
||||||
public static <T> T newInstance(Class<T> clazz, ConstructionOption... options) {
|
public static <T> T newInstance(Class<T> clazz, ConstructionOption... options) {
|
||||||
T ins = newInstance(clazz, new HashSet<Class<?>>(INITIAL_CAPACITY));
|
T ins = newInstance(clazz, new HashSet<Class<?>>(INITIAL_CAPACITY), options);
|
||||||
if (ins == null || CollectionUtil.contains(options, EXCEPT_LOOP_NESTING)) {
|
if (ins == null || CollectionUtil.contains(options, EXCEPT_LOOP_NESTING)) {
|
||||||
return ins;
|
return ins;
|
||||||
}
|
}
|
||||||
@@ -46,34 +49,38 @@ public class OmniConstructor {
|
|||||||
* @return 返回新创建的对象数组
|
* @return 返回新创建的对象数组
|
||||||
*/
|
*/
|
||||||
public static <T> T[] newArray(Class<T> clazz, int size, ConstructionOption... options) {
|
public static <T> T[] newArray(Class<T> clazz, int size, ConstructionOption... options) {
|
||||||
T[] array = (T[])newArray(clazz, size, new HashSet<Class<?>>(INITIAL_CAPACITY));
|
T[] array = (T[])newArray(clazz, size, new HashSet<Class<?>>(INITIAL_CAPACITY), options);
|
||||||
if (CollectionUtil.contains(options, EXCEPT_LOOP_NESTING)) {
|
if (CollectionUtil.contains(options, EXCEPT_LOOP_NESTING)) {
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
return handleCircleReference(array);
|
return handleCircleReference(array);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T newInstance(Class<T> clazz, Set<Class<?>> classPool) {
|
private static <T> T newInstance(Class<T> clazz, Set<Class<?>> classPool, ConstructionOption[] options) {
|
||||||
LogUtil.verbose(classPool.size() * 2, "Creating %s", clazz.getName());
|
LogUtil.verbose(classPool.size() * 2, "Creating %s", clazz.getName());
|
||||||
if (classPool.contains(clazz)) {
|
if (classPool.contains(clazz)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
classPool.add(clazz);
|
classPool.add(clazz);
|
||||||
|
T ins = createSpecialClass(clazz);
|
||||||
|
if (ins != null) {
|
||||||
|
return ins;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (clazz.isPrimitive()) {
|
if (clazz.isPrimitive()) {
|
||||||
return newPrimitive(clazz);
|
return newPrimitive(clazz);
|
||||||
} else if (clazz.equals(Class.class)) {
|
} else if (clazz.equals(Class.class)) {
|
||||||
return (T)Object.class;
|
return (T)Object.class;
|
||||||
} else if (clazz.isArray()) {
|
} else if (clazz.isArray()) {
|
||||||
return (T)newArray(clazz.getComponentType(), 0, classPool);
|
return (T)newArray(clazz.getComponentType(), 0, classPool, options);
|
||||||
} else if (clazz.isEnum()) {
|
} else if (clazz.isEnum()) {
|
||||||
return newEnum(clazz);
|
return newEnum(clazz);
|
||||||
} else if (clazz.isInterface()) {
|
} else if (clazz.isInterface()) {
|
||||||
return newInterface(clazz);
|
return newInterface(clazz, options);
|
||||||
} else if (Modifier.isAbstract(clazz.getModifiers())) {
|
} else if (Modifier.isAbstract(clazz.getModifiers())) {
|
||||||
return newAbstractClass(clazz);
|
return newAbstractClass(clazz, options);
|
||||||
}
|
}
|
||||||
return newObject(clazz, classPool);
|
return newObject(clazz, classPool, options);
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
throw new ClassConstructionException("Failed to find constructor", e);
|
throw new ClassConstructionException("Failed to find constructor", e);
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
@@ -89,35 +96,36 @@ public class OmniConstructor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T newObject(Class<T> clazz, Set<Class<?>> classPool)
|
private static <T> T newObject(Class<T> clazz, Set<Class<?>> classPool, ConstructionOption[] options)
|
||||||
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
Object ins = createInstance(clazz, classPool);
|
Object ins = createInstance(clazz, classPool, options);
|
||||||
if (!TypeUtil.isBasicType(clazz)) {
|
if (!TypeUtil.isBasicType(clazz)) {
|
||||||
for (Field f : TypeUtil.getAllFields(clazz)) {
|
for (Field f : TypeUtil.getAllFields(clazz)) {
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
// skip "$jacocoData" field added by jacoco
|
// skip "$jacocoData" field added by jacoco
|
||||||
if (f.get(ins) == null && !f.getName().startsWith(DOLLAR)) {
|
if (f.get(ins) == null && !f.getName().startsWith(DOLLAR)) {
|
||||||
f.set(ins, newInstance(f.getType(), classPool));
|
f.set(ins, newInstance(f.getType(), classPool, options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (T)ins;
|
return (T)ins;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object newArray(Class<?> clazz, int size, Set<Class<?>> classPool) {
|
private static Object newArray(Class<?> clazz, int size, Set<Class<?>> classPool, ConstructionOption[] options) {
|
||||||
// primary[] cannot be cast to Object[], have to use Object instead of T[]
|
// primary[] cannot be cast to Object[], have to use Object instead of T[]
|
||||||
Object array = Array.newInstance(clazz, size);
|
Object array = Array.newInstance(clazz, size);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
Array.set(array, i, newInstance(clazz, classPool));
|
Array.set(array, i, newInstance(clazz, classPool, options));
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T newAbstractClass(Class<T> clazz) {
|
private static <T> T newAbstractClass(Class<T> clazz, ConstructionOption[] options) throws InstantiationException {
|
||||||
return null;
|
return CollectionUtil.contains(options, EXCEPT_INTERFACE) ? null
|
||||||
|
: ConstructionUtil.generateSubClassOf(clazz, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T newInterface(Class<T> clazz) {
|
private static <T> T newInterface(Class<T> clazz, ConstructionOption[] options) throws InstantiationException {
|
||||||
if (clazz.equals(List.class)) {
|
if (clazz.equals(List.class)) {
|
||||||
return (T)Collections.emptyList();
|
return (T)Collections.emptyList();
|
||||||
} else if (clazz.equals(Map.class)) {
|
} else if (clazz.equals(Map.class)) {
|
||||||
@@ -125,7 +133,7 @@ public class OmniConstructor {
|
|||||||
} else if (clazz.equals(Set.class)) {
|
} else if (clazz.equals(Set.class)) {
|
||||||
return (T)Collections.emptySet();
|
return (T)Collections.emptySet();
|
||||||
}
|
}
|
||||||
return null;
|
return newAbstractClass(clazz, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T newEnum(Class<T> clazz)
|
private static <T> T newEnum(Class<T> clazz)
|
||||||
@@ -180,7 +188,7 @@ public class OmniConstructor {
|
|||||||
}
|
}
|
||||||
classPool.put(type, instance);
|
classPool.put(type, instance);
|
||||||
for (Field f : TypeUtil.getAllFields(type)) {
|
for (Field f : TypeUtil.getAllFields(type)) {
|
||||||
if (f.getName().startsWith("$") || isStaticFinalField(f)) {
|
if (f.getName().startsWith(DOLLAR) || isStaticFinalField(f)) {
|
||||||
// skip static-final fields and fields e.g. "$jacocoData"
|
// skip static-final fields and fields e.g. "$jacocoData"
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -225,13 +233,9 @@ public class OmniConstructor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object createInstance(Class<?> clazz, Set<Class<?>> classPool)
|
private static Object createInstance(Class<?> clazz, Set<Class<?>> classPool, ConstructionOption[] options)
|
||||||
throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||||
Object ins = createSpecialClass(clazz);
|
Constructor<?> constructor = TypeUtil.getBestConstructor(clazz);
|
||||||
if (ins != null) {
|
|
||||||
return ins;
|
|
||||||
}
|
|
||||||
Constructor<?> constructor = getBestConstructor(clazz);
|
|
||||||
if (constructor == null) {
|
if (constructor == null) {
|
||||||
throw new ClassConstructionException("Fail to invoke constructor of " + clazz.getName());
|
throw new ClassConstructionException("Fail to invoke constructor of " + clazz.getName());
|
||||||
}
|
}
|
||||||
@@ -242,41 +246,42 @@ public class OmniConstructor {
|
|||||||
} else {
|
} else {
|
||||||
Object[] args = new Object[types.length];
|
Object[] args = new Object[types.length];
|
||||||
for (int i = 0; i < types.length; i++) {
|
for (int i = 0; i < types.length; i++) {
|
||||||
args[i] = types[i].equals(clazz) ? null : newInstance(types[i], classPool);
|
args[i] = types[i].equals(clazz) ? null : newInstance(types[i], classPool, options);
|
||||||
}
|
}
|
||||||
return constructor.newInstance(args);
|
return constructor.newInstance(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object createSpecialClass(Class<?> clazz) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
private static <T> T createSpecialClass(Class<T> clazz) {
|
||||||
try {
|
if (clazz.getName().equals("java.net.URL")) {
|
||||||
// java.net.URL is loaded before OmniClassHandler, cannot be instrumented
|
// loaded before OmniClassHandler, cannot be instrumented
|
||||||
if (clazz.getName().equals("java.net.URL")) {
|
try {
|
||||||
return clazz.getDeclaredConstructor(String.class).newInstance("https://");
|
return clazz.getDeclaredConstructor(String.class).newInstance("https://");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (clazz.getName().equals("java.nio.charset.Charset")) {
|
||||||
|
// better to use its default instance
|
||||||
|
return (T) Charset.defaultCharset();
|
||||||
|
} else if (clazz.getName().equals("java.util.Date")) {
|
||||||
|
// better to use current time
|
||||||
|
return (T) new Date();
|
||||||
|
} else if (clazz.getName().equals("java.nio.ByteBuffer")) {
|
||||||
|
// has package-private abstract methods
|
||||||
|
try {
|
||||||
|
return (T) newInstance(Class.forName("java.nio.DirectByteBuffer"));
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (clazz.getName().equals("java.nio.CharBuffer")) {
|
||||||
|
// has package-private abstract methods
|
||||||
|
try {
|
||||||
|
return (T) newInstance(Class.forName("java.nio.StringCharBuffer"));
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Constructor<?> getBestConstructor(Class<?> clazz) {
|
|
||||||
Constructor<?> bestConstructor = null;
|
|
||||||
int minimalExceptionCount = 999;
|
|
||||||
int minimalParameterCount = 999;
|
|
||||||
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
|
|
||||||
Class<?>[] parameterTypes = constructor.getParameterTypes();
|
|
||||||
Class<?>[] exceptionTypes = constructor.getExceptionTypes();
|
|
||||||
if (parameterTypes.length == 1 && parameterTypes[0].equals(Void.class)) {
|
|
||||||
return constructor;
|
|
||||||
} else if (exceptionTypes.length < minimalExceptionCount
|
|
||||||
|| (exceptionTypes.length == minimalExceptionCount && parameterTypes.length < minimalParameterCount)) {
|
|
||||||
minimalExceptionCount = exceptionTypes.length;
|
|
||||||
minimalParameterCount = parameterTypes.length;
|
|
||||||
bestConstructor = constructor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bestConstructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
package com.alibaba.testable.core.util;
|
package com.alibaba.testable.core.util;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
public class CollectionUtil {
|
public class CollectionUtil {
|
||||||
|
|
||||||
public static Object[] slice(Object[] args, int pos) {
|
/**
|
||||||
int size = args.length - pos;
|
* Join a collection into string
|
||||||
if (size <= 0) {
|
* @param collection many items with proper toString() method
|
||||||
return new Object[0];
|
* @param joinSymbol splitter of echo items
|
||||||
}
|
* @return a joined string
|
||||||
Object[] slicedArgs = new Object[size];
|
*/
|
||||||
System.arraycopy(args, pos, slicedArgs, 0, size);
|
public static String joinToString(Collection<?> collection, String joinSymbol) {
|
||||||
return slicedArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String join(Collection<?> collection, String joinSymbol) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for(Iterator<?> i = collection.iterator(); i.hasNext(); sb.append((String)i.next())) {
|
for(Iterator<?> i = collection.iterator(); i.hasNext(); sb.append(i.next().toString())) {
|
||||||
if (sb.length() != 0) {
|
if (sb.length() != 0) {
|
||||||
sb.append(joinSymbol);
|
sb.append(joinSymbol);
|
||||||
}
|
}
|
||||||
@@ -25,6 +20,12 @@ public class CollectionUtil {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether target exist in collection
|
||||||
|
* @param collection many items to find from
|
||||||
|
* @param target an item to be found
|
||||||
|
* @return whether target exist in collection
|
||||||
|
*/
|
||||||
public static <T> boolean contains(T[] collection, T target) {
|
public static <T> boolean contains(T[] collection, T target) {
|
||||||
for (T item : collection) {
|
for (T item : collection) {
|
||||||
if (target.equals(item)) {
|
if (target.equals(item)) {
|
||||||
@@ -33,4 +34,31 @@ public class CollectionUtil {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check two collection has any equaled item
|
||||||
|
* @param collectionLeft the first collection
|
||||||
|
* @param collectionRight the second collection
|
||||||
|
* @return whether any equaled item found
|
||||||
|
*/
|
||||||
|
public static <T> boolean containsAny(Collection<T> collectionLeft, Collection<T> collectionRight) {
|
||||||
|
for (T left : collectionLeft) {
|
||||||
|
for (T right : collectionRight) {
|
||||||
|
if (left.equals(right)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an immutable list
|
||||||
|
* @param items elements to add
|
||||||
|
* @return list of the provided items
|
||||||
|
*/
|
||||||
|
public static <T> List<T> fastListOf(T... items) {
|
||||||
|
return Arrays.asList(items);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
package com.alibaba.testable.core.util;
|
||||||
|
|
||||||
|
import com.alibaba.testable.core.compile.InMemoryJavaCompiler;
|
||||||
|
import com.alibaba.testable.core.model.ConstructionOption;
|
||||||
|
import com.alibaba.testable.core.tool.OmniConstructor;
|
||||||
|
|
||||||
|
import java.lang.reflect.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.core.constant.ConstPool.DOT;
|
||||||
|
import static com.alibaba.testable.core.model.ConstructionOption.EXCEPT_CONSTRUCTOR_PARAMETER;
|
||||||
|
import static com.alibaba.testable.core.model.ConstructionOption.EXCEPT_RETURN_VALUE;
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.entryOf;
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.mapOf;
|
||||||
|
|
||||||
|
public class ConstructionUtil {
|
||||||
|
|
||||||
|
private static final String TESTABLE_IMPL = "$TestableImpl";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default value of basic and special types
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> DEFAULT_VALUES = mapOf(
|
||||||
|
entryOf("java.lang.String", "\"mock\""),
|
||||||
|
entryOf("byte", "'\0'"),
|
||||||
|
entryOf("java.lang.Byte", "'\0'"),
|
||||||
|
entryOf("char", "'\0'"),
|
||||||
|
entryOf("java.lang.Character", "'\0'"),
|
||||||
|
entryOf("double", "1.0D"),
|
||||||
|
entryOf("java.lang.Double", "1.0D"),
|
||||||
|
entryOf("float", "1.0F"),
|
||||||
|
entryOf("java.lang.Float", "1.0F"),
|
||||||
|
entryOf("int", "1"),
|
||||||
|
entryOf("java.lang.Integer", "1"),
|
||||||
|
entryOf("short", "1"),
|
||||||
|
entryOf("java.lang.Short", "1"),
|
||||||
|
entryOf("long", "1L"),
|
||||||
|
entryOf("java.lang.Long", "1L"),
|
||||||
|
entryOf("boolean", "true"),
|
||||||
|
entryOf("java.lang.Boolean", "true"),
|
||||||
|
entryOf("java.nio.charset.Charset", "java.nio.charset.Charset.defaultCharset()")
|
||||||
|
);
|
||||||
|
|
||||||
|
public static <T> T generateSubClassOf(Class<T> clazz, ConstructionOption[] options) throws InstantiationException {
|
||||||
|
StringBuilder sourceCode = new StringBuilder();
|
||||||
|
String packageName = adaptName(clazz.getPackage().getName());
|
||||||
|
String subclassName = getSubclassName(clazz);
|
||||||
|
Map<String, String> noMapping = new HashMap<String, String>();
|
||||||
|
sourceCode.append("package ")
|
||||||
|
.append(packageName)
|
||||||
|
.append(";\npublic class ")
|
||||||
|
.append(subclassName)
|
||||||
|
.append(getTypeParameters(clazz.getTypeParameters(), true, noMapping))
|
||||||
|
.append(clazz.isInterface() ? " implements " : " extends ")
|
||||||
|
.append(getClassName(clazz, noMapping))
|
||||||
|
.append(getTypeParameters(clazz.getTypeParameters(), false, noMapping))
|
||||||
|
.append(" {\n");
|
||||||
|
sourceCode.append("\tpublic ").append(subclassName).append("() { ")
|
||||||
|
.append(invokeConstructorOf(clazz, noMapping, options)).append(" }\n");
|
||||||
|
for (String method : generateMethodsOf(clazz, new HashSet<String>(), noMapping, options)) {
|
||||||
|
sourceCode.append(method);
|
||||||
|
}
|
||||||
|
sourceCode.append("}");
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (T) InMemoryJavaCompiler.newInstance()
|
||||||
|
.useParentClassLoader(clazz.getClassLoader())
|
||||||
|
.useOptions("-Xlint:unchecked")
|
||||||
|
.ignoreWarnings()
|
||||||
|
.compile(packageName + DOT + subclassName, sourceCode.toString())
|
||||||
|
.newInstance();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new InstantiationException(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String invokeConstructorOf(Class<?> clazz, Map<String, String> genericTypes,
|
||||||
|
ConstructionOption[] options) {
|
||||||
|
if (clazz.isInterface()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
Constructor<?> constructor = TypeUtil.getBestConstructor(clazz, false);
|
||||||
|
StringBuilder invocation = new StringBuilder("super(");
|
||||||
|
Type[] genericParameterTypes = constructor.getGenericParameterTypes();
|
||||||
|
Class<?>[] parameterTypes = constructor.getParameterTypes();
|
||||||
|
for (int i = 0; i < parameterTypes.length; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
invocation.append(", ");
|
||||||
|
}
|
||||||
|
invocation.append(getDefaultValue(getClassName(genericParameterTypes[i], genericTypes),
|
||||||
|
getClassName(parameterTypes[i]),
|
||||||
|
CollectionUtil.contains(options, EXCEPT_CONSTRUCTOR_PARAMETER), genericTypes));
|
||||||
|
}
|
||||||
|
invocation.append(");");
|
||||||
|
return invocation.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> generateMethodsOf(Class<?> clazz, Set<String> methodPool,
|
||||||
|
Map<String, String> genericTypes, ConstructionOption[] options) {
|
||||||
|
Set<String> methods = new HashSet<String>();
|
||||||
|
// in a very special situation, getDeclaredMethods() could fetch method declaration in the parent interface
|
||||||
|
// as none-abstract, that will cause the corresponding abstract method in current class be skipped.
|
||||||
|
// it happens to e.g. CharSequence.subSequence(int,int) and CharBuffer.subSequence(int,int)
|
||||||
|
// so we would pass the methods to pool after all method in current level have been handled.
|
||||||
|
Set<String> thisLevelMethodPool = new HashSet<String>();
|
||||||
|
for (Method m : clazz.getDeclaredMethods()) {
|
||||||
|
StringBuilder methodSignatureBuilder = new StringBuilder(m.getName());
|
||||||
|
for (Type p : m.getGenericParameterTypes()) {
|
||||||
|
methodSignatureBuilder.append("#").append(getParameterName(p, genericTypes));
|
||||||
|
}
|
||||||
|
String methodSignature = methodSignatureBuilder.toString();
|
||||||
|
if (methodPool.contains(methodSignature)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
thisLevelMethodPool.add(methodSignature);
|
||||||
|
if (Modifier.isAbstract(m.getModifiers())) {
|
||||||
|
StringBuilder sourceCode = new StringBuilder();
|
||||||
|
sourceCode.append("\tpublic ")
|
||||||
|
.append(getTypeParameters(m.getTypeParameters(), true, genericTypes))
|
||||||
|
.append(getClassName(m.getGenericReturnType(), genericTypes))
|
||||||
|
.append(" ").append(m.getName()).append("(");
|
||||||
|
Type[] parameters = m.getGenericParameterTypes();
|
||||||
|
for (int i = 0; i < parameters.length; i++) {
|
||||||
|
sourceCode.append(getParameterName(parameters[i], genericTypes)).append(" p").append(i);
|
||||||
|
if (i < parameters.length - 1) {
|
||||||
|
sourceCode.append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourceCode.append(") {\n");
|
||||||
|
String returnType = getClassName(m.getGenericReturnType(), genericTypes);
|
||||||
|
if (!"void".equals(returnType)) {
|
||||||
|
sourceCode.append("\t\treturn ")
|
||||||
|
.append(getDefaultValue(returnType, getClassName(m.getReturnType()),
|
||||||
|
CollectionUtil.contains(options, EXCEPT_RETURN_VALUE), genericTypes))
|
||||||
|
.append(";\n");
|
||||||
|
}
|
||||||
|
sourceCode.append("\t}\n");
|
||||||
|
methods.add(sourceCode.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
methodPool.addAll(thisLevelMethodPool);
|
||||||
|
List<Type> superTypes = new ArrayList<Type>(Arrays.asList(clazz.getGenericInterfaces()));
|
||||||
|
if (clazz.getGenericSuperclass() != null) {
|
||||||
|
superTypes.add(clazz.getGenericSuperclass());
|
||||||
|
}
|
||||||
|
for (Type t : superTypes) {
|
||||||
|
if (t instanceof ParameterizedType) {
|
||||||
|
ParameterizedType pt = (ParameterizedType) t;
|
||||||
|
methods.addAll(generateMethodsOf((Class<?>) pt.getRawType(), methodPool, parseGenericTypes(pt), options));
|
||||||
|
} else if (t instanceof Class) {
|
||||||
|
methods.addAll(generateMethodsOf((Class<?>) t, methodPool, Collections.<String, String>emptyMap(), options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getDefaultValue(String genericTypeName, String simpleTypeName, boolean alwaysUseNullParameter,
|
||||||
|
Map<String, String> genericTypes) {
|
||||||
|
if (DEFAULT_VALUES.containsKey(genericTypeName)) {
|
||||||
|
return DEFAULT_VALUES.get(genericTypeName);
|
||||||
|
} else if (genericTypeName.startsWith("java.") || alwaysUseNullParameter) {
|
||||||
|
return "null";
|
||||||
|
} else {
|
||||||
|
return "(" + genericTypeName + ") " + getClassName(OmniConstructor.class, genericTypes) +
|
||||||
|
".newInstance(" + simpleTypeName + ".class)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> parseGenericTypes(ParameterizedType type) {
|
||||||
|
Map<String, String> templateTypeMap = new HashMap<String, String>();
|
||||||
|
Type[] actualTypeArguments = type.getActualTypeArguments();
|
||||||
|
TypeVariable<? extends Class<?>>[] rawTypedParameters = ((Class<?>) type.getRawType()).getTypeParameters();
|
||||||
|
for (int i = 0; i < actualTypeArguments.length; i++) {
|
||||||
|
templateTypeMap.put(getClassName(rawTypedParameters[i]), getClassName(actualTypeArguments[i]));
|
||||||
|
}
|
||||||
|
return templateTypeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String adaptName(String name) {
|
||||||
|
// create class in 'java' package will cause 'prohibited package name' error
|
||||||
|
return name.replaceAll("^java\\.", "testable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getTypeParameters(TypeVariable<?>[] typeParameters, boolean withScope,
|
||||||
|
Map<String, String> genericTypes) {
|
||||||
|
if (typeParameters.length > 0) {
|
||||||
|
StringBuilder sb = new StringBuilder("<");
|
||||||
|
for (int i = 0; i < typeParameters.length; i++) {
|
||||||
|
sb.append(typeParameters[i].getName());
|
||||||
|
if (withScope) {
|
||||||
|
sb.append(" extends ");
|
||||||
|
Type[] bounds = typeParameters[i].getBounds();
|
||||||
|
for (int j = 0; j < bounds.length; j++) {
|
||||||
|
sb.append(getClassName(bounds[j], genericTypes));
|
||||||
|
if (j < bounds.length - 1) {
|
||||||
|
sb.append(" & ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i < typeParameters.length - 1) {
|
||||||
|
sb.append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append("> ");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getTypeParameters(Type[] typeParameters, Map<String, String> genericTypes) {
|
||||||
|
if (typeParameters.length > 0) {
|
||||||
|
StringBuilder sb = new StringBuilder("<");
|
||||||
|
for (int i = 0; i < typeParameters.length; i++) {
|
||||||
|
sb.append(getClassName(typeParameters[i], genericTypes));
|
||||||
|
if (i < typeParameters.length - 1) {
|
||||||
|
sb.append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append("> ");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getWildcardType(WildcardType wildcardType, Map<String, String> genericTypes) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
Type[] lowerBounds = wildcardType.getLowerBounds();
|
||||||
|
Type[] bounds = lowerBounds;
|
||||||
|
if (lowerBounds.length > 0) {
|
||||||
|
sb.append("? super ");
|
||||||
|
} else {
|
||||||
|
Type[] upperBounds = wildcardType.getUpperBounds();
|
||||||
|
if (upperBounds.length == 0 || upperBounds[0].equals(Object.class)) {
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
bounds = upperBounds;
|
||||||
|
sb.append("? extends ");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean firstItem = true;
|
||||||
|
for (Type type : bounds) {
|
||||||
|
if (!firstItem) {
|
||||||
|
sb.append(" & ");
|
||||||
|
}
|
||||||
|
|
||||||
|
firstItem = false;
|
||||||
|
sb.append(getClassName(type, genericTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getClassName(Type clazz) {
|
||||||
|
return (clazz instanceof Class) ? ((Class<?>)clazz).getCanonicalName() : clazz.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getClassName(Type clazz, Map<String, String> genericTypes) {
|
||||||
|
if (clazz instanceof Class) {
|
||||||
|
return ((Class<?>)clazz).getCanonicalName();
|
||||||
|
} else if (clazz instanceof GenericArrayType) {
|
||||||
|
return getClassName(((GenericArrayType) clazz).getGenericComponentType(), genericTypes) + "[]";
|
||||||
|
} else if (clazz instanceof TypeVariable) {
|
||||||
|
String name = ((TypeVariable<?>)clazz).getName();
|
||||||
|
return genericTypes.containsKey(name) ? genericTypes.get(name) : name;
|
||||||
|
} else if (clazz instanceof ParameterizedType) {
|
||||||
|
ParameterizedType ptClazz = (ParameterizedType)clazz;
|
||||||
|
return getClassName(ptClazz.getRawType()) + getTypeParameters(ptClazz.getActualTypeArguments(), genericTypes);
|
||||||
|
} else if (clazz instanceof WildcardType) {
|
||||||
|
return getWildcardType((WildcardType)clazz, genericTypes);
|
||||||
|
}
|
||||||
|
return clazz.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getParameterName(Type parameter, Map<String, String> genericTypes) {
|
||||||
|
if (parameter instanceof Class && ((Class<?>)parameter).isArray()) {
|
||||||
|
return getParameterName(((Class<?>)parameter).getComponentType(), genericTypes) + "[]";
|
||||||
|
}
|
||||||
|
return getClassName(parameter, genericTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSubclassName(Class<?> clazz) {
|
||||||
|
return clazz.getSimpleName() + TESTABLE_IMPL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.alibaba.testable.core.util;
|
package com.alibaba.testable.core.util;
|
||||||
|
|
||||||
import com.alibaba.testable.core.model.MockContext;
|
import com.alibaba.testable.core.model.MockContext;
|
||||||
|
import com.alibaba.testable.core.tool.CollectionTool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author flin
|
* @author flin
|
||||||
@@ -31,7 +32,7 @@ public class InvokeRecordUtil {
|
|||||||
mockContext.invokeRecord.get(mockMethodName).add(args);
|
mockContext.invokeRecord.get(mockMethodName).add(args);
|
||||||
LogUtil.verbose(" Mock constructor \"%s\" invoked in %s::%s", mockMethodName, testClass, testCaseName);
|
LogUtil.verbose(" Mock constructor \"%s\" invoked in %s::%s", mockMethodName, testClass, testCaseName);
|
||||||
} else {
|
} else {
|
||||||
mockContext.invokeRecord.get(mockMethodName).add(CollectionUtil.slice(args, 1));
|
mockContext.invokeRecord.get(mockMethodName).add(CollectionTool.slice(args, 1));
|
||||||
LogUtil.verbose(" Mock method \"%s\" invoked in %s::%s\"", mockMethodName, testClass, testCaseName);
|
LogUtil.verbose(" Mock method \"%s\" invoked in %s::%s\"", mockMethodName, testClass, testCaseName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.alibaba.testable.core.util;
|
|||||||
|
|
||||||
import com.alibaba.testable.core.exception.MemberAccessException;
|
import com.alibaba.testable.core.exception.MemberAccessException;
|
||||||
import com.alibaba.testable.core.model.MockContext;
|
import com.alibaba.testable.core.model.MockContext;
|
||||||
|
import com.alibaba.testable.core.tool.CollectionTool;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -71,7 +72,7 @@ public class MockAssociationUtil {
|
|||||||
return construct(originClass, args);
|
return construct(originClass, args);
|
||||||
} else if (args[0] == null) {
|
} else if (args[0] == null) {
|
||||||
try {
|
try {
|
||||||
return invokeStatic(originClass, originMethod, CollectionUtil.slice(args, 1));
|
return invokeStatic(originClass, originMethod, CollectionTool.slice(args, 1));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
if (e instanceof MemberAccessException && e.getCause() instanceof NoSuchFieldException) {
|
if (e instanceof MemberAccessException && e.getCause() instanceof NoSuchFieldException) {
|
||||||
throw new NullPointerException("Invoking method \"" + originMethod + "\" of null object");
|
throw new NullPointerException("Invoking method \"" + originMethod + "\" of null object");
|
||||||
@@ -79,7 +80,7 @@ public class MockAssociationUtil {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return invoke(args[0], originMethod, CollectionUtil.slice(args, 1));
|
return invoke(args[0], originMethod, CollectionTool.slice(args, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,35 @@ package com.alibaba.testable.core.util;
|
|||||||
|
|
||||||
public class StringUtil {
|
public class StringUtil {
|
||||||
|
|
||||||
public static String repeat(String str, int n) {
|
/**
|
||||||
|
* repeat a text many times
|
||||||
|
* @param text content to repeat
|
||||||
|
* @param times count of repeating
|
||||||
|
* @return repeated string
|
||||||
|
*/
|
||||||
|
public static String repeat(String text, int times) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < times; i++) {
|
||||||
sb.append(str);
|
sb.append(text);
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* concat several strings into a single string
|
||||||
|
* @param delimiter symbol to add between provided strings
|
||||||
|
* @param s an array of string to join
|
||||||
|
* @return joined string
|
||||||
|
*/
|
||||||
|
public static String join(String delimiter, String... s) {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
for (int i = 0; i < s.length; i++) {
|
||||||
|
stringBuilder.append(s[i]);
|
||||||
|
if (i != s.length - 1) {
|
||||||
|
stringBuilder.append(delimiter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,42 @@ public class TypeUtil {
|
|||||||
|| clazz.equals(Class.class) || clazz.equals(String.class);
|
|| clazz.equals(Class.class) || clazz.equals(String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find the simplest constructor for specified class
|
||||||
|
* @param clazz any class
|
||||||
|
* @return best constructor
|
||||||
|
*/
|
||||||
|
public static Constructor<?> getBestConstructor(Class<?> clazz, boolean useGeneratedConstructor) {
|
||||||
|
Constructor<?> bestConstructor = null;
|
||||||
|
int minimalExceptionCount = 999;
|
||||||
|
int minimalParameterCount = 999;
|
||||||
|
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
|
||||||
|
Class<?>[] parameterTypes = constructor.getParameterTypes();
|
||||||
|
Class<?>[] exceptionTypes = constructor.getExceptionTypes();
|
||||||
|
if (parameterTypes.length == 1 && parameterTypes[0].equals(Void.class)) {
|
||||||
|
// if generated constructor allowed then use it, otherwise skip and continue
|
||||||
|
if (useGeneratedConstructor) {
|
||||||
|
return constructor;
|
||||||
|
}
|
||||||
|
} else if (exceptionTypes.length < minimalExceptionCount
|
||||||
|
|| (exceptionTypes.length == minimalExceptionCount && parameterTypes.length < minimalParameterCount)) {
|
||||||
|
minimalExceptionCount = exceptionTypes.length;
|
||||||
|
minimalParameterCount = parameterTypes.length;
|
||||||
|
bestConstructor = constructor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestConstructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find the simplest constructor including the testable generated one
|
||||||
|
* @param clazz any class
|
||||||
|
* @return best constructor
|
||||||
|
*/
|
||||||
|
public static Constructor<?> getBestConstructor(Class<?> clazz) {
|
||||||
|
return getBestConstructor(clazz, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* type equals
|
* type equals
|
||||||
* @param classesLeft class to be compared
|
* @param classesLeft class to be compared
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package com.alibaba.testable.core.compile;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.function.Executable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class InMemoryJavaCompilerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compile_WhenTypical() throws Exception {
|
||||||
|
final StringBuffer sourceCode = new StringBuffer();
|
||||||
|
|
||||||
|
sourceCode.append("package org.mdkt;\n");
|
||||||
|
sourceCode.append("public class HelloClass {\n");
|
||||||
|
sourceCode.append(" public String hello() { return \"hello\"; }");
|
||||||
|
sourceCode.append("}");
|
||||||
|
|
||||||
|
Class<?> helloClass = InMemoryJavaCompiler.newInstance().compile("org.mdkt.HelloClass", sourceCode.toString());
|
||||||
|
assertNotNull(helloClass);
|
||||||
|
assertEquals(1, helloClass.getDeclaredMethods().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compileAll_WhenTypical() throws Exception {
|
||||||
|
String cls1 = "public class A{ public B b() { return new B(); }}";
|
||||||
|
String cls2 = "public class B{ public String toString() { return \"B!\"; }}";
|
||||||
|
|
||||||
|
Map<String, Class<?>> compiled = InMemoryJavaCompiler.newInstance().addSource("A", cls1).addSource("B", cls2).compileAll();
|
||||||
|
|
||||||
|
assertNotNull(compiled.get("A"));
|
||||||
|
assertNotNull(compiled.get("B"));
|
||||||
|
|
||||||
|
Class<?> aClass = compiled.get("A");
|
||||||
|
Object a = aClass.newInstance();
|
||||||
|
assertEquals("B!", aClass.getMethod("b").invoke(a).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compile_WhenSourceContainsInnerClasses() throws Exception {
|
||||||
|
final StringBuffer sourceCode = new StringBuffer();
|
||||||
|
|
||||||
|
sourceCode.append("package org.mdkt;\n");
|
||||||
|
sourceCode.append("public class HelloClass {\n");
|
||||||
|
sourceCode.append(" private static class InnerHelloWorld { int inner; }\n");
|
||||||
|
sourceCode.append(" public String hello() { return \"hello\"; }");
|
||||||
|
sourceCode.append("}");
|
||||||
|
|
||||||
|
Class<?> helloClass = InMemoryJavaCompiler.newInstance().compile("org.mdkt.HelloClass", sourceCode.toString());
|
||||||
|
assertNotNull(helloClass);
|
||||||
|
assertEquals(1, helloClass.getDeclaredMethods().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compile_whenError() throws Exception {
|
||||||
|
final StringBuffer sourceCode = new StringBuffer();
|
||||||
|
|
||||||
|
sourceCode.append("package org.mdkt;\n");
|
||||||
|
sourceCode.append("public classHelloClass {\n");
|
||||||
|
sourceCode.append(" public String hello() { return \"hello\"; }");
|
||||||
|
sourceCode.append("}");
|
||||||
|
assertThrows(CompilationException.class, new Executable() {
|
||||||
|
@Override
|
||||||
|
public void execute() throws Throwable {
|
||||||
|
InMemoryJavaCompiler.newInstance().compile("org.mdkt.HelloClass", sourceCode.toString());
|
||||||
|
}
|
||||||
|
}, "Unable to compile the source");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compile_WhenFailOnWarnings() throws Exception {
|
||||||
|
final StringBuffer sourceCode = new StringBuffer();
|
||||||
|
|
||||||
|
sourceCode.append("package org.mdkt;\n");
|
||||||
|
sourceCode.append("public class HelloClass {\n");
|
||||||
|
sourceCode.append(" public java.util.List<String> hello() { return new java.util.ArrayList(); }");
|
||||||
|
sourceCode.append("}");
|
||||||
|
assertThrows(CompilationException.class, new Executable() {
|
||||||
|
@Override
|
||||||
|
public void execute() throws Throwable {
|
||||||
|
InMemoryJavaCompiler.newInstance().compile("org.mdkt.HelloClass", sourceCode.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compile_WhenIgnoreWarnings() throws Exception {
|
||||||
|
final StringBuffer sourceCode = new StringBuffer();
|
||||||
|
|
||||||
|
sourceCode.append("package org.mdkt;\n");
|
||||||
|
sourceCode.append("public class HelloClass {\n");
|
||||||
|
sourceCode.append(" public java.util.List<String> hello() { return new java.util.ArrayList(); }");
|
||||||
|
sourceCode.append("}");
|
||||||
|
Class<?> helloClass = InMemoryJavaCompiler.newInstance().ignoreWarnings().compile("org.mdkt.HelloClass", sourceCode.toString());
|
||||||
|
List<?> res = (List<?>) helloClass.getMethod("hello").invoke(helloClass.newInstance());
|
||||||
|
assertEquals(0, res.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compile_WhenWarningsAndErrors() throws Exception {
|
||||||
|
final StringBuffer sourceCode = new StringBuffer();
|
||||||
|
|
||||||
|
sourceCode.append("package org.mdkt;\n");
|
||||||
|
sourceCode.append("public class HelloClass extends xxx {\n");
|
||||||
|
sourceCode.append(" public java.util.List<String> hello() { return new java.util.ArrayList(); }");
|
||||||
|
sourceCode.append("}");
|
||||||
|
assertThrows(CompilationException.class, new Executable() {
|
||||||
|
@Override
|
||||||
|
public void execute() throws Throwable {
|
||||||
|
InMemoryJavaCompiler.newInstance().compile("org.mdkt.HelloClass", sourceCode.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.alibaba.testable.core.matcher;
|
||||||
|
|
||||||
|
import com.alibaba.testable.core.tool.OmniConstructor;
|
||||||
|
import com.alibaba.testable.core.tool.PrivateAccessor;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.listOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class InvocationVerifierTest {
|
||||||
|
|
||||||
|
private InvocationVerifier invocationVerifier = OmniConstructor.newInstance(InvocationVerifier.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_matches_object() {
|
||||||
|
assertTrue((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches", "abc", "abc"));
|
||||||
|
assertTrue((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches", 1L, 1L));
|
||||||
|
|
||||||
|
assertFalse((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches", "xyz", "abc"));
|
||||||
|
assertFalse((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches", 1L, "abc"));
|
||||||
|
assertFalse((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches", 1L, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_matches_array() {
|
||||||
|
assertTrue((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches",
|
||||||
|
new String[] {"abc", "xyz"}, new String[] {"abc", "xyz"}));
|
||||||
|
|
||||||
|
assertFalse((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches",
|
||||||
|
new String[] {"abc", "xyz"}, new String[] {"xyz", "abc"}));
|
||||||
|
assertFalse((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches",
|
||||||
|
new String[] {"abc", "xyz"}, new String[] {"xyz"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_matches_matcher() {
|
||||||
|
assertTrue((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches",
|
||||||
|
InvocationMatcher.anyArray(), new String[] {"abc", "xyz"}));
|
||||||
|
assertTrue((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches",
|
||||||
|
InvocationMatcher.anyString(), "abc"));
|
||||||
|
|
||||||
|
assertFalse((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches",
|
||||||
|
InvocationMatcher.anyString(), new String[] {"abc", "xyz"}));
|
||||||
|
assertFalse((Boolean)PrivateAccessor.invoke(invocationVerifier, "matches",
|
||||||
|
InvocationMatcher.anyArray(), "abc"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package com.alibaba.testable.core.tool;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CollectionToolTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_slice_array() {
|
||||||
|
String[] fullWords = arrayOf("this", "is", "a", "simple", "example");
|
||||||
|
String[] partOfWords = slice(fullWords, 1, 3);
|
||||||
|
assertEquals(3, partOfWords.length);
|
||||||
|
assertEquals("is", partOfWords[0]);
|
||||||
|
assertEquals("a", partOfWords[1]);
|
||||||
|
assertEquals("simple", partOfWords[2]);
|
||||||
|
|
||||||
|
Integer[] fibonacci = arrayOf(1, 1, 2, 3, 5, 8, 13, 21);
|
||||||
|
Integer[] partOfFibonacci = slice(fibonacci, 4);
|
||||||
|
assertEquals(4, partOfFibonacci.length);
|
||||||
|
assertEquals(5, partOfFibonacci[0]);
|
||||||
|
assertEquals(8, partOfFibonacci[1]);
|
||||||
|
assertEquals(13, partOfFibonacci[2]);
|
||||||
|
assertEquals(21, partOfFibonacci[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_create_array() {
|
||||||
|
String[] manyWords = arrayOf("this", "is", "an", "example");
|
||||||
|
assertEquals(4, manyWords.length);
|
||||||
|
assertEquals("this", manyWords[0]);
|
||||||
|
assertEquals("is", manyWords[1]);
|
||||||
|
assertEquals("an", manyWords[2]);
|
||||||
|
assertEquals("example", manyWords[3]);
|
||||||
|
|
||||||
|
Double[] manyValues = arrayOf(1.0D, 2.0D, 3.0D);
|
||||||
|
assertEquals(3, manyValues.length);
|
||||||
|
assertEquals(1.0D, manyValues[0]);
|
||||||
|
assertEquals(2.0D, manyValues[1]);
|
||||||
|
assertEquals(3.0D, manyValues[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_create_list() {
|
||||||
|
List<String> manyWords = listOf("this", "is", "an", "example");
|
||||||
|
assertEquals(4, manyWords.size());
|
||||||
|
assertEquals("this", manyWords.get(0));
|
||||||
|
assertEquals("is", manyWords.get(1));
|
||||||
|
assertEquals("an", manyWords.get(2));
|
||||||
|
assertEquals("example", manyWords.get(3));
|
||||||
|
|
||||||
|
List<Double> manyValues = listOf(1.0D, 2.0D, 3.0D);
|
||||||
|
assertEquals(3, manyValues.size());
|
||||||
|
assertEquals(1.0D, manyValues.get(0));
|
||||||
|
assertEquals(2.0D, manyValues.get(1));
|
||||||
|
assertEquals(3.0D, manyValues.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_create_set() {
|
||||||
|
Set<String> manyWords = setOf("this", "is", "an", "example");
|
||||||
|
assertEquals(4, manyWords.size());
|
||||||
|
assertTrue(manyWords.contains("this"));
|
||||||
|
assertTrue(manyWords.contains("is"));
|
||||||
|
assertTrue(manyWords.contains("an"));
|
||||||
|
assertTrue(manyWords.contains("example"));
|
||||||
|
|
||||||
|
Set<Double> manyValues = setOf(1.0D, 2.0D, 3.0D);
|
||||||
|
assertEquals(3, manyValues.size());
|
||||||
|
assertTrue(manyValues.contains(1.0D));
|
||||||
|
assertTrue(manyValues.contains(2.0D));
|
||||||
|
assertTrue(manyValues.contains(3.0D));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_create_map() {
|
||||||
|
Map<String, String> manyWords = mapOf(
|
||||||
|
entryOf("language", "java"),
|
||||||
|
entryOf("type", "unittest"),
|
||||||
|
entryOf("library", "testable")
|
||||||
|
);
|
||||||
|
assertEquals(3, manyWords.size());
|
||||||
|
assertEquals("java", manyWords.get("language"));
|
||||||
|
assertEquals("unittest", manyWords.get("type"));
|
||||||
|
assertEquals("testable", manyWords.get("library"));
|
||||||
|
|
||||||
|
Map<Integer, Double> manyValues = mapOf(
|
||||||
|
entryOf(1, 10.12D),
|
||||||
|
entryOf(2, 20.24D),
|
||||||
|
entryOf(3, 30.36D)
|
||||||
|
);
|
||||||
|
assertEquals(3, manyValues.size());
|
||||||
|
assertEquals(10.12D, manyValues.get(1));
|
||||||
|
assertEquals(20.24D, manyValues.get(2));
|
||||||
|
assertEquals(30.36D, manyValues.get(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_create_ordered_map() {
|
||||||
|
Map<String, String> manyWords = orderedMapOf(
|
||||||
|
entryOf("language", "java"),
|
||||||
|
entryOf("type", "unittest"),
|
||||||
|
entryOf("library", "testable")
|
||||||
|
);
|
||||||
|
assertEquals(3, manyWords.size());
|
||||||
|
String[] wordKeys = manyWords.keySet().toArray(new String[0]);
|
||||||
|
assertEquals("language", wordKeys[0]);
|
||||||
|
assertEquals("type", wordKeys[1]);
|
||||||
|
assertEquals("library", wordKeys[2]);
|
||||||
|
|
||||||
|
Map<Integer, Double> manyValues = orderedMapOf(
|
||||||
|
entryOf(1, 10.12D),
|
||||||
|
entryOf(2, 20.24D),
|
||||||
|
entryOf(3, 30.36D)
|
||||||
|
);
|
||||||
|
assertEquals(3, manyValues.size());
|
||||||
|
Integer[] valueKeys = manyValues.keySet().toArray(new Integer[0]);
|
||||||
|
assertEquals(1, valueKeys[0]);
|
||||||
|
assertEquals(2, valueKeys[1]);
|
||||||
|
assertEquals(3, valueKeys[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,17 +2,19 @@ package com.alibaba.testable.core.util;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static com.alibaba.testable.core.tool.PrivateAccessor.invokeStatic;
|
import static com.alibaba.testable.core.util.CollectionUtil.fastListOf;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
class CollectionUtilTest {
|
class CollectionUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void should_slice_array() {
|
void should_check_collection_contains_any_element() {
|
||||||
Object[] args = new Object[]{"1", "2", "3"};
|
assertTrue(CollectionUtil.containsAny(
|
||||||
Object[] slicedArgs = invokeStatic(CollectionUtil.class, "slice", args, 1);
|
fastListOf("a", "b"), fastListOf("b", "c")
|
||||||
assertEquals(2, slicedArgs.length);
|
));
|
||||||
assertEquals("2", slicedArgs[0]);
|
assertFalse(CollectionUtil.containsAny(
|
||||||
assertEquals("3", slicedArgs[1]);
|
fastListOf("a", "b"), fastListOf("c", "d")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package com.alibaba.testable.core.util;
|
||||||
|
|
||||||
|
import com.alibaba.testable.core.model.ConstructionOption;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sun.reflect.generics.factory.CoreReflectionFactory;
|
||||||
|
import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl;
|
||||||
|
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
|
||||||
|
import sun.reflect.generics.reflectiveObjects.TypeVariableImpl;
|
||||||
|
import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl;
|
||||||
|
import sun.reflect.generics.scope.MethodScope;
|
||||||
|
import sun.reflect.generics.tree.SimpleClassTypeSignature;
|
||||||
|
import sun.reflect.generics.tree.TypeArgument;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.alibaba.testable.core.tool.PrivateAccessor.construct;
|
||||||
|
import static com.alibaba.testable.core.tool.PrivateAccessor.invokeStatic;
|
||||||
|
import static com.alibaba.testable.core.tool.CollectionTool.arrayOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
class ConstructionUtilTest {
|
||||||
|
|
||||||
|
public interface EmptyInterface {}
|
||||||
|
|
||||||
|
public interface RealInterface {
|
||||||
|
void noParameterMethod();
|
||||||
|
int primaryTypeParameterMethod(double d, boolean b);
|
||||||
|
String clazzAndArrayParameterMethod(String s, byte[] b);
|
||||||
|
EmptyInterface interfaceAndSelfReferenceParameterMethod(RealInterface i, EmptyInterface[] e);
|
||||||
|
<T> T templatedParameterMethod(T i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class AbstractClazz implements RealInterface {
|
||||||
|
@Override
|
||||||
|
public void noParameterMethod() {}
|
||||||
|
public abstract <T extends String> T getByName(T name);
|
||||||
|
public abstract String getByTags(List<? extends String> tags);
|
||||||
|
public static <T> T useless() { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class ParameterizedClazz<S extends String, P> extends AbstractClazz implements RealInterface, EmptyInterface {
|
||||||
|
public abstract S getById(S id);
|
||||||
|
public abstract S getByIds(List<S> ids);
|
||||||
|
public abstract P getByMap(Map<String, P> m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface StringMap extends Map<String, Object> {
|
||||||
|
<K> K nextKey(K k);
|
||||||
|
<V> V nextValue(V v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Inner$Interface {}
|
||||||
|
|
||||||
|
private final ConstructionOption[] options = arrayOf();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_generate_empty_interface() throws Exception {
|
||||||
|
EmptyInterface ins = ConstructionUtil.generateSubClassOf(EmptyInterface.class, options);
|
||||||
|
assertNotNull(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_generate_real_interface() throws Exception {
|
||||||
|
RealInterface ins = ConstructionUtil.generateSubClassOf(RealInterface.class, options);
|
||||||
|
assertNotNull(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_generate_abstract_class() throws Exception {
|
||||||
|
RealInterface ins = ConstructionUtil.generateSubClassOf(AbstractClazz.class, options);
|
||||||
|
assertNotNull(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_generate_parameterized_class() throws Exception {
|
||||||
|
RealInterface ins = ConstructionUtil.generateSubClassOf(ParameterizedClazz.class, options);
|
||||||
|
assertNotNull(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_generate_implicit_generic_interface() throws Exception {
|
||||||
|
StringMap ins = ConstructionUtil.generateSubClassOf(StringMap.class, options);
|
||||||
|
assertNotNull(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_generate_name_with_dollar() throws Exception {
|
||||||
|
Inner$Interface ins = ConstructionUtil.generateSubClassOf(Inner$Interface.class, options);
|
||||||
|
assertNotNull(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_get_class_name() throws Exception {
|
||||||
|
// common class, e.g. String
|
||||||
|
assertEquals("java.lang.String",
|
||||||
|
invokeStatic(ConstructionUtil.class, "getClassName", String.class));
|
||||||
|
// array class, e.g. String[]
|
||||||
|
assertEquals("java.lang.String[]",
|
||||||
|
invokeStatic(ConstructionUtil.class, "getClassName", GenericArrayTypeImpl.make(String.class)));
|
||||||
|
// typed class, e.g. T
|
||||||
|
Method fakeMethod = construct(Method.class, String.class, "fake", new Class[0], String.class, new Class[0], Modifier.PUBLIC, 0, "", null, null, null);
|
||||||
|
assertEquals("T", invokeStatic(ConstructionUtil.class, "getClassName", TypeVariableImpl.make(
|
||||||
|
fakeMethod, "T", arrayOf(SimpleClassTypeSignature.make("java.lang.String", false, new TypeArgument[0])),
|
||||||
|
CoreReflectionFactory.make(fakeMethod, MethodScope.make(fakeMethod)))));
|
||||||
|
// wildcard class, e.g. ? extends String
|
||||||
|
assertEquals("? extends java.lang.String", invokeStatic(ConstructionUtil.class, "getClassName", WildcardTypeImpl.make(
|
||||||
|
arrayOf(SimpleClassTypeSignature.make("java.lang.String", false, new TypeArgument[0])),
|
||||||
|
new SimpleClassTypeSignature[0],
|
||||||
|
CoreReflectionFactory.make(String.class, MethodScope.make(fakeMethod)))));
|
||||||
|
// parameterized class, e.g. Map<String, Object>
|
||||||
|
assertEquals("java.util.Map<java.lang.String, java.lang.Object>",
|
||||||
|
invokeStatic(ConstructionUtil.class, "getClassName", ParameterizedTypeImpl.make(Map.class,
|
||||||
|
arrayOf(String.class, Object.class), null)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.alibaba.testable.agent.util;
|
package com.alibaba.testable.core.util;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -13,4 +13,11 @@ class StringUtilTest {
|
|||||||
assertEquals("abcabcabc", StringUtil.repeat("abc", 3));
|
assertEquals("abcabcabc", StringUtil.repeat("abc", 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_join_text() {
|
||||||
|
assertEquals("abc", StringUtil.join("", "a", "b", "c"));
|
||||||
|
assertEquals("a", StringUtil.join(",", "a"));
|
||||||
|
assertEquals("ab,cd,ef", StringUtil.join(",", new String[]{"ab", "cd", "ef"}));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.testable</groupId>
|
<groupId>com.alibaba.testable</groupId>
|
||||||
<artifactId>testable-parent</artifactId>
|
<artifactId>testable-parent</artifactId>
|
||||||
<version>0.7.6</version>
|
<version>0.7.9</version>
|
||||||
<relativePath>../testable-parent</relativePath>
|
<relativePath>../testable-parent</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>testable-maven-plugin</artifactId>
|
<artifactId>testable-maven-plugin</artifactId>
|
||||||
@@ -85,6 +85,9 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>jar</goal>
|
<goal>jar</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -99,6 +102,13 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>sign</goal>
|
<goal>sign</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<!-- Prevent `gpg` from using pinentry programs -->
|
||||||
|
<gpgArguments>
|
||||||
|
<arg>--pinentry-mode</arg>
|
||||||
|
<arg>loopback</arg>
|
||||||
|
</gpgArguments>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.alibaba.testable;
|
|||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.apache.maven.artifact.Artifact;
|
import org.apache.maven.artifact.Artifact;
|
||||||
|
import org.apache.maven.artifact.repository.ArtifactRepository;
|
||||||
import org.apache.maven.plugin.AbstractMojo;
|
import org.apache.maven.plugin.AbstractMojo;
|
||||||
import org.apache.maven.plugin.MojoExecutionException;
|
import org.apache.maven.plugin.MojoExecutionException;
|
||||||
import org.apache.maven.plugins.annotations.LifecyclePhase;
|
import org.apache.maven.plugins.annotations.LifecyclePhase;
|
||||||
@@ -34,6 +35,12 @@ public class TestableMojo extends AbstractMojo
|
|||||||
@Parameter(property = "plugin.artifactMap", required = true, readonly = true)
|
@Parameter(property = "plugin.artifactMap", required = true, readonly = true)
|
||||||
private Map<String, Artifact> pluginArtifactMap;
|
private Map<String, Artifact> pluginArtifactMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Location of local maven repository
|
||||||
|
*/
|
||||||
|
@Parameter(defaultValue = "${localRepository}", readonly = true, required = true)
|
||||||
|
private ArtifactRepository localRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JavaAgent log level (mute/debug/verbose)
|
* JavaAgent log level (mute/debug/verbose)
|
||||||
*/
|
*/
|
||||||
@@ -56,6 +63,11 @@ public class TestableMojo extends AbstractMojo
|
|||||||
* Name of the Testable Agent artifact.
|
* Name of the Testable Agent artifact.
|
||||||
*/
|
*/
|
||||||
private static final String AGENT_ARTIFACT_NAME = "com.alibaba.testable:testable-agent";
|
private static final String AGENT_ARTIFACT_NAME = "com.alibaba.testable:testable-agent";
|
||||||
|
/**
|
||||||
|
* Testable agent jar path in local repository
|
||||||
|
*/
|
||||||
|
private static final String AGENT_LOCAL_REPOSITORY_PATH =
|
||||||
|
"/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar";
|
||||||
/**
|
/**
|
||||||
* Name of the property used in maven-osgi-test-plugin.
|
* Name of the property used in maven-osgi-test-plugin.
|
||||||
*/
|
*/
|
||||||
@@ -104,7 +116,12 @@ public class TestableMojo extends AbstractMojo
|
|||||||
getLog().error("failed to find testable agent jar");
|
getLog().error("failed to find testable agent jar");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return " -javaagent:" + testableAgentArtifact.getFile().getAbsolutePath();
|
String agentJarPath = testableAgentArtifact.getFile().getAbsolutePath();
|
||||||
|
if (agentJarPath.endsWith("target/classes")) {
|
||||||
|
// Just for testcases within testable project itself
|
||||||
|
agentJarPath = localRepository.getBasedir() + AGENT_LOCAL_REPOSITORY_PATH;
|
||||||
|
}
|
||||||
|
return " -javaagent:" + agentJarPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getEffectivePropertyKey() {
|
private String getEffectivePropertyKey() {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.alibaba.testable</groupId>
|
<groupId>com.alibaba.testable</groupId>
|
||||||
<artifactId>testable-parent</artifactId>
|
<artifactId>testable-parent</artifactId>
|
||||||
<version>0.7.6</version>
|
<version>0.7.9</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>testable-parent</name>
|
<name>testable-parent</name>
|
||||||
<description>Unit test enhancement toolkit</description>
|
<description>Unit test enhancement toolkit</description>
|
||||||
@@ -33,16 +33,16 @@
|
|||||||
<java.version>1.6</java.version>
|
<java.version>1.6</java.version>
|
||||||
<project.compiler.level>1.6</project.compiler.level>
|
<project.compiler.level>1.6</project.compiler.level>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<plugin.compiler.version>3.8.1</plugin.compiler.version>
|
<plugin.compiler.version>3.10.1</plugin.compiler.version>
|
||||||
<plugin.surefire.version>3.0.0-M5</plugin.surefire.version>
|
<plugin.surefire.version>3.0.0-M7</plugin.surefire.version>
|
||||||
<plugin.jar.version>3.2.0</plugin.jar.version>
|
<plugin.jar.version>3.3.0</plugin.jar.version>
|
||||||
<plugin.shade.version>3.2.4</plugin.shade.version>
|
<plugin.shade.version>3.4.1</plugin.shade.version>
|
||||||
<plugin.source.version>3.2.0</plugin.source.version>
|
<plugin.source.version>3.2.1</plugin.source.version>
|
||||||
<plugin.javadoc.version>3.2.0</plugin.javadoc.version>
|
<plugin.javadoc.version>3.4.1</plugin.javadoc.version>
|
||||||
<plugin.gpg.version>1.6</plugin.gpg.version>
|
<plugin.gpg.version>3.0.1</plugin.gpg.version>
|
||||||
<plugin.staging.version>1.6.8</plugin.staging.version>
|
<plugin.staging.version>1.6.13</plugin.staging.version>
|
||||||
<plugin.maven.version>3.6.0</plugin.maven.version>
|
<plugin.maven.version>3.7.0</plugin.maven.version>
|
||||||
<testable.version>0.7.6</testable.version>
|
<testable.version>0.7.9</testable.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
@@ -73,6 +73,9 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>jar</goal>
|
<goal>jar</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -87,6 +90,13 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>sign</goal>
|
<goal>sign</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<!-- Prevent `gpg` from using pinentry programs -->
|
||||||
|
<gpgArguments>
|
||||||
|
<arg>--pinentry-mode</arg>
|
||||||
|
<arg>loopback</arg>
|
||||||
|
</gpgArguments>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.alibaba.testable</groupId>
|
<groupId>com.alibaba.testable</groupId>
|
||||||
<artifactId>testable-parent</artifactId>
|
<artifactId>testable-parent</artifactId>
|
||||||
<version>0.7.6</version>
|
<version>0.7.9</version>
|
||||||
<relativePath>../testable-parent</relativePath>
|
<relativePath>../testable-parent</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>testable-processor</artifactId>
|
<artifactId>testable-processor</artifactId>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<name>testable-processor</name>
|
<name>testable-processor</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<junit.version>5.6.2</junit.version>
|
<junit.version>5.9.1</junit.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -92,6 +92,9 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>jar</goal>
|
<goal>jar</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -106,6 +109,13 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>sign</goal>
|
<goal>sign</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<!-- Prevent `gpg` from using pinentry programs -->
|
||||||
|
<gpgArguments>
|
||||||
|
<arg>--pinentry-mode</arg>
|
||||||
|
<arg>loopback</arg>
|
||||||
|
</gpgArguments>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
|
|||||||
(JCAssign)jcExpressionStatement.expr);
|
(JCAssign)jcExpressionStatement.expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// visitExec could be an invoke
|
// visitExec could be an invocation
|
||||||
jcExpressionStatement.expr = checkAndExchange(jcExpressionStatement.expr);
|
jcExpressionStatement.expr = checkAndExchange(jcExpressionStatement.expr);
|
||||||
super.visitExec(jcExpressionStatement);
|
super.visitExec(jcExpressionStatement);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ for md in docs/zh-cn/doc/setup.md docs/en-us/doc/setup.md; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
echo "Done. Please setup maven configuration for sonatype, and manually run \"mvn clean deploy -Prelease\""
|
echo "Done. Please setup maven configuration for sonatype, and manually run \"mvn clean deploy -Prelease\""
|
||||||
echo "Then commit the new version with \"git add . && git commit -m 'release v${NEXT}' && git tag v${NEXT} && git push --tag\""
|
echo "Then commit the new version with \"git add . && git commit -m 'release v${NEXT}' && git push && git tag v${NEXT} && git push --tag\""
|
||||||
|
|||||||
Reference in New Issue
Block a user