From fd7272b8e5e7d5e5992d6d28ebfa1d172d1916f6 Mon Sep 17 00:00:00 2001 From: Chao Zhang Date: Sun, 30 Aug 2020 11:43:30 -0700 Subject: [PATCH 1/2] Fix crash when real hashCode() is called in mocktionsession --- .../extended/tests/StaticMockitoSession.java | 24 +++- dexmaker-mockito-inline-extended/build.gradle | 2 +- .../mockito/inline/InlineStaticMockMaker.java | 2 +- .../dx/mockito/inline/MarkerToHandlerMap.java | 118 ++++++++++++++++++ .../dx/mockito/inline/tests/MemoryLeaks.java | 2 +- dexmaker-mockito-tests/build.gradle | 2 +- dexmaker-tests/build.gradle | 2 +- 7 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/MarkerToHandlerMap.java diff --git a/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/StaticMockitoSession.java b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/StaticMockitoSession.java index 7c7941be..eff42abf 100644 --- a/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/StaticMockitoSession.java +++ b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/StaticMockitoSession.java @@ -16,6 +16,7 @@ package com.android.dx.mockito.inline.extended.tests; +import android.app.PendingIntent; import android.content.ContentResolver; import android.provider.Settings; @@ -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 @@ -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(); @@ -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(); + } } diff --git a/dexmaker-mockito-inline-extended/build.gradle b/dexmaker-mockito-inline-extended/build.gradle index 0f15f9ae..1417139f 100644 --- a/dexmaker-mockito-inline-extended/build.gradle +++ b/dexmaker-mockito-inline-extended/build.gradle @@ -19,7 +19,7 @@ android { } defaultConfig { - minSdkVersion 1 + minSdkVersion 9 targetSdkVersion 28 versionName VERSION_NAME } diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/InlineStaticMockMaker.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/InlineStaticMockMaker.java index f764e07d..05b067de 100644 --- a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/InlineStaticMockMaker.java +++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/InlineStaticMockMaker.java @@ -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 markerToHandler = new HashMap<>(); + private final Map markerToHandler = new MarkerToHandlerMap(); private final Map classToMarker = new HashMap<>(); /** diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/MarkerToHandlerMap.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/MarkerToHandlerMap.java new file mode 100644 index 00000000..182755ef --- /dev/null +++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/MarkerToHandlerMap.java @@ -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 { + + private final Map 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 m) { + for (Entry entry : m.entrySet()) { + put(new MockMarkerKey(entry.getKey()), entry.getValue()); + } + } + + @Override + public void clear() { + markerToHandler.clear(); + } + + @Override + public Set keySet() { + Set set = new HashSet<>(entrySet().size()); + for (MockMarkerKey key : markerToHandler.keySet()) { + set.add(key.mockMarker); + } + return set; + } + + @Override + public Collection values() { + return markerToHandler.values(); + } + + @Override + public Set> entrySet() { + Set> set = new HashSet<>(entrySet().size()); + for (Entry entry : markerToHandler.entrySet()) { + set.add(new AbstractMap.SimpleImmutableEntry<>(entry.getKey().mockMarker, entry.getValue())); + } + return set; + } + + private static class MockMarkerKey { + + private 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); + } + } +} diff --git a/dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MemoryLeaks.java b/dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MemoryLeaks.java index d78bb10a..55f5893a 100644 --- a/dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MemoryLeaks.java +++ b/dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MemoryLeaks.java @@ -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]; diff --git a/dexmaker-mockito-tests/build.gradle b/dexmaker-mockito-tests/build.gradle index 5172daf8..b4ea3988 100644 --- a/dexmaker-mockito-tests/build.gradle +++ b/dexmaker-mockito-tests/build.gradle @@ -11,7 +11,7 @@ android { } defaultConfig { - minSdkVersion 8 + minSdkVersion 14 targetSdkVersion 28 versionName VERSION_NAME diff --git a/dexmaker-tests/build.gradle b/dexmaker-tests/build.gradle index 59062bfb..ead1ab41 100644 --- a/dexmaker-tests/build.gradle +++ b/dexmaker-tests/build.gradle @@ -6,7 +6,7 @@ android { defaultConfig { applicationId 'com.linkedin.dexmaker' - minSdkVersion 8 + minSdkVersion 14 targetSdkVersion 28 versionCode 1 versionName VERSION_NAME From 6cfc89c6b7f945de33ff6197e700b3bc1f111cd4 Mon Sep 17 00:00:00 2001 From: Chao Zhang Date: Thu, 3 Sep 2020 11:42:38 -0700 Subject: [PATCH 2/2] Update dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/MarkerToHandlerMap.java Co-authored-by: Drew Hannay --- .../java/com/android/dx/mockito/inline/MarkerToHandlerMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/MarkerToHandlerMap.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/MarkerToHandlerMap.java index 182755ef..74a38b87 100644 --- a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/MarkerToHandlerMap.java +++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/MarkerToHandlerMap.java @@ -94,7 +94,7 @@ public Set> entrySet() { private static class MockMarkerKey { - private Object mockMarker; + private final Object mockMarker; public MockMarkerKey(Object mockMarker) { this.mockMarker = mockMarker;