1 package org.robolectric; 2 3 import static com.google.common.truth.Truth.assertThat; 4 import static java.lang.invoke.MethodHandles.constant; 5 import static java.lang.invoke.MethodHandles.dropArguments; 6 import static java.lang.invoke.MethodHandles.insertArguments; 7 import static java.lang.invoke.MethodType.methodType; 8 import static org.junit.Assert.assertArrayEquals; 9 import static org.junit.Assert.assertEquals; 10 import static org.junit.Assert.assertNotNull; 11 import static org.junit.Assert.assertNull; 12 import static org.junit.Assert.assertSame; 13 import static org.junit.Assert.assertTrue; 14 import static org.junit.Assert.fail; 15 import static org.mockito.Matchers.any; 16 import static org.mockito.Matchers.anyString; 17 import static org.mockito.Mockito.mock; 18 import static org.mockito.Mockito.when; 19 import static org.robolectric.util.ReflectionHelpers.newInstance; 20 import static org.robolectric.util.ReflectionHelpers.setStaticField; 21 22 import java.lang.invoke.MethodHandle; 23 import java.lang.invoke.MethodHandles; 24 import java.lang.invoke.MethodType; 25 import java.lang.invoke.SwitchPoint; 26 import java.lang.reflect.Array; 27 import java.lang.reflect.Constructor; 28 import java.lang.reflect.Field; 29 import java.lang.reflect.Method; 30 import java.lang.reflect.Modifier; 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.List; 34 import javax.annotation.Nonnull; 35 import org.junit.Before; 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 import org.junit.runners.JUnit4; 39 import org.mockito.Mockito; 40 import org.robolectric.internal.bytecode.ClassHandler; 41 import org.robolectric.internal.bytecode.InstrumentationConfiguration; 42 import org.robolectric.internal.bytecode.Interceptor; 43 import org.robolectric.internal.bytecode.Interceptors; 44 import org.robolectric.internal.bytecode.InvocationProfile; 45 import org.robolectric.internal.bytecode.InvokeDynamic; 46 import org.robolectric.internal.bytecode.InvokeDynamicSupport; 47 import org.robolectric.internal.bytecode.MethodRef; 48 import org.robolectric.internal.bytecode.MutableClass; 49 import org.robolectric.internal.bytecode.RobolectricInternals; 50 import org.robolectric.internal.bytecode.SandboxClassLoader; 51 import org.robolectric.internal.bytecode.ShadowConstants; 52 import org.robolectric.internal.bytecode.ShadowImpl; 53 import org.robolectric.internal.bytecode.ShadowInvalidator; 54 import org.robolectric.shadow.api.Shadow; 55 import org.robolectric.testing.AChild; 56 import org.robolectric.testing.AClassThatCallsAMethodReturningAForgettableClass; 57 import org.robolectric.testing.AClassThatExtendsAClassWithFinalEqualsHashCode; 58 import org.robolectric.testing.AClassThatRefersToAForgettableClass; 59 import org.robolectric.testing.AClassThatRefersToAForgettableClassInItsConstructor; 60 import org.robolectric.testing.AClassThatRefersToAForgettableClassInMethodCalls; 61 import org.robolectric.testing.AClassThatRefersToAForgettableClassInMethodCallsReturningPrimitive; 62 import org.robolectric.testing.AClassToForget; 63 import org.robolectric.testing.AClassToRemember; 64 import org.robolectric.testing.AClassWithEqualsHashCodeToString; 65 import org.robolectric.testing.AClassWithFunnyConstructors; 66 import org.robolectric.testing.AClassWithMethodReturningArray; 67 import org.robolectric.testing.AClassWithMethodReturningBoolean; 68 import org.robolectric.testing.AClassWithMethodReturningDouble; 69 import org.robolectric.testing.AClassWithMethodReturningInteger; 70 import org.robolectric.testing.AClassWithNativeMethod; 71 import org.robolectric.testing.AClassWithNativeMethodReturningPrimitive; 72 import org.robolectric.testing.AClassWithNoDefaultConstructor; 73 import org.robolectric.testing.AClassWithStaticMethod; 74 import org.robolectric.testing.AFinalClass; 75 import org.robolectric.testing.AnEnum; 76 import org.robolectric.testing.AnExampleClass; 77 import org.robolectric.testing.AnInstrumentedChild; 78 import org.robolectric.testing.AnUninstrumentedClass; 79 import org.robolectric.testing.AnUninstrumentedParent; 80 import org.robolectric.util.ReflectionHelpers; 81 import org.robolectric.util.Util; 82 83 @RunWith(JUnit4.class) 84 public class SandboxClassLoaderTest { 85 86 private ClassLoader classLoader; 87 private List<String> transcript = new ArrayList<>(); 88 private MyClassHandler classHandler = new MyClassHandler(transcript); 89 private ShadowImpl shadow; 90 91 @Before setUp()92 public void setUp() throws Exception { 93 shadow = new ShadowImpl(); 94 } 95 96 @Test shouldMakeClassesNonFinal()97 public void shouldMakeClassesNonFinal() throws Exception { 98 Class<?> clazz = loadClass(AFinalClass.class); 99 assertEquals(0, clazz.getModifiers() & Modifier.FINAL); 100 } 101 102 @Test forClassesWithNoDefaultConstructor_shouldCreateOneButItShouldNotCallShadow()103 public void forClassesWithNoDefaultConstructor_shouldCreateOneButItShouldNotCallShadow() throws Exception { 104 Constructor<?> defaultCtor = loadClass(AClassWithNoDefaultConstructor.class).getConstructor(); 105 assertTrue(Modifier.isPublic(defaultCtor.getModifiers())); 106 defaultCtor.setAccessible(true); 107 Object instance = defaultCtor.newInstance(); 108 assertThat((Object) shadow.extract(instance)).isNotNull(); 109 assertThat(transcript).isEmpty(); 110 } 111 112 @Test shouldDelegateToHandlerForConstructors()113 public void shouldDelegateToHandlerForConstructors() throws Exception { 114 Class<?> clazz = loadClass(AClassWithNoDefaultConstructor.class); 115 Constructor<?> ctor = clazz.getDeclaredConstructor(String.class); 116 assertTrue(Modifier.isPublic(ctor.getModifiers())); 117 ctor.setAccessible(true); 118 Object instance = ctor.newInstance("new one"); 119 assertThat(transcript).containsExactly( 120 "methodInvoked: AClassWithNoDefaultConstructor.__constructor__(java.lang.String new one)"); 121 122 Field nameField = clazz.getDeclaredField("name"); 123 nameField.setAccessible(true); 124 assertNull(nameField.get(instance)); 125 } 126 127 @Test shouldDelegateClassLoadForUnacquiredClasses()128 public void shouldDelegateClassLoadForUnacquiredClasses() throws Exception { 129 InstrumentationConfiguration config = mock(InstrumentationConfiguration.class); 130 when(config.shouldAcquire(anyString())).thenReturn(false); 131 when(config.shouldInstrument(any(MutableClass.class))).thenReturn(false); 132 ClassLoader classLoader = new SandboxClassLoader(config); 133 Class<?> exampleClass = classLoader.loadClass(AnExampleClass.class.getName()); 134 assertSame(getClass().getClassLoader(), exampleClass.getClassLoader()); 135 } 136 137 @Test shouldPerformClassLoadForAcquiredClasses()138 public void shouldPerformClassLoadForAcquiredClasses() throws Exception { 139 ClassLoader classLoader = new SandboxClassLoader(configureBuilder().build()); 140 Class<?> exampleClass = classLoader.loadClass(AnUninstrumentedClass.class.getName()); 141 assertSame(classLoader, exampleClass.getClassLoader()); 142 try { 143 exampleClass.getField(ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME); 144 fail("class shouldn't be instrumented!"); 145 } catch (Exception e) { 146 // expected 147 } 148 } 149 150 @Test shouldPerformClassLoadAndInstrumentLoadForInstrumentedClasses()151 public void shouldPerformClassLoadAndInstrumentLoadForInstrumentedClasses() throws Exception { 152 ClassLoader classLoader = new SandboxClassLoader(configureBuilder().build()); 153 Class<?> exampleClass = classLoader.loadClass(AnExampleClass.class.getName()); 154 assertSame(classLoader, exampleClass.getClassLoader()); 155 Field roboDataField = exampleClass.getField(ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME); 156 assertNotNull(roboDataField); 157 assertThat(Modifier.isPublic(roboDataField.getModifiers())).isTrue(); 158 159 // Java 9 doesn't allow updates to final fields from outside <init> or <clinit>: 160 // https://bugs.openjdk.java.net/browse/JDK-8157181 161 // Therefore, these fields need to be nonfinal / be made nonfinal. 162 assertThat(Modifier.isFinal(roboDataField.getModifiers())).isFalse(); 163 assertThat( 164 Modifier.isFinal(exampleClass.getField("STATIC_FINAL_FIELD").getModifiers())).isFalse(); 165 assertThat( 166 Modifier.isFinal(exampleClass.getField("nonstaticFinalField").getModifiers())).isFalse(); 167 } 168 169 @Test callingNormalMethodShouldInvokeClassHandler()170 public void callingNormalMethodShouldInvokeClassHandler() throws Exception { 171 Class<?> exampleClass = loadClass(AnExampleClass.class); 172 Method normalMethod = exampleClass.getMethod("normalMethod", String.class, int.class); 173 174 Object exampleInstance = exampleClass.getDeclaredConstructor().newInstance(); 175 assertEquals("response from methodInvoked: AnExampleClass.normalMethod(java.lang.String value1, int 123)", 176 normalMethod.invoke(exampleInstance, "value1", 123)); 177 assertThat(transcript).containsExactly( 178 "methodInvoked: AnExampleClass.__constructor__()", 179 "methodInvoked: AnExampleClass.normalMethod(java.lang.String value1, int 123)"); 180 } 181 182 @Test shouldGenerateClassSpecificDirectAccessMethod()183 public void shouldGenerateClassSpecificDirectAccessMethod() throws Exception { 184 Class<?> exampleClass = loadClass(AnExampleClass.class); 185 String methodName = shadow.directMethodName(exampleClass.getName(), "normalMethod"); 186 Method directMethod = exampleClass.getDeclaredMethod(methodName, String.class, int.class); 187 directMethod.setAccessible(true); 188 Object exampleInstance = exampleClass.getDeclaredConstructor().newInstance(); 189 assertEquals("normalMethod(value1, 123)", directMethod.invoke(exampleInstance, "value1", 123)); 190 assertThat(transcript).containsExactly( 191 "methodInvoked: AnExampleClass.__constructor__()"); 192 } 193 194 @Test soMockitoDoesntExplodeDueToTooManyMethods_shouldGenerateClassSpecificDirectAccessMethodWhichIsPrivateAndFinal()195 public void soMockitoDoesntExplodeDueToTooManyMethods_shouldGenerateClassSpecificDirectAccessMethodWhichIsPrivateAndFinal() throws Exception { 196 Class<?> exampleClass = loadClass(AnExampleClass.class); 197 String methodName = shadow.directMethodName(exampleClass.getName(), "normalMethod"); 198 Method directMethod = exampleClass.getDeclaredMethod(methodName, String.class, int.class); 199 assertTrue(Modifier.isPrivate(directMethod.getModifiers())); 200 assertTrue(Modifier.isFinal(directMethod.getModifiers())); 201 } 202 203 @Test callingStaticMethodShouldInvokeClassHandler()204 public void callingStaticMethodShouldInvokeClassHandler() throws Exception { 205 Class<?> exampleClass = loadClass(AClassWithStaticMethod.class); 206 Method normalMethod = exampleClass.getMethod("staticMethod", String.class); 207 208 assertEquals( 209 "response from methodInvoked: AClassWithStaticMethod.staticMethod(java.lang.String value1)", 210 normalMethod.invoke(null, "value1")); 211 assertThat(transcript).containsExactly( 212 "methodInvoked: AClassWithStaticMethod.staticMethod(java.lang.String value1)"); 213 } 214 215 @Test callingStaticDirectAccessMethodShouldWork()216 public void callingStaticDirectAccessMethodShouldWork() throws Exception { 217 Class<?> exampleClass = loadClass(AClassWithStaticMethod.class); 218 String methodName = shadow.directMethodName(exampleClass.getName(), "staticMethod"); 219 Method directMethod = exampleClass.getDeclaredMethod(methodName, String.class); 220 directMethod.setAccessible(true); 221 assertEquals("staticMethod(value1)", directMethod.invoke(null, "value1")); 222 } 223 224 @Test callingNormalMethodReturningIntegerShouldInvokeClassHandler()225 public void callingNormalMethodReturningIntegerShouldInvokeClassHandler() throws Exception { 226 Class<?> exampleClass = loadClass(AClassWithMethodReturningInteger.class); 227 classHandler.valueToReturn = 456; 228 229 Method normalMethod = exampleClass.getMethod("normalMethodReturningInteger", int.class); 230 Object exampleInstance = exampleClass.getDeclaredConstructor().newInstance(); 231 assertEquals(456, normalMethod.invoke(exampleInstance, 123)); 232 assertThat(transcript).containsExactly( 233 "methodInvoked: AClassWithMethodReturningInteger.__constructor__()", 234 "methodInvoked: AClassWithMethodReturningInteger.normalMethodReturningInteger(int 123)"); 235 } 236 237 @Test callingMethodReturningDoubleShouldInvokeClassHandler()238 public void callingMethodReturningDoubleShouldInvokeClassHandler() throws Exception { 239 Class<?> exampleClass = loadClass(AClassWithMethodReturningDouble.class); 240 classHandler.valueToReturn = 456; 241 242 Method normalMethod = exampleClass.getMethod("normalMethodReturningDouble", double.class); 243 Object exampleInstance = exampleClass.getDeclaredConstructor().newInstance(); 244 assertEquals(456.0, normalMethod.invoke(exampleInstance, 123d)); 245 assertThat(transcript).containsExactly( 246 "methodInvoked: AClassWithMethodReturningDouble.__constructor__()", 247 "methodInvoked: AClassWithMethodReturningDouble.normalMethodReturningDouble(double 123.0)"); 248 } 249 250 @Test callingNativeMethodShouldInvokeClassHandler()251 public void callingNativeMethodShouldInvokeClassHandler() throws Exception { 252 Class<?> exampleClass = loadClass(AClassWithNativeMethod.class); 253 Method normalMethod = exampleClass.getDeclaredMethod("nativeMethod", String.class, int.class); 254 Object exampleInstance = exampleClass.getDeclaredConstructor().newInstance(); 255 assertEquals("response from methodInvoked: AClassWithNativeMethod.nativeMethod(java.lang.String value1, int 123)", 256 normalMethod.invoke(exampleInstance, "value1", 123)); 257 assertThat(transcript).containsExactly( 258 "methodInvoked: AClassWithNativeMethod.__constructor__()", 259 "methodInvoked: AClassWithNativeMethod.nativeMethod(java.lang.String value1, int 123)"); 260 } 261 262 @Test directlyCallingNativeMethodShouldBeNoOp()263 public void directlyCallingNativeMethodShouldBeNoOp() throws Exception { 264 Class<?> exampleClass = loadClass(AClassWithNativeMethod.class); 265 Object exampleInstance = exampleClass.getDeclaredConstructor().newInstance(); 266 Method directMethod = findDirectMethod(exampleClass, "nativeMethod", String.class, int.class); 267 assertThat(Modifier.isNative(directMethod.getModifiers())).isFalse(); 268 269 assertThat(directMethod.invoke(exampleInstance, "", 1)).isNull(); 270 } 271 272 @Test directlyCallingNativeMethodReturningPrimitiveShouldBeNoOp()273 public void directlyCallingNativeMethodReturningPrimitiveShouldBeNoOp() throws Exception { 274 Class<?> exampleClass = loadClass(AClassWithNativeMethodReturningPrimitive.class); 275 Object exampleInstance = exampleClass.getDeclaredConstructor().newInstance(); 276 Method directMethod = findDirectMethod(exampleClass, "nativeMethod"); 277 assertThat(Modifier.isNative(directMethod.getModifiers())).isFalse(); 278 279 assertThat(directMethod.invoke(exampleInstance)).isEqualTo(0); 280 } 281 282 @Test shouldHandleMethodsReturningBoolean()283 public void shouldHandleMethodsReturningBoolean() throws Exception { 284 Class<?> exampleClass = loadClass(AClassWithMethodReturningBoolean.class); 285 classHandler.valueToReturn = true; 286 287 Method directMethod = exampleClass.getMethod("normalMethodReturningBoolean", boolean.class, boolean[].class); 288 directMethod.setAccessible(true); 289 Object exampleInstance = exampleClass.getDeclaredConstructor().newInstance(); 290 assertEquals(true, directMethod.invoke(exampleInstance, true, new boolean[0])); 291 assertThat(transcript).containsExactly( 292 "methodInvoked: AClassWithMethodReturningBoolean.__constructor__()", 293 "methodInvoked: AClassWithMethodReturningBoolean.normalMethodReturningBoolean(boolean true, boolean[] {})"); 294 } 295 296 @Test shouldHandleMethodsReturningArray()297 public void shouldHandleMethodsReturningArray() throws Exception { 298 Class<?> exampleClass = loadClass(AClassWithMethodReturningArray.class); 299 classHandler.valueToReturn = new String[]{"miao, mieuw"}; 300 301 Method directMethod = exampleClass.getMethod("normalMethodReturningArray"); 302 directMethod.setAccessible(true); 303 Object exampleInstance = exampleClass.getDeclaredConstructor().newInstance(); 304 assertThat(transcript).containsExactly( 305 "methodInvoked: AClassWithMethodReturningArray.__constructor__()"); 306 transcript.clear(); 307 assertArrayEquals(new String[]{"miao, mieuw"}, (String[]) directMethod.invoke(exampleInstance)); 308 assertThat(transcript).containsExactly( 309 "methodInvoked: AClassWithMethodReturningArray.normalMethodReturningArray()"); 310 } 311 312 @Test shouldInvokeShadowForEachConstructorInInheritanceTree()313 public void shouldInvokeShadowForEachConstructorInInheritanceTree() throws Exception { 314 loadClass(AChild.class).getDeclaredConstructor().newInstance(); 315 assertThat(transcript).containsExactly( 316 "methodInvoked: AGrandparent.__constructor__()", 317 "methodInvoked: AParent.__constructor__()", 318 "methodInvoked: AChild.__constructor__()"); 319 } 320 321 @Test shouldRetainSuperCallInConstructor()322 public void shouldRetainSuperCallInConstructor() throws Exception { 323 Class<?> aClass = loadClass(AnInstrumentedChild.class); 324 Object o = aClass.getDeclaredConstructor(String.class).newInstance("hortense"); 325 assertEquals("HORTENSE's child", aClass.getSuperclass().getDeclaredField("parentName").get(o)); 326 assertNull(aClass.getDeclaredField("childName").get(o)); 327 } 328 329 @Test shouldCorrectlySplitStaticPrepFromConstructorChaining()330 public void shouldCorrectlySplitStaticPrepFromConstructorChaining() throws Exception { 331 Class<?> aClass = loadClass(AClassWithFunnyConstructors.class); 332 Object o = aClass.getDeclaredConstructor(String.class).newInstance("hortense"); 333 assertThat(transcript).containsExactly( 334 "methodInvoked: AClassWithFunnyConstructors.__constructor__(" + AnUninstrumentedParent.class.getName() + " UninstrumentedParent{parentName='hortense'}, java.lang.String foo)", 335 "methodInvoked: AClassWithFunnyConstructors.__constructor__(java.lang.String hortense)"); 336 337 // should not run constructor bodies... 338 assertEquals(null, getDeclaredFieldValue(aClass, o, "name")); 339 assertEquals(null, getDeclaredFieldValue(aClass, o, "uninstrumentedParent")); 340 } 341 342 @Test shouldGenerateClassSpecificDirectAccessMethodForConstructorWhichDoesNotCallSuper()343 public void shouldGenerateClassSpecificDirectAccessMethodForConstructorWhichDoesNotCallSuper() throws Exception { 344 Class<?> aClass = loadClass(AClassWithFunnyConstructors.class); 345 Object instance = aClass.getConstructor(String.class).newInstance("horace"); 346 assertThat(transcript).containsExactly( 347 "methodInvoked: AClassWithFunnyConstructors.__constructor__(" + AnUninstrumentedParent.class.getName() + " UninstrumentedParent{parentName='horace'}, java.lang.String foo)", 348 "methodInvoked: AClassWithFunnyConstructors.__constructor__(java.lang.String horace)"); 349 transcript.clear(); 350 351 // each directly-accessible constructor body will need to be called explicitly, with the correct args... 352 353 Class<?> uninstrumentedParentClass = loadClass(AnUninstrumentedParent.class); 354 Method directMethod = findDirectMethod(aClass, "__constructor__", uninstrumentedParentClass, String.class); 355 Object uninstrumentedParentIn = uninstrumentedParentClass.getDeclaredConstructor(String.class).newInstance("hortense"); 356 assertEquals(null, directMethod.invoke(instance, uninstrumentedParentIn, "foo")); 357 assertThat(transcript).isEmpty(); 358 359 assertEquals(null, getDeclaredFieldValue(aClass, instance, "name")); 360 Object uninstrumentedParentOut = getDeclaredFieldValue(aClass, instance, "uninstrumentedParent"); 361 assertEquals("hortense", getDeclaredFieldValue(uninstrumentedParentClass, uninstrumentedParentOut, "parentName")); 362 363 Method directMethod2 = findDirectMethod(aClass, "__constructor__", String.class); 364 assertEquals(null, directMethod2.invoke(instance, "hortense")); 365 assertThat(transcript).isEmpty(); 366 367 assertEquals("hortense", getDeclaredFieldValue(aClass, instance, "name")); 368 } 369 findDirectMethod(Class<?> declaringClass, String methodName, Class<?>... argClasses)370 private Method findDirectMethod(Class<?> declaringClass, String methodName, Class<?>... argClasses) throws NoSuchMethodException { 371 String directMethodName = shadow.directMethodName(declaringClass.getName(), methodName); 372 Method directMethod = declaringClass.getDeclaredMethod(directMethodName, argClasses); 373 directMethod.setAccessible(true); 374 return directMethod; 375 } 376 377 @Test shouldNotInstrumentFinalEqualsHashcode()378 public void shouldNotInstrumentFinalEqualsHashcode() throws ClassNotFoundException { 379 loadClass(AClassThatExtendsAClassWithFinalEqualsHashCode.class); 380 } 381 382 @Test shouldAlsoInstrumentEqualsAndHashCodeAndToStringWhenDeclared()383 public void shouldAlsoInstrumentEqualsAndHashCodeAndToStringWhenDeclared() throws Exception { 384 Class<?> theClass = loadClass(AClassWithEqualsHashCodeToString.class); 385 Object instance = theClass.getDeclaredConstructor().newInstance(); 386 assertThat(transcript).containsExactly("methodInvoked: AClassWithEqualsHashCodeToString.__constructor__()"); 387 transcript.clear(); 388 389 instance.toString(); 390 assertThat(transcript).containsExactly("methodInvoked: AClassWithEqualsHashCodeToString.toString()"); 391 transcript.clear(); 392 393 classHandler.valueToReturn = true; 394 //noinspection ResultOfMethodCallIgnored,ObjectEqualsNull 395 instance.equals(null); 396 assertThat(transcript).containsExactly("methodInvoked: AClassWithEqualsHashCodeToString.equals(java.lang.Object null)"); 397 transcript.clear(); 398 399 classHandler.valueToReturn = 42; 400 //noinspection ResultOfMethodCallIgnored 401 instance.hashCode(); 402 assertThat(transcript).containsExactly("methodInvoked: AClassWithEqualsHashCodeToString.hashCode()"); 403 } 404 405 @Test shouldRemapClasses()406 public void shouldRemapClasses() throws Exception { 407 setClassLoader(new SandboxClassLoader(createRemappingConfig())); 408 Class<?> theClass = loadClass(AClassThatRefersToAForgettableClass.class); 409 assertEquals(loadClass(AClassToRemember.class), theClass.getField("someField").getType()); 410 assertEquals(Array.newInstance(loadClass(AClassToRemember.class), 0).getClass(), theClass.getField("someFields").getType()); 411 } 412 createRemappingConfig()413 private InstrumentationConfiguration createRemappingConfig() { 414 return configureBuilder() 415 .addClassNameTranslation(AClassToForget.class.getName(), AClassToRemember.class.getName()) 416 .build(); 417 } 418 419 @Test shouldFixTypesInFieldAccess()420 public void shouldFixTypesInFieldAccess() throws Exception { 421 setClassLoader(new SandboxClassLoader(createRemappingConfig())); 422 Class<?> theClass = loadClass(AClassThatRefersToAForgettableClassInItsConstructor.class); 423 Object instance = theClass.getDeclaredConstructor().newInstance(); 424 Method method = 425 theClass.getDeclaredMethod( 426 shadow.directMethodName(theClass.getName(), ShadowConstants.CONSTRUCTOR_METHOD_NAME)); 427 method.setAccessible(true); 428 method.invoke(instance); 429 } 430 431 @Test shouldFixTypesInMethodArgsAndReturn()432 public void shouldFixTypesInMethodArgsAndReturn() throws Exception { 433 setClassLoader(new SandboxClassLoader(createRemappingConfig())); 434 Class<?> theClass = loadClass(AClassThatRefersToAForgettableClassInMethodCalls.class); 435 assertNotNull(theClass.getDeclaredMethod("aMethod", int.class, loadClass(AClassToRemember.class), String.class)); 436 } 437 438 @Test shouldInterceptFilteredMethodInvocations()439 public void shouldInterceptFilteredMethodInvocations() throws Exception { 440 setClassLoader(new SandboxClassLoader(configureBuilder() 441 .addInterceptedMethod(new MethodRef(AClassToForget.class, "forgettableMethod")) 442 .build())); 443 444 Class<?> theClass = loadClass(AClassThatRefersToAForgettableClass.class); 445 Object instance = theClass.getDeclaredConstructor().newInstance(); 446 Object output = theClass.getMethod("interactWithForgettableClass").invoke(shadow.directlyOn(instance, (Class<Object>) theClass)); 447 assertEquals("null, get this!", output); 448 } 449 450 @Test shouldInterceptFilteredStaticMethodInvocations()451 public void shouldInterceptFilteredStaticMethodInvocations() throws Exception { 452 setClassLoader(new SandboxClassLoader(configureBuilder() 453 .addInterceptedMethod(new MethodRef(AClassToForget.class, "forgettableStaticMethod")) 454 .build())); 455 456 Class<?> theClass = loadClass(AClassThatRefersToAForgettableClass.class); 457 Object instance = theClass.getDeclaredConstructor().newInstance(); 458 Object output = theClass.getMethod("interactWithForgettableStaticMethod").invoke(shadow.directlyOn(instance, (Class<Object>) theClass)); 459 assertEquals("yess? forget this: null", output); 460 } 461 462 @Test byte_shouldBeHandledAsReturnValueFromInterceptHandler()463 public void byte_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 464 if (InvokeDynamic.ENABLED) return; 465 classHandler.valueToReturnFromIntercept = (byte) 10; 466 assertThat(invokeInterceptedMethodOnAClassToForget("byteMethod")).isEqualTo((byte) 10); 467 } 468 469 @Test byteArray_shouldBeHandledAsReturnValueFromInterceptHandler()470 public void byteArray_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 471 if (InvokeDynamic.ENABLED) return; 472 classHandler.valueToReturnFromIntercept = new byte[]{10, 12, 14}; 473 assertThat(invokeInterceptedMethodOnAClassToForget("byteArrayMethod")).isEqualTo(new byte[]{10, 12, 14}); 474 } 475 476 @Test int_shouldBeHandledAsReturnValueFromInterceptHandler()477 public void int_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 478 if (InvokeDynamic.ENABLED) return; 479 classHandler.valueToReturnFromIntercept = 20; 480 assertThat(invokeInterceptedMethodOnAClassToForget("intMethod")).isEqualTo(20); 481 } 482 483 @Test intArray_shouldBeHandledAsReturnValueFromInterceptHandler()484 public void intArray_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 485 if (InvokeDynamic.ENABLED) return; 486 classHandler.valueToReturnFromIntercept = new int[]{20, 22, 24}; 487 assertThat(invokeInterceptedMethodOnAClassToForget("intArrayMethod")).isEqualTo(new int[]{20, 22, 24}); 488 } 489 490 @Test long_shouldBeHandledAsReturnValueFromInterceptHandler()491 public void long_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 492 if (InvokeDynamic.ENABLED) return; 493 classHandler.valueToReturnFromIntercept = 30L; 494 assertThat(invokeInterceptedMethodOnAClassToForget("longMethod")).isEqualTo(30L); 495 } 496 497 @Test longArray_shouldBeHandledAsReturnValueFromInterceptHandler()498 public void longArray_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 499 if (InvokeDynamic.ENABLED) return; 500 classHandler.valueToReturnFromIntercept = new long[] {30L, 32L, 34L}; 501 assertThat(invokeInterceptedMethodOnAClassToForget("longArrayMethod")).isEqualTo(new long[] {30L, 32L, 34L}); 502 } 503 504 @Test float_shouldBeHandledAsReturnValueFromInterceptHandler()505 public void float_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 506 if (InvokeDynamic.ENABLED) return; 507 classHandler.valueToReturnFromIntercept = 40f; 508 assertThat(invokeInterceptedMethodOnAClassToForget("floatMethod")).isEqualTo(40f); 509 } 510 511 @Test floatArray_shouldBeHandledAsReturnValueFromInterceptHandler()512 public void floatArray_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 513 if (InvokeDynamic.ENABLED) return; 514 classHandler.valueToReturnFromIntercept = new float[] {50f, 52f, 54f}; 515 assertThat(invokeInterceptedMethodOnAClassToForget("floatArrayMethod")).isEqualTo(new float[] {50f, 52f, 54f}); 516 } 517 518 @Test double_shouldBeHandledAsReturnValueFromInterceptHandler()519 public void double_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 520 if (InvokeDynamic.ENABLED) return; 521 classHandler.valueToReturnFromIntercept = 80.0; 522 assertThat(invokeInterceptedMethodOnAClassToForget("doubleMethod")).isEqualTo(80.0); 523 } 524 525 @Test doubleArray_shouldBeHandledAsReturnValueFromInterceptHandler()526 public void doubleArray_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 527 if (InvokeDynamic.ENABLED) return; 528 classHandler.valueToReturnFromIntercept = new double[] {90.0, 92.0, 94.0}; 529 assertThat(invokeInterceptedMethodOnAClassToForget("doubleArrayMethod")).isEqualTo(new double[] {90.0, 92.0, 94.0}); 530 } 531 532 @Test short_shouldBeHandledAsReturnValueFromInterceptHandler()533 public void short_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 534 if (InvokeDynamic.ENABLED) return; 535 classHandler.valueToReturnFromIntercept = (short) 60; 536 assertThat(invokeInterceptedMethodOnAClassToForget("shortMethod")).isEqualTo((short) 60); 537 } 538 539 @Test shortArray_shouldBeHandledAsReturnValueFromInterceptHandler()540 public void shortArray_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 541 if (InvokeDynamic.ENABLED) return; 542 classHandler.valueToReturnFromIntercept = new short[] {70, 72, 74}; 543 assertThat(invokeInterceptedMethodOnAClassToForget("shortArrayMethod")).isEqualTo(new short[] {70, 72, 74}); 544 } 545 546 @Test void_shouldBeHandledAsReturnValueFromInterceptHandler()547 public void void_shouldBeHandledAsReturnValueFromInterceptHandler() throws Exception { 548 if (InvokeDynamic.ENABLED) return; 549 classHandler.valueToReturnFromIntercept = null; 550 assertThat(invokeInterceptedMethodOnAClassToForget("voidReturningMethod")).isNull(); 551 } 552 invokeInterceptedMethodOnAClassToForget(String methodName)553 private Object invokeInterceptedMethodOnAClassToForget(String methodName) throws Exception { 554 setClassLoader(new SandboxClassLoader(configureBuilder() 555 .addInterceptedMethod(new MethodRef(AClassToForget.class, "*")) 556 .build())); 557 Class<?> theClass = loadClass(AClassThatRefersToAForgettableClassInMethodCallsReturningPrimitive.class); 558 Object instance = theClass.getDeclaredConstructor().newInstance(); 559 Method m = theClass.getDeclaredMethod(methodName); 560 m.setAccessible(true); 561 return m.invoke(shadow.directlyOn(instance, (Class<Object>) theClass)); 562 } 563 564 @Nonnull configureBuilder()565 private InstrumentationConfiguration.Builder configureBuilder() { 566 InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder(); 567 builder.doNotAcquirePackage("java.") 568 .doNotAcquirePackage("sun.") 569 .doNotAcquirePackage("com.sun.") 570 .doNotAcquirePackage("org.robolectric.internal.") 571 ; 572 return builder; 573 } 574 575 @Test shouldPassArgumentsFromInterceptedMethods()576 public void shouldPassArgumentsFromInterceptedMethods() throws Exception { 577 if (InvokeDynamic.ENABLED) return; 578 classHandler.valueToReturnFromIntercept = 10L; 579 580 setClassLoader(new SandboxClassLoader(configureBuilder() 581 .addInterceptedMethod(new MethodRef(AClassToForget.class, "*")) 582 .build())); 583 584 Class<?> theClass = loadClass(AClassThatRefersToAForgettableClassInMethodCallsReturningPrimitive.class); 585 Object instance = theClass.getDeclaredConstructor().newInstance(); 586 shadow.directlyOn(instance, (Class<Object>) theClass, "longMethod"); 587 assertThat(transcript).containsExactly( 588 "methodInvoked: AClassThatRefersToAForgettableClassInMethodCallsReturningPrimitive.__constructor__()", 589 "intercept: org/robolectric/testing/AClassToForget/longReturningMethod(Ljava/lang/String;IJ)J with params (str str, 123 123, 456 456)"); 590 } 591 592 @Test shouldRemapClassesWhileInterceptingMethods()593 public void shouldRemapClassesWhileInterceptingMethods() throws Exception { 594 InstrumentationConfiguration config = configureBuilder() 595 .addClassNameTranslation(AClassToForget.class.getName(), AClassToRemember.class.getName()) 596 .addInterceptedMethod(new MethodRef(AClassThatCallsAMethodReturningAForgettableClass.class, "getAForgettableClass")) 597 .build(); 598 599 setClassLoader(new SandboxClassLoader(config)); 600 Class<?> theClass = loadClass(AClassThatCallsAMethodReturningAForgettableClass.class); 601 theClass.getMethod("callSomeMethod").invoke(shadow.directlyOn(theClass.getDeclaredConstructor().newInstance(), (Class<Object>) theClass)); 602 } 603 604 @Test shouldWorkWithEnums()605 public void shouldWorkWithEnums() throws Exception { 606 loadClass(AnEnum.class); 607 } 608 609 @Test shouldReverseAnArray()610 public void shouldReverseAnArray() throws Exception { 611 assertArrayEquals(new Integer[]{5, 4, 3, 2, 1}, Util.reverse(new Integer[]{1, 2, 3, 4, 5})); 612 assertArrayEquals(new Integer[]{4, 3, 2, 1}, Util.reverse(new Integer[]{1, 2, 3, 4})); 613 assertArrayEquals(new Integer[]{1}, Util.reverse(new Integer[]{1})); 614 assertArrayEquals(new Integer[]{}, Util.reverse(new Integer[]{})); 615 } 616 617 ///////////////////////////// 618 getDeclaredFieldValue(Class<?> aClass, Object o, String fieldName)619 private Object getDeclaredFieldValue(Class<?> aClass, Object o, String fieldName) throws Exception { 620 Field field = aClass.getDeclaredField(fieldName); 621 field.setAccessible(true); 622 return field.get(o); 623 } 624 625 public static class MyClassHandler implements ClassHandler { 626 private static final Object GENERATE_YOUR_OWN_VALUE = new Object(); 627 private List<String> transcript; 628 private Object valueToReturn = GENERATE_YOUR_OWN_VALUE; 629 private Object valueToReturnFromIntercept = null; 630 MyClassHandler(List<String> transcript)631 public MyClassHandler(List<String> transcript) { 632 this.transcript = transcript; 633 } 634 635 @Override classInitializing(Class clazz)636 public void classInitializing(Class clazz) { 637 } 638 639 @Override initializing(Object instance)640 public Object initializing(Object instance) { 641 return "a shadow!"; 642 } 643 methodInvoked(Class clazz, String methodName, Object instance, String[] paramTypes, Object[] params)644 public Object methodInvoked(Class clazz, String methodName, Object instance, String[] paramTypes, Object[] params) { 645 StringBuilder buf = new StringBuilder(); 646 buf.append("methodInvoked: ").append(clazz.getSimpleName()).append(".").append(methodName).append("("); 647 for (int i = 0; i < paramTypes.length; i++) { 648 if (i > 0) buf.append(", "); 649 Object param = params[i]; 650 Object display = param == null ? "null" : param.getClass().isArray() ? "{}" : param; 651 buf.append(paramTypes[i]).append(" ").append(display); 652 } 653 buf.append(")"); 654 transcript.add(buf.toString()); 655 656 if (valueToReturn != GENERATE_YOUR_OWN_VALUE) return valueToReturn; 657 return "response from " + buf.toString(); 658 } 659 660 @Override methodInvoked(String signature, boolean isStatic, Class<?> theClass)661 public Plan methodInvoked(String signature, boolean isStatic, Class<?> theClass) { 662 final InvocationProfile invocationProfile = new InvocationProfile(signature, isStatic, getClass().getClassLoader()); 663 return new Plan() { 664 @Override 665 public Object run(Object instance, Object[] params) throws Exception { 666 try { 667 return methodInvoked(invocationProfile.clazz, invocationProfile.methodName, instance, invocationProfile.paramTypes, params); 668 } catch (Throwable throwable) { 669 throw new RuntimeException(throwable); 670 } 671 } 672 673 @Override 674 public String describe() { 675 return invocationProfile.methodName; 676 } 677 }; 678 } 679 680 @Override public MethodHandle getShadowCreator(Class<?> theClass) { 681 return dropArguments(constant(String.class, "a shadow!"), 0, theClass); 682 } 683 684 @SuppressWarnings(value = {"UnusedDeclaration", "unused"}) 685 private Object invoke(InvocationProfile invocationProfile, Object instance, Object[] params) { 686 return methodInvoked(invocationProfile.clazz, invocationProfile.methodName, instance, 687 invocationProfile.paramTypes, params); 688 } 689 690 @Override public MethodHandle findShadowMethodHandle(Class<?> theClass, String name, MethodType type, 691 boolean isStatic) throws IllegalAccessException { 692 String signature = getSignature(theClass, name, type, isStatic); 693 InvocationProfile invocationProfile = new InvocationProfile(signature, isStatic, getClass().getClassLoader()); 694 695 try { 696 MethodHandle mh = MethodHandles.lookup().findVirtual(getClass(), "invoke", 697 methodType(Object.class, InvocationProfile.class, Object.class, Object[].class)); 698 mh = insertArguments(mh, 0, this, invocationProfile); 699 700 if (isStatic) { 701 return mh.bindTo(null).asCollector(Object[].class, type.parameterCount()); 702 } else { 703 return mh.asCollector(Object[].class, type.parameterCount() - 1); 704 } 705 } catch (NoSuchMethodException e) { 706 throw new AssertionError(e); 707 } 708 } 709 710 public String getSignature(Class<?> caller, String name, MethodType type, boolean isStatic) { 711 String className = caller.getName().replace('.', '/'); 712 // Remove implicit first argument 713 if (!isStatic) type = type.dropParameterTypes(0, 1); 714 return className + "/" + name + type.toMethodDescriptorString(); 715 } 716 717 718 @Override 719 public Object intercept(String signature, Object instance, Object[] params, Class theClass) throws Throwable { 720 StringBuilder buf = new StringBuilder(); 721 buf.append("intercept: ").append(signature).append(" with params ("); 722 for (int i = 0; i < params.length; i++) { 723 if (i > 0) buf.append(", "); 724 Object param = params[i]; 725 Object display = param == null ? "null" : param.getClass().isArray() ? "{}" : param; 726 buf.append(params[i]).append(" ").append(display); 727 } 728 buf.append(")"); 729 transcript.add(buf.toString()); 730 return valueToReturnFromIntercept; 731 } 732 733 @Override 734 public <T extends Throwable> T stripStackTrace(T throwable) { 735 return throwable; 736 } 737 } 738 739 private void setClassLoader(ClassLoader classLoader) { 740 this.classLoader = classLoader; 741 } 742 743 private Class<?> loadClass(Class<?> clazz) throws ClassNotFoundException { 744 if (classLoader == null) { 745 classLoader = new SandboxClassLoader(configureBuilder().build()); 746 } 747 748 setStaticField(classLoader.loadClass(InvokeDynamicSupport.class.getName()), "INTERCEPTORS", 749 new Interceptors(Collections.<Interceptor>emptyList())); 750 setStaticField(classLoader.loadClass(Shadow.class.getName()), "SHADOW_IMPL", 751 newInstance(classLoader.loadClass(ShadowImpl.class.getName()))); 752 753 ShadowInvalidator invalidator = Mockito.mock(ShadowInvalidator.class); 754 when(invalidator.getSwitchPoint(any(Class.class))).thenReturn(new SwitchPoint()); 755 756 String className = RobolectricInternals.class.getName(); 757 Class<?> robolectricInternalsClass = ReflectionHelpers.loadClass(classLoader, className); 758 ReflectionHelpers.setStaticField(robolectricInternalsClass, "classHandler", classHandler); 759 ReflectionHelpers.setStaticField(robolectricInternalsClass, "shadowInvalidator", invalidator); 760 761 return classLoader.loadClass(clazz.getName()); 762 } 763 } 764