• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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