method invocation could contain LabelNode and LineNumberNode

This commit is contained in:
金戟 2020-12-15 11:23:17 +08:00
parent 179e71c7c2
commit 28bde0b85a
4 changed files with 56 additions and 28 deletions

View File

@ -41,8 +41,24 @@ class DemoMatcher {
val longArray = arrayOf(1L, 2L)
methodToBeMocked(1, 2)
methodToBeMocked(1L, 2.0)
// below two invocations are equivalent
methodToBeMocked(listOf(1), setOf(1.0f))
// multiple lines method invocation
methodToBeMocked(object : ArrayList<Int?>() {
init {
add(1)
}
}, object : HashSet<Float?>() {
init {
add(1.0f)
}
})
// below two invocations are equivalent
methodToBeMocked(1.0, mapOf(1 to 1.0f))
methodToBeMocked(1.0, object : HashMap<Int?, Float?>(2) { init { put(1, 1.0f) } })
methodToBeMocked(floatList, floatList)
methodToBeMocked(longArray)
methodToBeMocked(arrayOf(1.0, 2.0))

View File

@ -44,8 +44,8 @@ internal class DemoMatcherTest {
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.anyInt(), 2)
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.anyLong(), InvokeMatcher.anyNumber())
// Note: Must use `::class.javaObjectType` for primary types check in Kotlin
InvokeVerifier.verify("methodWithArguments").with(1.0, InvokeMatcher.anyMapOf(Int::class.javaObjectType, Float::class.javaObjectType))
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyList(), InvokeMatcher.anySetOf(Float::class.javaObjectType))
InvokeVerifier.verify("methodWithArguments").with(1.0, InvokeMatcher.anyMapOf(Int::class.javaObjectType, Float::class.javaObjectType)).times(2)
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyList(), InvokeMatcher.anySetOf(Float::class.javaObjectType)).times(2)
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyList(), InvokeMatcher.anyListOf(Float::class.javaObjectType))
InvokeVerifier.verify("methodWithArrayArgument").with(InvokeMatcher.anyArrayOf(Long::class.javaObjectType))
InvokeVerifier.verify("methodWithArrayArgument").with(InvokeMatcher.anyArray())
@ -70,12 +70,12 @@ internal class DemoMatcherTest {
@Test
fun should_match_with_times() {
demoMatcher.callMethodWithNumberArguments()
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(3)
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(4)
demoMatcher.callMethodWithNumberArguments()
var gotError = false
try {
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(4)
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(5)
} catch (e: VerifyFailedError) {
gotError = true
}

View File

@ -2,8 +2,6 @@ package com.alibaba.testable.demo
import com.alibaba.testable.core.annotation.MockConstructor
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.annotation.MockWith
import com.alibaba.testable.core.model.MockDiagnose
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import java.util.*
@ -12,7 +10,6 @@ import java.util.*
* 演示模板方法可以被Mock
* Demonstrate template method can be mocked
*/
@MockWith(diagnose = MockDiagnose.ENABLE)
internal class DemoTemplateTest {
private val demoTemplate = DemoTemplate()

View File

@ -67,6 +67,8 @@ public class SourceClassHandler extends BaseClassHandler {
instructions = replaceMemberCallOps(cn, mn, memberInjectMethodName, instructions,
node.owner, node.getOpcode(), rangeStart, i);
i = rangeStart;
} else {
LogUtil.warn("Potential missed mocking at %s:%s", mn.name, getLineNum(instructions, i));
}
} else if (ConstPool.CONSTRUCTOR.equals(node.name)) {
// it's a new operation
@ -119,28 +121,9 @@ public class SourceClassHandler extends BaseClassHandler {
}
private int getMemberMethodStart(AbstractInsnNode[] instructions, int rangeEnd) {
int stackLevel = ClassUtil.getParameterTypes(((MethodInsnNode)instructions[rangeEnd]).desc).size();
int stackLevel = getInitialStackLevel((MethodInsnNode)instructions[rangeEnd]);
for (int i = rangeEnd - 1; i >= 0; i--) {
switch (instructions[i].getOpcode()) {
case Opcodes.INVOKESPECIAL:
case Opcodes.INVOKEVIRTUAL:
case Opcodes.INVOKEINTERFACE:
stackLevel += stackEffectOfInvocation(instructions[i]) + 1;
if (((MethodInsnNode)instructions[i]).name.equals(ConstPool.CONSTRUCTOR)) {
// constructor must be INVOKESPECIAL and implicitly pop 1 more stack
stackLevel++;
}
break;
case Opcodes.INVOKESTATIC:
case Opcodes.INVOKEDYNAMIC:
stackLevel += stackEffectOfInvocation(instructions[i]);
break;
case -1:
// reach LineNumberNode or LabelNode
return i + 1;
default:
stackLevel -= BytecodeUtil.stackEffect(instructions[i].getOpcode());
}
stackLevel += getStackLevelChange(instructions[i]);
if (stackLevel < 0) {
return i;
}
@ -148,6 +131,38 @@ public class SourceClassHandler extends BaseClassHandler {
return -1;
}
private int getInitialStackLevel(MethodInsnNode instruction) {
int stackLevel = ClassUtil.getParameterTypes((instruction).desc).size();
switch (instruction.getOpcode()) {
case Opcodes.INVOKESPECIAL:
case Opcodes.INVOKEVIRTUAL:
case Opcodes.INVOKEINTERFACE:
return stackLevel;
case Opcodes.INVOKESTATIC:
case Opcodes.INVOKEDYNAMIC:
return stackLevel - 1;
default:
return 0;
}
}
private int getStackLevelChange(AbstractInsnNode instruction) {
switch (instruction.getOpcode()) {
case Opcodes.INVOKESPECIAL:
case Opcodes.INVOKEVIRTUAL:
case Opcodes.INVOKEINTERFACE:
return stackEffectOfInvocation(instruction) + 1;
case Opcodes.INVOKESTATIC:
case Opcodes.INVOKEDYNAMIC:
return stackEffectOfInvocation(instruction);
case -1:
// either LabelNode or LineNumberNode
return 0;
default:
return -BytecodeUtil.stackEffect(instruction.getOpcode());
}
}
private int stackEffectOfInvocation(AbstractInsnNode instruction) {
String desc = ((MethodInsnNode)instruction).desc;
return ClassUtil.getParameterTypes(desc).size() - (ClassUtil.getReturnType(desc).isEmpty() ? 0 : 1);