mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-07 19:00:45 +08:00
Merge pull request #234 from zcbbpo/FIXED-LAMBDA-MR
[Lambda] Fixed method reference external var
This commit is contained in:
commit
397fc3d2fa
@ -0,0 +1,243 @@
|
||||
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 jimca
|
||||
*/
|
||||
@SuppressWarnings({"WrapperTypeMayBePrimitive", "ResultOfMethodCallIgnored", "MismatchedReadAndWriteOfArray", "unused"})
|
||||
public class ExternalLambdaDemo {
|
||||
|
||||
public void string1() {
|
||||
String s = "";
|
||||
consumesFunction2(s::contains);
|
||||
}
|
||||
|
||||
public void string2() {
|
||||
String s = "";
|
||||
consumesFunction2(s::charAt);
|
||||
|
||||
}
|
||||
|
||||
public void string3() {
|
||||
String s = "";
|
||||
consumesFunction0(s::notify);
|
||||
}
|
||||
|
||||
public void byte1() {
|
||||
Byte s = 1;
|
||||
consumesSupplier(s::floatValue);
|
||||
}
|
||||
|
||||
public void byte2() {
|
||||
Byte s = 1;
|
||||
consumesFunction2(s::compareTo);
|
||||
}
|
||||
|
||||
public void byte3() {
|
||||
Byte s = 1;
|
||||
consumesFunction0(s::notify);
|
||||
}
|
||||
|
||||
public void char1() {
|
||||
Character s = 1;
|
||||
consumesFunction0(s::toString);
|
||||
}
|
||||
|
||||
public void char2() {
|
||||
Character s = 1;
|
||||
consumesFunction2(s::compareTo);
|
||||
}
|
||||
|
||||
public void char3() {
|
||||
Character s = 1;
|
||||
consumesFunction0(s::notify);
|
||||
}
|
||||
|
||||
public void short1() {
|
||||
Short s = 1;
|
||||
consumesFunction0(s::toString);
|
||||
}
|
||||
|
||||
public void short2() {
|
||||
Short s = 1;
|
||||
consumesFunction2(s::compareTo);
|
||||
}
|
||||
|
||||
public void short3() {
|
||||
Short s = 1;
|
||||
consumesFunction0(s::notify);
|
||||
}
|
||||
|
||||
public void int1() {
|
||||
Integer s = 1;
|
||||
consumesFunction0(s::toString);
|
||||
}
|
||||
|
||||
public void int2() {
|
||||
Integer s = 1;
|
||||
consumesFunction2(s::compareTo);
|
||||
}
|
||||
|
||||
public void int3() {
|
||||
Integer s = 1;
|
||||
consumesFunction0(s::notify);
|
||||
}
|
||||
|
||||
public void long1() {
|
||||
Long s = 1L;
|
||||
consumesFunction0(s::toString);
|
||||
}
|
||||
|
||||
public void long2() {
|
||||
Long s = 1L;
|
||||
consumesFunction2(s::compareTo);
|
||||
}
|
||||
|
||||
public void long3() {
|
||||
Long s = 1L;
|
||||
consumesFunction0(s::notify);
|
||||
}
|
||||
|
||||
public void float1() {
|
||||
Float s = 1f;
|
||||
consumesFunction0(s::toString);
|
||||
}
|
||||
|
||||
public void float2() {
|
||||
Float s = 1f;
|
||||
consumesFunction2(s::compareTo);
|
||||
}
|
||||
|
||||
public void float3() {
|
||||
Float s = 1f;
|
||||
consumesFunction0(s::notify);
|
||||
}
|
||||
|
||||
public void double1() {
|
||||
Double s = 1d;
|
||||
consumesFunction0(s::toString);
|
||||
}
|
||||
|
||||
public void double2() {
|
||||
Double s = 1d;
|
||||
consumesFunction2(s::compareTo);
|
||||
}
|
||||
|
||||
public void double3() {
|
||||
Double s = 1d;
|
||||
consumesFunction0(s::notify);
|
||||
}
|
||||
|
||||
public void bool1() {
|
||||
Boolean s = true;
|
||||
consumesFunction0(s::toString);
|
||||
}
|
||||
|
||||
public void bool2() {
|
||||
Boolean s = true;
|
||||
consumesFunction2(s::compareTo);
|
||||
}
|
||||
|
||||
public void bool3() {
|
||||
Boolean s = true;
|
||||
consumesFunction0(s::notify);
|
||||
}
|
||||
|
||||
public void stringArray1() {
|
||||
String[] array = new String[]{""};
|
||||
consumesFunction0(array::toString);
|
||||
}
|
||||
|
||||
public void stringArray2() {
|
||||
String[] array = new String[]{""};
|
||||
consumesFunction2(array::equals);
|
||||
}
|
||||
|
||||
public void stringArray3() {
|
||||
String[] array = new String[]{""};
|
||||
consumesFunction0(array::notify);
|
||||
}
|
||||
|
||||
public void intArray1() {
|
||||
int[] array = new int[]{1};
|
||||
consumesFunction0(array::toString);
|
||||
}
|
||||
|
||||
public void intArray2() {
|
||||
int[] array = new int[]{1};
|
||||
consumesFunction2(array::equals);
|
||||
}
|
||||
|
||||
public void intArray3() {
|
||||
int[] array = new int[]{1};
|
||||
consumesFunction0(array::notify);
|
||||
}
|
||||
|
||||
|
||||
public void mul() {
|
||||
String s = "";
|
||||
consumesTwoFunction2(s::contains, s::contains);
|
||||
}
|
||||
|
||||
|
||||
public void externalClass() {
|
||||
LambdaDemo lambdaDemo = new LambdaDemo();
|
||||
consumesFunction0(lambdaDemo::methodReference0);
|
||||
}
|
||||
|
||||
|
||||
public void interClass() {
|
||||
A a = new A();
|
||||
consumesFunction2(a::m1);
|
||||
consumesFunction2(a::m2);
|
||||
}
|
||||
|
||||
public void function3() {
|
||||
ExternalLambdaDemo externalLambdaDemo = new ExternalLambdaDemo();
|
||||
consumesFunction3(externalLambdaDemo::f3);
|
||||
}
|
||||
|
||||
public Boolean f3(String s1, Long l) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void consumesFunction0(Runnable f) {
|
||||
f.run();
|
||||
}
|
||||
|
||||
private <T> void consumesFunction1(Consumer<T> f) {
|
||||
f.accept(null);
|
||||
}
|
||||
|
||||
private <T, R> void consumesFunction2(Function<T, R> f) {
|
||||
f.apply(null);
|
||||
}
|
||||
|
||||
private <T1, T2, R> void consumesFunction3(BiFunction<T1, T2, R> f) {
|
||||
f.apply(null, null);
|
||||
}
|
||||
|
||||
private <T> void consumesSupplier(Supplier<T> supplier) {
|
||||
supplier.get();
|
||||
}
|
||||
|
||||
private <T, R> void consumesTwoFunction2(Function<T, R> f1, Function<T, R> f2) {
|
||||
f1.apply(null);
|
||||
f2.apply(null);
|
||||
}
|
||||
|
||||
public static class A {
|
||||
public String m1(int i) {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String m2(Integer i) {
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package com.alibaba.demo.lambda;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockDiagnose;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import com.alibaba.testable.core.model.LogLevel;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
|
||||
|
||||
/**
|
||||
* @author zcbbpo
|
||||
*/
|
||||
public class ExternalLambdaDemoTest {
|
||||
private final ExternalLambdaDemo lambdaDemo = new ExternalLambdaDemo();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@MockDiagnose(LogLevel.VERBOSE)
|
||||
public static class Mock {
|
||||
|
||||
@MockMethod(targetClass = String.class, targetMethod = "contains")
|
||||
public boolean mockContains(CharSequence s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = Byte.class, targetMethod = "floatValue")
|
||||
public float mockFloatValue() {
|
||||
return 0.1f;
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = Double.class, targetMethod = "compareTo")
|
||||
public int mockCompareTo(Double anotherDouble) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = LambdaDemo.class, targetMethod = "methodReference0")
|
||||
public String mockMethodReference0() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = ExternalLambdaDemo.class, targetMethod = "f3")
|
||||
public Boolean mockF3(String s1, Long l) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldMockString1() {
|
||||
lambdaDemo.string1();
|
||||
verify("mockContains").withTimes(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMockByte1() {
|
||||
lambdaDemo.byte1();
|
||||
verify("mockFloatValue").withTimes(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMockDouble2() {
|
||||
lambdaDemo.double2();
|
||||
verify("mockCompareTo").withTimes(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMul() {
|
||||
lambdaDemo.mul();
|
||||
verify("mockContains").withTimes(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalClass() {
|
||||
lambdaDemo.externalClass();
|
||||
verify("mockMethodReference0").withTimes(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFunction3() {
|
||||
lambdaDemo.function3();
|
||||
verify("mockF3").withTimes(1);
|
||||
}
|
||||
|
||||
}
|
@ -12,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
* @author zcbbpo
|
||||
*/
|
||||
public class LambdaDemoTest {
|
||||
private LambdaDemo lambdaDemo = new LambdaDemo();
|
||||
private final LambdaDemo lambdaDemo = new LambdaDemo();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@MockDiagnose(LogLevel.VERBOSE)
|
||||
|
@ -369,6 +369,7 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
|
||||
// process for method reference
|
||||
for (Handle handle : invokeDynamicList) {
|
||||
// the jdk auto generation method
|
||||
if (handle.getName().startsWith("lambda$")) {
|
||||
continue;
|
||||
}
|
||||
@ -379,7 +380,15 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
// lambda new method reference
|
||||
continue;
|
||||
}
|
||||
boolean isStatic = tag == Opcodes.H_INVOKESTATIC;
|
||||
|
||||
// external mean:
|
||||
// public void foo() {
|
||||
// String s = "";
|
||||
// consumes(s::contains);
|
||||
//}
|
||||
boolean external = tag == Opcodes.H_INVOKEVIRTUAL;
|
||||
|
||||
boolean isStatic = tag == Opcodes.H_INVOKESTATIC || external;
|
||||
|
||||
String desc = handle.getDesc();
|
||||
String parameters = desc.substring(desc.indexOf("(") + 1, desc.lastIndexOf(")"));
|
||||
@ -391,8 +400,15 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
len = external ? len + 1 : len;
|
||||
|
||||
String[] refineParameterArray = new String[len];
|
||||
int index = 0;
|
||||
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;
|
||||
@ -400,14 +416,16 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
}
|
||||
}
|
||||
|
||||
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, desc, null, null);
|
||||
|
||||
|
||||
MethodVisitor mv = cn.visitMethod(isStatic ? ACC_PUBLIC + ACC_STATIC : ACC_PUBLIC, lambdaName, external ? externalDesc : desc, null, null);
|
||||
mv.visitCode();
|
||||
|
||||
Label l0 = new Label();
|
||||
mv.visitLabel(l0);
|
||||
if (!isStatic) {
|
||||
// add this
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
}
|
||||
for (int i = 0; i < refineParameterArray.length; i++) {
|
||||
@ -415,19 +433,19 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
mv.visitVarInsn(getLoadType(arg), isStatic ? i : i + 1);
|
||||
}
|
||||
|
||||
mv.visitMethodInsn(isStatic ? INVOKESTATIC : INVOKEVIRTUAL/*INVOKESPECIAL*/, handle.getOwner(), handle.getName(), desc, false);
|
||||
mv.visitMethodInsn(isStatic ? INVOKESTATIC : INVOKEVIRTUAL, handle.getOwner(), handle.getName(), desc, false);
|
||||
|
||||
mv.visitInsn(getReturnType(returnType));
|
||||
|
||||
Label l1 = new Label();
|
||||
mv.visitLabel(l1);
|
||||
|
||||
String localVarOwner = handle.getOwner();
|
||||
|
||||
// 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 + ";";
|
||||
}
|
||||
|
||||
@ -435,10 +453,11 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
continue;
|
||||
}
|
||||
|
||||
// add local var
|
||||
mv.visitLocalVariable(String.format("o%d", i), localVar, null, l0, l1, i);
|
||||
}
|
||||
} else {
|
||||
mv.visitLocalVariable("this", "L" + localVarOwner + ";", null, l0, l1, 0);
|
||||
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)) {
|
||||
@ -456,15 +475,36 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(";");
|
||||
}
|
||||
}
|
||||
sb.append(")");
|
||||
sb.append(returnType);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean isPrimitive(String type) {
|
||||
if (type.endsWith(";")) {
|
||||
|
Loading…
Reference in New Issue
Block a user