1 package org.robolectric;
2 
3 import static com.google.common.truth.Truth.assertThat;
4 import static org.junit.Assert.assertEquals;
5 import static org.junit.Assert.assertNotNull;
6 import static org.junit.Assert.assertSame;
7 import static org.junit.Assert.assertTrue;
8 
9 import java.io.IOException;
10 import org.junit.Before;
11 import org.junit.Test;
12 import org.junit.runner.RunWith;
13 import org.robolectric.annotation.Implementation;
14 import org.robolectric.annotation.Implements;
15 import org.robolectric.annotation.RealObject;
16 import org.robolectric.annotation.internal.Instrument;
17 import org.robolectric.internal.SandboxTestRunner;
18 import org.robolectric.internal.bytecode.SandboxConfig;
19 import org.robolectric.internal.bytecode.ShadowWrangler;
20 import org.robolectric.shadow.api.Shadow;
21 import org.robolectric.testing.Foo;
22 import org.robolectric.testing.ShadowFoo;
23 
24 @RunWith(SandboxTestRunner.class)
25 public class ShadowWranglerIntegrationTest {
26 
27   private static final boolean YES = true;
28 
29   private String name;
30 
31   @Before
setUp()32   public void setUp() throws Exception {
33     name = "context";
34   }
35 
36   @Test
37   @SandboxConfig(shadows = {ShadowForAClassWithDefaultConstructor_HavingNoConstructorDelegate.class})
testConstructorInvocation_WithDefaultConstructorAndNoConstructorDelegateOnShadowClass()38   public void testConstructorInvocation_WithDefaultConstructorAndNoConstructorDelegateOnShadowClass() throws Exception {
39     AClassWithDefaultConstructor instance = new AClassWithDefaultConstructor();
40     assertThat(Shadow.<Object>extract(instance)).isInstanceOf(ShadowForAClassWithDefaultConstructor_HavingNoConstructorDelegate.class);
41     assertThat(instance.initialized).isTrue();
42   }
43 
44   @Test
45   @SandboxConfig(shadows = { ShadowFoo.class })
testConstructorInvocation()46   public void testConstructorInvocation() throws Exception {
47     Foo foo = new Foo(name);
48     assertSame(name, shadowOf(foo).name);
49   }
50 
51   @Test
52   @SandboxConfig(shadows = {ShadowFoo.class})
testRealObjectAnnotatedFieldsAreSetBeforeConstructorIsCalled()53   public void testRealObjectAnnotatedFieldsAreSetBeforeConstructorIsCalled() throws Exception {
54     Foo foo = new Foo(name);
55     assertSame(name, shadowOf(foo).name);
56     assertSame(foo, shadowOf(foo).realFooField);
57 
58     assertSame(foo, shadowOf(foo).realFooInConstructor);
59     assertSame(foo, shadowOf(foo).realFooInParentConstructor);
60   }
61 
62   @Test
63   @SandboxConfig(shadows = {ShadowFoo.class})
testMethodDelegation()64   public void testMethodDelegation() throws Exception {
65     Foo foo = new Foo(name);
66     assertSame(name, foo.getName());
67   }
68 
69   @Test
70   @SandboxConfig(shadows = {WithEquals.class})
testEqualsMethodDelegation()71   public void testEqualsMethodDelegation() throws Exception {
72     Foo foo1 = new Foo(name);
73     Foo foo2 = new Foo(name);
74     assertEquals(foo1, foo2);
75   }
76 
77   @Test
78   @SandboxConfig(shadows = {WithEquals.class})
testHashCodeMethodDelegation()79   public void testHashCodeMethodDelegation() throws Exception {
80     Foo foo = new Foo(name);
81     assertEquals(42, foo.hashCode());
82   }
83 
84   @Test
85   @SandboxConfig(shadows = {WithToString.class})
testToStringMethodDelegation()86   public void testToStringMethodDelegation() throws Exception {
87     Foo foo = new Foo(name);
88     assertEquals("the expected string", foo.toString());
89   }
90 
91   @Test
92   @SandboxConfig(shadows = {ShadowFoo.class})
testShadowSelectionSearchesSuperclasses()93   public void testShadowSelectionSearchesSuperclasses() throws Exception {
94     TextFoo textFoo = new TextFoo(name);
95     assertEquals(ShadowFoo.class, Shadow.extract(textFoo).getClass());
96   }
97 
98   @Test
99   @SandboxConfig(shadows = {ShadowFoo.class, ShadowTextFoo.class})
shouldUseMostSpecificShadow()100   public void shouldUseMostSpecificShadow() throws Exception {
101     TextFoo textFoo = new TextFoo(name);
102     assertThat(shadowOf(textFoo)).isInstanceOf(ShadowTextFoo.class);
103   }
104 
105   @Test
testPrimitiveArrays()106   public void testPrimitiveArrays() throws Exception {
107     Class<?> objArrayClass = ShadowWrangler.loadClass("java.lang.Object[]", getClass().getClassLoader());
108     assertTrue(objArrayClass.isArray());
109     assertEquals(Object.class, objArrayClass.getComponentType());
110 
111     Class<?> intArrayClass = ShadowWrangler.loadClass("int[]", getClass().getClassLoader());
112     assertTrue(intArrayClass.isArray());
113     assertEquals(Integer.TYPE, intArrayClass.getComponentType());
114   }
115 
116   @Test
117   @SandboxConfig(shadows = ShadowThrowInShadowMethod.class)
shouldRemoveNoiseFromShadowedStackTraces()118   public void shouldRemoveNoiseFromShadowedStackTraces() throws Exception {
119     ThrowInShadowMethod instance = new ThrowInShadowMethod();
120 
121     Exception e = null;
122     try {
123       instance.method();
124     } catch (Exception e1) {
125       e = e1;
126     }
127 
128     assertNotNull(e);
129     assertEquals(IOException.class, e.getClass());
130     assertEquals("fake exception", e.getMessage());
131     StackTraceElement[] stackTrace = e.getStackTrace();
132 
133     assertThat(stackTrace[0].getClassName()).isEqualTo(ShadowThrowInShadowMethod.class.getName());
134     assertThat(stackTrace[0].getMethodName()).isEqualTo("method");
135     assertThat(stackTrace[0].getLineNumber()).isGreaterThan(0);
136 
137     assertThat(stackTrace[1].getClassName()).isEqualTo(ThrowInShadowMethod.class.getName());
138     assertThat(stackTrace[1].getMethodName()).isEqualTo("method");
139     assertThat(stackTrace[1].getLineNumber()).isLessThan(0);
140 
141     assertThat(stackTrace[2].getClassName()).isEqualTo(ShadowWranglerIntegrationTest.class.getName());
142     assertThat(stackTrace[2].getMethodName()).isEqualTo("shouldRemoveNoiseFromShadowedStackTraces");
143     assertThat(stackTrace[2].getLineNumber()).isGreaterThan(0);
144   }
145 
146   @Instrument
147   public static class ThrowInShadowMethod {
method()148     public void method() throws IOException {
149     }
150   }
151 
152   @Implements(ThrowInShadowMethod.class)
153   public static class ShadowThrowInShadowMethod {
method()154     public void method() throws IOException {
155       throw new IOException("fake exception");
156     }
157   }
158 
159 
160   @Test
161   @SandboxConfig(shadows = ShadowThrowInRealMethod.class)
shouldRemoveNoiseFromUnshadowedStackTraces()162   public void shouldRemoveNoiseFromUnshadowedStackTraces() throws Exception {
163     ThrowInRealMethod instance = new ThrowInRealMethod();
164 
165     Exception e = null;
166     try {
167       instance.method();
168     } catch (Exception e1) {
169       e = e1;
170     }
171 
172     assertNotNull(e);
173     assertEquals(IOException.class, e.getClass());
174     assertEquals("fake exception", e.getMessage());
175     StackTraceElement[] stackTrace = e.getStackTrace();
176 
177     assertThat(stackTrace[0].getClassName()).isEqualTo(ThrowInRealMethod.class.getName());
178     assertThat(stackTrace[0].getMethodName()).isEqualTo("method");
179     assertThat(stackTrace[0].getLineNumber()).isGreaterThan(0);
180 
181     assertThat(stackTrace[1].getClassName()).isEqualTo(ShadowWranglerIntegrationTest.class.getName());
182     assertThat(stackTrace[1].getMethodName()).isEqualTo("shouldRemoveNoiseFromUnshadowedStackTraces");
183     assertThat(stackTrace[1].getLineNumber()).isGreaterThan(0);
184   }
185 
186   @Instrument
187   public static class ThrowInRealMethod {
method()188     public void method() throws IOException {
189       throw new IOException("fake exception");
190     }
191   }
192 
193   @Implements(ThrowInRealMethod.class)
194   public static class ShadowThrowInRealMethod {
195   }
196 
197   @Test @SandboxConfig(shadows = {Shadow2OfChild.class, ShadowOfParent.class})
whenShadowMethodIsOverriddenInShadowWithSameShadowedClass_shouldUseOverriddenMethod()198   public void whenShadowMethodIsOverriddenInShadowWithSameShadowedClass_shouldUseOverriddenMethod() throws Exception {
199     assertThat(new Child().get()).isEqualTo("get from Shadow2OfChild");
200   }
201 
202   @Test @SandboxConfig(shadows = {Shadow22OfChild.class, ShadowOfParent.class})
whenShadowMethodIsNotOverriddenInShadowWithSameShadowedClass_shouldUseOverriddenMethod()203   public void whenShadowMethodIsNotOverriddenInShadowWithSameShadowedClass_shouldUseOverriddenMethod() throws Exception {
204     assertThat(new Child().get()).isEqualTo("get from Shadow2OfChild");
205   }
206 
207   @Test @SandboxConfig(shadows = {Shadow3OfChild.class, ShadowOfParent.class})
whenShadowMethodIsOverriddenInShadowOfAnotherClass_shouldNotUseShadowSuperclassMethods()208   public void whenShadowMethodIsOverriddenInShadowOfAnotherClass_shouldNotUseShadowSuperclassMethods() throws Exception {
209     assertThat(new Child().get()).isEqualTo("from child (from shadow of parent)");
210   }
211 
212   @Test @SandboxConfig(shadows = {ShadowOfParentWithPackageImpl.class})
whenShadowMethodIsntCorrectlyVisible_shouldNotUseShadowMethods()213   public void whenShadowMethodIsntCorrectlyVisible_shouldNotUseShadowMethods() throws Exception {
214     assertThat(new Parent().get()).isEqualTo("from parent");
215   }
216 
217   @Instrument
218   public static class Parent {
get()219     public String get() {
220       return "from parent";
221     }
222   }
223 
224   @Instrument
225   public static class Child extends Parent {
226     @Override
get()227     public String get() {
228       return "from child (" + super.get() + ")";
229     }
230   }
231 
232   @Implements(Parent.class)
233   public static class ShadowOfParent {
234     @Implementation
get()235     protected String get() {
236       return "from shadow of parent";
237     }
238   }
239 
240   @Implements(Parent.class)
241   public static class ShadowOfParentWithPackageImpl {
242     @Implementation
get()243     String get() {
244       return "from ShadowOfParentWithPackageImpl";
245     }
246   }
247 
248   @Implements(value = Child.class)
249   public static class ShadowOfChild extends ShadowOfParent {
250     @Implementation
251     @Override
get()252     protected String get() {
253       return "get from ShadowOfChild";
254     }
255   }
256 
257   @Implements(value = Child.class)
258   public static class Shadow2OfChild extends ShadowOfChild {
259     @Implementation
260     @Override
get()261     protected String get() {
262       return "get from Shadow2OfChild";
263     }
264   }
265 
266   @Implements(value = Child.class)
267   public static class Shadow22OfChild extends Shadow2OfChild {
268   }
269 
270   public static class SomethingOtherThanChild extends Child {
271   }
272 
273   @Implements(value = SomethingOtherThanChild.class)
274   public static class Shadow3OfChild extends ShadowOfChild {
275     @Implementation
276     @Override
get()277     protected String get() {
278       return "get from Shadow3OfChild";
279     }
280   }
281 
shadowOf(Foo foo)282   private ShadowFoo shadowOf(Foo foo) {
283     return (ShadowFoo) Shadow.extract(foo);
284   }
285 
shadowOf(TextFoo foo)286   private ShadowTextFoo shadowOf(TextFoo foo) {
287     return (ShadowTextFoo) Shadow.extract(foo);
288   }
289 
290   @Implements(Foo.class)
291   public static class WithEquals {
292     @Implementation
__constructor__(String s)293     protected void __constructor__(String s) {
294     }
295 
296     @Override
297     @Implementation
equals(Object o)298     public boolean equals(Object o) {
299       return true;
300     }
301 
302     @Override
303     @Implementation
hashCode()304     public int hashCode() {
305       return 42;
306     }
307 
308   }
309 
310   @Implements(Foo.class)
311   public static class WithToString {
312     @Implementation
__constructor__(String s)313     protected void __constructor__(String s) {
314     }
315 
316     @Override
317     @Implementation
toString()318     public String toString() {
319       return "the expected string";
320     }
321   }
322 
323   @Implements(TextFoo.class)
324   public static class ShadowTextFoo extends ShadowFoo {
325   }
326 
327   @Instrument
328   public static class TextFoo extends Foo {
TextFoo(String s)329     public TextFoo(String s) {
330       super(s);
331     }
332   }
333 
334   @Implements(Foo.class)
335   public static class ShadowFooParent {
336     @RealObject
337     private Foo realFoo;
338     public Foo realFooInParentConstructor;
339 
340     @Implementation
__constructor__(String name)341     protected void __constructor__(String name) {
342       realFooInParentConstructor = realFoo;
343     }
344   }
345 
346   @Instrument
347   public static class AClassWithDefaultConstructor {
348     public boolean initialized;
349 
AClassWithDefaultConstructor()350     public AClassWithDefaultConstructor() {
351       initialized = true;
352     }
353   }
354 
355   @Implements(AClassWithDefaultConstructor.class)
356   public static class ShadowForAClassWithDefaultConstructor_HavingNoConstructorDelegate {
357   }
358 
359   @SandboxConfig(shadows = ShadowAClassWithDifficultArgs.class)
shouldAllowLooseSignatureMatches()360   @Test public void shouldAllowLooseSignatureMatches() throws Exception {
361     assertThat(new AClassWithDifficultArgs().aMethod("bc")).isEqualTo("abc");
362   }
363 
364   @Implements(value = AClassWithDifficultArgs.class, looseSignatures = true)
365   public static class ShadowAClassWithDifficultArgs {
366     @Implementation
aMethod(Object s)367     protected Object aMethod(Object s) {
368       return "a" + s;
369     }
370   }
371 
372   @Instrument
373   public static class AClassWithDifficultArgs {
aMethod(CharSequence s)374     public CharSequence aMethod(CharSequence s) {
375       return s;
376     }
377   }
378 
379   @Test @SandboxConfig(shadows = ShadowOfAClassWithStaticInitializer.class)
classesWithInstrumentedShadowsDontDoubleInitialize()380   public void classesWithInstrumentedShadowsDontDoubleInitialize() throws Exception {
381     // if we didn't reject private shadow methods, __staticInitializer__ on the shadow
382     // would be executed twice.
383     new AClassWithStaticInitializer();
384     assertThat(ShadowOfAClassWithStaticInitializer.initCount).isEqualTo(1);
385     assertThat(AClassWithStaticInitializer.initCount).isEqualTo(1);
386   }
387 
388   @Instrument
389   public static class AClassWithStaticInitializer {
390     static int initCount;
391     static {
392       initCount++;
393     }
394   }
395 
396   @Instrument // because it's fairly common that people accidentally instrument their own shadows
397   @Implements(AClassWithStaticInitializer.class)
398   public static class ShadowOfAClassWithStaticInitializer {
399     static int initCount;
400     static {
401       initCount++;
402     }
403   }
404 
405   @Test @SandboxConfig(shadows = Shadow22OfAClassWithBrokenStaticInitializer.class)
staticInitializerShadowMethodsObeySameRules()406   public void staticInitializerShadowMethodsObeySameRules() throws Exception {
407     new AClassWithBrokenStaticInitializer();
408   }
409 
410   @Instrument
411   public static class AClassWithBrokenStaticInitializer {
412     static {
413       if (YES) throw new RuntimeException("broken!");
414     }
415   }
416 
417   @Implements(AClassWithBrokenStaticInitializer.class)
418   public static class Shadow2OfAClassWithBrokenStaticInitializer {
419     @Implementation
__staticInitializer__()420     protected static void __staticInitializer__() {
421       // don't call real static initializer
422     }
423   }
424 
425   @Implements(AClassWithBrokenStaticInitializer.class)
426   public static class Shadow22OfAClassWithBrokenStaticInitializer
427       extends Shadow2OfAClassWithBrokenStaticInitializer {
428   }
429 }
430