mockit.internal.expectations.invocation.ExpectedInvocation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmockit Show documentation
Show all versions of jmockit Show documentation
JMockit is a Java toolkit for automated developer testing.
It contains mocking/faking APIs and a code coverage tool, supporting both JUnit and TestNG.
The mocking APIs allow all kinds of Java code, without testability restrictions, to be tested
in isolation from selected dependencies.
/*
* Copyright (c) 2006 JMockit developers
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.expectations.invocation;
import java.io.*;
import java.util.*;
import javax.annotation.*;
import mockit.asm.*;
import mockit.internal.expectations.argumentMatching.*;
import mockit.internal.expectations.state.*;
import mockit.internal.reflection.*;
import mockit.internal.state.*;
import mockit.internal.util.*;
import mockit.internal.reflection.GenericTypeReflection.*;
import static mockit.internal.util.TypeDescriptor.getClassForType;
@SuppressWarnings("OverlyComplexClass")
public final class ExpectedInvocation
{
@Nonnull private static final Object UNDEFINED_DEFAULT_RETURN = new Object();
@Nullable public final Object instance;
@Nullable public Object replacementInstance;
public boolean matchInstance;
@Nonnull public final InvocationArguments arguments;
@Nullable private final ExpectationError invocationCause;
@Nullable Object defaultReturnValue;
public ExpectedInvocation(
@Nullable Object mock, @Nonnull String mockedClassDesc, @Nonnull String mockNameAndDesc, @Nullable String genericSignature,
@Nonnull Object[] args
) {
instance = mock;
arguments = new InvocationArguments(0, mockedClassDesc, mockNameAndDesc, genericSignature, args);
invocationCause = null;
defaultReturnValue = determineDefaultReturnValueFromMethodSignature();
}
public ExpectedInvocation(
@Nullable Object mock, int access, @Nonnull String mockedClassDesc, @Nonnull String mockNameAndDesc, boolean matchInstance,
@Nullable String genericSignature, @Nonnull Object[] args
) {
instance = mock;
this.matchInstance = matchInstance;
arguments = new InvocationArguments(access, mockedClassDesc, mockNameAndDesc, genericSignature, args);
invocationCause = new ExpectationError();
defaultReturnValue = determineDefaultReturnValueFromMethodSignature();
}
@Nullable
public AssertionError getInvocationCause() { return invocationCause; }
@Nonnull
private Object determineDefaultReturnValueFromMethodSignature() {
if (instance != null) {
Object rv = ObjectMethods.evaluateOverride(instance, getMethodNameAndDescription(), getArgumentValues());
if (rv != null) {
return rv;
}
}
return UNDEFINED_DEFAULT_RETURN;
}
// Simple getters //////////////////////////////////////////////////////////////////////////////////////////////////
@Nonnull public String getClassDesc() { return arguments.classDesc; }
@Nonnull public String getClassName() { return arguments.getClassName(); }
@Nonnull public String getMethodNameAndDescription() { return arguments.methodNameAndDesc; }
@Nonnull public Object[] getArgumentValues() { return arguments.getValues(); }
public boolean isConstructor() { return arguments.isForConstructor(); }
@Nullable
public Object getRecordedInstance() { return replacementInstance != null ? replacementInstance : instance; }
@Nonnull
public String getSignatureWithResolvedReturnType() {
String signature = arguments.genericSignature;
if (signature != null) {
// TODO: cache it for use in return type conversion, cascading, etc.
String classDesc = getClassDesc();
Class> mockedClass = instance != null ? instance.getClass() : ClassLoad.loadByInternalName(classDesc);
GenericTypeReflection reflection = new GenericTypeReflection(mockedClass, null);
signature = reflection.resolveSignature(classDesc, signature);
char firstTypeChar = signature.charAt(signature.indexOf(')') + 1);
if (firstTypeChar != 'T' && firstTypeChar != '[') {
return signature;
}
}
return arguments.methodNameAndDesc;
}
// Matching based on instance or mocked type ///////////////////////////////////////////////////////////////////////
public boolean isMatch(@Nullable Object mock, @Nonnull String invokedClassDesc, @Nonnull String invokedMethod) {
return
(invokedClassDesc.equals(getClassDesc()) || mock != null && TestRun.mockFixture().isCaptured(mock)) &&
(isMatchingGenericMethod(mock, invokedMethod) || isMatchingMethod(invokedMethod));
}
private boolean isMatchingGenericMethod(@Nullable Object mock, @Nonnull String invokedMethod) {
if (mock != null && instance != null) {
String genericSignature = arguments.genericSignature;
if (genericSignature != null) {
Class> mockedClass = mock.getClass();
if (mockedClass != instance.getClass()) {
GenericTypeReflection typeReflection = new GenericTypeReflection(mockedClass, null);
GenericSignature parsedSignature = typeReflection.parseSignature(genericSignature);
return parsedSignature.satisfiesSignature(invokedMethod);
}
}
}
return false;
}
private boolean isMatchingMethod(@Nonnull String invokedMethod) {
int returnTypeStartPos = getReturnTypePosition(invokedMethod);
if (returnTypeStartPos < 0) {
return false;
}
if (haveSameReturnTypes(invokedMethod, returnTypeStartPos)) {
return true;
}
// At this point the methods are known to differ only in return type, so check if the return type of
// the recorded one is assignable to the return type of the one invoked:
return isReturnTypeOfRecordedMethodAssignableToReturnTypeOfInvokedMethod(invokedMethod, returnTypeStartPos);
}
// Returns -1 if the method names or parameters are different.
private int getReturnTypePosition(@Nonnull String invokedMethod) {
String recordedMethod = getMethodNameAndDescription();
int i = 0;
while (true) {
char c = recordedMethod.charAt(i);
if (c != invokedMethod.charAt(i)) {
return -1;
}
i++;
if (c == ')') {
return i;
}
}
}
private boolean haveSameReturnTypes(@Nonnull String invokedMethod, @Nonnegative int returnTypeStartPos) {
String recordedMethod = getMethodNameAndDescription();
int n = invokedMethod.length();
if (n != recordedMethod.length()) {
return false;
}
int j = returnTypeStartPos;
while (true) {
char c = recordedMethod.charAt(j);
if (c != invokedMethod.charAt(j)) {
return false;
}
j++;
if (j == n) {
return true;
}
}
}
private boolean isReturnTypeOfRecordedMethodAssignableToReturnTypeOfInvokedMethod(
@Nonnull String invokedMethod, @Nonnegative int returnTypeStartPos
) {
String recordedMethod = getMethodNameAndDescription();
JavaType recordedRT = JavaType.getType(recordedMethod.substring(returnTypeStartPos));
JavaType invokedRT = JavaType.getType(invokedMethod.substring(returnTypeStartPos));
return getClassForType(invokedRT).isAssignableFrom(getClassForType(recordedRT));
}
public boolean isMatch(@Nonnull ExpectedInvocation other) {
return isMatch(other.instance, other.getClassDesc(), other.getMethodNameAndDescription(), null);
}
public boolean isMatch(
@Nullable Object replayInstance, @Nonnull String invokedClassDesc, @Nonnull String invokedMethod,
@Nullable Map