blob: 4affc63e61c24f6f07821f66e3423df039e52eeb [file] [log] [blame]
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.<Object>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 {
}
}