Skip to content

Commit

Permalink
Update to Mockito 2.2.29
Browse files Browse the repository at this point in the history
This commit also changes the next snapshot version to be
2.2.0 instead of 1.6.0. The intention is to make the major
and minor version of dexmaker stay in line with the major
and minor version of Mockito that is supported. Mockito's
patch version is updated with every commit released, and we
may want releases of dexmaker that don't change the version
of Mockito that we depend on so there's not much benefit in
staying in sync with that.

Also worth noting, this commit doesn't do anything to support
mocking final methods. This just fixes dexmaker to work with
Mockito 2.2.x core.
  • Loading branch information
Drew Hannay authored and drewhannay committed Dec 9, 2016
1 parent 6dff28a commit 5d2b790
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 31 deletions.
2 changes: 1 addition & 1 deletion dexmaker-mockito/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ repositories {
dependencies {
compile project(":dexmaker")

compile 'org.mockito:mockito-core:1.10.19'
compile 'org.mockito:mockito-core:2.2.29'
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,28 @@
package com.android.dx.mockito;

import com.android.dx.stock.ProxyBuilder;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Set;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.exceptions.stacktrace.StackTraceCleaner;
import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;
import org.mockito.plugins.MockMaker;
import org.mockito.plugins.StackTraceCleanerProvider;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Set;

/**
* Generates mock instances on Android's runtime.
*/
public final class DexmakerMockMaker implements MockMaker, StackTraceCleanerProvider {
private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();

@Override
public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
Class<T> typeToMock = settings.getTypeToMock();
Set<Class> interfacesSet = settings.getExtraInterfaces();
Set<Class<?>> interfacesSet = settings.getExtraInterfaces();
Class<?>[] extraInterfaces = interfacesSet.toArray(new Class[interfacesSet.size()]);
InvocationHandler invocationHandler = new InvocationHandlerAdapter(handler);

Expand All @@ -45,9 +47,9 @@ public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
Class[] classesToMock = new Class[extraInterfaces.length + 1];
classesToMock[0] = typeToMock;
System.arraycopy(extraInterfaces, 0, classesToMock, 1, extraInterfaces.length);
@SuppressWarnings("unchecked") // newProxyInstance returns the type of typeToMock
T mock = (T) Proxy.newProxyInstance(typeToMock.getClassLoader(),
classesToMock, invocationHandler);
// newProxyInstance returns the type of typeToMock
@SuppressWarnings("unchecked")
T mock = (T) Proxy.newProxyInstance(typeToMock.getClassLoader(), classesToMock, invocationHandler);
return mock;

} else {
Expand All @@ -67,16 +69,54 @@ public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
}
}

@Override
public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
adapter.setHandler(newHandler);
}

@Override
public TypeMockability isTypeMockable(final Class<?> type) {
return new TypeMockability() {
@Override
public boolean mockable() {
return !type.isPrimitive() && !Modifier.isFinal(type.getModifiers());
}

@Override
public String nonMockableReason() {
if (type.isPrimitive()) {
return "primitive type";
}

if (Modifier.isFinal(type.getModifiers())) {
return "final or anonymous class";
}

return "not handled type";
}
};
}

@Override
public MockHandler getHandler(Object mock) {
InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
return adapter != null ? adapter.getHandler() : null;
}

@Override
public StackTraceCleaner getStackTraceCleaner(final StackTraceCleaner defaultCleaner) {
return new StackTraceCleaner() {
@Override
public boolean isIn(StackTraceElement candidate) {
return defaultCleaner.isIn(candidate)
&& !candidate.getClassName().endsWith("_Proxy") // dexmaker class proxies
&& !candidate.getClassName().startsWith("$Proxy") // dalvik interface proxies
&& !candidate.getClassName().startsWith("com.google.dexmaker.mockito.");
}
};
}

