mirror of
https://github.com/alibaba/testable-mock.git
synced 2026-08-19 17:53:29 +08:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39b742b290 | ||
|
|
0a5666c0be | ||
|
|
006e742c28 | ||
|
|
237b9ebb15 | ||
|
|
7aedcf63b5 | ||
|
|
0f64d52718 | ||
|
|
4ed1323a62 | ||
|
|
ae1ba8b4ff | ||
|
|
00f25d3622 | ||
|
|
2e58ae8360 | ||
|
|
ff54c57608 | ||
|
|
351b3399e6 | ||
|
|
df9e2395cc | ||
|
|
b9064d3d47 | ||
|
|
c967456569 | ||
|
|
692b4435aa | ||
|
|
6379f16266 | ||
|
|
63a6a9fdca | ||
|
|
7e24503611 | ||
|
|
3120eba3af | ||
|
|
02f15177fc | ||
|
|
a25f14c0ec | ||
|
|
635cbf62fb | ||
|
|
17b31b39dd | ||
|
|
0d43d93a29 | ||
|
|
66db3cff7d | ||
|
|
3343a26de8 | ||
|
|
de2e520d95 | ||
|
|
59bcbfdf37 | ||
|
|
cdad0d2446 | ||
|
|
ba5102ffd9 | ||
|
|
db4e02a201 | ||
|
|
fda7ee23c7 | ||
|
|
140e54983b | ||
|
|
548d7ba21f |
@@ -9,7 +9,7 @@
|
||||
|
||||
阅读[这里](https://mp.weixin.qq.com/s/KyU6Eu7mDkZU8FspfSqfMw)了解更多故事。
|
||||
|
||||
<font size="5">**0.6版本已发布**</font>,从`0.5.x`升级到`0.6.x`版本请参考[版本升级指南](https://alibaba.github.io/testable-mock/#/zh-cn/doc/upgrade-guide)
|
||||
<font size="5">**0.7版本已发布**</font>,从`0.6.x`升级到`0.7.x`版本请参考[版本升级指南](https://alibaba.github.io/testable-mock/#/zh-cn/doc/upgrade-guide)
|
||||
|
||||
-----
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
|-- tool ➜ 项目开发过程中的工具脚本
|
||||
|-- demo
|
||||
| |-- java-demo ➜ Java语言的示例代码
|
||||
| `-- kotlin-demo ➜ Kotlin语言的示例代码
|
||||
| |-- kotlin-demo ➜ Kotlin语言的示例代码
|
||||
| |-- android-demo ➜ Android项目的示例代码
|
||||
| `-- spock-demo ➜ Spock测试框架的示例代码
|
||||
`-- docs ➜ 项目使用文档
|
||||
```
|
||||
|
||||
|
||||
10
README_EN.md
10
README_EN.md
@@ -9,11 +9,7 @@ Usage Document: https://alibaba.github.io/testable-mock/#/en-us/
|
||||
|
||||
## Roadmap
|
||||
|
||||
`TestableMock` is still under heavy development, the following version plans may be adjusted during the iteration
|
||||
|
||||
- `v0.6` it's the current version, refer to the [issue](https://github.com/alibaba/testable-mock/issues) list for the work in progress
|
||||
- `v0.7` better anti-refactoring support of mocking, allow quick mock all method in specified class
|
||||
- `v1.0` all functions are stable, a brand-new start
|
||||
`TestableMock` is still under heavy development, if you get sucked somehow, just go ahead raise an [issue](https://github.com/alibaba/testable-mock/issues) for it.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
@@ -27,7 +23,9 @@ Usage Document: https://alibaba.github.io/testable-mock/#/en-us/
|
||||
|-- tool ➜ Scripts for project maintain
|
||||
|-- demo
|
||||
| |-- java-demo ➜ Java code example
|
||||
| `-- kotlin-demo ➜ Kotlin code example
|
||||
| |-- kotlin-demo ➜ Kotlin code example
|
||||
| |-- android-demo ➜ Android app code example
|
||||
| `-- spock-demo ➜ Spock framwork code example
|
||||
`-- docs ➜ Source code of usage document
|
||||
```
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ dependencies {
|
||||
testImplementation 'androidx.test:runner:1.4.0-alpha05'
|
||||
testImplementation 'junit:junit:4.+'
|
||||
testImplementation 'org.robolectric:robolectric:4.5.1'
|
||||
testImplementation 'com.alibaba.testable:testable-all:0.6.10'
|
||||
testImplementation 'com.alibaba.testable:testable-all:0.7.7'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
}
|
||||
@@ -13,8 +13,8 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.6.10')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.10')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.7.7')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.7')
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<junit.version>5.6.2</junit.version>
|
||||
<testable.version>0.6.10</testable.version>
|
||||
<testable.version>0.7.7</testable.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -35,7 +35,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<version>3.0.0-M6</version>
|
||||
<configuration>
|
||||
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
|
||||
</configuration>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.alibaba.demo.lambda;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author jim
|
||||
*/
|
||||
public interface BaseDemo {
|
||||
default void blackHole(Object... ignore) {}
|
||||
|
||||
default void consumesRun(Runnable r) {
|
||||
r.run();
|
||||
}
|
||||
|
||||
default void consumesFunction0(Runnable f) {
|
||||
f.run();
|
||||
}
|
||||
|
||||
default <T> void consumesFunction1(Consumer<T> f) {
|
||||
f.accept(null);
|
||||
}
|
||||
|
||||
default <T, R> void consumesFunction2(Function<T, R> f) {
|
||||
f.apply(null);
|
||||
}
|
||||
|
||||
default <T1, T2, R> void consumesFunction3(BiFunction<T1, T2, R> f) {
|
||||
f.apply(null, null);
|
||||
}
|
||||
|
||||
default <T> void consumesSupplier(Supplier<T> supplier) {
|
||||
supplier.get();
|
||||
}
|
||||
|
||||
default <T, R> void consumesTwoFunction2(Function<T, R> f1, Function<T, R> f2) {
|
||||
f1.apply(null);
|
||||
f2.apply(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.alibaba.demo.lambda;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author jim
|
||||
*/
|
||||
@SuppressWarnings("SimplifyStreamApiCallChains")
|
||||
public class CollectionListCodeDemo implements BaseDemo {
|
||||
public void list() {
|
||||
List<User> userList = getUsers();
|
||||
double d = userList.stream()
|
||||
.map(v -> v)
|
||||
.map(v -> Function.<User>identity().apply(v))
|
||||
.map(User::getId)
|
||||
.mapToDouble(Double::valueOf)
|
||||
.reduce(0, Double::sum);
|
||||
//.reduce(0, (v1, v2) -> v1 + v2);
|
||||
blackHole(d);
|
||||
|
||||
blackHole(userList.stream()
|
||||
.map(User::getId)
|
||||
.mapToLong(Long::valueOf)
|
||||
.reduce(0, Long::sum));
|
||||
|
||||
blackHole(userList.stream()
|
||||
.map(User::getId)
|
||||
.mapToInt(Long::intValue)
|
||||
.reduce(0, Integer::sum));
|
||||
|
||||
Map<Long, User> map = userList.stream()
|
||||
.filter(User::isActive)
|
||||
.collect(Collectors.toMap(User::getId, Function.identity(), (v1, v2) -> v1));
|
||||
blackHole(map);
|
||||
|
||||
List<User> l1 = userList.stream()
|
||||
.collect(Collectors.toList())
|
||||
.stream()
|
||||
.collect(Collectors.toSet())
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(User::getName))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(v -> v.getValue().stream().anyMatch(User::isActive))
|
||||
.map(Map.Entry::getValue)
|
||||
.flatMap(Collection::parallelStream)
|
||||
.collect(Collectors.groupingBy(User::getId))
|
||||
.values()
|
||||
.stream()
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
blackHole(l1);
|
||||
|
||||
userList.stream()
|
||||
.map(User::isActive)
|
||||
.reduce(Boolean::logicalAnd)
|
||||
.ifPresent(this::blackHole);
|
||||
}
|
||||
|
||||
private List<User> getUsers() {
|
||||
List<User> userList = new ArrayList<>();
|
||||
for (int i=0; i<10; i++) {
|
||||
userList.add(getUser(i));
|
||||
}
|
||||
return userList;
|
||||
}
|
||||
|
||||
private User getUser(int index) {
|
||||
User u1 = new User();
|
||||
u1.id = (long)index;
|
||||
u1.name = "u" + index;
|
||||
u1.age = index;
|
||||
u1.active = true;
|
||||
return u1;
|
||||
}
|
||||
|
||||
private static class User {
|
||||
private Long id;
|
||||
private String name;
|
||||
private int age;
|
||||
private boolean active;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.alibaba.demo.lambda;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author jim
|
||||
*/
|
||||
public class InvokeInterfaceDemo implements BaseDemo {
|
||||
|
||||
public void collectionInterfaceDefaultOrStatic() {
|
||||
blackHole(collectionStreamTest());
|
||||
blackHole(arraysStreamTest());
|
||||
}
|
||||
|
||||
public void interfaceDefault() {
|
||||
ILambda l = new LambdaFoo();
|
||||
consumesRun(l::run);
|
||||
consumesFunction1(l::function1);
|
||||
}
|
||||
|
||||
public void interfaceStatic() {
|
||||
consumesRun(ILambda::staticRun);
|
||||
consumesFunction1(ILambda::staticFunction1);
|
||||
}
|
||||
|
||||
public Object collectionStreamTest() {
|
||||
List<List<String>> testList = new ArrayList<>();
|
||||
List<String> fooList = new ArrayList<>();
|
||||
fooList.add("123");
|
||||
fooList.add("456");
|
||||
testList.add(fooList);
|
||||
return testList.stream()
|
||||
//.flatMap(v -> v.stream())
|
||||
.flatMap(Collection::stream)
|
||||
.map(Double::valueOf)
|
||||
.map(BigDecimal::new)
|
||||
.reduce(BigDecimal::add);
|
||||
}
|
||||
|
||||
public Object arraysStreamTest() {
|
||||
List<String[]> zz = new ArrayList<>();
|
||||
zz.add(new String[]{"1", "2", "3"});
|
||||
return zz.stream()
|
||||
.flatMap(Arrays::stream)
|
||||
.map(Double::valueOf)
|
||||
.map(BigDecimal::new)
|
||||
.reduce(BigDecimal::add);
|
||||
}
|
||||
|
||||
public void objectStaticMethodReference() {
|
||||
List<Boolean> f = new ArrayList<>();
|
||||
f.add(false);
|
||||
f.add(false);
|
||||
f.add(false);
|
||||
blackHole(f.stream()
|
||||
.reduce(Boolean::logicalAnd)
|
||||
.get()
|
||||
);
|
||||
}
|
||||
|
||||
public interface ILambda {
|
||||
default void run() {
|
||||
|
||||
}
|
||||
|
||||
default void function1(String s) {
|
||||
|
||||
}
|
||||
|
||||
static void staticRun() {
|
||||
|
||||
}
|
||||
|
||||
static void staticFunction1(String s) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static class LambdaFoo implements ILambda {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import java.util.function.Supplier;
|
||||
* @author jimca
|
||||
*/
|
||||
@SuppressWarnings({"WrapperTypeMayBePrimitive", "ResultOfMethodCallIgnored", "MismatchedReadAndWriteOfArray", "unused"})
|
||||
public class ExternalLambdaDemo {
|
||||
public class InvokeVirtualDemo {
|
||||
|
||||
public void string1() {
|
||||
String s = "";
|
||||
@@ -197,8 +197,8 @@ public class ExternalLambdaDemo {
|
||||
}
|
||||
|
||||
public void function3() {
|
||||
ExternalLambdaDemo externalLambdaDemo = new ExternalLambdaDemo();
|
||||
consumesFunction3(externalLambdaDemo::f3);
|
||||
InvokeVirtualDemo invokeVirtualDemo = new InvokeVirtualDemo();
|
||||
consumesFunction3(invokeVirtualDemo::f3);
|
||||
}
|
||||
|
||||
public Boolean f3(String s1, Long l) {
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.alibaba.demo.lambda;
|
||||
|
||||
/**
|
||||
* @author jim
|
||||
*/
|
||||
public class StaticInstanceReference implements BaseDemo {
|
||||
|
||||
private static final StaticClassA STATIC_CLASS_A = new StaticClassA();
|
||||
|
||||
public void staticMethodReference() {
|
||||
consumesRun(STATIC_CLASS_A::doIt);
|
||||
consumesFunction1(STATIC_CLASS_A::function1);
|
||||
consumesFunction2(STATIC_CLASS_A::function2);
|
||||
consumesFunction3(STATIC_CLASS_A::function3);
|
||||
}
|
||||
|
||||
public static class StaticClassA {
|
||||
public void doIt() {
|
||||
}
|
||||
|
||||
public void function1(String s) {
|
||||
|
||||
}
|
||||
|
||||
public Integer function2(String s) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public Integer function3(String s, Double d) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.alibaba.demo.lambda;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockInvoke;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
/**
|
||||
* @author jim
|
||||
*/
|
||||
public class CollectionListCodeDemoTest {
|
||||
|
||||
private final CollectionListCodeDemo instance = new CollectionListCodeDemo();
|
||||
|
||||
public static class Mock {
|
||||
@MockInvoke(targetClass = String.class, targetMethod = "contains")
|
||||
public boolean mockContains(CharSequence s) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listTest() {
|
||||
instance.list();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.alibaba.demo.lambda;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockInvoke;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
|
||||
|
||||
|
||||
/**
|
||||
* @author jim
|
||||
*/
|
||||
public class InvokeInterfaceDemoTest {
|
||||
|
||||
private final InvokeInterfaceDemo instance = new InvokeInterfaceDemo();
|
||||
|
||||
public static class Mock {
|
||||
|
||||
@MockInvoke(targetClass = InvokeInterfaceDemo.ILambda.class, targetMethod = "run")
|
||||
private void mockILambdaRun() {
|
||||
}
|
||||
|
||||
@MockInvoke(targetClass = InvokeInterfaceDemo.ILambda.class, targetMethod = "function1")
|
||||
private void mockILambdaFunction1(String s) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@MockInvoke(targetClass = Collection.class, targetMethod = "stream")
|
||||
<E> Stream<E> mockStream() {
|
||||
List<E> fooList = new ArrayList<>();
|
||||
fooList.add((E) "123");
|
||||
fooList.add((E) "456");
|
||||
return fooList.stream();
|
||||
}
|
||||
|
||||
@MockInvoke(targetClass = Boolean.class, targetMethod = "logicalAnd")
|
||||
public static boolean mockLogicalAnd(boolean a, boolean b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMockCollectionStream() {
|
||||
instance.collectionInterfaceDefaultOrStatic();
|
||||
verifyInvoked("mockStream").withTimes(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMockInterfaceDefault() {
|
||||
instance.interfaceDefault();
|
||||
verifyInvoked("mockILambdaRun").withTimes(1);
|
||||
verifyInvoked("mockILambdaFunction1").withTimes(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMockObjectStaticMethodReference() {
|
||||
instance.objectStaticMethodReference();
|
||||
verifyInvoked("mockLogicalAnd").withTimes(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMockInterfaceStatic() {
|
||||
instance.interfaceStatic();
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked
|
||||
/**
|
||||
* @author zcbbpo
|
||||
*/
|
||||
public class ExternalLambdaDemoTest {
|
||||
private final ExternalLambdaDemo lambdaDemo = new ExternalLambdaDemo();
|
||||
public class InvokeVirtualDemoTest {
|
||||
private final InvokeVirtualDemo lambdaDemo = new InvokeVirtualDemo();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class Mock {
|
||||
@@ -34,7 +34,7 @@ public class ExternalLambdaDemoTest {
|
||||
return "";
|
||||
}
|
||||
|
||||
@MockInvoke(targetClass = ExternalLambdaDemo.class, targetMethod = "f3")
|
||||
@MockInvoke(targetClass = InvokeVirtualDemo.class, targetMethod = "f3")
|
||||
public Boolean mockF3(String s1, Long l) {
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.alibaba.demo.lambda;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockInvoke;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
|
||||
|
||||
|
||||
/**
|
||||
* @author jim
|
||||
*/
|
||||
public class StaticInstanceReferenceTest {
|
||||
|
||||
private final StaticInstanceReference instance = new StaticInstanceReference();
|
||||
|
||||
public static class Mock {
|
||||
@MockInvoke(targetClass = StaticInstanceReference.StaticClassA.class, targetMethod = "doIt")
|
||||
private void mockDoIt() {
|
||||
}
|
||||
|
||||
@MockInvoke(targetClass = StaticInstanceReference.StaticClassA.class)
|
||||
private Integer function2(String s) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMockDoIt() {
|
||||
instance.staticMethodReference();
|
||||
verifyInvoked("mockDoIt").withTimes(1);
|
||||
verifyInvoked("function2").withTimes(1);
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
|
||||
testImplementation("com.alibaba.testable:testable-all:0.6.10")
|
||||
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.6.10")
|
||||
testImplementation("com.alibaba.testable:testable-all:0.7.7")
|
||||
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.7.7")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<junit.version>5.6.2</junit.version>
|
||||
<testable.version>0.6.10</testable.version>
|
||||
<testable.version>0.7.7</testable.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -14,8 +14,8 @@ repositories {
|
||||
dependencies {
|
||||
testImplementation 'org.codehaus.groovy:groovy-all:3.0.7'
|
||||
testImplementation 'org.spockframework:spock-core:2.0-M5-groovy-3.0'
|
||||
testImplementation('com.alibaba.testable:testable-all:0.6.10')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.10')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.7.7')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.7')
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<testable.version>0.6.10</testable.version>
|
||||
<testable.version>0.7.7</testable.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
@@ -62,3 +62,9 @@ This framework will run Android unit tests on a standard JVM virtual machine, wh
|
||||
This problem is caused by the system `Class Path` content is too long, and has nothing to do with `TestableMock`. However, it should be noted that IntelliJ provides two auxiliary solutions: `JAR manifest` and `classpath file`. If `TestableMock` is used in the test, please select `JAR manifest`.
|
||||
|
||||

|
||||
|
||||
#### 10. Getting "no unique public constructor for [xxx]" error with `OmniConstructor`?
|
||||
|
||||
When the `omni.constructor.enhance.enable` configuration is enabled, `TestableMock` will dynamically add a constructor with a parameter of `java.lang.Void` when each class is loaded into memory to avoid the original type constructor exceptions that may be thrown. The above error occurs when some frameworks do not allow multiple constructors for some classes.
|
||||
|
||||
The solution is to add the package path where the error class is located to the `omni.constructor.enhance.pkgPrefix.excludes` configuration, which tell `TestableMock` not to dynamically add constructors to these classes. For details, please refer to the [Global Run Arguments](en-us/doc/javaagent-args.md) document.
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# Release Note
|
||||
|
||||
## 0.7.7
|
||||
|
||||
- only generate substitution method for mocked method references
|
||||
- fix compatibility issue with jdk 17
|
||||
|
||||
## 0.7.6
|
||||
- define package mapping rule according to order in config, to avoid a short path overwrite longer ones
|
||||
- fix compatibility issue with jdk 9+
|
||||
- skip classes generated by mockito during `OmniConstructor` bytecode enhancing
|
||||
|
||||
## 0.7.5
|
||||
- fix an issue cause mocking method with array return type fails
|
||||
|
||||
## 0.7.4
|
||||
- fix an `OmniConstructor.newInstance()` fail issue caused by `java.net.URL` class cannot be instrumented
|
||||
|
||||
## 0.7.3
|
||||
- fix an issue cause mock failed with referring to method defined in interface
|
||||
|
||||
## 0.7.2
|
||||
- fix an issue cause mock failed with static method reference
|
||||
|
||||
## 0.7.1
|
||||
- fix an issue cause mock class unrecognized after package-mapping
|
||||
|
||||
## 0.7.0
|
||||
- fix an improper exception issue when mock method with `associated` scope invoked
|
||||
- rename type `InvokeVerifier`/`InvockeMatcher` to `InvocationVerifier`/`InvocationMatcher`
|
||||
|
||||
@@ -16,7 +16,7 @@ It is recommended to add a `property` field that identifies the TestableMock ver
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
<testable.version>0.6.10</testable.version>
|
||||
<testable.version>0.7.7</testable.version>
|
||||
</properties>
|
||||
```
|
||||
|
||||
@@ -63,8 +63,8 @@ Add dependence of `TestableMock` in `build.gradle` file:
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
testImplementation('com.alibaba.testable:testable-all:0.6.10')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.10')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.7.7')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.7')
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
Upgrade Guide
|
||||
---
|
||||
|
||||
#### Upgrade to 0.7
|
||||
|
||||
In version `0.7` mock related annotations and methods are renamed, in order to avoid naming conflict and misunderstanding.
|
||||
|
||||
1. The `verify()` used for check mock method invocation status has been renamed to `verifyInvoked()`, as there are several static methods also named `verify()` in `com.sun` package.
|
||||
2. The annotation `@MockMethod` and `@MockConstructor` used for mark mock replacement method has been renamed to `@MockInvoke` and `@MockNew`, which is more suitable for its real usage.
|
||||
|
||||
The usage of those annotations and method has no change for below and above version `0.7.0`, so during the upgrading only rename is required.
|
||||
|
||||
### Upgrade to v0.6
|
||||
|
||||
In version `0.6`,`TestableMock` provided a new [quick complicated class construction](en-us/doc/omni-constructor.md) capability. Meanwhile, it includes a modification that is incompatible with the `0.5` version: class `PrivateAccessor` have been moved from `com.alibaba.testable.core.accessor` package to `com.alibaba.testable.core.tool` package.
|
||||
|
||||
46
docs/en-us/doc/use-intellij-plugin.md
Normal file
46
docs/en-us/doc/use-intellij-plugin.md
Normal file
@@ -0,0 +1,46 @@
|
||||
Use IntelliJ Plugin
|
||||
---
|
||||
|
||||
There are some typical operations in the process of using`TestableMock` library, such as define a mock class, copy signature of the method invoked and convert it to mock method definition. While the mock result does not match expectation, we have to debug the mock affection via [Self-Help Troubleshooting](zh-cn/doc/troubleshooting.md) guide during runtime. With the help of IDE plugin, part of the patterned operations can be automatically completed to further optimize the experience of `TestableMock`.
|
||||
|
||||
Currently `TestableMock` provided only [IntelliJ IDEA](https://www.jetbrains.com/idea/) plugin. (According to the [2020](https://www.jrebel.com/blog/2020-java-technology-report#IDE) and [2021](https://www.jrebel.com/blog/2021-java-technology-report) Java Technology Trends Report released by JRebel, IntelliJ IDEA has become the IDE of choice with the highest percentage of Java developers)
|
||||
|
||||
## Usage
|
||||
|
||||
Open preference dialog, search "Testable-Mock" in plugin market, reach `Testable-Mock Helper` plugin and click "install", then restart IDE.
|
||||
|
||||

|
||||
|
||||
#### 1. **Generate Mock Class**
|
||||
|
||||
After plugin installed, right click in any java class, choose "Generate Testable TestClass"
|
||||
|
||||

|
||||
|
||||
A test class containing a mock container class will be automatically generated in the same package path location as the current class in the `test` directory of the project (named after `CurrentClass + Test`)
|
||||
|
||||
#### 2. **Create Mock Method**
|
||||
|
||||
Select any method invocation, right click and choose "Copy Testable Mock-Method" or "Copy Mock-Method To TestClass"
|
||||
|
||||

|
||||
|
||||
The former will convert the method signature of selected invocation into a mock method definition and store it on the clipboard; the latter will directly insert the converted mock method into the corresponding mock class.
|
||||
|
||||
## Build from source code
|
||||
|
||||
Beside install plugin from market, you can also build it from source.
|
||||
|
||||
Make sure you have jdk 11 installed, execute below command:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zcbbpo/testable-idea
|
||||
cd testable-idea
|
||||
./gradlew clean build
|
||||
```
|
||||
|
||||
After build, the packaged zip file is lay in `build/distributions/` folder, you can install it by "Install Plugin From Disk" menu.
|
||||
|
||||

|
||||
|
||||
Finally, special thanks to the contribution of `TestableMock` IntelliJ IDEA plugin made by @[zcbbpo](https://github.com/zcbbpo).
|
||||
@@ -165,7 +165,7 @@ For complete code examples, see the `should_get_source_method_name()` and `shoul
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
- [Reusing Mock Methods](en-us/doc/mock-method-reusing.md)
|
||||
- [Use MockWith Annotation](en-us/doc/use-mock-with.md)
|
||||
- [Use Package Mapping](en-us/doc/use-package-mapping.md)
|
||||
- [Use IntelliJ Plugin](en-us/doc/use-intellij-plugin.md)
|
||||
- [Frequently Asked Questions](en-us/doc/frequently-asked-questions.md)
|
||||
- [Mock Invocation In Thread Pool](zh-cn/doc/with-thread-pool.md)
|
||||
- [Use TestableMock In IDE](en-us/doc/use-in-ide.md)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
| Spock | 动态代理 | 类 | 不能Mock私有/静态和构造方法 | 较复杂 | 一般 |
|
||||
| PowerMock | 自定义类加载器 | 类 | **任何方法皆可** | 较复杂 | **较好** |
|
||||
| JMockit | 运行时字节码修改 | 类 | 不能Mock构造方法(new操作符) | 较复杂 | 一般 |
|
||||
| TestableMock | 运行时字节码修改 | 方法 | **任何方法皆可** | **很容易** | 一般 |
|
||||
| TestableMock | 运行时字节码修改 | 方法 | **任何方法皆可** | **较容易** | **较好** |
|
||||
|
||||
`Mockito`是Java最老牌的Mock工具,稳定性和易用性较好,IntelliJ和Eclipse都有专用插件支持。相对不足之处在于Mock功能稍弱,在必要情况下需与其他Mock工具配合使用。
|
||||
|
||||
@@ -19,8 +19,12 @@
|
||||
|
||||
`JMockit`是一款功能性与易用性均居于`Mockito`与`PowerMock`之间的Mock工具,较好的弥补了两者各自的不足。该项目在2017年尝试推出JMockit2重写版本但未能完成,目前处于不活跃的维护状态。
|
||||
|
||||
相比之下,`TestabledMock`的功能与`PowerMock`基本平齐,且极易上手,只需掌握`@MockInvoke`注解就可以完成绝大多数任务。
|
||||
相比之下,`TestabledMock`的功能与`PowerMock`基本平齐,且极易上手,只需掌握一个`@MockInvoke`注解就可以完成绝大多数Mock操作。
|
||||
|
||||
当前`TestableMock`的主要不足在于,编写Mock方法时IDE无法即时提示方法参数是否正确匹配。若发现匹配效果不符合预期,需要通过[自助问题排查](zh-cn/doc/troubleshooting.md)文档提供的方法在运行期进行校验。这个功能理论上能够通过扩展主流IDE插件来补充,但目前暂无相关开发计划,参见[Issue-104](https://github.com/alibaba/testable-mock/issues/104)。
|
||||
## TestableMock的不足
|
||||
|
||||
此外,由于`TestableMock`独辟蹊径的采用“每个业务类拥有一个专属Mock容器类”的思维方式,将Mock方法定义与单元测试用例解耦,一方面使得Mock方法具有默认可复用性,单元测试用例也因此变得更干净纯粹,另一方面也导致Mock方法定义变得零散,生命周期管理起来相对困难,对现有开发者的Mock编写习惯会带来一定改变。
|
||||
为了便于开发者进行Mock工具的合理选型,我们认为客观的强调`TestabledMock`存在的不足,与阐述它的优点同样重要。
|
||||
|
||||
任何事物都有两面性。由于`TestableMock`独辟蹊径采用“每个业务类拥有一个专属Mock容器类”的思维方式,将Mock方法定义与单元测试用例解耦,一方面使得Mock方法具有默认可复用性,单元测试用例也因此变得更干净纯粹,另一方面也导致Mock方法定义变得零散,生命周期管理起来相对困难,对现有开发者的Mock编写习惯会带来一定改变。
|
||||
|
||||
此外,`TestableMock`最初的设计主要面向标准单元测试场景(每个类有自己的测试用例,测试类与被测类基本逐一对应)。为了最大限度的提升易用性,把需要用到的注解、操作步骤都减少到最少,`TestableMock`采用了许多“约定优于配置”的设计。譬如被测量、测试类与Mock容器类在同一包路径,且分别按约定命名等。对于单元测试较规范的项目而言,`TestableMock`将极大降低Mock引入的成本;对于规范不足的项目,依然可以结合`@MockWith`、包路径映射等功能完成相同的Mock效果,但应当尤其注意这些“花哨”的功能对代码可读性和可维护性带来的负面影响。
|
||||
|
||||
@@ -64,3 +64,9 @@ Kotlin语言中的`String`类型实际上是`kotlin.String`,而非`java.lang.S
|
||||
这个问题是由于系统ClassPath包含太多路径所致,与是否使用`TestableMock`无关。但需要注意的是,IntelliJ提供了两种辅助解决机制:`JAR manifest`和`classpath file`,若测试中使用了`TestableMock`,请选择`JAR manifest`。
|
||||
|
||||

|
||||
|
||||
#### 10. 使用`OmniConstructor`遇到"no unique public constructor for [xxx]"错误?
|
||||
|
||||
当启用`omni.constructor.enhance.enable`配置时,`TestableMock`会在每个类加载进内存时动态添加一个参数为`java.lang.Void`的构造方法,用于避开原类型构造方法里可能抛出的异常。若遇到某些框架不允许类型存在多个构造方法的时候,就会出现上述错误。
|
||||
|
||||
解决办法是将报错类所在的包路径添加到`omni.constructor.enhance.pkgPrefix.excludes`配置中,告知`TestableMock`不要对这些类动态添加构造方法即可。配置方法详见[全局运行参数](zh-cn/doc/javaagent-args.md)文档。
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# Release Note
|
||||
|
||||
## 0.7.7
|
||||
|
||||
- 仅对被Mock的方法引用生成替代方法 (issue-280)
|
||||
- 修复JDK 17的兼容性问题 (issue-283)
|
||||
|
||||
## 0.7.6
|
||||
- 依据定义顺序匹配包映射规则,防止短路径覆盖长路径(issue-275)
|
||||
- 修复JDK 9+的兼容性问题(issue-270 / pr-274)
|
||||
- 在`OmniConstructor`字节码增强时忽略Mockito生成的类型(issue-197)
|
||||
|
||||
## 0.7.5
|
||||
- 修复Mock数组类型返回值方法报错的问题(issue-267)
|
||||
|
||||
## 0.7.4
|
||||
- 修复`java.net.URL`类无法被instrument导致使用`OmniConstructor`构造报错的问题(issue-261)
|
||||
|
||||
## 0.7.3
|
||||
- 修复接口方法引用导致Mock报错的问题(issue-254)
|
||||
|
||||
## 0.7.2
|
||||
- 修复静态方法引用导致Mock报错的问题(issue-250)
|
||||
|
||||
## 0.7.1
|
||||
- 修复包路径映射时后识别不到Mock类的问题(issue-248)
|
||||
|
||||
## 0.7.0
|
||||
- 修复当`scope`为`associated`的Mock方法被`null`对象调用,且上下文与测试用例未关联时抛错不合理的问题(issue-163)
|
||||
- 类型`InvokeVerifier`和`InvockeMatcher`更名为`InvocationVerifier`和`InvocationMatcher`
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
<testable.version>0.6.10</testable.version>
|
||||
<testable.version>0.7.7</testable.version>
|
||||
</properties>
|
||||
```
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
testImplementation('com.alibaba.testable:testable-all:0.6.10')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.10')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.7.7')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.7')
|
||||
}
|
||||
```
|
||||
|
||||
@@ -110,4 +110,6 @@ test {
|
||||
> sourceCompatibility = '6' // 或7/8/9/...
|
||||
> ```
|
||||
>
|
||||
> **注意**:由于`Spock`采用基于字符串解析的特殊执行方式,`TestableTool`工具类在`Spock`框架中无法使用。
|
||||
>
|
||||
> 完整代码可参考`demo/spock-demo`示例项目。
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
版本升级说明
|
||||
---
|
||||
|
||||
#### 升级到0.7.x版本
|
||||
|
||||
在`0.7`版本重命名了Mock相关的注解和方法,以避免命名冲突和由于名字带来的理解歧义,包括两部分变化。
|
||||
|
||||
1. 用于验证Mock方法调用的`verify()`方法与包括`com.sun`包在内的多个JVM内置静态方法重名,在IDE自动添加`import`时会有误选的情况,该方法在新版本中更名为`verifyInvoked()`,同时所属的类型更名为`InvocationVerifier`。
|
||||
2. 注解`@MockMethod`和`@MockConstructor`的实际作用是对方法调用和New操作进行Mock,而其名称容易使人误解为是对方法和构造器本身进行了Mock,在新版本中这两个注解分别已被分别更名为`@MockInvoke`和`@MockNew`。
|
||||
|
||||
这些注解和方法的使用与之前版本并无差异,从`0.6.x`版本升级到`0.7.0`及以上版本时,请全局替换相关名称即可。
|
||||
|
||||
### 升级到0.6.x版本
|
||||
|
||||
在`0.6`版本中,`TestableMock`提供了[快速构造复杂参数对象](zh-cn/doc/omni-constructor.md)的能力,同时包含一处与`0.5`版本不兼容的修改,`PrivateAccessor`类型的包路径从`com.alibaba.testable.core.accessor`移到了`com.alibaba.testable.core.tool`。
|
||||
|
||||
46
docs/zh-cn/doc/use-intellij-plugin.md
Normal file
46
docs/zh-cn/doc/use-intellij-plugin.md
Normal file
@@ -0,0 +1,46 @@
|
||||
使用IntelliJ IDEA插件
|
||||
---
|
||||
|
||||
在使用`TestableMock`工具的过程中经常涉及一些固定操作,比如定义Mock容器类、复制需Mock的调用定义并创建Mock方法,在执行测试时若发现Mock匹配的效果不符合预期,则需要通过[自助问题排查](zh-cn/doc/troubleshooting.md)方法在运行期进行校验。通过IDE插件辅助,能够将部分模式化的操作自动完成,进一步优化`TestableMock`的使用体验。
|
||||
|
||||
目前`TestableMock`提供了 [IntelliJ IDEA](https://www.jetbrains.com/idea/) 的插件支持(根据JRebel发布的 [2020](https://www.jrebel.com/blog/2020-java-technology-report#IDE) 和 [2021](https://www.jrebel.com/blog/2021-java-technology-report) Java技术趋势报告,IntelliJ IDEA已成为Java开发者使用比例最高的首选IDE)。
|
||||
|
||||
## 使用方法
|
||||
|
||||
打开配置项,在插件市场中搜索“Testable-Mock”,选中`Testable-Mock Helper`插件,点击“安装”,然后重启IDE。
|
||||
|
||||

|
||||
|
||||
#### 1. **创建Mock容器类**
|
||||
|
||||
安装完插件后,在任意Java类点击右键,点击“生成Testable测试类”(英文版为“Generate Testable TestClass”)
|
||||
|
||||

|
||||
|
||||
在项目`test`目录下与当前类相同的包路径位置会自动生成一个包含Mock容器类的测试类(已`当前类+Test`命名)。
|
||||
|
||||
#### 2. **创建Mock方法**
|
||||
|
||||
选中任意方法调用,点击右键,点击“复制方法为Testable-Mock”(英文版为“Copy Testable Mock-Method”)或“复制Testable-Mock方法到测试类”(英文版为“Copy Mock-Method To TestClass”)
|
||||
|
||||

|
||||
|
||||
前者会将被选中调用的方法签名,并转换为Mock方法定义存放到剪贴板;后者则会将转换好的Mock方法直接插入到相应的Mock类里。
|
||||
|
||||
## 源码构建
|
||||
|
||||
除了通过插件市场安装,也可以直接从源码构建`Testable-Mock Helper`插件。
|
||||
|
||||
本地准备JDK 11环境,然后执行以下命令:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zcbbpo/testable-idea
|
||||
cd testable-idea
|
||||
./gradlew clean build
|
||||
```
|
||||
|
||||
构建完成后,在`build/distributions/`目录下可以找到构建好的插件zip包,通过“从本地磁盘安装”菜单加载该插件。
|
||||
|
||||

|
||||
|
||||
在此,特别感谢 @[zcbbpo](https://github.com/zcbbpo) 对`TestableMock`IntelliJ IDEA插件的贡献。
|
||||
@@ -165,7 +165,7 @@ private Data mockDemo() {
|
||||
|
||||
在测试用例中可用通过`InvocationVerifier.verifyInvoked()`方法,配合`with()`、`withInOrder()`、`without()`、`withTimes()`等方法实现对Mock调用情况的验证。
|
||||
|
||||
详见[校验Mock调用](zh-cn/doc/matcher.md)文档。
|
||||
详见[校验Mock调用](zh-cn/doc/invoke-matcher.md)文档。
|
||||
|
||||
### 4. 特别说明
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
- [复用Mock类与方法](zh-cn/doc/mock-method-reusing.md)
|
||||
- [使用MockWith注解](zh-cn/doc/use-mock-with.md)
|
||||
- [使用包路径映射](zh-cn/doc/use-package-mapping.md)
|
||||
- [使用IntelliJ插件](zh-cn/doc/use-intellij-plugin.md)
|
||||
- [常见使用问题](zh-cn/doc/frequently-asked-questions.md)
|
||||
- [Mock线程池内的调用](zh-cn/doc/with-thread-pool.md)
|
||||
- [在IDE运行单元测试](zh-cn/doc/use-in-ide.md)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.6.10</version>
|
||||
<version>0.7.7</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-agent</artifactId>
|
||||
@@ -14,8 +14,8 @@
|
||||
<name>testable-agent</name>
|
||||
|
||||
<properties>
|
||||
<asm.lib.version>8.0.1</asm.lib.version>
|
||||
<junit.version>5.6.2</junit.version>
|
||||
<asm.lib.version>9.3</asm.lib.version>
|
||||
<junit.version>5.8.2</junit.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -190,7 +190,10 @@ public class MockClassHandler extends BaseClassWithContextHandler {
|
||||
if (VOID_RES.equals(returnType)) {
|
||||
il.add(new InsnNode(POP));
|
||||
il.add(new InsnNode(RETURN));
|
||||
} else if (returnType.charAt(0) == TYPE_ARRAY || returnType.charAt(0) == TYPE_CLASS) {
|
||||
} else if (returnType.charAt(0) == TYPE_ARRAY) {
|
||||
il.add(new TypeInsnNode(CHECKCAST, returnType));
|
||||
il.add(new InsnNode(ARETURN));
|
||||
} else if (returnType.charAt(0) == TYPE_CLASS) {
|
||||
il.add(new TypeInsnNode(CHECKCAST, ClassUtil.toSlashSeparateJavaStyleName(returnType)));
|
||||
il.add(new InsnNode(ARETURN));
|
||||
} else {
|
||||
|
||||
@@ -38,7 +38,7 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
|
||||
@Override
|
||||
protected void transform(ClassNode cn) {
|
||||
if (isInterfaceOrAtom(cn) || isJunitTestClass(cn) || isUninstantiableClass(cn) || hasSpecialAnnotation(cn)) {
|
||||
if (isInterfaceOrAtom(cn) || isUniqueConstructorClass(cn) || isUninstantiableClass(cn) || hasSpecialAnnotation(cn)) {
|
||||
return;
|
||||
}
|
||||
addConstructorWithVoidTypeParameter(cn);
|
||||
@@ -89,7 +89,15 @@ public class OmniClassHandler extends BaseClassHandler {
|
||||
return (cn.access & ACC_INTERFACE) != 0 || cn.superName == null || VOID_TYPE.equals(cn.name);
|
||||
}
|
||||
|
||||
private boolean isJunitTestClass(ClassNode cn) {
|
||||
private boolean isUniqueConstructorClass(ClassNode cn) {
|
||||
// elastic plugin class should contain only one constructor
|
||||
if ("org/elasticsearch/plugins/Plugin".equals(cn.superName)) {
|
||||
return true;
|
||||
}
|
||||
// should skip mockito generated class
|
||||
if (cn.name.contains("$MockitoMock$")) {
|
||||
return true;
|
||||
}
|
||||
// junit require test class contains only one constructor
|
||||
for (MethodNode mn : cn.methods) {
|
||||
if (mn.visibleAnnotations == null) {
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
package com.alibaba.testable.agent.handler;
|
||||
|
||||
import com.alibaba.testable.agent.model.BasicType;
|
||||
import com.alibaba.testable.agent.model.BsmArg;
|
||||
import com.alibaba.testable.agent.model.MethodInfo;
|
||||
import com.alibaba.testable.agent.model.TravelStatus;
|
||||
import com.alibaba.testable.agent.util.BytecodeUtil;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
import com.alibaba.testable.agent.util.MethodUtil;
|
||||
import com.alibaba.testable.core.util.LogUtil;
|
||||
import org.objectweb.asm.Handle;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.*;
|
||||
import org.objectweb.asm.tree.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
|
||||
@@ -51,6 +44,10 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
@Override
|
||||
protected void transform(ClassNode cn) {
|
||||
LogUtil.diagnose("Found source class %s", cn.name);
|
||||
if (injectMethods.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<MethodInfo> memberInjectMethods = new HashSet<MethodInfo>();
|
||||
Set<MethodInfo> newOperatorInjectMethods = new HashSet<MethodInfo>();
|
||||
for (MethodInfo im : injectMethods) {
|
||||
@@ -61,15 +58,17 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
}
|
||||
}
|
||||
|
||||
resolveMethodReference(cn);
|
||||
if (!memberInjectMethods.isEmpty()) {
|
||||
resolveMethodReference(cn, memberInjectMethods);
|
||||
}
|
||||
|
||||
for (MethodNode m : cn.methods) {
|
||||
transformMethod(m, memberInjectMethods, newOperatorInjectMethods, cn);
|
||||
transformMethod(m, memberInjectMethods, newOperatorInjectMethods);
|
||||
}
|
||||
}
|
||||
|
||||
private void transformMethod(MethodNode mn, Set<MethodInfo> memberInjectMethods,
|
||||
Set<MethodInfo> newOperatorInjectMethods, ClassNode cn) {
|
||||
Set<MethodInfo> newOperatorInjectMethods) {
|
||||
LogUtil.verbose(" Found method %s", mn.name);
|
||||
if (mn.name.startsWith("$")) {
|
||||
// skip methods e.g. "$jacocoInit"
|
||||
@@ -341,85 +340,57 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
return Opcodes.INVOKEVIRTUAL == opcode && ClassUtil.isCompanionClassName(ownerClass);
|
||||
}
|
||||
|
||||
private void setFinalValue(Field ownerField, Object obj, Object value) throws Exception {
|
||||
ownerField.setAccessible(true);
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
modifiersField.setInt(ownerField, ownerField.getModifiers() & ~Modifier.FINAL);
|
||||
ownerField.set(obj, value);
|
||||
}
|
||||
|
||||
private List<Handle> fetchInvokeDynamicHandle(MethodNode mn) {
|
||||
List<Handle> handleList = new ArrayList<Handle>();
|
||||
private List<BsmArg> fetchInvokeDynamicHandle(MethodNode mn) {
|
||||
List<BsmArg> handleList = new ArrayList<BsmArg>();
|
||||
for (AbstractInsnNode instruction : mn.instructions) {
|
||||
if (instruction.getOpcode() == Opcodes.INVOKEDYNAMIC) {
|
||||
InvokeDynamicInsnNode invokeDynamicInsnNode = (InvokeDynamicInsnNode) instruction;
|
||||
handleList.add((Handle) invokeDynamicInsnNode.bsmArgs[1]);
|
||||
// java9+ String makeConcatWithConstants will ues INVOKEDYNAMIC to optimize.
|
||||
// When the mock source method has like <code>"abc" + "def"</code> the bsmArgs length was 1
|
||||
// https://openjdk.java.net/jeps/280
|
||||
if (invokeDynamicInsnNode.bsmArgs.length < 3) {
|
||||
continue;
|
||||
}
|
||||
BsmArg bsmArg = new BsmArg(invokeDynamicInsnNode.bsmArgs);
|
||||
handleList.add(bsmArg);
|
||||
}
|
||||
}
|
||||
return handleList;
|
||||
}
|
||||
|
||||
private void resolveMethodReference(ClassNode cn) {
|
||||
List<Handle> invokeDynamicList = new ArrayList<Handle>();
|
||||
private void resolveMethodReference(ClassNode cn, Set<MethodInfo> mockedMethods) {
|
||||
List<BsmArg> invokeDynamicList = new ArrayList<BsmArg>();
|
||||
for (MethodNode method : cn.methods) {
|
||||
List<Handle> handleList = fetchInvokeDynamicHandle(method);
|
||||
invokeDynamicList.addAll(handleList);
|
||||
List<BsmArg> handleList = fetchInvokeDynamicHandle(method);
|
||||
for (BsmArg arg : handleList) {
|
||||
for (MethodInfo mi : mockedMethods) {
|
||||
if (isMethodMocked(arg.getHandle(), mi)) {
|
||||
invokeDynamicList.add(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process for method reference
|
||||
for (Handle handle : invokeDynamicList) {
|
||||
for (BsmArg bsmArg : invokeDynamicList) {
|
||||
// the jdk auto generation method
|
||||
if (handle.getName().startsWith("lambda$")) {
|
||||
if (bsmArg.getHandle().getName().startsWith("lambda$")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int tag = handle.getTag();
|
||||
int tag = bsmArg.getHandle().getTag();
|
||||
|
||||
if (tag == Opcodes.H_NEWINVOKESPECIAL) {
|
||||
// lambda new method reference
|
||||
continue;
|
||||
}
|
||||
|
||||
// external mean:
|
||||
// public void foo() {
|
||||
// String s = "";
|
||||
// consumes(s::contains);
|
||||
//}
|
||||
boolean external = tag == Opcodes.H_INVOKEVIRTUAL;
|
||||
boolean isStatic = bsmArg.isStatic();
|
||||
Handle handle = bsmArg.getHandle();
|
||||
Type handleDesc = bsmArg.getHandleDesc();
|
||||
|
||||
boolean isStatic = tag == Opcodes.H_INVOKESTATIC || external;
|
||||
|
||||
String desc = handle.getDesc();
|
||||
String parameters = desc.substring(desc.indexOf("(") + 1, desc.lastIndexOf(")"));
|
||||
String returnType = desc.substring(desc.indexOf(")") + 1);
|
||||
String[] parameterArray = parameters.split(";");
|
||||
int len = parameterArray.length;
|
||||
for (String s : parameterArray) {
|
||||
if (s.isEmpty()) {
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
len = external ? len + 1 : len;
|
||||
|
||||
String[] refineParameterArray = new String[len];
|
||||
int index = external ? 1 : 0;
|
||||
if (external) {
|
||||
// The type should was reference type
|
||||
refineParameterArray[0] = "L" + handle.getOwner();
|
||||
}
|
||||
for (String s : parameterArray) {
|
||||
if (!s.isEmpty()) {
|
||||
refineParameterArray[index] = s;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
String externalDesc = buildDesc(refineParameterArray, returnType);
|
||||
|
||||
String lambdaName = String.format("Lambda$_%s_%d", handle.getName(), atomicInteger.incrementAndGet());
|
||||
MethodVisitor mv = cn.visitMethod(isStatic ? ACC_PUBLIC + ACC_STATIC : ACC_PUBLIC, lambdaName, external ? externalDesc : desc, null, null);
|
||||
String lambdaName = String.format("_Lambda$_%s_%d", handle.getName(), atomicInteger.incrementAndGet());
|
||||
MethodVisitor mv = cn.visitMethod(isStatic ? ACC_PUBLIC + ACC_STATIC : ACC_PUBLIC, lambdaName, handleDesc.getDescriptor(), null, null);
|
||||
mv.visitCode();
|
||||
|
||||
Label l0 = new Label();
|
||||
@@ -428,101 +399,65 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
// add this
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
}
|
||||
for (int i = 0; i < refineParameterArray.length; i++) {
|
||||
String arg = refineParameterArray[i];
|
||||
mv.visitVarInsn(getLoadType(arg), isStatic ? i : i + 1);
|
||||
Type[] argumentTypes = handleDesc.getArgumentTypes();
|
||||
int nextVar = isStatic ? 0 : 1;
|
||||
for (Type argumentType : argumentTypes) {
|
||||
String arg = argumentType.getDescriptor();
|
||||
mv.visitVarInsn(getLoadType(arg), nextVar);
|
||||
nextVar = isLongByte(argumentType) ? nextVar + 2 : nextVar + 1;
|
||||
}
|
||||
|
||||
mv.visitMethodInsn(isStatic ? INVOKESTATIC : INVOKEVIRTUAL, handle.getOwner(), handle.getName(), desc, false);
|
||||
if (tag == H_INVOKEINTERFACE) {
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, handle.getOwner(), handle.getName(), bsmArg.getOriginalHandleDesc(), handle.isInterface());
|
||||
} else {
|
||||
mv.visitMethodInsn(Opcodes.H_INVOKESTATIC == tag ? INVOKESTATIC : INVOKEVIRTUAL, handle.getOwner(), handle.getName(), bsmArg.getOriginalHandleDesc(), handle.isInterface());
|
||||
}
|
||||
|
||||
mv.visitInsn(getReturnType(returnType));
|
||||
mv.visitInsn(getReturnType(handleDesc.getReturnType().getDescriptor()));
|
||||
|
||||
Label l1 = new Label();
|
||||
mv.visitLabel(l1);
|
||||
|
||||
// static function was not required add this to first parameter
|
||||
if (isStatic) {
|
||||
for (int i = 0; i < refineParameterArray.length; i++) {
|
||||
String localVar = refineParameterArray[i];
|
||||
if (!isPrimitive(localVar)) {
|
||||
// primitive type and reference type difference
|
||||
localVar = localVar.endsWith(";") ? localVar : localVar + ";";
|
||||
}
|
||||
|
||||
if (localVar.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// add local var
|
||||
mv.visitLocalVariable(String.format("o%d", i), localVar, null, l0, l1, i);
|
||||
}
|
||||
visitLocalVariableByArguments(mv, 0, argumentTypes, l0, l1);
|
||||
} else {
|
||||
mv.visitLocalVariable("this", "L" + handle.getOwner() + ";", null, l0, l1, 0);
|
||||
for (int i = 0; i < refineParameterArray.length; i++) {
|
||||
String localVar = refineParameterArray[i];
|
||||
if (!isPrimitive(localVar) && !isPrimitiveArray(localVar)) {
|
||||
localVar = localVar.endsWith(";") ? localVar : localVar + ";";
|
||||
}
|
||||
if (localVar.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mv.visitLocalVariable(String.format("o%d", i), localVar, null, l0, l1, i + 1);
|
||||
}
|
||||
visitLocalVariableByArguments(mv, 1, argumentTypes, l0, l1);
|
||||
}
|
||||
// auto compute max
|
||||
mv.visitMaxs(-1, -1);
|
||||
mv.visitEnd();
|
||||
|
||||
try {
|
||||
// modify handle to the generation method
|
||||
setFinalValue(handle.getClass().getDeclaredField("name"), handle, lambdaName);
|
||||
// mark: should merge the below two if.
|
||||
if (!handle.getOwner().equals(cn.name) && isStatic) {
|
||||
setFinalValue(handle.getClass().getDeclaredField("owner"), handle, cn.name);
|
||||
}
|
||||
if (external) {
|
||||
setFinalValue(handle.getClass().getDeclaredField("owner"), handle, cn.name);
|
||||
setFinalValue(handle.getClass().getDeclaredField("descriptor"), handle, externalDesc);
|
||||
setFinalValue(handle.getClass().getDeclaredField("tag"), handle, H_INVOKESTATIC);
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
bsmArg.complete(cn.name, lambdaName);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildDesc(String[] refineParameterArray, String returnType) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("(");
|
||||
for (String s : refineParameterArray) {
|
||||
sb.append(s);
|
||||
if (!isPrimitive(s)) {
|
||||
sb.append(";");
|
||||
}
|
||||
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());
|
||||
}
|
||||
sb.append(")");
|
||||
sb.append(returnType);
|
||||
return sb.toString();
|
||||
return mockMethodInfo.getClazz().equals(targetHandle.getOwner()) &&
|
||||
mockMethodInfo.getName().equals(targetHandle.getName()) &&
|
||||
mockMethodInfo.getDesc().equals(targetHandle.getDesc());
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean isPrimitive(String type) {
|
||||
if (type.endsWith(";")) {
|
||||
type = type.substring(0, type.length() - 1);
|
||||
private void visitLocalVariableByArguments(MethodVisitor mv, final int initVar, Type[] argumentTypes, Label l0, Label l1) {
|
||||
int nextLocalVar = initVar;
|
||||
for (int i = 0; i < argumentTypes.length; i++) {
|
||||
Type argumentType = argumentTypes[i];
|
||||
String localVar = argumentType.getDescriptor();
|
||||
mv.visitLocalVariable(String.format("o%d", i), localVar, null, l0, l1, nextLocalVar);
|
||||
nextLocalVar = isLongByte(argumentType) ? nextLocalVar + 2 : nextLocalVar + 1;
|
||||
}
|
||||
return BasicType.basicType(type.charAt(0)).isPrimitive();
|
||||
}
|
||||
|
||||
private boolean isPrimitiveArray(String type) {
|
||||
if (!type.startsWith("[")) {
|
||||
return false;
|
||||
}
|
||||
if (type.endsWith(";")) {
|
||||
type = type.substring(0, type.length() - 1);
|
||||
}
|
||||
|
||||
type = type.replace("[", "");
|
||||
return BasicType.basicType(type.charAt(0)).isPrimitive();
|
||||
private boolean isLongByte(Type argumentType) {
|
||||
return double.class.getName().equals(argumentType.getClassName()) || long.class.getName().equals(argumentType.getClassName());
|
||||
}
|
||||
|
||||
private int getReturnType(String returnType) {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.alibaba.testable.agent.model;
|
||||
|
||||
import com.alibaba.testable.agent.util.StringUtil;
|
||||
import org.objectweb.asm.Handle;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
/**
|
||||
* @author jim
|
||||
*/
|
||||
public class BsmArg {
|
||||
private Type handleDesc;
|
||||
private Type methodDesc;
|
||||
|
||||
private Handle handle;
|
||||
|
||||
private final Object[] bsmArgs;
|
||||
|
||||
private final String originalHandleDesc;
|
||||
|
||||
public BsmArg(Object[] bsmArgs) {
|
||||
this.bsmArgs = bsmArgs;
|
||||
handle = (Handle) bsmArgs[1];
|
||||
methodDesc = (Type) bsmArgs[2];
|
||||
handleDesc = Type.getType(handle.getDesc());
|
||||
originalHandleDesc = handle.getDesc();
|
||||
|
||||
// H_INVOKEVIRTUAL: String s = "";consumes(s::contains);
|
||||
// H_INVOKEINTERFACE: list.stream().flatMap(Collection::stream)
|
||||
if (handle.getTag() == Opcodes.H_INVOKEVIRTUAL || handle.getTag() == Opcodes.H_INVOKEINTERFACE) {
|
||||
Type[] argumentTypes = handleDesc.getArgumentTypes();
|
||||
Type thisArgument = Type.getType("L" + handle.getOwner() + ";");
|
||||
|
||||
String handleDescString = refineHandle(thisArgument, argumentTypes);
|
||||
handle = new Handle(handle.getTag(), handle.getOwner(), handle.getName(), handleDescString, handle.isInterface());
|
||||
handleDesc = Type.getType(handleDescString);
|
||||
|
||||
if (/*handle.getTag() == Opcodes.H_INVOKEVIRTUAL || */methodDesc.getArgumentTypes().length == handleDesc.getArgumentTypes().length - 1) {
|
||||
Type[] methodArguments = methodDesc.getArgumentTypes();
|
||||
String methodDescString = refineHandle(thisArgument, methodArguments);
|
||||
methodDesc = Type.getType(methodDescString);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private String refineHandle(Type thisArgument, Type[] argumentTypes) {
|
||||
Type[] handleArguments = new Type[argumentTypes.length + 1];
|
||||
String[] handleArgs = new String[argumentTypes.length + 1];
|
||||
handleArguments[0] = thisArgument;
|
||||
System.arraycopy(argumentTypes, 0, handleArguments, 1, argumentTypes.length);
|
||||
|
||||
for (int i = 0; i < handleArguments.length; i++) {
|
||||
handleArgs[i] = handleArguments[i].getDescriptor();
|
||||
}
|
||||
|
||||
return "(" + StringUtil.join("", handleArgs) + ")" + handleDesc.getReturnType().getDescriptor();
|
||||
}
|
||||
|
||||
public Type getHandleDesc() {
|
||||
return handleDesc;
|
||||
}
|
||||
|
||||
public Type getMethodDesc() {
|
||||
return methodDesc;
|
||||
}
|
||||
|
||||
public Handle getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
public String getOriginalHandleDesc() {
|
||||
return originalHandleDesc;
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
int tag = handle.getTag();
|
||||
return tag == Opcodes.H_INVOKESTATIC || tag == Opcodes.H_INVOKEVIRTUAL || tag == Opcodes.H_INVOKEINTERFACE;
|
||||
}
|
||||
|
||||
public void complete(String owner, String methodName) {
|
||||
bsmArgs[1] = new Handle(isStatic()? Opcodes.H_INVOKESTATIC : Opcodes.H_INVOKEVIRTUAL, owner, methodName, handleDesc.getDescriptor(), false);
|
||||
}
|
||||
}
|
||||
@@ -63,15 +63,14 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
}
|
||||
|
||||
private byte[] transformMock(byte[] bytes, ClassNode cn) {
|
||||
String className = (GlobalConfig.getMockPackageMapping() == null) ? cn.name : mapPackage(cn.name);
|
||||
try {
|
||||
if (mockClassParser.isMockClass(cn)) {
|
||||
// it's a mock class
|
||||
bytes = new MockClassHandler(className).getBytes(bytes);
|
||||
bytes = new MockClassHandler(cn.name).getBytes(bytes);
|
||||
BytecodeUtil.dumpByte(cn, GlobalConfig.getDumpPath(), bytes);
|
||||
return bytes;
|
||||
}
|
||||
String mockClass = foundMockForSourceClass(className);
|
||||
String mockClass = foundMockForSourceClass(cn.name);
|
||||
if (mockClass != null) {
|
||||
// it's a source class with testable enabled
|
||||
List<MethodInfo> injectMethods = mockClassParser.getTestableMockMethods(mockClass);
|
||||
@@ -92,7 +91,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
LogUtil.error("Invalid mock method %s::%s - %s", e.getClassName(), e.getMethodName(), e.getMessage());
|
||||
System.exit(0);
|
||||
} catch (Throwable t) {
|
||||
LogUtil.warn("Failed to transform class " + className);
|
||||
LogUtil.warn("Failed to transform class " + cn.name);
|
||||
LogUtil.warn(t.toString());
|
||||
LogUtil.warn(ThreadUtil.getFirstRelatedStackLine(t));
|
||||
} finally {
|
||||
@@ -102,18 +101,8 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private String mapPackage(String name) {
|
||||
String dotSeparatedName = ClassUtil.toDotSeparatedName(name);
|
||||
for (String prefix : GlobalConfig.getMockPackageMapping().keySet()) {
|
||||
if (dotSeparatedName.startsWith(prefix)) {
|
||||
return ClassUtil.toSlashSeparatedName(GlobalConfig.getMockPackageMapping().get(prefix))
|
||||
+ name.substring(prefix.length());
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String foundMockForSourceClass(String className) {
|
||||
private String foundMockForSourceClass(String name) {
|
||||
String className = (GlobalConfig.getMockPackageMapping() == null) ? name : mapPackage(name);
|
||||
String mockClass = lookForMockWithAnnotationAsSourceClass(className);
|
||||
if (mockClass != null) {
|
||||
return mockClass;
|
||||
@@ -125,6 +114,17 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
return foundMockForInnerSourceClass(className);
|
||||
}
|
||||
|
||||
private String mapPackage(String name) {
|
||||
String dotSeparatedName = ClassUtil.toDotSeparatedName(name);
|
||||
for (String prefix : GlobalConfig.getMockPackageMapping().keySet()) {
|
||||
if (dotSeparatedName.startsWith(prefix)) {
|
||||
return ClassUtil.toSlashSeparatedName(GlobalConfig.getMockPackageMapping().get(prefix))
|
||||
+ name.substring(prefix.length());
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String foundMockForInnerSourceClass(String className) {
|
||||
return (className.contains(DOLLAR) && !className.endsWith(KOTLIN_POSTFIX_COMPANION)) ?
|
||||
foundMockForTestClass(ClassUtil.getTestClassName(className.substring(0, className.indexOf(DOLLAR)))) : null;
|
||||
|
||||
@@ -6,10 +6,7 @@ import com.alibaba.testable.core.util.LogUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.PROPERTY_TEMP_DIR;
|
||||
import static com.alibaba.testable.agent.constant.ConstPool.PROPERTY_USER_DIR;
|
||||
@@ -114,7 +111,7 @@ public class GlobalConfig {
|
||||
|
||||
public static void addMockPackageMapping(String originPkg, String mockClassPkg) {
|
||||
if (mockPkgMapping == null) {
|
||||
mockPkgMapping = new HashMap<String, String>(5);
|
||||
mockPkgMapping = new LinkedHashMap<String, String>(5);
|
||||
}
|
||||
mockPkgMapping.put(originPkg + DOT, mockClassPkg + DOT);
|
||||
}
|
||||
|
||||
@@ -19,4 +19,15 @@ public class StringUtil {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.6.10</version>
|
||||
<version>0.7.7</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-all</artifactId>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.6.10</version>
|
||||
<version>0.7.7</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-core</artifactId>
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<properties>
|
||||
<junit.version>5.6.2</junit.version>
|
||||
<transmittable.thread.local.version>2.12.1</transmittable.thread.local.version>
|
||||
<transmittable.thread.local.version>2.12.4</transmittable.thread.local.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -227,6 +227,10 @@ public class OmniConstructor {
|
||||
|
||||
private static Object createInstance(Class<?> clazz, Set<Class<?>> classPool)
|
||||
throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
Object ins = createSpecialClass(clazz);
|
||||
if (ins != null) {
|
||||
return ins;
|
||||
}
|
||||
Constructor<?> constructor = getBestConstructor(clazz);
|
||||
if (constructor == null) {
|
||||
throw new ClassConstructionException("Fail to invoke constructor of " + clazz.getName());
|
||||
@@ -244,6 +248,18 @@ public class OmniConstructor {
|
||||
}
|
||||
}
|
||||
|
||||
private static Object createSpecialClass(Class<?> clazz) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
try {
|
||||
// java.net.URL is loaded before OmniClassHandler, cannot be instrumented
|
||||
if (clazz.getName().equals("java.net.URL")) {
|
||||
return clazz.getDeclaredConstructor(String.class).newInstance("https://");
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Constructor<?> getBestConstructor(Class<?> clazz) {
|
||||
Constructor<?> bestConstructor = null;
|
||||
int minimalExceptionCount = 999;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.6.10</version>
|
||||
<version>0.7.7</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-maven-plugin</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.6.10</version>
|
||||
<version>0.7.7</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>testable-parent</name>
|
||||
<description>Unit test enhancement toolkit</description>
|
||||
@@ -42,7 +42,7 @@
|
||||
<plugin.gpg.version>1.6</plugin.gpg.version>
|
||||
<plugin.staging.version>1.6.8</plugin.staging.version>
|
||||
<plugin.maven.version>3.6.0</plugin.maven.version>
|
||||
<testable.version>0.6.10</testable.version>
|
||||
<testable.version>0.7.7</testable.version>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.6.10</version>
|
||||
<version>0.7.7</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-processor</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user