Skip to content

Commit 8564b43

Browse files
jselboJoshua Selbo
andauthored
Fix primitives support in GenericArrayReturnType for Android (#3753)
Fixes #3752 --------- Co-authored-by: Joshua Selbo <[email protected]>
1 parent bf3a809 commit 8564b43

4 files changed

Lines changed: 47 additions & 7 deletions

File tree

mockito-core/src/main/java/org/mockito/internal/util/Primitives.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public final class Primitives {
1414
private static final Map<Class<?>, Object> PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES =
1515
new HashMap<>();
1616

17+
/** Older JVM environments do not have {@link Class#descriptorString()}, so build our own mapping. */
18+
private static final Map<Class<?>, Character> PRIMITIVE_DESCRIPTORS = new HashMap<>();
19+
1720
/**
1821
* Returns the primitive type of the given class.
1922
* <p/>
@@ -61,6 +64,11 @@ public static <T> T defaultValue(Class<T> primitiveOrWrapperType) {
6164
return (T) PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.get(primitiveOrWrapperType);
6265
}
6366

67+
/** Returns the code corresponding to the given primitive type according to JVMS 4.3.2. */
68+
public static char getPrimitiveDescriptor(Class<?> primitiveType) {
69+
return PRIMITIVE_DESCRIPTORS.get(primitiveType);
70+
}
71+
6472
static {
6573
PRIMITIVE_TYPES.put(Boolean.class, Boolean.TYPE);
6674
PRIMITIVE_TYPES.put(Character.class, Character.TYPE);
@@ -70,6 +78,15 @@ public static <T> T defaultValue(Class<T> primitiveOrWrapperType) {
7078
PRIMITIVE_TYPES.put(Long.class, Long.TYPE);
7179
PRIMITIVE_TYPES.put(Float.class, Float.TYPE);
7280
PRIMITIVE_TYPES.put(Double.class, Double.TYPE);
81+
82+
PRIMITIVE_DESCRIPTORS.put(boolean.class, 'Z');
83+
PRIMITIVE_DESCRIPTORS.put(char.class, 'C');
84+
PRIMITIVE_DESCRIPTORS.put(byte.class, 'B');
85+
PRIMITIVE_DESCRIPTORS.put(short.class, 'S');
86+
PRIMITIVE_DESCRIPTORS.put(int.class, 'I');
87+
PRIMITIVE_DESCRIPTORS.put(long.class, 'J');
88+
PRIMITIVE_DESCRIPTORS.put(float.class, 'F');
89+
PRIMITIVE_DESCRIPTORS.put(double.class, 'D');
7390
}
7491

7592
static {

mockito-core/src/main/java/org/mockito/internal/util/reflection/GenericMetadataSupport.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Set;
2424
import org.mockito.exceptions.base.MockitoException;
2525
import org.mockito.internal.util.Checks;
26+
import org.mockito.internal.util.Primitives;
2627

2728
/**
2829
* This class can retrieve generic meta-data that the compiler stores on classes
@@ -273,6 +274,8 @@ protected Type getActualTypeArgumentFor(TypeVariable<?> typeParameter) {
273274
* @return {@link GenericMetadataSupport} representing this generic return type.
274275
*/
275276
public GenericMetadataSupport resolveGenericReturnType(Method method) {
277+
// Note for primitive arrays, some JVMs return a Class (e.g. byte[].class) while some
278+
// (notably Android) return GenericArrayType for this.
276279
Type genericReturnType = method.getGenericReturnType();
277280
// logger.log("Method '" + method.toGenericString() + "' has return type : " +
278281
// genericReturnType.getClass().getInterfaces()[0].getSimpleName() + " : " +
@@ -552,15 +555,14 @@ public Class<?> rawType() {
552555
for (int i = 0; i < arity; i++) {
553556
stringBuilder.append("[");
554557
}
558+
if (rawComponentType.isPrimitive()) {
559+
stringBuilder.append(Primitives.getPrimitiveDescriptor(rawComponentType));
560+
} else {
561+
stringBuilder.append("L").append(rawComponentType.getName()).append(";");
562+
}
555563
try {
556564
return Class.forName(
557-
stringBuilder
558-
.append("L")
559-
.append(rawComponentType.getName())
560-
.append(";")
561-
.toString(),
562-
false,
563-
rawComponentType.getClassLoader());
565+
stringBuilder.toString(), false, rawComponentType.getClassLoader());
564566
} catch (ClassNotFoundException e) {
565567
throw new IllegalStateException("This was not supposed to happen.", e);
566568
}

mockito-integration-tests/android-tests/src/androidTest/java/org/mockitousage/androidtest/BasicInstrumentedTests.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package org.mockitousage.androidtest
22

33
import androidx.test.ext.junit.runners.AndroidJUnit4
44
import org.junit.After
5+
import org.junit.Assert.assertArrayEquals
56
import org.junit.Before
67
import org.junit.Test
78
import org.junit.runner.RunWith
9+
import org.mockito.ArgumentMatchers.anyList
810
import org.mockito.Mock
911
import org.mockito.MockitoAnnotations
1012
import org.mockito.Mockito.mock
1113
import org.mockito.Mockito.verify
14+
import org.mockito.Mockito.`when`
1215

1316
@RunWith(AndroidJUnit4::class)
1417
class BasicInstrumentedTests {
@@ -74,4 +77,16 @@ class BasicInstrumentedTests {
7477
receiver.callInterfaceMethod()
7578
verify(basicInterface).interfaceMethod()
7679
}
80+
81+
//Regression test for issue #3752
82+
83+
@Test
84+
fun mockMethodWithArrayOfPrimitiveReturnType() {
85+
val mock = mock(HasArrayOfPrimitivesReturnType::class.java)
86+
`when`(mock.getData(anyList())).thenReturn(byteArrayOf(1, 2, 3))
87+
88+
val actual = mock.getData(emptyList())
89+
90+
assertArrayEquals(byteArrayOf(1, 2, 3), actual)
91+
}
7792
}

mockito-integration-tests/android-tests/src/main/java/org/mockitousage/androidtest/BasicClassesForTesting.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@ class BasicInterfaceReceiver(private val basicInterface: BasicInterface) {
3333
basicInterface.interfaceMethod()
3434
}
3535
}
36+
37+
interface HasArrayOfPrimitivesReturnType {
38+
39+
// Issue #3752 is reproducible only when method has a generic parameter
40+
fun getData(args: List<String>): ByteArray
41+
}

0 commit comments

Comments
 (0)