package org.robolectric; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; import org.robolectric.annotation.internal.Instrument; import org.robolectric.internal.SandboxTestRunner; import org.robolectric.internal.bytecode.SandboxConfig; import org.robolectric.internal.bytecode.ShadowWrangler; import org.robolectric.shadow.api.Shadow; import org.robolectric.testing.Foo; import org.robolectric.testing.ShadowFoo; @RunWith(SandboxTestRunner.class) public class ShadowWranglerIntegrationTest { private static final boolean YES = true; private String name; @Before public void setUp() throws Exception { name = "context"; } @Test @SandboxConfig(shadows = {ShadowForAClassWithDefaultConstructor_HavingNoConstructorDelegate.class}) public void testConstructorInvocation_WithDefaultConstructorAndNoConstructorDelegateOnShadowClass() throws Exception { AClassWithDefaultConstructor instance = new AClassWithDefaultConstructor(); assertThat(Shadow.extract(instance)).isInstanceOf(ShadowForAClassWithDefaultConstructor_HavingNoConstructorDelegate.class); assertThat(instance.initialized).isTrue(); } @Test @SandboxConfig(shadows = { ShadowFoo.class }) public void testConstructorInvocation() throws Exception { Foo foo = new Foo(name); assertSame(name, shadowOf(foo).name); } @Test @SandboxConfig(shadows = {ShadowFoo.class}) public void testRealObjectAnnotatedFieldsAreSetBeforeConstructorIsCalled() throws Exception { Foo foo = new Foo(name); assertSame(name, shadowOf(foo).name); assertSame(foo, shadowOf(foo).realFooField); assertSame(foo, shadowOf(foo).realFooInConstructor); assertSame(foo, shadowOf(foo).realFooInParentConstructor); } @Test @SandboxConfig(shadows = {ShadowFoo.class}) public void testMethodDelegation() throws Exception { Foo foo = new Foo(name); assertSame(name, foo.getName()); } @Test @SandboxConfig(shadows = {WithEquals.class}) public void testEqualsMethodDelegation() throws Exception { Foo foo1 = new Foo(name); Foo foo2 = new Foo(name); assertEquals(foo1, foo2); } @Test @SandboxConfig(shadows = {WithEquals.class}) public void testHashCodeMethodDelegation() throws Exception { Foo foo = new Foo(name); assertEquals(42, foo.hashCode()); } @Test @SandboxConfig(shadows = {WithToString.class}) public void testToStringMethodDelegation() throws Exception { Foo foo = new Foo(name); assertEquals("the expected string", foo.toString()); } @Test @SandboxConfig(shadows = {ShadowFoo.class}) public void testShadowSelectionSearchesSuperclasses() throws Exception { TextFoo textFoo = new TextFoo(name); assertEquals(ShadowFoo.class, Shadow.extract(textFoo).getClass()); } @Test @SandboxConfig(shadows = {ShadowFoo.class, ShadowTextFoo.class}) public void shouldUseMostSpecificShadow() throws Exception { TextFoo textFoo = new TextFoo(name); assertThat(shadowOf(textFoo)).isInstanceOf(ShadowTextFoo.class); } @Test public void testPrimitiveArrays() throws Exception { Class objArrayClass = ShadowWrangler.loadClass("java.lang.Object[]", getClass().getClassLoader()); assertTrue(objArrayClass.isArray()); assertEquals(Object.class, objArrayClass.getComponentType()); Class intArrayClass = ShadowWrangler.loadClass("int[]", getClass().getClassLoader()); assertTrue(intArrayClass.isArray()); assertEquals(Integer.TYPE, intArrayClass.getComponentType()); } @Test @SandboxConfig(shadows = ShadowThrowInShadowMethod.class) public void shouldRemoveNoiseFromShadowedStackTraces() throws Exception { ThrowInShadowMethod instance = new ThrowInShadowMethod(); Exception e = null; try { instance.method(); } catch (Exception e1) { e = e1; } assertNotNull(e); assertEquals(IOException.class, e.getClass()); assertEquals("fake exception", e.getMessage()); StackTraceElement[] stackTrace = e.getStackTrace(); assertThat(stackTrace[0].getClassName()).isEqualTo(ShadowThrowInShadowMethod.class.getName()); assertThat(stackTrace[0].getMethodName()).isEqualTo("method"); assertThat(stackTrace[0].getLineNumber()).isGreaterThan(0); assertThat(stackTrace[1].getClassName()).isEqualTo(ThrowInShadowMethod.class.getName()); assertThat(stackTrace[1].getMethodName()).isEqualTo("method"); assertThat(stackTrace[1].getLineNumber()).isLessThan(0); assertThat(stackTrace[2].getClassName()).isEqualTo(ShadowWranglerIntegrationTest.class.getName()); assertThat(stackTrace[2].getMethodName()).isEqualTo("shouldRemoveNoiseFromShadowedStackTraces"); assertThat(stackTrace[2].getLineNumber()).isGreaterThan(0); } @Instrument public static class ThrowInShadowMethod { public void method() throws IOException { } } @Implements(ThrowInShadowMethod.class) public static class ShadowThrowInShadowMethod { public void method() throws IOException { throw new IOException("fake exception"); } } @Test @SandboxConfig(shadows = ShadowThrowInRealMethod.class) public void shouldRemoveNoiseFromUnshadowedStackTraces() throws Exception { ThrowInRealMethod instance = new ThrowInRealMethod(); Exception e = null; try { instance.method(); } catch (Exception e1) { e = e1; } assertNotNull(e); assertEquals(IOException.class, e.getClass()); assertEquals("fake exception", e.getMessage()); StackTraceElement[] stackTrace = e.getStackTrace(); assertThat(stackTrace[0].getClassName()).isEqualTo(ThrowInRealMethod.class.getName()); assertThat(stackTrace[0].getMethodName()).isEqualTo("method"); assertThat(stackTrace[0].getLineNumber()).isGreaterThan(0); assertThat(stackTrace[1].getClassName()).isEqualTo(ShadowWranglerIntegrationTest.class.getName()); assertThat(stackTrace[1].getMethodName()).isEqualTo("shouldRemoveNoiseFromUnshadowedStackTraces"); assertThat(stackTrace[1].getLineNumber()).isGreaterThan(0); } @Instrument public static class ThrowInRealMethod { public void method() throws IOException { throw new IOException("fake exception"); } } @Implements(ThrowInRealMethod.class) public static class ShadowThrowInRealMethod { } @Test @SandboxConfig(shadows = {Shadow2OfChild.class, ShadowOfParent.class}) public void whenShadowMethodIsOverriddenInShadowWithSameShadowedClass_shouldUseOverriddenMethod() throws Exception { assertThat(new Child().get()).isEqualTo("get from Shadow2OfChild"); } @Test @SandboxConfig(shadows = {Shadow22OfChild.class, ShadowOfParent.class}) public void whenShadowMethodIsNotOverriddenInShadowWithSameShadowedClass_shouldUseOverriddenMethod() throws Exception { assertThat(new Child().get()).isEqualTo("get from Shadow2OfChild"); } @Test @SandboxConfig(shadows = {Shadow3OfChild.class, ShadowOfParent.class}) public void whenShadowMethodIsOverriddenInShadowOfAnotherClass_shouldNotUseShadowSuperclassMethods() throws Exception { assertThat(new Child().get()).isEqualTo("from child (from shadow of parent)"); } @Test @SandboxConfig(shadows = {ShadowOfParentWithPackageImpl.class}) public void whenShadowMethodIsntCorrectlyVisible_shouldNotUseShadowMethods() throws Exception { assertThat(new Parent().get()).isEqualTo("from parent"); } @Instrument public static class Parent { public String get() { return "from parent"; } } @Instrument public static class Child extends Parent { @Override public String get() { return "from child (" + super.get() + ")"; } } @Implements(Parent.class) public static class ShadowOfParent { @Implementation protected String get() { return "from shadow of parent"; } } @Implements(Parent.class) public static class ShadowOfParentWithPackageImpl { @Implementation String get() { return "from ShadowOfParentWithPackageImpl"; } } @Implements(value = Child.class) public static class ShadowOfChild extends ShadowOfParent { @Implementation @Override protected String get() { return "get from ShadowOfChild"; } } @Implements(value = Child.class) public static class Shadow2OfChild extends ShadowOfChild { @Implementation @Override protected String get() { return "get from Shadow2OfChild"; } } @Implements(value = Child.class) public static class Shadow22OfChild extends Shadow2OfChild { } public static class SomethingOtherThanChild extends Child { } @Implements(value = SomethingOtherThanChild.class) public static class Shadow3OfChild extends ShadowOfChild { @Implementation @Override protected String get() { return "get from Shadow3OfChild"; } } private ShadowFoo shadowOf(Foo foo) { return (ShadowFoo) Shadow.extract(foo); } private ShadowTextFoo shadowOf(TextFoo foo) { return (ShadowTextFoo) Shadow.extract(foo); } @Implements(Foo.class) public static class WithEquals { @Implementation protected void __constructor__(String s) { } @Override @Implementation public boolean equals(Object o) { return true; } @Override @Implementation public int hashCode() { return 42; } } @Implements(Foo.class) public static class WithToString { @Implementation protected void __constructor__(String s) { } @Override @Implementation public String toString() { return "the expected string"; } } @Implements(TextFoo.class) public static class ShadowTextFoo extends ShadowFoo { } @Instrument public static class TextFoo extends Foo { public TextFoo(String s) { super(s); } } @Implements(Foo.class) public static class ShadowFooParent { @RealObject private Foo realFoo; public Foo realFooInParentConstructor; @Implementation protected void __constructor__(String name) { realFooInParentConstructor = realFoo; } } @Instrument public static class AClassWithDefaultConstructor { public boolean initialized; public AClassWithDefaultConstructor() { initialized = true; } } @Implements(AClassWithDefaultConstructor.class) public static class ShadowForAClassWithDefaultConstructor_HavingNoConstructorDelegate { } @SandboxConfig(shadows = ShadowAClassWithDifficultArgs.class) @Test public void shouldAllowLooseSignatureMatches() throws Exception { assertThat(new AClassWithDifficultArgs().aMethod("bc")).isEqualTo("abc"); } @Implements(value = AClassWithDifficultArgs.class, looseSignatures = true) public static class ShadowAClassWithDifficultArgs { @Implementation protected Object aMethod(Object s) { return "a" + s; } } @Instrument public static class AClassWithDifficultArgs { public CharSequence aMethod(CharSequence s) { return s; } } @Test @SandboxConfig(shadows = ShadowOfAClassWithStaticInitializer.class) public void classesWithInstrumentedShadowsDontDoubleInitialize() throws Exception { // if we didn't reject private shadow methods, __staticInitializer__ on the shadow // would be executed twice. new AClassWithStaticInitializer(); assertThat(ShadowOfAClassWithStaticInitializer.initCount).isEqualTo(1); assertThat(AClassWithStaticInitializer.initCount).isEqualTo(1); } @Instrument public static class AClassWithStaticInitializer { static int initCount; static { initCount++; } } @Instrument // because it's fairly common that people accidentally instrument their own shadows @Implements(AClassWithStaticInitializer.class) public static class ShadowOfAClassWithStaticInitializer { static int initCount; static { initCount++; } } @Test @SandboxConfig(shadows = Shadow22OfAClassWithBrokenStaticInitializer.class) public void staticInitializerShadowMethodsObeySameRules() throws Exception { new AClassWithBrokenStaticInitializer(); } @Instrument public static class AClassWithBrokenStaticInitializer { static { if (YES) throw new RuntimeException("broken!"); } } @Implements(AClassWithBrokenStaticInitializer.class) public static class Shadow2OfAClassWithBrokenStaticInitializer { @Implementation protected static void __staticInitializer__() { // don't call real static initializer } } @Implements(AClassWithBrokenStaticInitializer.class) public static class Shadow22OfAClassWithBrokenStaticInitializer extends Shadow2OfAClassWithBrokenStaticInitializer { } }