private InvocationHandlerAdapter getInvocationHandlerAdapter(Object mock) {
if (mock == null) {
return null;
Expand All @@ -97,15 +137,4 @@ private InvocationHandlerAdapter getInvocationHandlerAdapter(Object mock) {

return null;
}

public StackTraceCleaner getStackTraceCleaner(final StackTraceCleaner defaultCleaner) {
return new StackTraceCleaner() {
public boolean isOut(StackTraceElement candidate) {
return defaultCleaner.isOut(candidate)
|| candidate.getClassName().endsWith("_Proxy") // dexmaker class proxies
|| candidate.getClassName().startsWith("$Proxy") // dalvik interface proxies
|| candidate.getClassName().startsWith("com.google.dexmaker.mockito.");
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,41 @@
package com.android.dx.mockito;

import com.android.dx.stock.ProxyBuilder;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.mockito.internal.debugging.LocationImpl;
import org.mockito.internal.invocation.InvocationImpl;
import org.mockito.internal.invocation.MockitoMethod;
import org.mockito.internal.invocation.realmethod.RealMethod;
import org.mockito.internal.progress.SequenceNumber;
import org.mockito.internal.util.ObjectMethodsGuru;
import org.mockito.invocation.MockHandler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
* Handles proxy method invocations to dexmaker's InvocationHandler by calling
* a MockitoInvocationHandler.
*/
final class InvocationHandlerAdapter implements InvocationHandler {
private MockHandler handler;
private final ObjectMethodsGuru objectMethodsGuru = new ObjectMethodsGuru();

public InvocationHandlerAdapter(MockHandler handler) {
this.handler = handler;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (objectMethodsGuru.isEqualsMethod(method)) {
// args can be null if the method invoked has no arguments, but Mockito expects a non-null array
args = args != null ? args : new Object[0];
if (isEqualsMethod(method)) {
return proxy == args[0];
} else if (objectMethodsGuru.isHashCodeMethod(method)) {
} else if (isHashCodeMethod(method)) {
return System.identityHashCode(proxy);
}

ProxiedMethod proxiedMethod = new ProxiedMethod(method);
return handler.handle(new InvocationImpl(proxy, proxiedMethod, args, SequenceNumber.next(),
proxiedMethod));
proxiedMethod, new LocationImpl()));
}

public MockHandler getHandler() {
Expand All @@ -60,41 +62,60 @@ public void setHandler(MockHandler handler) {
this.handler = handler;
}

private static boolean isEqualsMethod(Method method) {
return method.getName().equals("equals")
&& method.getParameterTypes().length == 1
&& method.getParameterTypes()[0] == Object.class;
}

private static boolean isHashCodeMethod(Method method) {
return method.getName().equals("hashCode")
&& method.getParameterTypes().length == 0;
}

private static class ProxiedMethod implements MockitoMethod, RealMethod {
private final Method method;

public ProxiedMethod(Method method) {
ProxiedMethod(Method method) {
this.method = method;
}

@Override
public String getName() {
return method.getName();
}

@Override
public Class<?> getReturnType() {
return method.getReturnType();
}

@Override
public Class<?>[] getParameterTypes() {
return method.getParameterTypes();
}

@Override
public Class<?>[] getExceptionTypes() {
return method.getExceptionTypes();
}

@Override
public boolean isVarArgs() {
return method.isVarArgs();
}

@Override
public Method getJavaMethod() {
return method;
}

@Override
public Object invoke(Object target, Object[] arguments) throws Throwable {
return ProxyBuilder.callSuper(target, method, arguments);
}

@Override
public boolean isAbstract() {
return Modifier.isAbstract(method.getModifiers());
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ org.gradle.jvmargs=-Djava.awt.headless=true
# org.gradle.parallel=true

GROUP_ID=com.linkedin.dexmaker
VERSION_NAME=1.6.0-SNAPSHOT
VERSION_NAME=2.2.0-SNAPSHOT

0 comments on commit 5d2b790

Please sign in to comment.