blob: f3f89d845894b3959d3d46d974a9dfcca95ee65f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Brad Reynolds (bug 146435)
* Jeanderson Candido <http://jeandersonbc.github.io> - Bug 444070
*******************************************************************************/
package org.eclipse.ui.tests.harness.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import junit.framework.AssertionFailedError;
/**
* Utility class for creating mock objects for public interfaces.
*
* @since 3.3
*
*/
public class Mocks {
public static interface EqualityComparator {
public boolean equals(Object o1, Object o2);
}
private static EqualityComparator defaultEqualityComparator = (o1, o2) -> o1 == null ? o2 == null : o1.equals(o2);
private static EqualityComparator indifferentEqualityComparator = (o1, o2) -> true;
private static interface Mock {
public MockInvocationHandler getMockInvocationHandler();
}
private static Method getMockInvocationHandlerMethod;
private static Method equalsMethod;
static {
try {
getMockInvocationHandlerMethod = Mock.class.getMethod("getMockInvocationHandler");
equalsMethod = Object.class.getMethod("equals", Object.class);
} catch (Exception e) {
// ignore, will lead to NullPointerExceptions later on
}
}
private static final class MockInvocationHandler implements
InvocationHandler {
private class MethodCall {
private final Method method;
private final Object[] args;
private Object returnValue = null;
public MethodCall(Method method, Object[] args) {
this.method = method;
this.args = args;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MethodCall)) {
return false;
}
MethodCall other = (MethodCall) obj;
if (other.method != method
|| (other.args == null && args != null)
|| (other.args != null && args == null)
|| (args != null && other.args.length != args.length)) {
return false;
}
if (args != null) {
for (int i = 0; i < args.length; i++) {
if (!equalityComparator.equals(args[i], other.args[i])) {
return false;
}
}
}
return true;
}
public void setReturnValue(Object object) {
returnValue = object;
}
public Object getReturnValue() {
return returnValue;
}
@Override
public String toString() {
return method.toString();
}
}
List<MethodCall> previousCallHistory = null;
List<MethodCall> currentCallHistory = new ArrayList<>();
private final boolean ordered;
private final EqualityComparator equalityComparator;
public MockInvocationHandler(boolean ordered,
EqualityComparator equalityComparator) {
this.ordered = ordered;
this.equalityComparator = equalityComparator;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (getMockInvocationHandlerMethod.equals(method)) {
return this;
}
if (equalsMethod.equals(method)) {
return Boolean.valueOf(proxy == args[0]);
}
MethodCall methodCall = new MethodCall(method, args);
if (previousCallHistory != null) {
// we are in replay mode
int indexOfMethodCall = previousCallHistory.indexOf(methodCall);
if (indexOfMethodCall != -1) {
// copy return value over to this method call
methodCall.setReturnValue(previousCallHistory
.get(indexOfMethodCall).getReturnValue());
} else {
throw new AssertionFailedError("unexpected method call: "
+ method.getName());
}
if (ordered) {
if (previousCallHistory.size() <= currentCallHistory.size()) {
throw new AssertionFailedError("extra method call: "
+ method.getName());
}
MethodCall previousCall = previousCallHistory
.get(currentCallHistory.size());
if (!methodCall.equals(previousCall)) {
throw new AssertionFailedError(
"different method call (expected:"
+ previousCall.method.getName()
+ ", actual:" + method.getName() + ")");
}
}
}
currentCallHistory.add(methodCall);
Class<?> returnType = method.getReturnType();
if (returnType.isPrimitive() && void.class != returnType) {
Object result = null;
Object returnValue = methodCall.getReturnValue();
if (returnType == boolean.class) {
result = (returnValue != null) ? (Boolean) returnValue
: Boolean.FALSE;
} else if (returnType == byte.class) {
result = (returnValue != null) ? (Byte) returnValue
: Byte.valueOf((byte) 0);
} else if (returnType == char.class) {
result = (returnValue != null) ? (Character) returnValue
: Character.valueOf((char) 0);
} else if (returnType == short.class) {
result = (returnValue != null) ? (Short) returnValue
: Short.valueOf((short) 0);
} else if (returnType == int.class) {
result = (returnValue != null) ? (Integer) returnValue
: Integer.valueOf(0);
} else if (returnType == long.class) {
result = (returnValue != null) ? (Long) returnValue
: Long.valueOf(0);
} else if (returnType == float.class) {
result = (returnValue != null) ? (Float) returnValue
: Float.valueOf(0);
} else if (returnType == double.class) {
result = (returnValue != null) ? (Double) returnValue
: Double.valueOf(0);
}
return result;
}
return methodCall.getReturnValue();
}
public void replay() {
previousCallHistory = currentCallHistory;
currentCallHistory = new ArrayList<>();
}
public void verify() {
if (previousCallHistory == null) {
if (currentCallHistory.isEmpty()) {
// mock object was not used at all
return;
}
throw new AssertionFailedError("unexpected");
}
if (ordered) {
int numMissingCalls = previousCallHistory.size()
- currentCallHistory.size();
if (numMissingCalls > 0) {
throw new AssertionFailedError("missing method calls ("
+ numMissingCalls + ", first is: " + previousCallHistory.get(currentCallHistory.size()) + ")");
}
for (int i = 0; i < previousCallHistory.size(); i++) {
if (!previousCallHistory.get(i).equals(
currentCallHistory.get(i))) {
throw new AssertionFailedError(
"method call did not match (" + i + " of "
+ currentCallHistory.size() + ")");
}
}
} else {
for (MethodCall methodCall : previousCallHistory) {
if (!currentCallHistory.contains(methodCall)) {
throw new AssertionFailedError("missing method call:"
+ methodCall.method.getName());
}
}
}
reset();
}
public void reset() {
previousCallHistory = null;
currentCallHistory = new ArrayList<>();
}
public void setLastReturnValue(Object object) {
MethodCall methodCall = currentCallHistory
.get(currentCallHistory.size() - 1);
methodCall.setReturnValue(object);
}
}
/**
* Creates a mock object that neither looks at the order of method calls nor
* at the arguments.
*
* @param interfaceType
* @return a mock object that checks for the order of method invocations but
* not for equality of method arguments
*/
public static <T> Object createRelaxedMock(Class<T> interfaceType) {
return createMock(interfaceType, false, indifferentEqualityComparator);
}
/**
* Creates a mock object that does not look at the arguments, but checks
* that the order of calls is as expected.
*
* @param interfaceType
* @return a mock object that checks for the order of method invocations but
* not for equality of method arguments
*/
public static <T> Object createOrderedMock(Class<T> interfaceType) {
return createMock(interfaceType, true, indifferentEqualityComparator);
}
/**
* creates a fussy mock object
*
* @param interfaceType
* @return a mock object that checks for the order of method invocations and
* for equality of method arguments
*/
public static <T> Object createMock(Class<T> interfaceType) {
return createMock(interfaceType, true, defaultEqualityComparator);
}
/**
* creates a fussy mock object with a comparator
*
* @param interfaceType
* @return a mock object that checks for the order of method invocations and
* uses the given comparator to compare method arguments
*/
public static <T> Object createMock(Class<T> interfaceType,
EqualityComparator equalityComparator) {
return createMock(interfaceType, true, equalityComparator);
}
private static <T> Object createMock(Class<T> interfaceType, boolean ordered,
EqualityComparator equalityComparator) {
if (!interfaceType.isInterface()) {
throw new IllegalArgumentException();
}
MockInvocationHandler mockInvocationHandler = new MockInvocationHandler(
ordered, equalityComparator);
Object newProxyInstance = Proxy.newProxyInstance(Mocks.class
.getClassLoader(), new Class[] { interfaceType, Mock.class },
mockInvocationHandler);
return newProxyInstance;
}
public static void startChecking(Object mock) {
getMockInvocationHandler(mock).replay();
}
public static void verify(Object mock) {
getMockInvocationHandler(mock).verify();
}
public static void reset(Object mock) {
getMockInvocationHandler(mock).reset();
}
private static MockInvocationHandler getMockInvocationHandler(Object mock) {
return ((Mock) mock).getMockInvocationHandler();
}
public static void setLastReturnValue(Object mock, Object object) {
getMockInvocationHandler(mock).setLastReturnValue(object);
}
}