Skip to content

Commit

Permalink
Fix crash when real hashCode() is called in mocktionsession (linkedin…
Browse files Browse the repository at this point in the history
…#170)

* Fix crash when real hashCode() is called in mocktionsession

Co-authored-by: Drew Hannay <[email protected]>
  • Loading branch information
Chao Zhang and drewhannay authored Sep 3, 2020
1 parent e4eba55 commit 7be489f
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.android.dx.mockito.inline.extended.tests;

import android.app.PendingIntent;
import android.content.ContentResolver;
import android.provider.Settings;

Expand All @@ -33,8 +34,9 @@
import static org.mockito.ArgumentMatchers.eq;

public class StaticMockitoSession {

@Test
public void strictUnnecessaryStubbing() throws Exception {
public void strictUnnecessaryStubbing() {
MockitoSession session = mockitoSession().spyStatic(Settings.Global.class).startMocking();

// Set up unnecessary stubbing
Expand All @@ -51,7 +53,7 @@ public void strictUnnecessaryStubbing() throws Exception {
}

@Test
public void lenientUnnecessaryStubbing() throws Exception {
public void lenientUnnecessaryStubbing() {
MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
.spyStatic(Settings.Global.class).startMocking();

Expand All @@ -61,4 +63,22 @@ public void lenientUnnecessaryStubbing() throws Exception {

session.finishMocking();
}

@Test
public void spyStatic() {
mockitoSession()
.initMocks(this)
.spyStatic(PendingIntent.class)
.startMocking()
.finishMocking();
}

@Test
public void mockStatic() {
mockitoSession()
.initMocks(this)
.mockStatic(PendingIntent.class)
.startMocking()
.finishMocking();
}
}
2 changes: 1 addition & 1 deletion dexmaker-mockito-inline-extended/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ android {
}

defaultConfig {
minSdkVersion 1
minSdkVersion 9
targetSdkVersion 28
versionName VERSION_NAME
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public final class InlineStaticMockMaker implements MockMaker {
* are modified, some are not. This list helps the {@link MockMethodAdvice} help figure out if a
* object's method calls should be intercepted.
*/
private final HashMap<Object, InvocationHandlerAdapter> markerToHandler = new HashMap<>();
private final Map<Object, InvocationHandlerAdapter> markerToHandler = new MarkerToHandlerMap();
private final Map<Class, Object> classToMarker = new HashMap<>();

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.android.dx.mockito.inline;

import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;

import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* A map for mock marker object -> {@link InvocationHandlerAdapter} but
* does not use the mock marker object as the key directly.
* The problem of not doing so is that the object's real hashCode() and equals() =
* methods will be invoked during
* {@link InlineStaticMockMaker#createMock(MockCreationSettings, MockHandler)}. This poses a
* potential test runtime error depending on the object's hashCode() implementation
*/
class MarkerToHandlerMap implements Map<Object, InvocationHandlerAdapter> {

private final Map<MockMarkerKey, InvocationHandlerAdapter> markerToHandler = new HashMap<>();

@Override
public int size() {
return markerToHandler.size();
}

@Override
public boolean isEmpty() {
return markerToHandler.isEmpty();
}

@Override
public boolean containsKey(Object key) {
return markerToHandler.containsKey(new MockMarkerKey(key));
}

@Override
public boolean containsValue(Object value) {
return markerToHandler.containsValue(value);
}

@Override
public InvocationHandlerAdapter get(Object key) {
return markerToHandler.get(new MockMarkerKey(key));
}

@Override
public InvocationHandlerAdapter put(Object key, InvocationHandlerAdapter value) {
return markerToHandler.put(new MockMarkerKey(key), value);
}

@Override
public InvocationHandlerAdapter remove(Object key) {
return markerToHandler.remove(new MockMarkerKey(key));
}

@Override
public void putAll(Map<?, ? extends InvocationHandlerAdapter> m) {
for (Entry<?, ? extends InvocationHandlerAdapter> entry : m.entrySet()) {
put(new MockMarkerKey(entry.getKey()), entry.getValue());
}
}

@Override
public void clear() {
markerToHandler.clear();
}

@Override
public Set<Object> keySet() {
Set<Object> set = new HashSet<>(entrySet().size());
for (MockMarkerKey key : markerToHandler.keySet()) {
set.add(key.mockMarker);
}
return set;
}

@Override
public Collection<InvocationHandlerAdapter> values() {
return markerToHandler.values();
}

@Override
public Set<Entry<Object, InvocationHandlerAdapter>> entrySet() {
Set<Entry<Object, InvocationHandlerAdapter>> set = new HashSet<>(entrySet().size());
for (Entry<MockMarkerKey, InvocationHandlerAdapter> entry : markerToHandler.entrySet()) {
set.add(new AbstractMap.SimpleImmutableEntry<>(entry.getKey().mockMarker, entry.getValue()));
}
return set;
}

private static class MockMarkerKey {

private final Object mockMarker;

public MockMarkerKey(Object mockMarker) {
this.mockMarker = mockMarker;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

MockMarkerKey mockMarkerKey = (MockMarkerKey) o;

return mockMarker == mockMarkerKey.mockMarker;
}

@Override
public int hashCode() {
return System.identityHashCode(mockMarker);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class MemoryLeaks {
private static final int ARRAY_LENGTH = 1 << 20; // 4 MB

@Test
public void callMethodWithMocksCycalically() {
public void callMethodWithMocksCyclically() {
for (int i = 0; i < 100; ++i) {
final A a = mock(A.class);
a.largeArray = new int[ARRAY_LENGTH];
Expand Down
2 changes: 1 addition & 1 deletion dexmaker-mockito-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ android {
}

defaultConfig {
minSdkVersion 8
minSdkVersion 14
targetSdkVersion 28
versionName VERSION_NAME

Expand Down
2 changes: 1 addition & 1 deletion dexmaker-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ android {

defaultConfig {
applicationId 'com.linkedin.dexmaker'
minSdkVersion 8
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName VERSION_NAME
Expand Down

0 comments on commit 7be489f

Please sign in to comment.