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